spring LazyInitializationException Sping Boot

1hdlvixo  于 2023-06-04  发布在  Spring
关注(0)|答案(3)|浏览(140)

我知道有很多类似的线程在那里,但我只是不能弄清楚从这些线程如何克服这个问题。
我有3个类汽车,品牌,颜色。一辆车只有一个品牌和一系列颜色。品牌有一个汽车列表。颜色没有任何关系。
为了简单起见,没有提供Getters、Setters、ToString和Constructors。我能够将对象保存到数据库中,并且数据库已经填充。


@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一样抓取,一切都运行得很好,但我知道这是一个不好的做法,应该使用懒惰加载。但是我一直得到LazyInitializationException。
我从错误中了解到需要一个会话,但我不知道如何提供一个会话,因为我正在使用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()上抛出;
错误:原因:org.hibernate.LazyInitializationException:无法初始化代理[com.readiness.moita.SrpingJPA.Models.Brand#1] -无会话

polkgigr

polkgigr1#

@OneToMany注解的默认值是FetchType.LAZY,因此您的集合是延迟加载的。
为了能够在检索到对象后访问集合,您需要处于事务上下文中(您需要打开会话)
当您致电:

carRepository.findAll();

在内部创建新的会话,检索对象,并且一旦findAll方法返回,会话就被关闭。
您应该做的是确保无论何时访问Car对象中的惰性集合(toString就是这样做的)都有一个打开的会话。
最简单的方法是让另一个服务处理汽车装载,并使用@Transactional注解showCars方法。由于AOP代理的处理方式,该方法位于另一个服务中。

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

然后你打电话:

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

任何用@Transactional注解的方法从它被调用的那一刻起,直到它在调用者中终止,都会有一个打开的会话。

oxcyiej7

oxcyiej72#

因为BrandFetchType是惰性的,所以它不会自动加载到调用fetchAll()的会话中。要将其自动加载到会话中,您需要:
变更

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

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

Ex

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

如果不想将fetch类型设置为eager,则需要将对toString的调用移动到服务方法Ex

@Component
public CarService implements ICarService {

    @Autowired
    CarRepository carRepository;

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

但是,正确的方法是编写一个条件查询或HQL

dwbf0jvd

dwbf0jvd3#

除了提供的答案之外,由于它是只读操作,我建议使用@Transactional(readOnly = true)
它允许spring进行一些优化。
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#readOnly()

相关问题