1

我在 Web 应用程序中使用带有 OpenJPA 的 Tomcat JDBC 连接池。应用程序看不到更新的数据。具体来说,另一个 java 应用程序在数据库中添加或删除记录,但 Web 应用程序永远不会看到这些更新。这是一个相当严重的问题。我一定缺少一些基本的东西。

如果我从实现中删除连接池,Web 应用程序会看到更新。就好像 Web 应用程序的提交永远不会在 Connection 上调用。

版本信息:

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 在 props 中有足够的信息来配置连接,而无需使用 DataSource。

//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 上调用提交,连接上也没有相应的提交。由于事务期间执行的所有查询都是只读的,因此 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,结果发现 MySQL 的默认级别是 4,而 DB2 是 2。这是 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 中存在一个错误。解决方法是自己在连接上调用 commit。

从 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

有趣的是,这个错误只在使用连接池时出现。我建议这是因为永远不会在重用的连接上调用提交。如果没有连接池,OpenJPA 每次都会获得一个新连接,因此所有同时发生的更新都会在下一个查询中找到。

于 2012-12-05T16:54:48.700 回答
0

Julie,我正在使用 tomcat 7(带有本地池)和 openjpa 2.2。我遇到了一个问题,即 JPA 有时(!)在页面刷新后会带来陈旧的数据。

当我添加

defaultAutoCommit="true"

在连接池配置 (server.xml) 中,这成功了。

defaultAutoCommit - (boolean) 此池创建的连接的默认自动提交状态。如果未设置,则默认为 JDBC 驱动程序默认值(如果未设置,则不会调用 setAutoCommit 方法。)

于 2012-12-22T05:40:38.227 回答