JPA。如何返回null而不是LazyInitializationException

Arg*_*don 5 java spring hibernate jpa

我有两个具有“一对多”关系的表。我使用Jpa + Spring JpaRepository。有时我必须从带有内部对象的数据库中获取对象。有时我不必。存储库始终返回带有内部对象的对象。我尝试从数据库中获取“所有者”,并且总是获得Set书籍;没关系。但是,当我阅读此内部Book的字段时,会收到LazyInitializationException。如何获取null而不是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);
}
Run Code Online (Sandbox Code Playgroud)

Dav*_*aga 1

LazyInitializationException之所以会得到这样的结果,是因为您正在访问事务上下文之外设置的书籍内容,很可能是因为它已经关闭。例子:

您可以在 Service 类的方法中使用 DAO 或 Spring Data 存储库从数据库获取 Owner:

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

此时,您有一个 Owner 对象,其中有一个为空的 books Set,并且仅当有人想要访问其内容时才会填充。仅当存在未完成的交易时才能填充账本集。不幸的是,该findOne方法已经打开并关闭了事务,因此没有打开的事务,LazyInitializationException当您执行类似的操作时,您将得到臭名昭著的结果owner.getBooks().size()

您有几个选择:

使用@Transactional

正如 OndrejM 所说,您需要以在同一事务中执行的方式包装代码。最简单的方法是使用 Spring 的@Transactional注释:

@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;
}
Run Code Online (Sandbox Code Playgroud)

使用 fetch = FetchType.EAGER

fetch = FecthType.LAZY您的定义中有@Column,这就是为什么 Set 被延迟加载(如果未指定,这也是 JPA 默认使用的获取类型)。如果您希望在从数据库获取 Owner 对象后立即自动完全填充 Set,您应该像这样定义它:

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

如果该Book实体不是很重并且每个实体都Owner没有大量书籍,那么从数据库中取出该所有者的所有书籍并不是犯罪。但您还应该意识到,如果您检索一个列表,Owner您也会检索所有这些所有者的所有书籍,并且该Book实体也可能正在加载它所依赖的其他对象。

  • 为什么它是一个被接受的答案?作者知道这一点,他问如何才能避免抛出异常。 (9认同)