1

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;
4

2 に答える 2

1

答えは、OpenJPA にバグがあるということです。回避策は、接続でコミットを自分で呼び出すことです。

EntityManager から接続を取得し、それを java.sql.Connection にキャストして commit() を呼び出します。

バグは、OpenJPA が読み取り専用トランザクションの接続で commit を呼び出さないことです。autoCommit が true に設定されていれば問題ありませんが、OpenJPA は autoCommit を false にすることを主張しています。EntityManager の作成時に、基礎となる接続で autoCommit が true に設定されている場合、OpenJPA はそれを false に設定します。

以下のログ フラグメントでは、EntityManager を作成する前に DataSource から接続を取得し、autoCommit と transactionIsolation をログに記録しています。EntityManager の作成中に、autoCommit が false に設定されたことがログに示されます。

 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getTransactionIsolation Args: 
 DEBUG: .store.impl.EMHandlerImpl.em: Isolation level: 4 autoCommit: true
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection close Args: 
 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: false 
 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 の場合は、逆のことが起こります。以下のログ フラグメントでは、autoCommit が false に設定されていません。

DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getTransactionIsolation Args: 
 DEBUG: .store.impl.EMHandlerImpl.em: Isolation level: 2 autoCommit: true
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection close Args: 
 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 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

さらに、autoCommit 設定が最初は false である場合、OpenJPA は DB2 に対して true を設定し、MySQL に対してはそのままにします。これらのケースのログは表示しません。

完全を期すために、更新中に観察したことについて言及します。DB2 の場合、autoCommit は false に設定され、更新が実行され、commit が接続で呼び出され、autoCommit が true に戻されます。DB2 のログ フラグメントを次に示します。

DEBUG: .store.impl.EMHandlerImpl.commitTransaction: >>>calling COMMIT transaction 544096693
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection getTransactionIsolation Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****setAutoCommit Args: false 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection isReadOnly Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: INSERT INT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: INSERT INT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: INSERT INT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: UPDATE DAT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: UPDATE DAT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****commit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****setAutoCommit Args: true 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection close Args: 
 DEBUG: .store.impl.EMHandlerImpl.commitTransaction: >>>finished COMMIT transaction 544096693

MySQL の場合、autoCommit はすでに false であるため、更新中に変更されません。とにかくログは次のとおりです。

 DEBUG: .store.impl.EMHandlerImpl.commitTransaction: >>>calling COMMIT transaction 2103121779
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****getAutoCommit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection isReadOnly Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: INSERT INT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: INSERT INT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: INSERT INT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: UPDATE DAT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection prepareStatement Args: UPDATE DAT 1003 1007 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection !****commit Args: 
 DEBUG: .store.util.CommitInterceptor.invoke: Method called on connection close Args: 
 DEBUG: .store.impl.EMHandlerImpl.commitTransaction: >>>finished COMMIT transaction 2103121779

このバグがコネクション プーリングを使用している場合にのみ現れるのは興味深いことです。これは、再利用される接続で commit が呼び出されないためだと思います。接続プールを使用しない場合、OpenJPA は毎回新しい接続を取得するため、同時に行われたすべての更新は次のクエリで検出されます。

于 2012-12-05T16:54:48.700 に答える
0

ジュリー、私は tomcat 7 (ネイティブ プール付き) と openjpa 2.2 を使用しています。JPA が時々 (!) ページの更新後に古いデータをもたらすという問題に遭遇しました。

追加したら

defaultAutoCommit="true"

接続プール構成 (server.xml) で、これでうまくいきました。

defaultAutoCommit - (boolean) このプールによって作成された接続のデフォルトの自動コミット状態。設定されていない場合、デフォルトは JDBC ドライバーのデフォルトです (設定されていない場合、setAutoCommit メソッドは呼び出されません)。

于 2012-12-22T05:40:38.227 に答える