【问题标题】:OpenJPA + Tomcat JDBC Connection Pooling = stale dataOpenJPA + Tomcat JDBC 连接池 = 陈旧数据
【发布时间】:2012-12-03 23:02:11
【问题描述】:

我在 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

这里是创建数据源的代码片段(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);

这没有效果。我在 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;

【问题讨论】:

    标签: mysql tomcat connection-pooling openjpa autocommit


    【解决方案1】:

    答案是 OpenJPA 中存在一个错误。解决方法是自己在连接上调用 commit。

    从 EntityManager 获取连接,将其转换为 java.sql.Connection 并调用 commit()。

    错误是 OpenJPA 从不为 只读 事务调用连接上的提交。如果 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 每次都会获得一个新连接,因此所有同时发生的更新都会在下一个查询中找到。

    【讨论】:

      【解决方案2】:

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

      当我添加了

      defaultAutoCommit="true"
      

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-02
        • 2015-12-26
        • 2013-05-01
        • 2014-12-26
        • 1970-01-01
        • 1970-01-01
        • 2012-07-12
        • 2014-12-20
        相关资源
        最近更新 更多