The best way to map a many-to-many association with extra columns when using JPA and Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

For a simple many-to-many database relationship, you can use the @ManyToMany JPA annotation and, therefore, hide the join table.

However, sometimes you need more than the two Foreign Key columns in the join table, and, for this purpose, you need to replace the @ManyToMany association with two bidirectional @OneToMany associations. Unlike unidirectional @OneToMany, the bidirectional relationship is the best way to map a one-to-many database relationship that requires a collection of Child elements on the parent side

In this article, we are going to see how you can map a many-to-many database relationship using an intermediary entity for the join table. This way, we can map additional columns that would be otherwise impossible to persist using the @ManyToMany JPA annotation.

Domain Model

Assuming we have the following database tables:

PostTag with extra columns

The first thing we need is to map the composite Primary Key which belongs to the intermediary join table. As explained in this article, we need an @Embeddable type to hold the composite entity identifier:

@Embeddable
public class PostTagId
    implements Serializable {

    @Column(name = "post_id")
    private Long postId;

    @Column(name = "tag_id")
    private Long tagId;

    private PostTagId() {}

    public PostTagId(
        Long postId, 
        Long tagId) {
        this.postId = postId;
        this.tagId = tagId;
    }

    //Getters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) 
            return false;

        PostTagId that = (PostTagId) o;
        return Objects.equals(postId, that.postId) && 
               Objects.equals(tagId, that.tagId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(postId, tagId);
    }
}

There are two very important aspects to take into consideration when mapping an @Embeddable composite identifier:

  1. You need the @Embeddable type to be Serializable
  2. The @Embeddable type must override the default equals and hashCode methods based on the two Primary Key identifier values.

Next, we need to map the join table using a dedicated entity:

@Entity(name = "PostTag")
@Table(name = "post_tag")
public class PostTag {

    @EmbeddedId
    private PostTagId id;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("postId")
    private Post post;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("tagId")
    private Tag tag;

    @Column(name = "created_on")
    private Date createdOn = new Date();

    private PostTag() {}

    public PostTag(Post post, Tag tag) {
        this.post = post;
        this.tag = tag;
        this.id = new PostTagId(post.getId(), tag.getId());
    }

    //Getters and setters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass())
            return false;

        PostTag that = (PostTag) o;
        return Objects.equals(post, that.post) &&
               Objects.equals(tag, that.tag);
    }

    @Override
    public int hashCode() {
        return Objects.hash(post, tag);
    }
}

The Tag entity is going to map the @OneToMany side for the tag attribute in the PostTag join entity:

@Entity(name = "Tag")
@Table(name = "tag")
@NaturalIdCache
@Cache(
    usage = CacheConcurrencyStrategy.READ_WRITE
)
public class Tag {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String name;

