1
votes

Hibernate @ManyToMany org.hibernate.LazyInitializationException

Je souhaite traiter une erreur courante, mais je ne trouve pas la bonne réponse. l'erreur est:

@Entity
@Data
@Table
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = "posts")
public class Tag {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator")
    @SequenceGenerator(name="id_generator", sequenceName = "seq_tag", allocationSize = 1)
    private Long id;

    private String tagName;

    @ManyToMany(mappedBy="tags")
    List<Post> posts = new ArrayList<>();

    public void addPost(Post post) {
        posts.add(post);
        post.getTags().add(this);
    }

    public void removePost(Post post) {
        posts.remove(post);
        post.getTags().remove(this);
    }
}

Comment éviter l'utilisation de @Transactional?

J'essaie de faire les actions suivantes dans mon SpringBootTest:

@Entity
@Data
@Table
@NoArgsConstructor
@AllArgsConstructor
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_generator")
    @SequenceGenerator(name="post_generator", sequenceName = "seq_post", allocationSize = 1)
    private Long id;

    @Enumerated(EnumType.STRING)
    private Status status;

    //Some code

    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    })
    @JoinTable(
            name = "post_tag",
            joinColumns = {@JoinColumn(name = "post_id")},
            inverseJoinColumns = {@JoinColumn(name = "tag_id")})
    private List<Tag> tags = new ArrayList<>();

    public void addTag(Tag tag) {
        tags.add(tag);
        tag.getPosts().add(this);
    }

    public void removeTag(Tag tag) {
        tags.remove(tag);
        tag.getPosts().remove(this);
    }
}

L'erreur est dans cette partie:

    public void addTag(Tag tag) {
        tags.add(tag);
        tag.getPosts().add(this);
    }

Je suis ouvert à toute question supplémentaire liée à ce problème p>

Je ne souhaite pas utiliser les réponses suivantes: - Fetch.EAGER (ou quelque chose lié au refus de Fetch.LAZY) - hibernate.enable_lazy_load_no_trans = True

Le code:

Post.java

@SpringBootTest
@ActiveProfiles("test")
class PostRepositoryTest {

    @Autowired
    private PostRepository postRepository;

    @Autowired
    private TagRepository tagRepository;

    @AfterEach
    void deleteFromDB(){
        postRepository.deleteAll();
        tagRepository.deleteAll();
    }

    @Test
    void deletePostWithMultipleTags(){

        Post post = new Post();

        Tag tag = new Tag(1L,"tag1", null);
        Tag tag2 = new Tag(2L,"tag2", null);
        Tag tag3 = new Tag(3L,"tag3", null);
        tagRepository.save(tag);
        tagRepository.save(tag2);
        tagRepository.save(tag3);

        post.setTitle("testTitle");

        Tag tagNew = tagRepository.findByTagName(tag.getTagName());

        post.addTag(tagNew);
        post.addTag(tagRepository.findByTagName(tag2.getTagName()));
        post.addTag(tagRepository.findByTagName(tag3.getTagName()));
        System.out.println(post);

        postRepository.save(post);

    }
}

Tag.java

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: newsProject.post.entity.Tag.posts, could not initialize proxy - no Session


0 commentaires

3 Réponses :


-1
votes

La solution rapide serait soit:

  1. Annotez votre méthode de test avec @Transactional (qui annulera toutes les modifications et aura peut-être des effets secondaires indésirables. voir ce lien pour plus d'informations)

  2. Annotez votre méthode de test avec @Rollback (false) , qui couvrira une transaction autour de votre test et pas l'annulation.


0 commentaires

1
votes

J'ai essayé votre cas et cela fonctionne pour moi. Veuillez essayer:

@Test
@Transactional
public void test() {
    Post post = new Post();
    post.setTitle("post");

    Tag tag1 = new Tag();
    tag1.setTagName("tag1");
    tag1.getPosts().add(post);

    Tag tag2 = new Tag();
    tag2.setTagName("tag2");
    tag2.getPosts().add(post);

    post.getTags().add(tag1);
    post.getTags().add(tag2);

    postRepository.save(post1);
}

Nous bénéficions de l'enregistrement en cascade ici. N'oubliez pas d'ajouter @Transactional à votre méthode.


0 commentaires

0
votes

La réponse est simple. Nous pouvons éviter @Transactional à 100%.

  1. Ajoutez les lignes suivantes à la classe de test:
@Test
    void deletePostWithMultipleTags(){

        Tag tag = new Tag(1L,"tag1", null);
        Tag tag2 = new Tag(2L,"tag2", null);
        Tag tag3 = new Tag(3L,"tag3", null);
        tagRepository.saveAll(Arrays.asList(tag, tag2, tag3));
        tagRepository.flush();

        Post post = new Post();
        post.setTitle("testTitle");

        List<Tag> tags =  tagRepository.findByNameIn(Arrays.asList(tag.getName(), tag2.getName(), tag3.getName(),));
        for (Tag tagItem : tags){
          post.addTag(tahItem);
        }

        postRepository.saveAndFlush(post);

    }

pendant que nous testons le travail de JPA.

  1. Utilisation dans l'interface du référentiel étend JpaRepository <... ...> au lieu de CrudRepository <... ...> p>

  2. dans tagRepository ajoutez la ligne:

List<Tag> findByNameIn(List<String> names);

voir la documentation.

  1. Nous pouvons maintenant modifier ou tester ceci:
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Rollback(false)
@ActiveProfiles("test")
class PostRepositoryTest {

Après toutes ces actions, vous pouvez éviter l'utilisation de @Transactional


0 commentaires