Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data, JPA @ManyToOne lazy initialization not working

I know there are many similar questions about this trouble but nothing works for me.

I have @ManyToOne relationship between Aim and User.

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false, updatable = false)
private User user;

and

@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Collection<Aim> userAims;

respectively.

@Override
@Transactional(propagation = Propagation.REQUIRED)
@PreAuthorize("isAuthenticated() and principal.user.isEnabled() == true")
public Aim findById(String aimId) throws NumberFormatException, EntityNotFoundException {
    Aim aim = null;
    try {
        aim = aimRepository.findOne(Long.parseLong(aimId));
    } catch (NumberFormatException e) {
        throw new InvalidDataAccessApiUsageException(e.getMessage(), e);
    }
    if (aim == null) throw new EntityNotFoundException("Aim with id: " + aimId + " not found!");
    return aim;
}

@OneToMany associations work fine with lazy fetching. Method isn't nested to another @Transactional method so @Transactional works fine.

enter image description here

enter image description here

So the record exists.

  1. Classes User and Aim aren't final and implement Serializable
  2. Some sources advice to put annotations on getters. It also doesn't work.
  3. @Fetch(FetchMode.SELECT) the same situation =\
  4. Query via Hibernate results the same, but HQL query with left join fetch works fine
  5. My FK is ON UPDATE CASCADE ON INSERT CASCADE
  6. optional = false also tried...

Pay attention that I haven't the LazyInitException



Thanks in advance!

like image 982
InsFi Avatar asked May 26 '15 23:05

InsFi


1 Answers

I'm guessing from the code in your findById method, and by the reference to "lazy initialization not working" in the title, that you are wanting to find an Aim object by it's numeric Id, along with the associated User object.

In order to do this with lazy-loading, you need to 'get' the associated object, and (most importantly) you need to 'get' one of the associated entity's fields.

So the code inside the try block should be:

aim = aimRepository.findOne(Long.parseLong(aimId));
if (aim != null && aim.getUser() != null) {
    aim.getUser().getUserId(); // doesn't need to be assigned to anything
}

Alternatively, if you have a logger available you can use the userId in a debug or trace log message:

if (aim != null && aim.getUser() != null) {
    logger.debug("Lazy-loaded User " + aim.getUser().getUserId());
}

This has the added benefit that you can debug how things are lazy-loaded.

By the way, we found out the hard way that making a find routine throw an Exception when it doesn't find something is a bad idea. This is because you might want to use the find routine to find out if an Entity does NOT exist. If that is happening within a transaction, your exception may trigger an unwanted rollback (unless you specifically ignore it). Better to return null and check for that instead of using a try ... catch.

like image 166
DuncanKinnear Avatar answered Sep 22 '22 05:09

DuncanKinnear