【问题标题】:Java more than one DB connection in UserTransactionUserTransaction 中的 Java 多个 DB 连接
【发布时间】:2016-12-13 21:40:42
【问题描述】:
static void clean() throws Exception {
  final UserTransaction tx = InitialContext.doLookup("UserTransaction");
  tx.begin();

  try {
    final DataSource ds = InitialContext.doLookup(Databases.ADMIN);
    Connection connection1 = ds.getConnection();
    Connection connection2 = ds.getConnection();
    PreparedStatement st1 = connection1.prepareStatement("XXX delete records XXX"); // delete data

    PreparedStatement st2 = connection2.prepareStatement("XXX insert records XXX"); // insert new data that is same primary as deleted data above

    st1.executeUpdate();
    st1.close();
    connection1.close();
    st2.executeUpdate();
    st2.close();
    connection2.close();
    tx.commit();
  } finally {
    if (tx.getStatus() == Status.STATUS_ACTIVE) {
      tx.rollback();
    }
  }
}

我有一个网络应用程序,DAODataSource 作为对象来创建单独的连接来执行数据库操作。

所以我有一个UserTransaction,里面有两个DAO 对象在做单独的动作,第一个是删除,第二个是插入。删除是删除一些记录以允许插入发生,因为插入将插入相同的主键数据。

我取出DAO 层并将逻辑转换为上面的代码。有一点看不懂,根据上面的代码,插入操作应该是失败的,因为代码(UserTransaction里面)取了两个不同的连接,他们不认识,第一个删除没有'显然没有提交,所以第二个语句(插入)应该失败(由于唯一约束),因为两个数据库操作不在同一个连接中,第二个连接无法检测到未提交的更改。但令人惊讶的是,它并没有失败,而且两种语句都可以完美运行。

谁能帮忙解释一下?可以进行任何配置来实现此结果吗?还是我的理解有误?

【问题讨论】:

  • 你说你有一个网络应用程序。我想它在某种容器中运行?哪一个?
  • 它正在Weblogic服务器中运行。
  • 能不能根据这个来看看约束:stackoverflow.com/a/39012274/4823977

标签: database jakarta-ee transactions database-connection commit


【解决方案1】:

由于您的应用程序在 weblogic 服务器中运行,java-EE-container 正在为您管理事务和连接。如果您在 java-ee 事务中多次调用 DataSource#getConnection,您将获得多个 Connection 实例加入同一个事务。通常这些连接使用相同的会话连接到数据库。使用 oracle,您可以在 @Stateless ejb 中使用以下 sn-p 进行检查:

@Resource(lookup="jdbc/myDS")
private DataSource ds;

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
@Schedule(hour="*", minute="*", second="42")
public void testDatasource() throws SQLException {

    try ( Connection con1 = ds.getConnection();
          Connection con2 = ds.getConnection();
        ) {

        String sessId1 = null, sessId2 = null;
        try (ResultSet rs1 = con1.createStatement().executeQuery("select userenv('SESSIONID') from dual") ){
            if ( rs1.next() ) sessId1 = rs1.getString(1);
        };
        try (ResultSet rs2 = con2.createStatement().executeQuery("select userenv('SESSIONID') from dual") ){
            if ( rs2.next() ) sessId2 = rs2.getString(1);
        };

        LOG.log( Level.INFO," con1={0}, con2={1}, sessId1={2}, sessId2={3}"
               , new Object[]{ con1, con2, sessId1, sessId2}
               );
    }

}

这会产生以下日志消息:

con1=com.sun.gjc.spi.jdbc40.ConnectionWrapper40@19f32aa, 
con2=com.sun.gjc.spi.jdbc40.ConnectionWrapper40@1cb42e0, 
sessId1=9347407, 
sessId2=9347407

请注意,您会获得具有相同会话 ID 的不同 Connection 实例。

更多详情请见this question