    @OneToMany(
        mappedBy = "tag",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<PostTag> posts = new ArrayList<>();

    public Tag() {
    }

    public Tag(String name) {
        this.name = name;
    }

    //Getters and setters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Tag tag = (Tag) o;
        return Objects.equals(name, tag.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

The Tag entity is marked with the following Hibernate-specific annotations:

  1. The @NaturalId annotation allows us to fetch the Tag entity by its business key.
  2. The @Cache annotation marks the cache concurrency strategy.
  3. The @NaturalIdCache tells Hibernate to cache the entity identifier associated with a given business key.

For more details about the @NaturalId and @NaturalIdCache annotations, check out this article.

With these annotations in place, we can fetch the Tag entity without needing to hit the database.

And the Post entity is going to map the @OneToMany side for the post attribute in the PostTag join entity:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToMany(
        mappedBy = "post", 
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    private List<PostTag> tags = new ArrayList<>();

    public Post() {
    }

    public Post(String title) {
        this.title = title;
    }

    //Getters and setters omitted for brevity

    public void addTag(Tag tag) {
        PostTag postTag = new PostTag(this, tag);
        tags.add(postTag);
        tag.getPosts().add(postTag);
    }

    public void removeTag(Tag tag) {
        for (Iterator<PostTag> iterator = tags.iterator(); 
             iterator.hasNext(); ) {
            PostTag postTag = iterator.next();

            if (postTag.getPost().equals(this) &&
                    postTag.getTag().equals(tag)) {
                iterator.remove();
                postTag.getTag().getPosts().remove(postTag);
                postTag.setPost(null);
                postTag.setTag(null);
            }
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) 
            return false;

        Post post = (Post) o;
        return Objects.equals(title, post.title);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title);
    }
}

Notice that the Post entity features the addTag and removeTag utility methods which are needed by every bidirectional association so that all sides of the association stay in sync.

While we could have added the same add/remove methods to the Tag entity, it’s unlikely that these associations will be set from the Tag entity because the users operate with Post entities.

To better visualize the entity relationships, check out the following diagram:

Double side bidirectional PostTag

Testing time

First, let’s persist some Tag entities which we’ll later associate to a Post:

Tag misc = new Tag("Misc");
Tag jdbc = new Tag("JDBC");
Tag hibernate = new Tag("Hibernate");
Tag jooq = new Tag("jOOQ");

doInJPA(entityManager -> {
    entityManager.persist( misc );
    entityManager.persist( jdbc );
    entityManager.persist( hibernate );
    entityManager.persist( jooq );
});

Now, when we persist two Post entities:

Session session = entityManager
    .unwrap( Session.class );

Tag misc = session
    .bySimpleNaturalId(Tag.class)
    .load( "Misc" );

Tag jdbc = session
    .bySimpleNaturalId(Tag.class)
    .load( "JDBC" );

Tag hibernate = session
    .bySimpleNaturalId(Tag.class)
    .load( "Hibernate" );

Tag jooq = session
    .bySimpleNaturalId(Tag.class)
    .load( "jOOQ" );

Post hpjp1 = new Post(
    "High-Performance Java Persistence 1st edition"
);
hpjp1.setId(1L);

hpjp1.addTag(jdbc);
hpjp1.addTag(hibernate);
hpjp1.addTag(jooq);
hpjp1.addTag(misc);

entityManager.persist(hpjp1);

Post hpjp2 = new Post(
    "High-Performance Java Persistence 2nd edition"
);
hpjp2.setId(2L);

hpjp2.addTag(jdbc);
hpjp2.addTag(hibernate);
hpjp2.addTag(jooq);

entityManager.persist(hpjp2);

Hibernate generates the following SQL statements:

INSERT INTO post (title, id) 
VALUES ('High-Performance Java Persistence 1st edition', 1)

INSERT INTO post_tag (created_on, post_id, tag_id) 
VALUES ('2017-07-26 13:14:08.988', 1, 2)

INSERT INTO post_tag (created_on, post_id, tag_id) 
VALUES ('2017-07-26 13:14:08.989', 1, 3)

INSERT INTO post_tag (created_on, post_id, tag_id) 
VALUES ('2017-07-26 13:14:08.99', 1, 4)

INSERT INTO post_tag (created_on, post_id, tag_id) 
VALUES ('2017-07-26 13:14:08.99', 1, 1)

INSERT INTO post (title, id) 
VALUES ('High-Performance Java Persistence 2nd edition', 2)

INSERT INTO post_tag (created_on, post_id, tag_id) 
VALUES ('2017-07-26 13:14:08.992', 2, 3)

INSERT INTO post_tag (created_on, post_id, tag_id) 
VALUES ('2017-07-26 13:14:08.992', 2, 4)

INSERT INTO post_tag (created_on, post_id, tag_id) 
VALUES ('2017-07-26 13:14:08.992', 2, 2)

Now, since the Misc Tag entity was added by mistake, we can remove it as follows:

Tag misc = entityManager.unwrap( Session.class )
    .bySimpleNaturalId(Tag.class)
    .load( "Misc" );

Post post = entityManager.createQuery(
    "select p " +
    "from Post p " +
    "join fetch p.tags pt " +
    "join fetch pt.tag " +
    "where p.id = :postId", Post.class)
.setParameter( "postId", 1L )
.getSingleResult();

post.removeTag( misc );

Hibernate generating the following SQL statements:

SELECT p.id AS id1_0_0_,
       p_t.created_on AS created_1_1_1_,
       p_t.post_id AS post_id2_1_1_,
       p_t.tag_id AS tag_id3_1_1_,
       t.id AS id1_2_2_,
       p.title AS title2_0_0_,
       p_t.post_id AS post_id2_1_0__,
       p_t.created_on AS created_1_1_0__,
       p_t.tag_id AS tag_id3_1_0__,
       t.name AS name2_2_2_
FROM   post p
INNER JOIN 
       post_tag p_t ON p.id = p_t.post_id
INNER JOIN 
       tag t ON p_t.tag_id = t.id
WHERE  p.id = 1

SELECT p_t.tag_id AS tag_id3_1_0_,
       p_t.created_on AS created_1_1_0_,
       p_t.post_id AS post_id2_1_0_,
       p_t.created_on AS created_1_1_1_,
       p_t.post_id AS post_id2_1_1_,
       p_t.tag_id AS tag_id3_1_1_
FROM   post_tag p_t
WHERE  p_t.tag_id = 1

DELETE 
FROM   post_tag 
WHERE  post_id = 1 AND tag_id = 1

The second SELECT query is needed by this line in the removeTag utility method:

postTag.getTag().getPosts().remove(postTag);

However, if you don’t need to navigate all Post entities associated to a Tag, you can remove the posts collection from the Tag entity and this secondary SELECT statement will not be executed anymore.

Using a single-side bidirectional association

The Tag entity will not map the PostTag @OneToMany bidirectional association anymore.

@Entity(name = "Tag")
@Table(name = "tag")
@NaturalIdCache
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Tag {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String name;

    public Tag() {
    }

    public Tag(String name) {
        this.name = name;
    }

    //Getters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) 
            return false;

        Tag tag = (Tag) o;
        return Objects.equals(name, tag.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

The PostTag entity and its PostTagId @Embeddable are identical with the previous example.

However, the Post entity addTag and removeTag are simplified as follows:

public void addTag(Tag tag) {
    PostTag postTag = new PostTag(this, tag);
    tags.add(postTag);
}

public void removeTag(Tag tag) {
    for (Iterator<PostTag> iterator = tags.iterator(); 
         iterator.hasNext(); ) {
        PostTag postTag = iterator.next();

        if (postTag.getPost().equals(this) &&
                postTag.getTag().equals(tag)) {
            iterator.remove();
            postTag.setPost(null);
            postTag.setTag(null);
        }
    }
}

The rest of the Post entity is the same as with the previous example as seen in the following diagram:

Single side bidirectional PostTag

Inserting the PostTag entities is going to render the same SQL statements as seen before.

But when removing the PostTag entity, Hibernate is going to execute a single SELECT query as well as a single DELETE statement:

SELECT p.id AS id1_0_0_,
       p_t.created_on AS created_1_1_1_,
       p_t.post_id AS post_id2_1_1_,
       p_t.tag_id AS tag_id3_1_1_,
       t.id AS id1_2_2_,
       p.title AS title2_0_0_,
       p_t.post_id AS post_id2_1_0__,
       p_t.created_on AS created_1_1_0__,
       p_t.tag_id AS tag_id3_1_0__,
       t.name AS name2_2_2_
FROM   post p
INNER JOIN 
       post_tag p_t ON p.id = p_t.post_id
INNER JOIN 
       tag t ON p_t.tag_id = t.id
WHERE  p.id = 1

DELETE 
FROM   post_tag 
WHERE  post_id = 1 AND tag_id = 1

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

While mapping the many-to-many database relationship using the @ManyToMany annotation is undoubtedly simpler, when you need to persist extra columns in the join table, you need to map the join table as a dedicated entity.

Although a little bit more work, the association works just as its @ManyToMany counterpart, and this time we can List collections without worrying about SQL statement performance issues.

When mapping the intermediary join table, it’s better to map only one side as a bidirectional @OneToMany association since otherwise a second SELECT statement will be issued while removing the intermediary join entity.

FREE EBOOK
Newsletter logo
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence,
  • a 10% discount coupon for my book.

57 Comments on “The best way to map a many-to-many association with extra columns when using JPA and Hibernate

  1. Hello vladmihalcea,

    Thank you so much for posting. It was really helpful for my use case.
    However, using this mapping it not able to update/delete the mapped id for post_tag table.

    It throws “DataIntegrityViolationException: A different object with the same identifier value was already associated with the session”

    I tried modifying the equals and hashcode method, still no luck.
    I tried adding orphanRemoval=true, still no luck.
    I tried removing cascade=CascadeType.ALL, still no luck -> It throws exception “Unable to find PostTag record using with Id”.

    Can you please suggest me a way to achieve this.

    Thanks, hoping for a quick response.

    • All the code is on GitHub and works like a charm. Try to compare yours with the one in my High-Performance Java Persistence repository and you will surely spot the problem.

      • Thank you so much for your quick response.
        I had gone through the repository code, however I couldn’t find the example which you mentioned in the post regarding Composite key + extra column using
        @EmbeddedId private PostTagId id;

        My use case:-
        TableA
        TableB
        TableC

        TableA_B_C_Map ( TableA_id, TableB_id, TableC_id)

      • I’m also available for consulting if you are interested.

  2. Hello, i try to use @MapsId but always get the following error (missing column [city_city_id] in table [location]). Hibernate adds city prefix to my field city_id. when i use it with @JoinColumn everything is fine, but as i undarstand it should work without joincolumn.Does anybody aware of this issue?

  3. It seems impossible to save Post and multiple tags at the same time? I get always exception “Could not set field value value by reflection to org.hibernate.id.IdentifierGeneratorHelper$1”.

    But when I save first tag and then post – it works.

  4. Thanks it worked for me. Been struggling for a week on this stuff.

  5. Nice Post. Very much helpful. Since I need to add extra column in my join_table. I have a quick question , do I need to create Join_table schema in my database . Previously I did n’t have join_table schema(create join_table) in my db_script, it was generated automatically by hibernate JPA.

  6. Hi Vlad,

    Thanks for the example. Could you please help with below exception.
    I tried the above example in Spring boot application. But I am getting exception:

    ERROR 9146 — [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: Could not set field value [1] value by reflection : [class com.bean.PostTagId.postId] setter of com.bean.PostTagId.postId; nested exception is org.hibernate.PropertyAccessException: Could not set field value [1] value by reflection : [class com.bean.PostTagId.postId] setter of com.bean.PostTagId.postId] with root cause

    java.lang.NullPointerException: null
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:57) ~[na:1.8.0_192]
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:75) ~[na:1.8.0_192]
    at java.lang.reflect.Field.set(Field.java:764) ~[na:1.8.0_192]

    • A NPE is fairly easy to debug and fix. More, you should figure out the issue in your integration tests.

  7. In trying to implement this, I’m running into an infinite loop in the response. Any ideas why this might be happening? I tried adding @JsonIgnoreProperties and have had no luck.

    Thanks!

    • It sounds like a JSON serialization problem. Use @JsonIgnore on the child side of the bidirectional association.

    • Hi Drew! You should use DTO’s for the json but if you dont want to change you can use @JsonBackReference and @JsonManageReference

      • Stefania and Vlad,

        Thanks for the help on this! I was finally able to get this working and have detailed it in my StackOverflow. Basically, my issue was that I had mistakenly named a method prepended with “get” in my class. This was causing that method to be added to my object, which was not being ignored, so THAT was causing my infinite loop.

        Thank you both again for the replies, and thank you always for the great content Vlad!

  8. Hello Vlad,
    This article proved very helpful to me. However , I want to know how can I set the value of extra column (in your case created_on) and persist into database. I am trying to do similar for my project but I am not able to insert the data.

  9. Why postTag.getTag().getPosts().remove(postTag); doesnt throw Lazy load exception?

  10. Hi @Vladmihalcea,
    Thanks for this great article! I have one query though.
    If the many to many relationship is only with one table rather than 2. How can we achieve that?
    To put more context:

    FORMULA – table

    ID – E.g. value is auto incremented sequence “1” or “2” or “3”
    NAMED_IDENTIFIER – E.g. value is “F1” or “F2” or “F3″
    FORMULA_EXPRESSION – E.g. value is ” + 10″ or ” * 10″ or “F1 * F2”

    FORMULA_REL – table

    PARENT_FORMULA_ID – E.g. value is “F3” referring to FORMULA.ID
    CHILD_FORMULA_ID – E.g. value is “F1” or “F2” referring to FORMULA.ID

    Does this above example applies to this case as well?

    • This question is too complex to answer in a comment. You need to ask it on the Hibernate forum.

  11. Hi Vlad,
    I have the same scenario and I’m trying to implementing a soft delete with @SQLDelete annotation for update tables but I have a problem when updating the PostTag entity with invalid index exception 🙁
    In your opinion is possible to use @SQLDelete to update an entity on CascadeType.REMOVE with an @Embeddable id?
    Thank you in advance
    Stefania

    • Try to replicate the use case in my High-Performance Java Persistence GitHub repository and send me a Pull Request when it’s done. I’ll check it out afterwards.

  12. Hey, Vlad!
    Why can’t I create a new Tag, persist it, create a new Post, add the previous tag to it and then persist it? I get a detached entity error and I’m struggling to fix this. I’ve put up the question on SO but had no helpful answers (https://stackoverflow.com/questions/53997499/how-to-do-unidirectional-many-to-many-relationships-with-already-existing-regist).

    What I want to do is:
    Tag tag = new Tag(“tagname”);
    tagRepository.save(tag);
    Post post = new Post(“posttitle”);
    postRepository.save(post);

    • Check out the Relationship module of my High-Performance Java Persistence video course. Not only that you’ll find the answer to your question, but you are going to learn other useful tricks as well.

  13. Excellent article.

    I have only one question. Is there a way to avoid having to manually set the IDs for Post entities before persisting them? I.e., is it possible to use a @GeneratedValue for Post IDs and have the association table use that value?

    Many thanks,
    George

    • Yes. You can use generated identifers for parent entities: Post or Tag. Only the PostTag child entity requires a composite identifer which cannot be auto-generated since the ids are taken from the Post and Tag entities.

      • Thank you, it works.

        However, I ran into another problem.

        Following your example and creating a single-side bidirectional association, I am unable to run integration tests on the Post entity if I try to add any tags to it. Hibernate won’t even persist the Posts containing tags, complaining about detached entities, throwing a PersistentObjectException:

        org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.api.domain.tag.Tag; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.api.domain.tag.Tag

        or if I try to make a post request (with a Post object containing one or more Tags) it throws the following exception:

        org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: null; nested exception is com.fasterxml.jackson.databind.JsonMappingException: N/A at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 8, column: 5] (through reference chain: com.api.domain.post.Post["tags"])
        ...
        Caused by: java.lang.NullPointerException: null
        at com.api.domain.post.Post.addTag(Post.java:86)

        Any ideas why this happens and how to solve it?

      • All the test cases in this article are on GitHub and they run like a charm. Try to compare my test cases to yours and see where they differ.

      • The only differences in the relevant tests are:

        1) You are assigning IDs to Post entities manually through the setter method while I’m using @GeneratedValue(s).

