@Transactional 在 Spring 使用 CrudRepository 启动时不起作用

@Transactional not working in Spring Boot with CrudRepository

我试图在我的实体之间实现双向关系。

学生

@Table(name = "students")
@Entity
public class Student {

    @Id
   // @GeneratedValue(strategy = GenerationType.AUTO)
    private long album;
    @NotNull
    private String name;
    @NotNull
    private String surname;

    @OneToMany(mappedBy = "student", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
    private List<StudentSection> studentSections;

    @Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
    public void addSection(Section section){
        if(this.studentSections == null){
            this.studentSections = new ArrayList<>();
        }
        StudentSection studentSectionToAdd = new StudentSection();
        studentSectionToAdd.setStudent(this);
        studentSectionToAdd.setSection(section);
        this.studentSections.add(studentSectionToAdd);   //here
        section.addStudentSection(studentSectionToAdd);
    }
}

多对多关系中的连接实体

@Table(name = "student_section")
@Entity
public class StudentSection {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private Integer grade;
    private Date date;

    @NotNull
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
    @JoinColumn(name = "student_id")
    private Student student;

    @NotNull
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
    @JoinColumn(name = "section_id")
    private Section section;

}

和部分

@Table(name = "sections")
@Entity
public class Section {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NotNull
    private String name;
    @NotNull
    private Integer sizeOfSection;
    @NotNull
    private Boolean isActive;

    @OneToMany(mappedBy = "section", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
    private List<StudentSection> studentSections;

    void addStudentSection(StudentSection studentSection){
        if(this.studentSections == null){
            this.studentSections = new ArrayList<>();
        }
        this.studentSections.add(studentSection);
    }

}

我 运行 遇到 Student.addSection() 方法的问题。尝试执行它时,我在 this.studentSections.add(studentSectionToAdd); 行收到一个错误,说 failed to lazily initialize a collection of role: Student.studentSections, could not initialize proxy - no Session 我阅读了它并发现解决此问题的最佳方法是向该方法添加 @T运行sactional 注释,但是它没有改变任何东西,我无法让它工作。 我还尝试将 Student.addSection() 方法移动到 StudentServiceImpl

@Service
@Primary
public class StudentServiceImpl implements StudentService {

    protected StudentRepository studentRepository;
    @Autowired
    public StudentServiceImpl(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = true, noRollbackFor = Exception.class)
    public void addSection(Student student, Section section) {
        if (student.getStudentSections() == null) {
            student.setStudentSections(new ArrayList<>());
        }
        StudentSection studentSectionToAdd = new StudentSection();
        studentSectionToAdd.setStudent(student);
        studentSectionToAdd.setSection(section);
        student.getStudentSections().add(studentSectionToAdd);
        //section.addStudentSection(studentSectionToAdd);

    }
}

但我仍然遇到错误。

我也在使用 CrudRepository 从数据库中检索实体。

@Repository
public interface StudentRepository extends CrudRepository<Student, Long> {
    Student findByName(String name);
}

这是我调用方法的地方

@Component
public class DatabaseLoader implements CommandLineRunner {

    private final StudentRepository studentRepository;
    private final SectionRepository sectionRepository;
    private final StudentSectionRepository studentSectionRepository;
    private final StudentService studentService;

    @Autowired
    public DatabaseLoader(StudentRepository studentRepository, SectionRepository sectionRepository, StudentSectionRepository studentSectionRepository,
                            StudentService studentService) {
        this.studentRepository = studentRepository;
        this.sectionRepository = sectionRepository;
        this.studentSectionRepository = studentSectionRepository;
        this.studentService = studentService;
    }

    @Override
    public void run(String... strings) throws Exception {

        //Testing entities
        Student student =  new Student();
        student.setAlbum(1L);
        student.setName("student");
        student.setSurname("test");
        this.studentRepository.save(student);

        Section section = new Section();
        section.setName("section");
        section.setSizeOfSection(10);
        section.setIsActive(true);
        this.sectionRepository.save(section);

        //end
        //Adding Student to a Section test
        Student student1 = studentRepository.findByName("student");
        //student1.setStudentSections(this.studentSectionRepository.findAllByStudent(student1));
        Section section1 = sectionRepository.findByName("section");
        //section1.setStudentSections(this.studentSectionRepository.findAllByStudent(student1));
        studentService.addSection(student1, section1);
        this.studentRepository.save(student1);
        //end test

    }
}

此外,当我从这里的数据库中检索 StudentSection 列表并将它们设置为在添加新关系之前将它们设置为两个对象时,它工作正常,但这并不是我真正想要的解决方案。

关于这个 @Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class) public void addSection(Section section){

@Transactional 仅适用于 spring 管理的 bean,实体不受 spring.

管理

你得到这个异常是因为你尝试在会话外加载惰性关系(因为你的实体实际上处于分离状态)。

重新附加 --> entityManager.merge(student);

但最好的办法是在查询时加载关系。例如使用 EntityGraph -->

@EntityGraph(attributePaths="studentSections") Student findByName(String name);

问题是从 run() 方法到 studentRepositorystudentService 的每次调用都是分开的 sessions/transactions.

这实际上就像您这样做一样:

...
beginTransaction();
this.studentRepository.save(student);
commit();

...
beginTransaction();
this.sectionRepository.save(section);
commit();

beginTransaction();
Student student1 = studentRepository.findByName("student");
commit();

beginTransaction();
Section section1 = sectionRepository.findByName("section");
commit();

// This does it's own transaction because of @Transactional
studentService.addSection(student1, section1);

beginTransaction();
this.studentRepository.save(student1);
commit();

由于这里的transaction = session,意味着student1被detach,懒加载的studentSections集合无法在session外按需加载,所以代码失败。

插入一个新学生和一个新部分并将它们关联起来实际上应该是一个事务,因此如果后面的步骤失败,则全部回滚。

这基本上意味着您希望整个 run() 方法成为一个事务,因此在您的情况下,run() 方法应该是 @Transactional,而不是 addSection()方法。

通常,在 3-tiered approach 中,您会将事务边界放在 service 层上:

  • 表示层。这是@Controllerclasses,或run()简单命令的方法-行程序.

  • 逻辑层。 这是 @Service classes。这是你放@Transactional的地方,所以每个服务调用都是一个原子事务,即它要么成功要么失败,就数据库更新而言,没有一半更新。

  • 数据层。这是@Repository@Entity classes.

因此,您应该在 run() 方法中保留 StudentSection objects 的实例化和初始化,但是其余代码,包括。 save(),应移至@Service class.

中的单个方法