OpenJPA + Tomcat JDBC连接池=陈旧数据

时间:2012-12-03 23:02:12

标签: mysql tomcat connection-pooling openjpa autocommit

我在Web应用程序中使用Tomcat JDBC连接池和OpenJPA。该应用程序没有看到更新的数据。具体来说,另一个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上调用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);

这没有效果。我已经阅读了关于stackoverflow的其他帖子,OpenJPA将autoCommit设置为false,我在日志中看到了这一点,但仅在提交包含数据库更新的事务时。

我最近一直在检查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;

2 个答案:

答案 0 :(得分: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,并将其单独留给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每次都会获得一个全新的连接,因此所有同时发生的更新都会在下一个查询中找到。

答案 1 :(得分:0)

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

当我添加

defaultAutoCommit="true"

在连接池配置(server.xml)中,完成了这一操作。

  

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