        2) You are persisting Post entities by calling EntityManager’s persist() method, while I’m doing the same by calling the save() method of my @RepositoryRestResource interface which extends CrudRepository.

        The models are pretty much identical.

      • The save method should call persist if the id is null. You can change my examples to use @GeneratedValue and they will still work.

        You also need to do a comparison debug to see why your example is not working.

  14. Hi,

    thanks for a helpful post; I’ve now (finally) got my program working as intended. One question though:

    For the class PostTagId you define it as public static,

    public static class PostTagId

    The static part didn’t work with my compiler and when I looked more it seems like a static class only can be nested inside another class, i.e. not top level.

    Should the PostTagId be inside something else, like PostTag? Or have I misunderstood?

    • The classes are static in my unit tests as they confined to the test class. You don’t need to use that in your application.

  15. Hey Vlad,

    Could you please explain if this self reference in the PostTag mapping entity is not causing issues? I tried serializing the Post class directly and what you get when you see the JSON for tags is a long descending nest of Post -> Tag -> Post -> Tag, etc.

    Does JPA handle this in the background and not recognize this or will this potentially cause serious memory/performance issues? Is this resolved by the FetchType.lazy? What if I just want to call a FindAll() and get back all the Tags? Do I need to make a second query somehow?

    public void addTag(Tag tag) {
    PostTag postTag = new PostTag(this, tag);
    tags.add(postTag);
    tag.getPosts().add(postTag);
    }

    • JPA is about persisting data into the database. It has nothing to do with JSON which is handled by Jackson. You need the @JsonIgnore annotation on one side of a bidirectional association, and that applies to any hierarchical model, not just to JPA entities.

  16. First of all,
    I’d like to thank you for your blog. There is a plenty of great material about persistence.

    @vladmihalcea, I followed the tutorial described above to implement a many-to-many with extra attribute (in my case a list of integers). Nevertheless, it is not working as expected. The code succesfully saves the data into the database since I can check the data directly on the DB console. But I’m unable to load a domain class from the other endpoint.
    On the other hand, if I create a JPARepository to access the intermediary entity I’m able to retrieve the data. Do you have any tip about how to fix it?

    Thanks in advance!

    Domain classes
    Vacina class => https://pastebin.com/KJgSkgiN (equivalent to Post class)
    CalendarioVacinal class => https://pastebin.com/6FjBWJW0 (equivalente to Tag class)

    Intermediary entities
    VacinaCalendarioVacinal class => https://pastebin.com/hJvALVt0 (equivalent to PostTag class)
    VacinaCalendarioVacinalId class => https://pastebin.com/MxpmbcTr (equivalent to PostTagId class)

    JPA repositories
    VacinaRepository class => https://pastebin.com/csHHzuNf
    CalendarioVacinalRepository class => https://pastebin.com/qB5i6Kiu
    VacinaCalendarioVacinalRepository class => https://pastebin.com/SA5fgPDu

    Test class
    JUnit Test class => https://pastebin.com/GCBSQhpg

    • All the code for these tutorials is on GitHub, and it works just fine. Compare your implementation with mine and see the difference.

  17. Hi, What about doing this with Kotlin? Is kind of nightmare handling inmutability and secondary constructors and so on… Thanks!

    • I haven’t used Kotlin, so maybe the Kotlin devs can help you with your question.

  18. Is it possible when saving a Post, to use cascade to also persist new Tags that were added to the Post? As I see it, this solution works only when both Post and Tags have already been persisted (because only then you know their ids to form the @EmbeddedId).

    • I think it should work with @EmbeddedId as well if you are using @MapsId.

  19. Hi,

    we have some issues working with this kind of table configuration. We are using Hibernate version 5.3.6.

    We are not able to load data into the mapped class even if the query generated by hibernate looks ok.
    We use session.get(class, id)

    In order to have the query running properly we had to add the @JoinColumn annotation in @MapsId as by default hibernate add an _ID at the end of the map class.
    Note that we have a maximum of 4 rows per table.

    We have a jvm error when trying to access collection object. We also have these Hibernat error.

    10-10-2018 18:37:00.517] [Test worker] WARN org.hibernate.engine.loading.internal.LoadContexts – HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@6f4e6518<rs=com.mchange.v2.c3p0.impl.NewProxyResultSet@6178010c [wrapping: null]>
    [10-10-2018 18:37:00.526] [Test worker] WARN org.hibernate.engine.loading.internal.CollectionLoadContext – HHH000160: On CollectionLoadContext#cleanup, localLoadingCollectionKeys contained [1] entries
    [10-10-2018 18:37:00.527] [Test worker] WARN org.hibernate.engine.loading.internal.LoadContexts – HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@3ba2288a<rs=com.mchange.v2.c3p0.impl.NewProxyResultSet@191f363d [wrapping: null]>

    Thanks in advance.

  20. Dear Vlad,
    Thanks for these great resources, but I have to agree with Vishal’s suggestion given above. I was constantly getting EntityManagerFactory exception, until I introduced these two annotations to the joined table. It is probably caused by some versioning issues (or possibly naming Hibernate’s conventions conflict). If you want, I can provide you with current versions of my stack.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.