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();
}
}
}
我有一个网络应用,DAO
以DataSource
为对象创建单独的连接以执行数据库操作。
所以我有UserTransaction
,里面有两个DAO
对象做分离动作,第一个是删除,第二个是插入。删除是删除一些记录以允许插入,因为插入将插入相同的主键数据。
我取出DAO
图层并将逻辑转换为上面的代码。有一点我无法理解,基于上面的代码,插入操作应该失败,因为代码(在UserTransaction
内部)采用两个不同的连接,他们彼此不认识,并且第一次删除避难所明显提交,因此第二个语句(插入)应该失败(由于唯一约束),因为两个数据库操作不在同一连接中,第二个连接不能检测未提交的更改。但令人惊讶的是,它并没有失败,这两种说法都可以完美地运作。
任何人都可以帮忙解释一下吗?可以通过任何配置来实现这一结果吗?或者我的理解是否错误?
答案 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(无法保证其他数据库),关闭连接就是其中之一。
"如果禁用自动提交模式并关闭连接 没有明确提交或回滚您的上次更改,那么 运行隐式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/