33

I have an small console application and I am using spring-data-jpa with hibernate. I really can not figure out how to lazy initialize collections when using spring-data-jpa with its repositories, in an standalone console application. Here is some of my code:

@Entity
public class User {
...
    @OneToMany(cascade=CascadeType.ALL)
    @JoinColumn(name="USER_ORDER_ID")
    private Set<Order> orders = new HashSet<Order>();
...
}

repository:

public interface UserRepository extends PagingAndSortingRepository<User, Long> {

    public ArrayList<User> findByFirstNameIgnoreCase(String firstName);
}

service impl:

@Service
@Repository
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserRepository userRepository;

public ArrayList<User> findByFirstNameIgnoreCase(String firstName) {
    ArrayList<User> users = new ArrayList<User>();
    users = userRepository.findByFirstNameIgnoreCase(firstName);
    return users;
}

my main method:

...
user = userRepository.findByFirstNameIgnoreCase("john").get(0);
orders = user.getOrders();
for (Order order : orders) {
  LOGGER.info("getting orders: " + order.getId());
}    

the foreach loop gets an exception:

EVERE: failed to lazily initialize a collection of role: com.aki.util.User.orders, no session or session was closed org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:

Please note that I do not have this problem when running this from an webapp with some kind of OpenSessionInViewFilter.

ThinkingStiff
  • 64,767
  • 30
  • 146
  • 239
aki
  • 1,731
  • 2
  • 19
  • 24
  • Check this: http://stackoverflow.com/questions/15359306/how-to-load-lazy-fetched-items-from-hibernate-jpa-in-my-controller – Ryan S Jan 05 '15 at 19:29

3 Answers3

17

One solution can be to make User.orders an eagerly fetched collection by

@OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Order> orders = new HashSet<Order>();

Entity associations are lazily loaded by default. This means that the orders Set is actually just a proxy object that won't get initialized until you invoke a method on it. This is good, because the associated Order objects won't get loaded unless they are needed. However, this can cause problems, if you try to access the uninitialized collection outside of a running transaction.

If you know that in most of the cases you will need the User's Orders, it makes sense to make the association eagerly fetched. Otherwise you will have to ensure that the collection gets initialized/loaded inside a transaction. The OpenSessionInViewFilter you mentioned makes sure that the transaction stays open during the request processing, that is why you don't have this issue in yout webapp.

In case you must keep it lazily loaded, try using Spring's TransactionTemplate to wrap the code in your main method:

TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus status) {
    ...
    }
});
zagyi
  • 17,223
  • 4
  • 51
  • 48
  • 5
    this is not what I am looking for. perhaps I wasn't clear that I want to do lazy initialization. I have edited my question. – aki Feb 18 '13 at 15:12
  • In that case, see my edited answer for the use of a `TransactionTemplate`. – zagyi Feb 18 '13 at 15:25
  • I really hate working with TransactionTemplate, but it works. No other suggestions? – aki Feb 18 '13 at 16:43
  • I guess not. Thanks zagyi. – aki Feb 19 '13 at 15:46
  • I'm affraid I don't have any other sound idea. Of course you can still try to do some refactoring, and put all the code that has to run inside a transaction into a higher level service class that is `@Transactional` annotated, and that you can call from `main()`. Anyway thanks for accepting my answer. – zagyi Feb 19 '13 at 16:08
  • I already tried that but it had no effect. How is @Transactional going to influence a session to the database? – aki Feb 19 '13 at 23:12
  • 4
    Annotating a method with `@Transactional` does _roughly_ the same as wrapping it in a `TransactionTemplate`, which is basically opening/committing a transaction. If your `@Transactional` had no effect, it might be for several different reasons. One typical would be that you put it on a method that is not an implementation of an interface method. (Remember that Spring AOP uses JDK proxies by default, which means it can only affect methods defined on interfaces.) – zagyi Feb 20 '13 at 00:01
  • as you can see, my UserServiceImpl is implementation of UserService. still it does not work. – aki Feb 20 '13 at 07:42
  • Based on your original description `@Transactional` on `UserServiceImpl` _does_ work, otherwise you would already get an exception when calling `userRepository.findByFirstNameIgnoreCase()` from main(). The fact that it only happens when calling `order.getId()` in the foreach loop proves this. – zagyi Feb 20 '13 at 09:16
  • I was talking in terms of sessions. Method that is anotated with @Transactional does not guarantee that the collection will be lazy loaded. this method still fails to lazy load the collection `@Transactional public getSomeOrders(Long userId) { User user = userRepository.findOne(userId); Set userOrders = user.getOrders; };` – aki Feb 20 '13 at 09:57
  • 4
    The reason why your `getSomeOrders()` method above fails to initialize the `userOrders` set is because `user.getOrders()` only returns a proxy instead of a real set (that's the whole point of lazy loading). You must call a method on the proxy in order to get it initialized. A common technique is to call `size()`. So try adding `userOrders.size()` and it will work. – zagyi Feb 20 '13 at 16:44
10

However, I found a way. This method is inside my service implementation:

public Set<Order> fetchUserOrders(Long userId) {
    User user = userRepository.findOne(userId);
    Hibernate.initialize(user.getOrders());
    Set<Order> orders = user.getOrders();
    return orders;
}

Note: this is as @zagyi sugested in one of his comments, but also pay attention to the statement: Hibernate.initialize(user.getOrders()); without this the collection still wouldn't be initialized and you will get an error.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
aki
  • 1,731
  • 2
  • 19
  • 24
  • 9
    Just a remark: `Hibernate.initialize()` will tie your application to Hibernate. It's alright if you don't care. In case you do care, use `user.getOrders().size()` instead. While this won't reflect your intention at all, it doesn't rely on non-JPA calls, and you can always declare a helper method with a more descriptive name which receives a collection and just calls `size()` on it. – zagyi Feb 20 '13 at 16:51
  • 1
    That works. You are right, it is much better to use user.getOrders().size. Not that I am going to switch, from hibernate, anytime soon. I could swear that I tried it before. Although I am not sure that it was from the service layer :). Anyway thank you very much for your help. – aki Feb 20 '13 at 18:52
  • Does this mean this goes into the for loop to get all the usersWithOrders ? Lets say I am pulling findAll on Users, that means usersWithOrders, do I have to do Hibernate.initialize on every user by looping ? I know EAGER would work, but in case of Lazy, is this the only way ? – PavanSandeep Mar 09 '15 at 03:58
  • 3
    Neither Hibernate.initialize nor .getter().size() works when it is spring-data-jpa and hibernate, I am writing this just to save some others' time... – Tiina Mar 03 '17 at 02:50
  • @Tina Did you fix it? – Kay Mar 30 '18 at 07:13
-3

This worked for me:

@Component
public class Test {

    @Inject 
    OrderDAO orderDAO;

    @PersistenceUnit
    private EntityManagerFactory emf;

    @PostConstruct
    public void test(){
        EntityManager em= emf.createEntityManager();
        for (Order o:orderDAO.findAll()){
            o=em.find(o.getClass(),o.getId());
            System.out.println(o.getLazyField());
        }
        em.close();
    }
}
DD.
  • 21,498
  • 52
  • 157
  • 246