我有两张“一对多”关系的桌子。我使用Jpa + Spring JpaRepository。有时我必须从内部对象的数据库中获取对象。有时候我没必要。存储库始终返回具有内部对象的对象。 我试图从数据库中获取“所有者”,我总是得到Set书;没关系。但是当我读到这本内部书的字段时,我得到了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);
}
答案 0 :(得分:1)
LazyInitializationException
的目的是在加载的实体失去与数据库的连接但尚未加载现在请求的数据时引发错误。默认情况下,实体内的所有集合都是懒惰加载的,即在请求时,通常通过调用它们的操作(例如size()或isEmpty())。
您应该包装调用存储库的代码,然后在单个事务中使用该实体,以便在事务完成之前,实体不会断开与DB的连接。如果不这样做,存储库将自己创建一个事务来加载数据,然后立即关闭事务。然后返回的实体没有交易,如果ots集合中有一些元素,则无法判断。而是抛出LazyInitializationException
。
答案 1 :(得分:0)
您将获得LazyInitializationException
因为您正在访问交易环境之外的图书内容,很可能是因为它已经关闭。例如:
您可以在Service类的方法中使用DAO或Spring Data存储库从数据库中获取所有者:
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You try to access the Set here
return owner;
}
此时您有一个所有者对象,其中一个书籍Set为空,只有当有人想要访问其内容时才会填充。只有在打开交易时才能填充图书集。不幸的是,findOne
方法已经打开并且已经关闭了交易,因此没有开放交易,当您执行类似LazyInitializationException
的操作时,您将获得臭名昭着的owner.getBooks().size()
。
您有几个选择:
正如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;
}
您fetch = FecthType.LAZY
定义中有@Column
,这就是懒惰加载Set的原因(如果没有指定,这也是JPA默认使用的提取类型)。如果您希望在从数据库获取Owner对象后立即自动完全填充Set,您应该像这样定义它:
@OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();
如果Book
实体不是很重,并且每个Owner
都没有大量的书籍,那么从该数据库中提取该所有者的所有书籍并不构成犯罪。但是您还应该知道,如果您检索Owner
列表,那么您正在从所有这些所有者中检索所有书籍,并且Book
实体可能正在加载其依赖的其他对象。