UserTransaction中的Java多个数据库连接

时间:2016-08-08 07:40:58

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

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

任何人都可以帮忙解释一下吗?可以通过任何配置来实现这一结果吗?或者我的理解是否错误?

5 个答案:

答案 0 :(得分:6)

由于您的应用程序在weblogic服务器中运行,因此java-EE-container正在为您管理事务和连接。如果您在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

答案 1 :(得分:2)

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

答案 2 :(得分:0)

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

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

很可能会打印true

你可以尝试

connection1.setAutoCommit(false); 

并查看是否会改变行为。

除此之外,如果你在连接上调用close()并且之前没有发出commit或rollback语句,那么它并没有真正定义。因此,强烈建议在关闭连接之前发出其中一个,请参阅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设置就出了问题。

答案 3 :(得分:0)

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

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

From Oracle's docs

  

"如果禁用自动提交模式并关闭连接   没有明确提交或回滚您的上次更改,那么   运行隐式COMMIT操作"。

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

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

底线:"逻辑"交易经理并不总能全面了解您的情况 有时,事务在没有明确原因的情况下启动和提交。有时他们甚至被DB忽略了。

PS:我以为你曾经使用过Oracle,但是对于其他数据库来说也是如此。例如,MySQL's list of implicit commit reasons

答案 4 :(得分:0)

如果禁用自动提交模式并关闭连接 没有明确提交或回滚您的上次更改, 然后执行隐式COMMIT操作。

请查看以下链接了解详情:

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