简体   繁体   中英

JUnit hibernate: testing lazy associations fetched

I have a User model with a lazily-loaded Sites collection:

@Entity
public class User {

    @Id
    @GeneratedValue
    private Integer id;

    @OneToMany(fetch = FetchType.LAZY)
    private Set<Site> sites;

    ...

and a HQL query in my UserDao which loads a user by id, and also fetches the associated sites:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Query;
import org.springframework.stereotype.Repository;

@Repository
public class userDaoImpl implements UserDao() {

    @Inject
    private SessionFactory sessionFactory;

    public Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }

    public User findByIdFetchSites(final Integer id) {
        String queryString = "SELECT user FROM User user LEFT JOIN FETCH user.sites WHERE user.id = :id";
        Query qry = getCurrentSession().createQuery(queryString).setInteger("id", id);
        return qry.uniqueResult();
    }

    ...

I want to test that the sites are being fetched along with the user when I run this Dao method. I have a JUnit test class running against HSQLDB, as follows:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:/test-context.xml" })
@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)
@Transactional
public class UserDaoImplTest {

@Inject private UserDaoImpl userDaoImpl;

@Test
public void retrievesUserFetchesSites() {
    Set<Site> sites = new HashSet<Site>();
    ...

    User user = new User();
    user.setSites(sites);
    userDaoImpl.saveOrUpdate(user); 

    User retrievedUser = userDaoImpl.findByIdFetchSites(1);

    assertTrue(retrievedUser.getSites().containsAll(sites));
}

Where @Transactional is the spring transactional annotation (org.springframework.transaction.annotation.Transactional). The test passes even when I run it against a simple findUserById DAO method, for which the sites are not explicitly fetched:

public User findById(final Integer id) {
    String queryString = "SELECT user FROM User user WHERE user.id = :id";
    Query qry = getCurrentSession().createQuery(queryString).setInteger("id", id);
    return qry.uniqueResult();
}

I'm not entirely sure why the sites are being fetched before the assertion (I can't see it in a select statement in the logs), but what I think I want to do is close the current session before the assertion occurs; something like:

@Test
public void retrievesUserFetchesSites() {
    Set<Site> sites = new HashSet<Site>();
    ...

    User user = new User();
    user.setSites(sites);
    userDaoImpl.saveOrUpdate(user); 

    User retrievedUser = userDaoImpl.findByIdFetchSites(1);

    userDaoImpl.getCurrentSession().close();

    assertTrue(retrievedUser.getSites().containsAll(sites));
}

However, when I tried this I got the following exception:

org.springframework.transaction.TransactionSystemException: Could not roll back Hibernate transaction; nested exception is org.hibernate.TransactionException: rollback failed
at org.springframework.orm.hibernate4.HibernateTransactionManager.doRollback

which I assume is due to @DirtiesContext (I need a clean db before each test). How can I ensure that hibernate doesn't do lazy loading for my associations during the test?

The problem is that, in the same transaction, you create a user with sites, and then retrieve this user. What happens is that the persist() call puts the user with its sites in the session cache, and the query, whatever it does, returns the user that is already in the cache.

So you have the following solutions:

  • Save the user and its sites in a first transaction, then call your DAO method in a second transaction and use Hibernate.isInitialized() to test that the collection of users is loaded by the query. Note that calling retrievedUser.getSites().containsAll(sites) will trigger the lazy-loading of the sites, and is thus not a correct way to test that the collection is eagerly fetched by the query.
  • Save the user and its sites, the clear the session, then call your DAO method, then use Hibernate.isInitialized() to test that the collection of users is loaded by the query.
  • Make the test non-transactional, and the DAO transactional, so that the DAO returns detached entities. Then getting elements from the collection will throw an exception if the DAO has not fetched the collection eagerly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM