Java UserTransaction 中有多个 DB 连接

Java more than one DB connection in UserTransaction

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 内)采用两个不同的连接,它们彼此不认识,而第一个删除显然没有提交,所以第二个语句(插入)应该失败(由于唯一约束),因为两个数据库操作不在同一个连接中,第二个连接无法检测到未提交的更改。但令人惊讶的是,它并没有失败,而且这两个语句都可以完美运行。

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

我猜你在连接上启用了自动提交。这是创建新连接时的默认设置,如此处所述 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: 您也可以尝试 "correct" 方法:

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

如果没有失败,则说明您的 UserTransactions 设置有问题。

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

实现 UserTransaction 接口的对象表示 "logical transaction"。它并不总是映射到数据库引擎尊重的真实 "physical" 事务。
例如,有些情况会导致事务 隐式提交(以及 隐式启动)。对于 Oracle(不能为其他数据库提供担保),关闭连接就是其中之一。

From Oracle's docs:

"If the auto-commit mode is disabled and you close the connection without explicitly committing or rolling back your last changes, then an implicit COMMIT operation is run".

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

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

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

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

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

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

详情请查看下方link:

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

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

中的以下代码片段进行检查
@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