maple_shaft
maple_shaft

Reputation: 10463

Bizarre Spring Transactional JUnit tests with Hibernate entity is null

I have been annoyed and bothered by this problem for a while and finally worked up an example to show what is happening. Hopefully somebody else has the slightest clue what is going on.

I have a Spring Transactional JUnit test with @Rollback(true). The test is wrapped in a HibernateTransaction that rollsback the database changes at the end of the unit test automatically. This appears to be working, however in this bizarre scenario with this one query and only in my unit tests will this one @Transactional Business Logic method return 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);
}

So the method playerBo.getCurrentTeam always returns correctly in the application, and always returns correctly if I place a breakpoint anywhere in the unit test then step through the code one line at a time. It will most of the time fail however when simply running the unit test without debugging.

I thought perhaps there is some racing condition occurring here, but even if I put a Thread.sleep(400000L); statement before calling this Transactional method it will still fail.

Code for the transactional method:

@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;
}

The Transaction attributes are all default for a Spring Hibernate4 TransactionManager.

You can see in the code example that I have clearly created this Team entity and the log shows the generated ID for the new record. I can query the record directly by that ID using HQL and it WILL return, but then this one HQL query above in this Transactional method still will return null unless I step through it in debug mode, then it works.

Is this a problem with nested transactions because I was under the impression that nothing gets rolled back until the outermost transaction is rolledback. Why only on this one particular method? Is it a bug with Hibernate 4 or Spring 3.1.1? I am using MySQL InnoDB, could this be an issue with the way that MySQL InnoDB handles database transactions?

Any suggestions of additional things to try are welcome because I am completely out of ideas here.

Upvotes: 0

Views: 850

Answers (1)

JB Nizet
JB Nizet

Reputation: 692271

My guess is that the problem comes from the use of current_timestamp(). You probably created the TeamPlayer with now as the start date, and if you put a breakpoint, the current timestamp is systematically begger than the start date, whereas if you don't put a breakpoint, the code is fast enough, 10% of the time, to have a the current timestamp equal to the start date of the team player.

Upvotes: 1

Related Questions