Web アプリケーションで OpenJPA とともに Tomcat JDBC 接続プールを使用しています。アプリケーションは、更新されたデータを認識しません。具体的には、別の Java アプリケーションがデータベースにレコードを追加または削除しますが、Web アプリケーションはこれらの更新を認識しません。これはかなり深刻な問題です。基本的なものが欠けているに違いありません。
実装から接続プールを削除すると、Web アプリケーションは更新を認識します。あたかも Web アプリケーションのコミットが接続で呼び出されないかのようです。
バージョン情報:
Tomcat JDBC 接続プール: org.apache.tomcat tomcat-jdbc 7.0.21
OpenJPA: org.apache.openjpa openjpa 2.0.1
以下は、DataSource (DataSourceHelper.findOrCreateDataSource メソッド) を作成するコード フラグメントです。
PoolConfiguration props = new PoolProperties();
props.setUrl(URL);
props.setDefaultAutoCommit(false);
props.setDriverClassName(dd.getClass().getName());
props.setUsername(username);
props.setPassword(pw);
props.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"+
"org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer;"+
"org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx;"+
"org.apache.tomcat.jdbc.pool.interceptor.ResetAbandonedTimer");
props.setLogAbandoned(true);
props.setSuspectTimeout(120);
props.setJmxEnabled(true);
props.setInitialSize(2);
props.setMaxActive(100);
props.setTestOnBorrow(true);
if (URL.toUpperCase().contains(DB2)) {
props.setValidationQuery("VALUES (1)");
} else if (URL.toUpperCase().contains(MYSQL)) {
props.setValidationQuery("SELECT 1");
props.setConnectionProperties("relaxAutoCommit=true");
} else if (URL.toUpperCase().contains(ORACLE)) {
props.setValidationQuery("select 1 from dual");
}
props.setValidationInterval(3000);
dataSource = new DataSource();
dataSource.setPoolProperties(props);
以下は、DataSource を使用して EntityManagerFactory を作成するコードです。
//props contains the connection url, user name, and password
DataSource dataSource = DataSourceHelper.findOrCreateDataSource("DATAMGT", URL, username, password);
props.put("openjpa.ConnectionFactory", dataSource);
emFactory = (OpenJPAEntityManagerFactory) Persistence.createEntityManagerFactory("DATAMGT", props);
そのように DataSource をコメントアウトすると、機能します。OpenJPA には、DataSource を使用せずに接続を構成するための十分な情報が props に含まれていることに注意してください。
//props contains the connection url, user name, and password
//DataSource dataSource = DataSourceHelper.findOrCreateDataSource("DATAMGT", URL, username, password);
//props.put("openjpa.ConnectionFactory", dataSource);
emFactory = (OpenJPAEntityManagerFactory) Persistence.createEntityManagerFactory("DATAMGT", props);
どういうわけか、OpenJPA と接続プールの組み合わせが正しく機能していません。
アップデート:
実際、基盤となるデータベースが MySQL の場合は失敗するようです。基礎となるデータベースが DB2 の場合、プールの有無にかかわらず正しく動作します。
更新#2:
JdbcInterceptor をプールに追加して、接続で呼び出されるメソッドをログに記録しました。データベースが DB2 の場合、EntityManager の作成時に setAutoCommit(true) が呼び出されます。データベースが MySQL の場合、呼び出されません。
これにより、動作の違いが説明されます。アプリケーションが EntityManager で commit を呼び出している場合でも、対応するコミットが Connection にありません。トランザクション中に実行されるクエリはすべて読み取り専用であるため、OpenJPA ではコミットは必要ないと考えているようです。
MySQL からのログは次のとおりです。
INFO : .store.EMHandler.getConfig: ******************Start JPA Properties:
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionDriverName: com.mysql.jdbc.Driver
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionPassword: *******
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionUserName: *******
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionURL: jdbc:mysql://localhost:3306/datamgt
INFO : .store.EMHandler.getConfig: *********openjpa.Log: log4j
INFO : .store.EMHandler.getConfig: ***** Found Driver :com.mysql.jdbc.Driver class: class com.mysql.jdbc.Driver
INFO : .store.EMHandler.getConfig: ******************End JPA Properties:
DEBUG: .store.impl.EMHandlerImpl.em: ********EntityManagerFactory created
DEBUG: .store.impl.EMHandlerImpl.em: ******** Creating EntityManager
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getMetaData Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getMetaData Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection close Args:
DEBUG: .store.impl.EMHandlerImpl.em: ********Entity manager created
以下は DB2 からのログです (setAutoCommit に注意してください)。
INFO : .store.EMHandler.getConfig: ******************Start JPA Properties:
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionDriverName: com.ibm.db2.jcc.DB2Driver
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionPassword: *******
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionUserName: *******
INFO : .store.EMHandler.getConfig: *********openjpa.ConnectionURL: jdbc:db2://localhost:50000/DATAMGT
INFO : .store.EMHandler.getConfig: *********openjpa.Log: log4j
INFO : .store.EMHandler.getConfig: ***** Found Driver :com.ibm.db2.jcc.DB2Driver class: class com.ibm.db2.jcc.DB2Driver
INFO : .store.EMHandler.getConfig: ******************End JPA Properties:
DEBUG: .store.impl.EMHandlerImpl.em: ********EntityManagerFactory created
DEBUG: .store.impl.EMHandlerImpl.em: ******** Creating EntityManager
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****setAutoCommit Args: true
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getMetaData Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getMetaData Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection createStatement Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getTransactionIsolation Args:
DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection close Args:
DEBUG: .store.impl.EMHandlerImpl.em: ********Entity manager created
この発見以来、EntityManagerFactory を作成する前に、autoCommit を true に設定しようとしました。
dataSource.setDefaultAutoCommit(true);
これは効果がありません。OpenJPA が autoCommit を false に設定する、stackoverflow に関する他の投稿を読みました。ログでこれを見ましたが、データベースへの更新を含むトランザクションをコミットする場合のみです。
最近、transactionIsolationLevel を調べたところ、DB2 が 2 であるのに対し、MySQL のデフォルト レベルは 4 であることがわかりました。これは、java.sql.Connection クラスからのこれらの定義です。2 は 4 よりもさらにリラックスしているため、おそらくこれが原因ではないことに注意してください。
/**
* A constant indicating that
* dirty reads are prevented; non-repeatable reads and phantom
* reads can occur. This level only prohibits a transaction
* from reading a row with uncommitted changes in it.
*/
int TRANSACTION_READ_COMMITTED = 2;
/**
* A constant indicating that
* dirty reads and non-repeatable reads are prevented; phantom
* reads can occur. This level prohibits a transaction from
* reading a row with uncommitted changes in it, and it also
* prohibits the situation where one transaction reads a row,
* a second transaction alters the row, and the first transaction
* rereads the row, getting different values the second time
* (a "non-repeatable read").
*/
int TRANSACTION_REPEATABLE_READ = 4;