有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java JPA。如何返回null而不是LazyInitializationException

我有两张“一对多”关系的桌子。我使用Jpa+Spring Jpa。有时我必须从数据库中获取具有内部对象的对象。有时候我不必这么做。存储库总是返回带有内部对象的对象。 我试着从数据库中获取“所有者”,我总是得到固定的书籍;没关系。但当我阅读这本内部书籍的字段时,我得到了LazyInitializationException。如何获取null而不是异常

@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);
}

共 (2) 个答案

  1. # 1 楼答案

    您获得LazyInitializationException是因为您正在访问事务上下文之外的书籍集的内容,很可能是因为它已经关闭。例如:

    通过DAO或Spring数据存储库,在服务类的方法中从数据库中获得所有者:

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

    此时,您有了一个所有者对象,其图书集为空,只有当有人想要访问其内容时才会填充。只有在存在打开的事务时,才能填充图书集。不幸的是,findOne方法已经打开并关闭了事务,因此没有打开的事务,当您执行类似owner.getBooks().size()的操作时,您将得到臭名昭著的LazyInitializationException

    你有几个选择:

    使用@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;
    }
    

    使用fetch=FetchType。急切的

    fetch = FecthType.LAZY定义中有@Column,这就是为什么要延迟加载集合(如果没有指定,这也是JPA默认使用的获取类型)。如果希望在从数据库中获取所有者对象后立即自动填充集合,则应如下定义:

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

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

  2. # 2 楼答案

    LazyInitializationException的目的是在加载的实体与数据库失去连接,但尚未加载现在请求的数据时引发错误。默认情况下,一个实体内的所有集合都是延迟加载的,即在请求时加载,通常是通过对它们调用一个操作(例如size()或isEmpty())

    您应该包装调用存储库的代码,然后在单个事务中使用该实体,以便该实体在事务完成之前不会断开与DB的连接。如果您不这样做,存储库将自己创建一个事务来加载数据,并在之后立即关闭该事务。然后返回的实体没有事务,并且不可能判断ots集合是否有一些元素。而是抛出LazyInitializationException