前回書いた、JPA Cache Storeの副産物です。これを書いた時、JPAにJTAとリソースのルックアップもできるように頑張ろうと思ったのですが、面倒過ぎてやめました…。
ただ、DataSourceくらいはJNDIルックアップできるようになったので、今度はJavaでちゃんと書きたいと思います。
参考にしたのは、このあたりです。
How can I bind a DataSource to an InitialContext for JUnit testing?
http://stackoverflow.com/questions/3461310/how-can-i-bind-a-datasource-to-an-initialcontext-for-junit-testing
Implementing standalone JPA JTA Hibernate application outside J2EE server using Infinispan 2nd level cache
https://docs.jboss.org/author/display/ISPN/Implementing+standalone+JPA+JTA+Hibernate+application+outside+J2EE+server+using+Infinispan+2nd+level+cache
Naming on JBoss
http://docs.jboss.org/jbossas/jboss4guide/r1/html/ch3.chapter.html
DataSourceの利用
http://www.techscore.com/tech/Java/JavaEE/JDBC/6-2/
というわけで、JNDIを使うのにはJBossのライブラリを使うことにしました。
Mavenの依存関係定義は、こんな感じです。
<dependency> <groupId>jboss</groupId> <artifactId>jnp-client</artifactId> <version>4.2.2.GA</version> </dependency> <dependency> <groupId>jboss</groupId> <artifactId>jnpserver</artifactId> <version>4.2.2.GA</version> </dependency> <dependency> <groupId>org.jboss</groupId> <artifactId>jboss-common-core</artifactId> <version>2.2.22.GA</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency>
「jboss-common-core」というのは、コンパイルには必要ないのですが、ないと実行時にエラーになります…。MySQLのドライバが入っているのは、MySQLの提供するDataSourceを使用するためですね。
では、コードの方へ。以下のimport文を書いているものとします。
import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NamingException; import javax.sql.DataSource; import org.jnp.interfaces.NamingContext; import org.jnp.server.Main; import org.jnp.server.NamingServer; import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
java.sqlパッケージのものは、DataSourceを使った後で一応SELECT文を投げて動作確認するためだけに使います。JNDIには直接関係ありません。
JNDIサーバのセットアップ。
private static void setUpJndi() { try { NamingServer namingServer = new NamingServer(); NamingContext.setLocal(namingServer); Main namingMain = new Main(); namingMain.setInstallGlobalService(true); namingMain.setPort(-1); namingMain.start(); } catch (NamingException e) { throw new RuntimeException(e); } catch (Exception e) { throw new RuntimeException(e); } }
続いて、InitialContextにDataSourceを登録しますが、bindしたInitialContextを使うかそうでないかで、設定が微妙に変わります…。
まずは、InitialContextを使い回すパターンの方から。
InitialContextの引数にjava.util.Propertiesを渡してインスタンスを生成し、JNDIリソースを作成します。
private static InitialContext bindDataSource() { MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl("jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=utf8"); dataSource.setUser("kazuhira"); dataSource.setPassword("password"); Properties properties = new Properties(); properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); properties.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); try { InitialContext context = new InitialContext(properties); context.createSubcontext("jdbc"); context.bind("jdbc/mysql", dataSource); // java:comp/〜で書くなら、こんな感じ context.createSubcontext("java:comp"); context.createSubcontext("java:comp/env"); context.createSubcontext("java:comp/env/jdbc"); context.bind("java:comp/env/jdbc/mysql", dataSource); return context; } catch (NamingException e) { throw new RuntimeException(e); } }
InitialContext#createSubcontextで、ディレクトリを掘り進めていきます。最後に、bindしたInitialContextを返却するメソッドになっています。
続いて、上記メソッドが返却したInitialContextを引数に受けることを期待したメソッド。
private static void lookupAndExecute(InitialContext context) { try { DataSource dataSource = (DataSource) context.lookup("jdbc/mysql"); try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM user"); ResultSet rs = ps.executeQuery()) { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); while (rs.next()) { for (int i = 1; i <= columnCount; i++) { String columnName = meta.getColumnName(i); System.out.println(columnName + " = " + rs.getString(i)); } } } catch (SQLException e) { throw new RuntimeException(e); } } catch (NamingException e) { throw new RuntimeException(e); } }
これで、DataSourceのルックアップとJDBCプログラミングが可能になりました。
続いて、bindしたInitialContextを使わないパターン。
先ほどは、InitialContextのコンストラクタに設定を渡していましたが、よく見かける
InitialContext context = new InitialContext(); DataSource dataSource = (DataSource) context.lookup("jdbc/mysql");
みたいなコードだと、そんな設定渡していませんからね。
事実、先ほどの例のJNDIの設定で、ルックアップしているコードをこのように変更すると
javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
みたいなエラーを見ることになります。
なので、そのように実装しましょう。
DataSourceを登録するところは、このように実装します。
private static void setUpDataSource() { MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl("jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=utf8"); dataSource.setUser("kazuhira"); dataSource.setPassword("password"); // システムプロパティで設定するか、「jndi.properties」を使用する // System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); // System.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); try { InitialContext context = new InitialContext(); context.createSubcontext("jdbc"); context.bind("jdbc/mysql", dataSource); // java:comp/〜で書くなら、こんな感じ context.createSubcontext("java:comp"); context.createSubcontext("java:comp/env"); context.createSubcontext("java:comp/env/jdbc"); context.bind("java:comp/env/jdbc/mysql", dataSource); } catch (NamingException e) { throw new RuntimeException(e); } }
System.setPropertyしているところはコメントアウトしていますが、ここはコメントアウトを解除するか、「jndi.properites」という名前のファイルを用意して、同様の内容を定義します。
今回は、「jndi.properties」ファイルを用意することにしました。
src/main/resources/jndi.properties
### JBossNS properties java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory ## ここは不要 ## java.naming.provider.url=jnp://localhost:1099 java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
プロバイダのURLは要りません…。
すると、InitialContextを使い回さなくたり、別途設定しなくても動作するようになります。
private static void lookupAndExecute() { try { InitialContext context = new InitialContext(); DataSource dataSource = (DataSource) context.lookup("jdbc/mysql"); try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM user"); ResultSet rs = ps.executeQuery()) { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); while (rs.next()) { for (int i = 1; i <= columnCount; i++) { String columnName = meta.getColumnName(i); System.out.println(columnName + " = " + rs.getString(i)); } } } catch (SQLException e) { throw new RuntimeException(e); } } catch (NamingException e) { throw new RuntimeException(e); } }
簡単でしたが、スタンドアロンでJNDIを使うサンプルってことで。
最後に、作成したコード全体です。両方のパターンを書いていますが、同一リソースを登録するようになっているので、どちらか片方しか有効にできません。あしからず。
src/main/java/standalone/jndi/StandaloneJndiExample.java
package standalone.jndi; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NamingException; import javax.sql.DataSource; import org.jnp.interfaces.NamingContext; import org.jnp.server.Main; import org.jnp.server.NamingServer; import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; public class StandaloneJndiExample { public static void main(String[] args) { setUpJndi(); // InitialContextを使い回す場合 /* InitialContext context = bindDataSource(); lookupAndExecute(context); */ // bindしたInitialContextは使用しない場合 setUpDataSource(); lookupAndExecute(); } private static void setUpJndi() { try { NamingServer namingServer = new NamingServer(); NamingContext.setLocal(namingServer); Main namingMain = new Main(); namingMain.setInstallGlobalService(true); namingMain.setPort(-1); namingMain.start(); } catch (NamingException e) { throw new RuntimeException(e); } catch (Exception e) { throw new RuntimeException(e); } } private static InitialContext bindDataSource() { MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl("jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=utf8"); dataSource.setUser("kazuhira"); dataSource.setPassword("password"); Properties properties = new Properties(); properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); properties.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); try { InitialContext context = new InitialContext(properties); context.createSubcontext("jdbc"); context.bind("jdbc/mysql", dataSource); // java:comp/〜で書くなら、こんな感じ context.createSubcontext("java:comp"); context.createSubcontext("java:comp/env"); context.createSubcontext("java:comp/env/jdbc"); context.bind("java:comp/env/jdbc/mysql", dataSource); return context; } catch (NamingException e) { throw new RuntimeException(e); } } private static void lookupAndExecute(InitialContext context) { try { DataSource dataSource = (DataSource) context.lookup("jdbc/mysql"); try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM user"); ResultSet rs = ps.executeQuery()) { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); while (rs.next()) { for (int i = 1; i <= columnCount; i++) { String columnName = meta.getColumnName(i); System.out.println(columnName + " = " + rs.getString(i)); } } } catch (SQLException e) { throw new RuntimeException(e); } } catch (NamingException e) { throw new RuntimeException(e); } } private static void setUpDataSource() { MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setUrl("jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=utf8"); dataSource.setUser("kazuhira"); dataSource.setPassword("password"); // システムプロパティで設定するか、「jndi.properties」を使用する // System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); // System.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces"); try { InitialContext context = new InitialContext(); context.createSubcontext("jdbc"); context.bind("jdbc/mysql", dataSource); // java:comp/〜で書くなら、こんな感じ context.createSubcontext("java:comp"); context.createSubcontext("java:comp/env"); context.createSubcontext("java:comp/env/jdbc"); context.bind("java:comp/env/jdbc/mysql", dataSource); } catch (NamingException e) { throw new RuntimeException(e); } } private static void lookupAndExecute() { try { InitialContext context = new InitialContext(); DataSource dataSource = (DataSource) context.lookup("jdbc/mysql"); try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT * FROM user"); ResultSet rs = ps.executeQuery()) { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); while (rs.next()) { for (int i = 1; i <= columnCount; i++) { String columnName = meta.getColumnName(i); System.out.println(columnName + " = " + rs.getString(i)); } } } catch (SQLException e) { throw new RuntimeException(e); } } catch (NamingException e) { throw new RuntimeException(e); } } }