Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA. How to return null instead of LazyInitializationException

I have two tables with 'one to many' relationship. I use Jpa + Spring JpaRepository. Sometimes I have to get object from Database with internal object. Sometimes I dont't have to. Repositories always return object with internal objects. I try to get 'Owner' from Database and I always get Set books; It's OK. But when I read fields of this internal Book , I get LazyInitializationException. How to get null instead of Exception?

@Entity
@Table(name = "owners")
@NamedEntityGraph(name = "Owner.books",
attributeNodes = @NamedAttributeNode("books"))
public class Owner implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "owner_id", nullable = false, unique = true)
private Long id;

@Column(name = "owner_name", nullable = false)
private String name;

@OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
private Set<Book> books= new HashSet<>(0);

public Worker() {
}
}



@Entity
@Table(name = "books")
@NamedEntityGraph(name = "Book.owner",
attributeNodes = @NamedAttributeNode("owner"))
public class Book implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "book_id", unique = true, nullable = false)
private Long id;

@Column(name = "book_name", nullable = false, unique = true)
private String name;


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "owner_id")
private Owner owner;

public Task() {
}
}

public interface BookRepository  extends JpaRepository<Book,Long>{

  @Query("select t from Book t")
  @EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
  List<Book> findAllWithOwner();

  @Query("select t from Book t where t.id = :aLong")
  @EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
  Book findOneWithOwner(Long aLong);
}
like image 398
Argamidon Avatar asked Oct 24 '15 12:10

Argamidon


2 Answers

You are getting LazyInitializationException because you are accessing the content of the books Set outside the context of a transaction, most likely because it's already closed. Example:

You get an Owner from the database with your DAO or Spring Data repository, in a method in your Service class:

public Owner getOwner(Integer id) {
    Owner owner = ownerRepository.findOne(id);
    // You try to access the Set here
    return owner;
}

At this point you have an Owner object, with a books Set which is empty, and will only be populated when someone wants to access its contents. The books Set can only be populated if there is an open transaction. Unfortunately, the findOne method has opened and already closed the transaction, so there's no open transaction and you will get the infamous LazyInitializationException when you do something like owner.getBooks().size().

You have a couple of options:

Use @Transactional

As OndrejM said you need to wrap the code in a way that it all executes in the same transaction. And the easiest way to do it is using Spring's @Transactional annotation:

@Transactional
public Owner getOwner(Integer id) {
    Owner owner = ownerRepository.findOne(id);
    // You can access owner.getBooks() content here because the transaction is still open
    return owner;
}

Use fetch = FetchType.EAGER

You have fetch = FecthType.LAZY in you @Column definition and that's why the Set is being loaded lazily (this is also the fetch type that JPA uses by default if none is specified). If you want the Set to be fully populated automatically right after you get the Owner object from the database you should define it like this:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();

If the Book entity is not very heavy and every Owner does not have a huge amount of books it's not a crime to bring all the books from that owner from the database. But you should also be aware that if you retrieve a list of Owner you are retrieving all the books from all those owners too, and that the Book entity might be loading other objects it depends on as well.

like image 66
David Lizárraga Avatar answered Oct 18 '22 09:10

David Lizárraga


The purpose of LazyInitializationException is to to raise an error when the loaded entity has lost connection to the database but not yet loaded data which is now requested. By default, all collections inside an entity are loaded lazily, i.e. at the point when requested, usually by calling an operation on them (e.g. size() or isEmpty()).

You should wrap the code that calls the repository and then works with the entity in a single transaction, so that the entity does not loose connection to DB until the transaction is finished. If you do not do that, the repository will create a transaction on its own to load the data, and close the transaction right after. Returned entity is then without transaction and it is not possible to tell, if ots collections have some elements or not. Instead, LazyInitializationException is thrown.

like image 22
OndroMih Avatar answered Oct 18 '22 11:10

OndroMih