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

2022-09-01 19:33:18

经过几天对这个问题的调查,我决定提交这个问题,因为正在发生的事情显然没有意义。

案例

我的计算机配置了本地 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 隔离级别是 READ COMMITED,因此即使我们使用相同的连接,即使使用相同的连接,也不应产生任何问题。所以我的问题是:为什么会这样?你有什么想法吗?JDBC 回滚是否如我所知是同步的,或者在某些情况下,即使它尚未完全完成,它也可以继续进行?

这些是我的主要数据库参数:处理 100 个会话 172 个事务 189


答案 1

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

编辑:正如托尼奥指出的那样,我不够清楚。@Before和@After的顺序在测试前和测试之后的运行方面是有保证的。问题是在我的情况下,有时@Before和@After搞砸了。

预期:

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

但有时我会遇到以下顺序:

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

我不确定这是否是一个错误。当时我深入研究了它的深度,它似乎是某种(处理器?)调度相关的魔力。在我们的例子中,这个问题的解决方案是在单个线程上运行测试,并手动调用初始化和清理进程...像这样:

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方法。请更新我,如果你有钱斯给它一个尝试。:)

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


答案 2

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

config.setMaximumPoolSize(1);

您可能需要将超时设置得更高,因为测试队列中的测试将等待轮到它,并且可能会超时。我通常不会建议这样的解决方案,但您的设置不是最佳的,这将导致竞争条件和数据丢失。但是,祝你好运。


推荐