我已经被这个问题困扰了一段时间,并最终编写了一个例子来展示正在发生的事情。希望其他人能够发现正在发生的事情。
我有@Rollback(true)
的Spring Transactional JUnit测试。测试包装在一个HibernateTransaction中,它会在单元测试结束时自动回滚数据库更改。这似乎是有效的,但是在这个奇怪的情况下,这个查询只有在我的单元测试中,这一个@Transactional
业务逻辑方法才会返回null。
@Test
@Rollback(true)
public void testObscureIssue() throws Exception {
// Not important...
l = createLeague();
t1 = createTeam(l);
User u = userBo.getUser(1L, false);
Player player = TestUtils.getInstance().createTestData(Player.class, 1).get(0);
player.setUser(u);
player.setGender("M");
player.setStartingActivityLevel(ActivityLevelEnum.Sedentary);
playerBo.addOrUpdate(player);
TeamPlayer tp = new TeamPlayer(t1, player);
leagueStructureBo.addOrUpdate(tp);
// This test will pass 10% of the time, seemingly random. Randomness only inside of unit test
Team t = playerBo.getCurrentTeam(player.getPlayerID());
if (t == null) throw new OutOfMemoryError("What is this... I don't even...");
Team expected = playerBo.getCurrentTeam(player.getPlayerID());
assertNotNull(expected);
assertEquals(t1, expected);
}
因此方法playerBo.getCurrentTeam
总是在应用程序中正确返回,并且如果我在单元测试中的任何位置放置断点然后一次一行地执行代码,则始终正确返回。然而,在没有调试的情况下简单地运行单元测试时,大部分时间都会失败。
我想也许这里会出现一些竞争条件,但即使我在调用此Transactional方法之前放了一个Thread.sleep(400000L);
语句,它仍然会失败。
交易方法的代码:
@Override
@Transactional
public Team getCurrentTeam(long playerId) {
String qry = "select t from Team as t inner join t.teamPlayers as tp " +
"inner join tp.player as tpp where tpp.playerID = :playerId and (((current_timestamp() between tp.startDate and tp.endDate " +
"and tp.endDate is not null) or (tp.endDate is null and current_timestamp() > tp.startDate)))";
Object wtf = sessionFactory.getCurrentSession().createQuery(qry)
.setParameter("playerId", new Long(playerId)).uniqueResult();
return (Team)wtf;
}
事务属性都是Spring Hibernate4 TransactionManager的默认属性。
您可以在代码示例中看到我已经清楚地创建了此Team实体,日志显示了新记录的生成ID。我可以使用HQL直接通过该ID查询记录,它将返回,但是此Transactional方法中的上述一个HQL查询仍将返回null,除非我在调试模式中单步执行它,然后它可以工作。
这是嵌套事务的问题,因为我的印象是在最外层事务回滚之前不会回滚任何内容。为什么只有这一个特定的方法呢?这是Hibernate 4或Spring 3.1.1的错误吗?我正在使用MySQL InnoDB,这可能是MySQL InnoDB处理数据库事务的方式的问题吗?
欢迎任何其他尝试的建议,因为我完全没有想法。
答案 0 :(得分:1)
我的猜测是问题来自于使用current_timestamp()
。你可能创建了TeamPlayer,现在作为开始日期,如果你设置一个断点,当前时间戳系统地比开始日期更早,而如果你没有设置断点,代码就足够了,10%的时间,使当前时间戳等于团队成员的开始日期。