懒加载异常 Spring Boot

8

我知道有很多类似的帖子,但是我无法从那些帖子中弄清楚如何解决这个问题。

我有三个类:Car(汽车)、Brand(品牌)和Color(颜色)。 一辆汽车只属于一个品牌并且有一个颜色列表。 品牌有一个汽车列表。 颜色没有任何关系。

为了简单起见,未提供Getter、Setter、ToString和Constructor。 我能够将对象保存到数据库中,并且数据库已经填充好了。

--------------------------------------------------------------------------------

@Entity
@Table(catalog = "spring_project")
public class Car {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String model;

@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable( name = "car_color", catalog = "spring_project",
            joinColumns         = { @JoinColumn(name = "car_id") },
            inverseJoinColumns  = { @JoinColumn(name = "colors_id") }
)
private List<Color> colors = new ArrayList<>();

@ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="brand_id", referencedColumnName="id")
private Brand brand;

--------------------------------------------------------------------------------

@Entity
@Table(catalog = "spring_project")
public class Brand {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@OneToMany(mappedBy = "brand", fetch = FetchType.LAZY)
private List<Car> cars = new ArrayList<>();

--------------------------------------------------------------------------------

@Entity
@Table(catalog = "spring_project")
public class Color {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

--------------------------------------------------------------------------------

如果我像Eager一样获取数据,一切都运行得很好,但我知道这是不好的做法,应该使用Lazy加载。但我一直收到LazyInitializationException。

从错误中我理解需要一个session,但我不知道如何提供一个,因为我使用的是Spring Data JPA,也不知道在哪里声明一个...

@SpringBootApplication
public class SrpingJpaApplication {

private static final Logger log = 
LoggerFactory.getLogger(SrpingJpaApplication.class);

public static void main(String[] args) {
    SpringApplication.run(SrpingJpaApplication.class, args);
}

@Bean
public CommandLineRunner demo(CarRepository carRepository,
                              ColorRepository colorRepository,
                              BrandRepository brandRepository) {
    return (args) -> {

        log.info("Reads all cars....");
        for (Car c : carRepository.findAll()) {
            System.out.println(c.toString());
        }

    };
}

}

非常感谢。

编辑----->>>

错误出现在c.toString();

错误:Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [com.readiness.moita.SrpingJPA.Models.Brand#1] - no Session


可能是重复问题:https://dev59.com/kVUL5IYBdhLWcg3wt6EK - The Head Rush
尝试在你的 demo 方法上加上 @Transactional 注解,这样 Spring 就会处理会话管理。 - Aditya Narayan Dixit
3个回答

6

The default for the @OneToMany annotation is FetchType.LAZY so your collections are loaded lazily.

In order to be able to access the collection after you've retrieved the object you need to be in a transactional context (you need an open session)

When you call:

carRepository.findAll();

internally a new session is created, the object is retrieved and as soon as the findAll method returns the session is closed.

What you should do is make sure you have an open session whenever you access the lazy collection in your Car object (which the toString does).

The simplest way is to have another service handle the car loading and annotate the showCars method with @Transactional. The method is in another service because of the way AOP proxies are handled.

@Service
public CarService {

    final CarRepository carRepository;

    public CarService(CarRepository carRepository) {
        this.carRepository = carRepository;
    }    

    @Transactional
    public void showCars(String... args) {
        for (Car c : carRepository.findAll()) {
            System.out.println(c.toString());
        }
    }   
}

and then you call:

@Bean
public CommandLineRunner demo(CarService carService) {
    return (args) -> service.showCars(args);
}

Any method annotated with @Transactional will have an open session from the moment it is called right until it terminates in the caller.


3

BrandFetchType 是懒加载的,调用 fetchAll() 方法时不会自动加载到会话中。如果要自动加载到会话中,则需要:

更改

@ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="brand_id", referencedColumnName="id")
private Brand brand;

to

@ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)

Ex

@ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="brand_id", referencedColumnName="id")
private Brand brand;

如果您不想将获取类型设置为eager,则需要将调用toString的操作移动到一个服务方法中,例如:

@Component
public CarService implements ICarService {

    @Autowired
    CarRepository carRepository;

    @Transactional
    public void printAllCars() {
        for (Car c : carRepository.findAll()) {
            System.out.println(c.toString());
        }
    }   
}

然而,正确的做法是编写一个条件查询或HQL。


1
我在我的问题中提到了这一点...我希望能够以某种方式将获取设置为延迟,并在需要时通过代码手动打开会话。 - Bruno Miguel
我的回答下面也回答了这个问题。 - Radu Sebastian LAZIN

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接