经过几天的调查后,我决定提交这个问题,因为显然没有任何意义。
案例
我的计算机配置了本地Oracle Express数据库。 我有一个带有几个JUnit测试的JAVA项目扩展了一个父类(我知道它不是一个“最佳实践”),它在@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();
}
}
这个项目有数百个测试(不是并行),使用这个连接(在localhost上)使用Sql2o执行查询(插入/更新和选择),但是只在外部管理连接的事务和clousure(通过上面的测试)。 数据库完全为空以进行ACID测试。
所以预期的结果是在DB中插入一些东西,然后进行断言然后回滚。以这种方式,第二次测试将找不到先前测试添加的任何数据,以保持隔离级别。
问题 一起运行所有测试(按顺序),90%的时间它们正常工作。 10%的一个或两个测试,随机,失败,因为以前的测试数据库中存在脏数据(例如,重复唯一)。查看日志,正确完成以前测试的回滚。事实上,如果我检查数据库,它是空的) 如果我在具有更高性能但相同JDK(相同的Oracle DB XE)的服务器中执行此测试,则此故障率将增加到50%。
这很奇怪,我不知道,因为测试之间的连接不同,每次调用回滚。 JDBC隔离级别是READ COMMITTED,因此即使我们使用相同的连接,即使使用相同的连接也不会产生任何问题。 所以我的问题是: 为什么会这样?你有什么主意吗?如我所知,JDBC回滚是同步的还是在某些情况下它可以继续前进,即使它没有完全完成?
这些是我的主要数据库参数: 过程100 会议172 交易189
答案 0 :(得分:4)
我在2 - 3年前遇到过同样的问题(我已经花了很多时间才能做到这一点)。问题是@Before和@After不是总是真正顺序。 [您可以通过在调试中启动该过程并在带注释的方法中放置一些断点来尝试此操作。
编辑:Tonio指出,我不够清楚。 @Before和@After的顺序在测试之前和之后的运行方面都是保证的。在我的情况下,问题是@Before和@After有时搞砸了。
预期:
@Before - > test1() - > @After - > @Before - > @ test2() - > @After
但有时我会遇到以下顺序:
@Before - > test1() - > @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方法。如果您有尝试,请更新我。 :)
我希望它也能解决你的问题。
答案 1 :(得分:1)
如果您的问题只需要“解决”(例如,不是“最佳做法”)而不考虑性能,只是按顺序完成测试,请尝试设置:
config.setMaximumPoolSize(1);
您可能需要设置更高的超时,因为测试队列中的测试将等待轮到并且可能会超时。我通常不建议像这样的解决方案,但你的设置不是最理想的,它会导致竞争条件和数据丢失。但是,测试祝你好运。
答案 2 :(得分:1)
尝试对Oracle中的所有语句配置审核。然后找到同时生活的会话。我认为测试中存在问题。 JDBC回滚是同步的。提交可以配置为commit nowait
但我不认为你在测试中做得特别。
同时注意并行dml。在同一事务中的一个表上,您无法执行并行dml +任何其他没有提交的dml,因为您获得了Ora-12838。
你有自动交易吗?测试中的业务逻辑可以手动回滚它们,在测试期间,自动事务就像另一个会话,并且它看不到来自父会话的任何提交。
答案 3 :(得分:1)
不确定这是否会解决问题,但您可以尝试:
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) {}
}
}
真的有两个'修复'在关闭之后循环,以确保在返回池之前连接已关闭。其次,在测试之前创建一个保存点,然后将其恢复。
答案 4 :(得分:1)
正如所有其他答案所指出的那样,很难说所提供的信息出了什么问题。此外,即使您设法通过audit查找当前问题,也不代表您的测试没有数据错误。
但是这里有另一种选择:因为你已经有了一个空白的数据库模式,你可以将它导出到SQL文件。然后在每次测试之前:
它可以节省大量的调试时间,确保每次运行测试时数据库都处于原始状态。所有这些都可以在脚本中完成。
注意:Oracle Enterprise有flashback function来支持您的操作。此外,如果您可以设法使用Hibernate之类的内容,那么您可以使用其他内存数据库(例如HSQLDB)来提高测试速度并保持数据的一致性集。
编辑:这似乎难以置信,但以防万一:
connection.rollback()
只有在您不致电commit
()时才会生效 在它之前。
答案 5 :(得分:1)
在你的回答得到所有确认之后,我对单元测试中的Rollbacks和交易行为并不感到生气,我深深地检查了所有查询和所有可能的原因并且幸运(是的,幸运的是......即使我为此感到羞耻,我让自己的思绪自由)所有的工作都按预期进行(交易,之前,之后等)。
有些查询可以获得一些复杂视图的结果(并从根本上深入配置到DAO层中)来识别单行信息。
此视图基于MAX of a TIMESTAMP
,以便识别特定事件的最新情况(在现实生活中,几个月后发生的事件)。
准备数据库以进行单元测试,每个测试按顺序添加这些事件。 在某些情况下,当同一事务下的这些插入查询特别快时,在同一毫秒中添加与同一对象相关的更多事件(使用JODA DateTime手动添加TIMESTAMP)和日期的MAX,返回两个或更多价值观。 出于这个原因,它解释了这样一个事实:在性能更高的计算机/服务器上,这种情况比较慢的计算机/服务器更频繁。 此视图用于更多测试,并且取决于测试,错误是不同的并且是随机的(将NULL值添加为主键,重复的主键等)。
例如:在以下INSERT SELECT
查询中明显存在以下错误:
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 ........
之后将参数myId添加为Sql2o参数
MyView是
SELECT ID, MAX(MDATE) FROM TABLEV WHERE.... GROUP BY ...
当视图由于相同的最大日期返回至少2个结果时,它会失败,因为ID是固定的(由开头的序列生成但是第二次使用参数存储)。这会生成违反的PK约束。
这只是一个案例,但由于这种随机行为让我(和我的同事)发疯...
在这些事件插入之间添加1毫秒的睡眠,它是固定的。现在我们正在努力找到一个不同的解决方案,即使这种情况(在同一毫秒内交互两次的用户)也不会在生产系统中发生 但重要的是,没有像往常一样发生魔术!
现在你可以侮辱我:)。
答案 6 :(得分:-1)
你可以做一件事增加号码。最大池大小中的连接数,并在提交操作的同一位置回滚操作,而不是在@after语句中使用它。 希望它能奏效。