我在Spring Boot项目中的一个单元测试类中遇到问题,我无法解释。以下是我的测试方法:
// This test creates a record with callback id 5a6775ab4b0af8693ba97c5b
@Test
public void testCreate() throws Exception {
Callback callback = fixtures.getCallback(TestFixtureFactory.EMAILS_SUCCESS);
boolean created = createCallbackDao.createCallback(callback);
assertThat(created).as("Check create callback succeeded").isTrue();
}
// This test creates a record with callback id 5a6775ab4b0af8693ba97c5x and then tries to create it again,
// which is expected to fail with a unique constraint violation.
@Test
public void testAlreadyExists() throws Exception {
Callback callbackA = fixtures.getCallback(TestFixtureFactory.EMAILS_DUPLICATE);
boolean a = createCallbackDao.createCallback(callbackA);
assertThat(a).as("Check first create callback succeeded").isTrue();
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> {
Callback callbackB = fixtures.getCallback(TestFixtureFactory.EMAILS_DUPLICATE);
boolean b = createCallbackDao.createCallback(callbackB);
});
}
首先执行testAlreadyExists()测试并按预期传递,但第二次执行的testCreate()失败,并发生唯一约束违规。
我已经尝试过单独运行每个测试,并且正如预期的那样,都会在执行此操作时通过。
以下是运行两个测试时testCreate()的日志输出:
2018-03-19 13:00:21.347 INFO 10646 --- [ main] c.y.p.apicallback.dao.impl.CallbackDao : Create callback for callback id = 5a6775ab4b0af8693ba97c5b
2018-03-19 13:00:21.347 INFO 10646 --- [ main] c.y.p.apicallback.dao.impl.CallbackDao : Callback=Callback [id=13, callbackId=5a6775ab4b0af8693ba97c5b, ...]
2018-03-19 13:00:21.349 DEBUG 10646 --- [ main] org.hibernate.SQL : insert into api_callback (created, last_modified, callback_id, last_update, message, request_source, state, id) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into api_callback (created, last_modified, callback_id, last_update, message, request_source, state, id) values (?, ?, ?, ?, ?, ?, ?, ?)
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [TIMESTAMP] - [Mon Mar 19 13:00:21 GMT 2018]
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [2018-03-19 13:00:21.311]
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [5a6775ab4b0af8693ba97c5x]
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [TIMESTAMP] - [Tue Jan 23 17:49:31 GMT 2018]
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [5] as [VARCHAR] - [Send email request completed.]
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [8] as [BIGINT] - [7]
2018-03-19 13:00:21.350 WARN 10646 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 23505, SQLState: 23505
2018-03-19 13:00:21.351 ERROR 10646 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Unique index or primary key violation: "API_CALLBACK_U1_INDEX_4 ON API_CALLBACK(CALLBACK_ID) VALUES ('5a6775ab4b0af8693ba97c5x', 1)"; SQL statement:
insert into api_callback (created, last_modified, callback_id, last_update, message, request_source, state, id) values (?, ?, ?, ?, ?, ?, ?, ?) [23505-196]
2018-03-19 13:00:21.351 INFO 10646 --- [ main] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2018-03-19 13:00:21.351 ERROR 10646 --- [ main] c.y.p.apicallback.dao.impl.CallbackDao : Error creating Callback, time(ms)=4!
org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:112) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
以下是调用DAO方法的代码:
@Override
public boolean createCallback(Callback callback) throws Exception {
LOG.info("Create callback for callback id = {}", callback.getCallbackId());
Stopwatch timer = Stopwatch.createStarted();
Session session = null;
Transaction txn = null;
try {
session = sessionFactory.getCurrentSession();
txn = session.getTransaction();
LOG.info("Callback={}", callback);
session.persist(callback);
session.flush();
txn.commit();
LOG.info("CallbackDao.createCallback: company={}, callbackId={}, id={}, time(ms)={}",
callback.getRequest().getUser(), callback.getCallbackId(), callback.getId(), timer.elapsed(TimeUnit.MILLISECONDS));
return true;
} catch (Exception e) {
LOG.error(String.format("Error creating Callback, time(ms)=%d!", timer.elapsed(TimeUnit.MILLISECONDS)), e);
if (txn.getStatus() == TransactionStatus.ACTIVE || txn.getStatus() == TransactionStatus.MARKED_ROLLBACK) {
txn.rollback();
}
throw e;
}
}
API_CALLBACK表在CALLBACK_ID列上具有生成的主键值(ID)和唯一索引。在调用DAO方法以保留记录之前,ID值是从数据库序列派生的。
问题是应该插入数据库的回调记录是按如下方式记录的回调记录:
2018-03-19 13:00:21.347 INFO 10646 --- [ main] c.y.p.apicallback.dao.impl.CallbackDao : Callback=Callback [id=13, callbackId=5a6775ab4b0af8693ba97c5b, ...]
但是,根据Hibernate记录的绑定变量,实际上是尝试插入具有不同id和回调id值的记录:
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [5a6775ab4b0af8693ba97c5x] (should be 5a6775ab4b0af8693ba97c5b)
2018-03-19 13:00:21.350 TRACE 10646 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [8] as [BIGINT] - [7] (should be 13)
在testAlreadyExists()测试期间,ID 7和回调标识5a6775ab4b0af8693ba97c5x的记录被插入到本地(H2)数据库中。
我无法弄清楚为什么DAO类显示它正在使用一个对象实例,但Hibernate试图坚持另一个。有人可以帮忙吗?
答案 0 :(得分:0)
感谢kirinya(请参阅上面的评论),解决方案是使用注释javax.transaction.Transactional
使测试类具有事务性。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { TestDatabaseConfiguration.class })
@Transactional
public class TestCreateCallbackDao {
...
}