@Transactional не работает в Spring Boot с CrudRepository - PullRequest
0 голосов
/ 11 апреля 2020

Я пытался реализовать двунаправленные отношения между моими сущностями.

Студент

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

связующий объект в связи ManyToMany

@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 , что я прочитал об этом и обнаружил, что лучший способ исправить это - добавить аннотацию @Transactional к методу, однако это ничего не изменило и я не могу заставить его работать. Я также попытался переместить метод 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 из базы данных здесь и устанавливаю их в оба объекта перед добавлением нового отношения, все работает нормально, но это не совсем то решение, к которому я иду.

Ответы [ 2 ]

1 голос
/ 12 апреля 2020

Проблема в том, что каждый вызов от run() метода до studentRepository и studentService - это отдельные сеансы / транзакции.

Это фактически так, как если бы вы сделали это:

...
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();

Так как транзакция = сеанс здесь, это означает, что student1 отсоединен, и что загруженная с отложенной загрузкой коллекция studentSections не может быть загружена по требованию вне сеанса, и, следовательно, код завершается ошибкой.

Вставка нового студента и нового раздела и их привязка должны быть действительно одной транзакцией, поэтому, если последующий шаг завершится неудачей, все откатывается.

Что означает, что вы хотите, чтобы весь метод run() был единым целым транзакции, поэтому в вашем случае это run() метод, который должен быть @Transactional, а не addSection() метод.

Как правило, при 3-уровневом подходе вы установит границы транзакций для слоя service :

  • Уровень представления. Это @Controller классы или метод run() для простая программа командной строки.

  • Логи c уровня. Это @Service классов. Это то место, куда вы положили @Transactional, поэтому каждый вызов службы представляет собой транзакцию atomi c, то есть он либо завершается успешно, либо завершается неудачей, поскольку обновления базы данных не выполняются наполовину.

  • Уровень данных. Это @Repository и @Entity классов.

Таким образом, вы должны сохранить создание и инициализацию Student и Section объекты в методе run(), но остальная часть кода, вкл. save(), следует переместить в один метод в классе @Service.

0 голосов
/ 11 апреля 2020

Об этом @Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class) public void addSection(Section section){

@Transactional работает только для bean-компонентов, управляемых пружиной, а объекты не управляются Spring.

Вы получаете это исключение, потому что вы пытаетесь загрузить ленивые отношения вне сеанс (потому что ваша сущность находится в отдельном состоянии).

Повторное присоединение -> entityManager.merge(student);

Но лучшее, что нужно сделать, это загрузить отношение во время запроса . Используя EntityGraph например ->

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...