Hibernate试图坚持错误的对象

时间:2018-03-19 13:40:56

标签: java hibernate

我在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试图坚持另一个。有人可以帮忙吗?

1 个答案:

答案 0 :(得分:0)

感谢kirinya(请参阅上面的评论),解决方案是使用注释javax.transaction.Transactional使测试类具有事务性。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { TestDatabaseConfiguration.class })
@Transactional
public class TestCreateCallbackDao {
...
}