Java 不同OracleDB连接之间的共享事务

Java 不同OracleDB连接之间的共享事务,java,oracle,junit,transactions,sql2o,Java,Oracle,Junit,Transactions,Sql2o,经过几天的调查,我决定提交这个问题,因为发生的事情显然毫无意义 案例 我的计算机配置了本地Oracle Express数据库。 我有一个JAVA项目,其中包含几个JUnit测试,这些测试扩展了父类(我知道这不是“最佳实践”),父类在@Before方法中打开一个OJDBC连接(使用一个包含10个连接的静态Hikari连接池),并在@After中回滚它 public class BaseLocalRollbackableConnectorTest { private static Logger lo

经过几天的调查,我决定提交这个问题,因为发生的事情显然毫无意义

案例

我的计算机配置了本地Oracle Express数据库。 我有一个JAVA项目,其中包含几个JUnit测试,这些测试扩展了父类(我知道这不是“最佳实践”),父类在@Before方法中打开一个OJDBC连接(使用一个包含10个连接的静态Hikari连接池),并在@After中回滚它

public class BaseLocalRollbackableConnectorTest {
private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
protected Connection connection;

@Before
public void setup() throws SQLException{
    logger.debug("Getting connection and setting autocommit to FALSE");
    connection = StaticConnectionPool.getPooledConnection();
}

@After
public void teardown() throws SQLException{ 
    logger.debug("Rollback connection");
    connection.rollback();
    logger.debug("Close connection");
    connection.close();
}
StacicConnectionPool

public class StaticConnectionPool {

private static HikariDataSource ds;

private static final Logger log = LoggerFactory.getLogger(StaticConnectionPool.class);

public static Connection getPooledConnection() throws SQLException {

    if (ds == null) {
        log.debug("Initializing ConnectionPool");
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(10);
        config.setDataSourceClassName("oracle.jdbc.pool.OracleDataSource");
        config.addDataSourceProperty("url", "jdbc:oracle:thin:@localhost:1521:XE");
        config.addDataSourceProperty("user", "MyUser");
        config.addDataSourceProperty("password", "MyPsw");
        config.setAutoCommit(false);
        ds = new HikariDataSource(config);

    }
    return ds.getConnection();

}
}

该项目有数百个测试(不是并行测试),它们使用此连接(在本地主机上)使用Sql2o执行查询(插入/更新和选择),但事务和连接关闭仅由外部管理(通过上面的测试)。 数据库完全为空,无法进行ACID测试

因此,预期的结果是在数据库中插入一些内容,进行断言,然后回滚。这样,第二次测试将不会发现前一次测试为保持隔离级别而添加的任何数据

问题 同时(按顺序)运行所有测试,90%的测试工作正常。10%的一个或两个测试会随机失败,因为数据库中有以前测试的脏数据(例如重复的唯一数据)。查看日志,之前测试的回滚已正确完成。事实上,如果我检查数据库,它是空的) 如果我在性能更高但JDK和Oracle DB XE相同的服务器上执行此测试,则故障率将增加到50%

这很奇怪,我不知道,因为测试之间的连接不同,每次都调用回滚。JDBC隔离级别是读提交的,因此即使我们使用相同的连接,这也不会产生任何问题,即使使用相同的连接。 所以我的问题是: 为什么会这样?你知道吗?正如我所知,JDBC回滚是同步的吗?或者在某些情况下,即使它没有完全完成,它也可以向前推进

以下是我的主要数据库参数: 处理100 会议172
事务189

如果您的问题只需要“解决”(例如,不需要“最佳实践”),而不管性能如何,只需按顺序完成测试,请尝试设置:

config.setMaximumPoolSize(1);

您可能需要将超时设置得更高,因为测试队列中的测试将等待轮到它并可能超时。我通常不建议这样的解决方案,但您的设置是次优的,这将导致比赛条件和数据丢失。不过,祝您测试顺利。

尝试在Oracle中对所有语句配置审核。然后查找同时活动的会话。我认为测试中存在问题。JDBC回滚是同步的。Commit可以配置为
Commit nowait
,但我认为您在测试中并没有什么特别之处

还要注意并行dml。在同一事务中的一个表上,如果没有提交,您无法并行执行dml+任何其他dml,因为您得到了Ora-12838


你有自动交易吗?测试中的业务逻辑可以手动回滚它们,并且在测试期间,自动事务就像另一个会话,它看不到来自父会话的任何提交。

不确定这是否会修复它,但您可以尝试:

public class BaseLocalRollbackableConnectorTest {
  private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
  protected Connection connection;
  private Savepoint savepoint;

  @Before
  public void setup() throws SQLException{
    logger.debug("Getting connection and setting autocommit to FALSE");
    connection = StaticConnectionPool.getPooledConnection();
    savepoint = connection.setSavepoint();
  }

  @After
  public void teardown() throws SQLException{ 
    logger.debug("Rollback connection");
    connection.rollback(savepoint);
    logger.debug("Close connection");
    connection.close();
    while (!connection.isClosed()) {
      try { Thread.sleep(500); } catch (InterruptedException ie) {}
    }
}

实际上,这里有两个“修复”——在关闭后循环,以确保连接在返回池之前关闭。第二,在测试之前创建一个保存点,然后再进行恢复。

正如所有其他答案所指出的,很难说所提供的信息出了什么问题。此外,即使您设法通过找到当前问题,也不意味着您的测试没有数据错误

但这里有一个替代方案:因为您已经有一个空白的数据库模式,所以可以将其导出到SQL文件。然后在每次测试之前:

  • 删除模式
  • 重新创建架构
  • 输入样本数据(如果需要)
  • 这样可以节省大量调试时间,确保每次运行测试时数据库都处于原始状态。所有这些都可以在脚本中完成

    注意:Oracle Enterprise具有支持您的操作的功能。此外,如果您能够设法使用和诸如此类的数据库,那么您还可以利用其他内存数据库(如)来提高测试速度并保持数据集中的一致性

    编辑:这似乎不可信,但只是以防万一:
    连接.回滚()
    仅在您不调用
    提交时生效()
    在它之前


    我在2-3年前遇到过同样的问题(我花了很多时间来弄清楚这一点)。问题是@Before和@After并不总是连续的。[您可以通过在调试中启动进程并在带注释的方法中放置一些断点来尝试此操作

    编辑:如前所述,我不够清楚。@Before和@After的顺序是在测试前和测试后运行的。问题是在我的情况下,@Before和@After有时会出错

    预期:

    @Before->test1()->@After->@Before->@test2()->@After

    但有时我会经历以下顺序:

    @之前->测试1()->@Before->@After->->@test2()->@After

    我不确定这是不是一个bug。当时我深入研究了它,它看起来像是某种(处理器?)调度相关的魔法。 该问题的解决方案是在单个线程上运行测试,并手动调用init和cleanup进程……类似于以下内容:

    public class BaseLocalRollbackableConnectorTest {
        private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
        protected Connection connection;
    
        public void setup() throws SQLException{
            logger.debug("Getting connection and setting autocommit to FALSE");
            connection = StaticConnectionPool.getPooledConnection();
        }
    
        public void teardown() throws SQLException{ 
            logger.debug("Rollback connection");
            connection.rollback();
            logger.debug("Close connection");
            connection.close();
        }
    
        @Test
        public void test() throws Exception{
            try{
                setup();
                //test
            }catch(Exception e){ //making sure that the teardown will run even if the test is failing 
                teardown();
                throw e;
            }
            teardown();
        }
    }
    
    我还没有测试过它,但一个更优雅的解决方案可能是在同一对象上同步@Before和@After方法。如果您有机会尝试,请更新我。:)


    我希望它也能解决你的问题。

    毕竟你的回答证实了我是
    INSERT INTO TABLE1 (ID,COL1,COL2,COL3) 
      SELECT :myId, T.VAL1, T.VAL2, T.VAL3 
      FROM MyView v 
      JOIN Table2 t on t.ID = v.ID
      WHERE ........
    
    SELECT ID, MAX(MDATE) FROM TABLEV WHERE.... GROUP BY ...