【讨论】:

  • 谢谢!我一直在想如何打印会话标识以证明连接是否相同,一直在查看Connection/DataSource API,没有给我答案,现在我知道我们必须使用 SQL 来弄清楚那个。
  • 我认为DataSource.getConnetion 是独立于供应商的,因为在我的应用程序中使用的是Weblogic,所以实际行为应该依赖于Weblogic 实现,我通读了Weblogic 的大部分文档,但没有我可以得到确认的答案。
  • 我刚学了一个新框架Hibernate,你知道SessionFactory.openSessionSessionFactory.getCurrentSession Transaction上下文和Transaction上下文之外的行为吗?
  • 我认为你应该把它作为一个新的 SO-Question 发布。
【解决方案2】:

唯一正确执行此操作的方法是对该事务中涉及的所有数据库使用事务管理器和两阶段提交 XA 驱动程序。

【讨论】:

  • for all databases involved in this transaction,我这里只有一个数据库(Oracle)。
【解决方案3】:

我的猜测是您在连接上启用了自动提交。这是创建新连接时的默认设置,如此处所述 https://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html

System.out.println(connection1.getAutoCommit());

很可能会打印true

你可以试试

connection1.setAutoCommit(false); 

看看这是否会改变行为。

除此之外,如果您在连接上调用 close() 并且没有事先发出提交或回滚语句,它并没有真正定义会发生什么。因此强烈建议在关闭连接之前发出两者之一,请参阅https://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#close()

编辑 1: 如果 autocommit 为 false,则可能是由于 close 的未定义行为所致。如果切换语句会发生什么? :

st2.executeUpdate();
st2.close();
connection2.close();
st1.executeUpdate();
st1.close();
connection1.close();

编辑 2: 您也可以尝试“正确”的做法:

st1.executeUpdate();
st1.close();
st2.executeUpdate();
st2.close();
tx.commit();
connection1.close();
connection2.close();

如果没有失败,那么您的 UserTransactions 设置有问题。

【讨论】:

  • 我打印出来的值,AutoCommit是假的,在UserTransaction内得到的任何连接都应该是假的。
  • 修改了我的答案,因为 AutoCommit 是错误的。
  • 你检查过这里的答案吗:stackoverflow.com/a/39012274/4823977 ?你能添加数据库版本等吗?
【解决方案4】:

根据您的数据库,这是很正常的情况。

实现UserTransaction 接口的对象表示“逻辑事务”。它并不总是映射到数据库引擎尊重的真实“物理”事务。
例如,有些情况会导致事务隐式提交(以及隐式启动)。在 Oracle 的情况下(不能保证其他数据库),关闭连接就是其中之一。

From Oracle's docs

"如果禁用了自动提交模式并且您关闭了连接 无需明确提交或回滚您的最后更改,然后 隐式 COMMIT 操作正在运行”。

但是隐式提交可能还有其他可能的原因:选择更新、各种锁定语句、DDL 等等。它们是特定于数据库的。

那么,回到我们的代码。
通过关闭连接提交第一个事务。 然后另一个事务由 DML 在第二个连接上隐式启动。它插入非冲突更改,第二个connection.close() 提交它们而不违反PK。 tx.commit() 甚至没有机会提交任何东西(怎么可能呢?连接已经关闭)。

底线:“逻辑”事务管理器并不总能为您提供全貌。
有时事务在没有明确原因的情况下启动和提交。有时它们甚至被数据库忽略。

PS:我假设您使用的是 Oracle,但上述说法也适用于其他数据库。例如,MySQL's list of implicit commit reasons

【讨论】:

  • 是的,我正在使用 Oracle 数据库。我尝试写一个简单的代码,插入后抛出错误(连接关闭后),插入可以成功回滚,如果没有错误,close不会立即关闭连接,它会将关闭委托给要处理的事务管理器。
  • 非常肯定,在我的示例中没有这样的隐式提交。
【解决方案5】:

如果禁用了自动提交模式并且您关闭了连接 无需明确提交或回滚您的最后更改, 然后执行一个隐式的COMMIT 操作。

详情请查看以下链接:

http://in.relation.to/2005/10/20/pop-quiz-does-connectionclose-result-in-commit-or-rollback/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    • 2020-12-10
    • 1970-01-01
    • 1970-01-01
    • 2011-09-23
    相关资源
    最近更新 更多