Mapping a many-to-many join table with extra column using JPA


Intro

It is not straightforward to realise a many-to-many association with JPA when in the join table there is at least an extra column. In this small tutorial I’m going to show how to design entity objects that will handle the many-to-many relation and which annotations are needed in order to fix a redundancy that we will see in the solution adopted in the following wiki.

This tutorial is a mix up of different sources. The first solution I’m going to show is the one suggested in a wiki.

Mapping a Join Table with Additional Columns (in a JPA pure style)

Source: en.wikibooks.org

A frequent problem is that two classes have a ManyToMany relationship, but the relational join table has additional data. For example if Employee has a ManyToMany with Project but the PROJ_EMP join table also has an IS_TEAM_LEAD column. In this case the best solution is to create a class that models the join table. So an ProjectAssociation class would be created. It would have a ManyToOne to Employee and Project, and attributes for the additional data. Employee and Project would have a OneToMany to the ProjectAssociation. Some JPA providers also provide additional support for mapping to join tables with additional data.

Unfortunately mapping this type of model becomes more complicated in JPA because it requires a composite primary key. The association object’s Id is composed of the Employee and Project ids. The JPA spec does not allow an Id to be used on a ManyToOne so the association class must have two duplicate attributes to also store the ids, and use an IdClass, these duplicate attributes must be kept in synch with the ManyToOne attributes. Some JPA providers may allow a ManyToOne to be part of an Id, so this may be simpler with some JPA providers. To make your life simpler, I would recommend adding a generated Id attribute to the association class. This will give the object a simpler Id and not require duplicating the Employee and Project ids.

This same pattern can be used no matter what the additional data in the join table is. Another usage is if you have a Map relationship between two objects, with a third unrelated object or data representing the Map key. The JPA spec requires that the Map key be an attribute of the Map value, so the association object pattern can be used to model the relationship.

If the additional data in the join table is only required on the database and not used in Java, such as auditing information, it may also be possible to use database triggers to automatically set the data.

Example join table association object annotations

As you can see in this solution the ProjectAssociation class contains twice the information related to Employee and Project. As explained above, this is due to JPA specification. Googling I found another solution to this problem that allowed me to avoid the redundancy.

Hibernate annotations: The many-to-many association with composite key (in a pure JPA style without redundancy)

Source: boris.kirzner.info

This post contains an evolution of another solution realized in 2006. Since things changed a bit in the meanwhile, the original solution was not the best.

Basically what the author is trying to do is to hold a relation between three item: Produc, Item and ProductItem.

Here is the last part of the post in which the author introduce the solution:

The database part is the same: we have three tables (item, product and product_item), two POJO classes, and two classes for a many-to-many association and its primary key. The main difference from Marsel’s solution is that I’m not using any kind of “fake” properties on ProductItem in order to reference Item and Product, but just a plain transient properties delegating to ProductItemPk.

Here is the source:

This solution is perfect from a model point of view. I used this solution in my project and together with Spring, Hibernate and Maven I’ve been able to generate the schema in an automatic fashion (hbm2dll plugin). The schema produced is exactly what you would expect. Unfortunately JPA doesn’t allow developer to use this configuration to work with inserts and updates. For instance if you have an Item object with few ProductItem and you perform an insert on the Item, ProductItem object contained in the list will not be inserted in the database. Same stuff happens for a Product and its ProductItem list. JPA in this case won’t help us anymore. The only way to make inserts and updates to work in cascade, we must recur to provider’s specific annotation. The source code below is the final evolution of the previous example. The JPA provider I used is Hibernate. Here is the code for the Product class, use the same annotation for the Item class as well and Hibernate will take care to insert/update ProductItem (if any) as well.

Since I only have experience with Hibernate I invite all the reader that have expertise with other JPA provider to reply to this post with the solution offered by other framework such as iBatis.

Thanks for have reading this tutorial and feel free to reply with comments.

-Sieze

If you think this article was usefull to you, please donate a few mBTC to 1D4Li5ckG81JLbBt3Kb2UT1wQqHZddVqEL :)

About these ads

About giannigar

Senior Software Engineer - Groupon - Dublin, Ireland

Posted on September 4, 2009, in Java, JPA, Programming and tagged , , , , , , . Bookmark the permalink. 46 Comments.

  1. I am getting the exception mappedBy reference an unknown target entity property

    Are you familiar with this?

    • Maybe I know what the problem is. In your Item class the following method needs to have its annotation mappedBy value set as below

      @OneToMany(fetch = FetchType.LAZY, mappedBy = “pk.item”)
      public List getProductItems() {

  2. Thanks for the response.
    I think i got that resolved.
    I had an additional @Id in my association entity.
    When i removed that, the exception went away.

    Does any of you know how to persist these multi values into the Product entity productItems list?

    • if you use the annotation
      @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
      org.hibernate.annotations.CascadeType.DELETE_ORPHAN})

      upon the “getProductItems” it should work straight away

  3. i have this in my xhtml

    ROC

    The user has a list of userRoc association entity list (userRocList)
    But the display list above has to display ROC list.

    With above code, if i try to save, its giving the following exception

    aused by: org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of com.cox.waps.model.WapsUserRoc.id
    Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class

    • I too get the same error. Mine is one-to-many mapping using bag.
      Did you get your error resolved? If so can you post your solution so that other can benefit from that.


  4. ROC

  5. It would be very helpful if you can post some example of insert,update and select with this association.

    • I agree, I followed the example but as with all other examples on solving this hibernate issue, I failed to get it working properly.
      In this case I have seen tons of exceptions, like for one not being able to insert ProductItem (SQL expects id’s while Hibernate provided the entity)

  6. Hello, buddy!
    Users hibernate3-maven-plugin: If you declare “getPk” and “setPk” as “private” hibernate3-maven-plugin will generate DDL script incorrectly.
    I had to declare as “public” both of them to correct this. Being:
    “public getPk ProductItemPk()” and
    “public void setPk (ProductItemPk pk)”

  7. I get the following exception when deleting Product:
    org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [ProductItem#ProductItemPk@0]

    What is the right way to delete Product or Item and have the deletion cascade to ProductItem?

  8. Amazing Article. Possibly one of the few ones, which target this issue. Thanks a million !

  9. I have a problem when i save the product with cascading, they tell to me that :

    java.lang.NullPointerException
    at org.hibernate.type.AbstractType.getHashCode(AbstractType.java:136)
    at org.hibernate.type.AbstractType.getHashCode(AbstractType.java:144)
    at org.hibernate.type.EntityType.getHashCode(EntityType.java:312)
    at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:212)
    at org.hibernate.engine.EntityKey.generateHashCode(EntityKey.java:126)
    at org.hibernate.engine.EntityKey.(EntityKey.java:70)
    at org.hibernate.engine.StatefulPersistenceContext.getDatabaseSnapshot(StatefulPersistenceContext.java:263)
    at org.hibernate.engine.ForeignKeys.isTransient(ForeignKeys.java:212)
    at org.hibernate.event.def.AbstractSaveEventListener.getEntityState(AbstractSaveEventListener.java:535)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:103)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:534)
    at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:526)
    at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:241)
    at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291)
    at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239)
    at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
    at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:319)
    at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:265)
    at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:242)
    at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
    at org.hibernate.engine.Cascade.cascade(Cascade.java:153)
    at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:479)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:357)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:562)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:550)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:546)
    at stroupa.service.ManyToManyDaoImpl.saveProduct(ManyToManyDaoImpl.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:107)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy28.saveProduct(Unknown Source)
    at stroupa.service.ManyToManyDaoTest.testSaveProduct(ManyToManyDaoTest.java:72)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
    at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
    at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
    at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
    at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66)
    at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
    at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
    at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
    at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

    can u help me plz ??

  10. Ciao,

    Vedo che usi mssql, non ho chiaro il motivo dell’errore, ma verifica che gli attributo che compongono la chiave oltre ad avere i vincoli di PK contengano anche il “not null”.

    Ho fatto questo post un po’ di anni fa e non ricordo bene come funzionano le cose qui :)

    Vedo che lavori a roma… io ci son stato per un paio di anni li… con chi sei?

    Regards,
    Gio

    • Ciao!

      Non pensavo fossi/sapessi l’italiano!

      In realtà sono in provincia di Bergamo.. :) Non so come mai ti sia uscito Roma! Adesso provo..!

      Grazie della risposta e buona giornata!

  11. Hello,

    Good article, very interresting.

    But i have a little problem.

    I create a Item and a Product. Hibernate cascades correctly the object into the productitem.

    But if i update a product and save, the JVM quit immediatly.
    Invalid access of stack red zone 0x10c673ff8 rip=0x1010c6e81

    ./catalina.sh: line 315: 25963 Bus error

    Why ?

    Thanks for your response

    • Hi,

      apparently this is not a code issue, try to upgrade your jvm, or to run the code on another machine, is very unusual that whichever error might cause a JVM to quit.

      Try to mess a little bit around and let me know how it goes. Best of luck!

      -Sieze

  12. I prefer adding generated Id attribute to resolve this.

  13. Please help me. If I have Table A, B, and C. And I want them to have joined table A_B_C. having many to many relationship for all tables. How should I do that? Thank you in advance.

  14. Hello i have error

    run:
    Exception in thread “main” java.lang.ExceptionInInitializerError
    at testjpa.Main.main(Main.java:22)
    Caused by: Exception [EclipseLink-30005] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.PersistenceUnitLoadingException
    Exception Description: An exception was thrown while searching for persistence archives with ClassLoader: sun.misc.Launcher$AppClassLoader@1a16869
    Internal Exception: javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.EntityManagerSetupException
    Exception Description: Predeployment of PersistenceUnit [testJPAPU] failed.
    Internal Exception: Exception [EclipseLink-7298] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.ValidationException
    Exception Description: The mapping [product] from the embedded ID class [class model.ProductItemPk] is an invalid mapping for this class. An embeddable class that is used with an embedded ID specification (attribute [pk] from the source [class model.ProductItem]) can only contain basic mappings. Either remove the non basic mapping or change the embedded ID specification on the source to be embedded.
    at org.eclipse.persistence.exceptions.PersistenceUnitLoadingException.exceptionSearchingForPersistenceResources(PersistenceUnitLoadingException.java:126)
    at org.eclipse.persistence.jpa.PersistenceProvider.createEntityManagerFactory(PersistenceProvider.java:133)
    at org.eclipse.persistence.jpa.PersistenceProvider.createEntityManagerFactory(PersistenceProvider.java:65)
    at javax.persistence.Persistence.createEntityManagerFactory(Unknown Source)
    at javax.persistence.Persistence.createEntityManagerFactory(Unknown Source)
    at utils.PersistanceUtilities.(PersistanceUtilities.java:23)
    … 1 more
    Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.EntityManagerSetupException
    Exception Description: Predeployment of PersistenceUnit [testJPAPU] failed.
    Internal Exception: Exception [EclipseLink-7298] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.ValidationException
    Exception Description: The mapping [product] from the embedded ID class [class model.ProductItemPk] is an invalid mapping for this class. An embeddable class that is used with an embedded ID specification (attribute [pk] from the source [class model.ProductItem]) can only contain basic mappings. Either remove the non basic mapping or change the embedded ID specification on the source to be embedded.
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:991)
    at org.eclipse.persistence.internal.jpa.deployment.JPAInitializer.callPredeploy(JPAInitializer.java:88)
    at org.eclipse.persistence.jpa.PersistenceProvider.createEntityManagerFactory(PersistenceProvider.java:124)
    … 5 more
    Caused by: Exception [EclipseLink-28018] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.EntityManagerSetupException
    Exception Description: Predeployment of PersistenceUnit [testJPAPU] failed.
    Internal Exception: Exception [EclipseLink-7298] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.ValidationException
    Exception Description: The mapping [product] from the embedded ID class [class model.ProductItemPk] is an invalid mapping for this class. An embeddable class that is used with an embedded ID specification (attribute [pk] from the source [class model.ProductItem]) can only contain basic mappings. Either remove the non basic mapping or change the embedded ID specification on the source to be embedded.
    at org.eclipse.persistence.exceptions.EntityManagerSetupException.predeployFailed(EntityManagerSetupException.java:210)
    … 8 more
    Caused by: Exception [EclipseLink-7298] (Eclipse Persistence Services – 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.ValidationException
    Exception Description: The mapping [product] from the embedded ID class [class model.ProductItemPk] is an invalid mapping for this class. An embeddable class that is used with an embedded ID specification (attribute [pk] from the source [class model.ProductItem]) can only contain basic mappings. Either remove the non basic mapping or change the embedded ID specification on the source to be embedded.
    at org.eclipse.persistence.exceptions.ValidationException.invalidMappingForEmbeddedId(ValidationException.java:1249)
    at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.EmbeddedIdAccessor.process(EmbeddedIdAccessor.java:141)
    at org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor.processAccessors(MetadataDescriptor.java:1269)
    at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor.processAccessors(ClassAccessor.java:825)
    at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.processAccessors(EntityAccessor.java:847)
    at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.process(EntityAccessor.java:708)
    at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage2(MetadataProject.java:1333)
    at org.eclipse.persistence.internal.jpa.metadata.MetadataProcessor.processORMMetadata(MetadataProcessor.java:461)
    at org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor.processORMetadata(PersistenceUnitProcessor.java:390)
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:945)
    … 7 more
    Java Result: 1
    BUILD SUCCESSFUL (total time: 0 seconds)

    please help me

  15. Hi,

    thanks a lot for this very good article, it’s work well.
    I have just one little problem : how fill list of ProductItem in Product entity?
    I thought that
    product.getProductItems()
    must be enougth thanks to FetchType.LAZY, but it doesn’t work : my list is still empty…
    Is there some trick to do that?

    Thanks again
    M.

  16. Thanks for this example. I have errors on
    @AssociationOverride(name = “pk.item”, joinColumns = @JoinColumn(name = “item_id”)),
    @AssociationOverride(name = “pk.product”, joinColumns = @JoinColumn(name = “product_id”))

    Errors are :
    - Persistent type of override attribute pk.item cannot be resolved
    - Persistent type of override attribute pk.product cannot be resolved

    Any idea about this problem ?
    Thanks for your help

  17. Hello,
    I’ve tryed the example and it works but it is impossible for me having 3 JOIN.
    I mean….i can make the JOIN between Product and ProductItem but not between Product, ProductItem and Item.
    Can anybody tell me why?
    I’m trying different solutions but with not success…….

    For Example:
    - If i try to make the query from ProductItem, no JOIN happen at all :(
    - Also if i put FetchType.EAGER it doesn’t JOIN from the ProductItem table
    ……

    I’m using Criteria to make the query.

    Waiting for suggestions

    Thanks in advance.
    Mauro.

  18. Hi guys,

    I hope my problem can be solved easly.

    The situation is this:
    I’m trying to make a query that no matter from which Java class i start, i need to filter the result during the query via the name attribute of both Product and Item entity.

    E.G.
    I want to make a select that takes all the rows that match with the value “product1″ for the table Product and the value “item1″ for the table Item.

    If i make the query without Restrictions everything works but i obtain all the rows.
    Criteria criteria = session.createCriteria(ProductItem.class);

    So the 3 tables Join without problem.

    I want to take just few of them filtering the result during the query.
    Does anybody have an idea of how to get what i want?

  19. I kinda used the Wiki model and mixed the suggestion of cascade and it worked..
    org.hibernate.annotations.CascadeType.DELETE_ORPHAN is deprecated and i have used oprphanRemoval=true

    @OneToMany(fetch = FetchType.LAZY, mappedBy = “employee”,orphanRemoval=true, cascade =
    {CascadeType.PERSIST, CascadeType.MERGE})
    @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE
    })

    Thanks!

  20. I’m not sure but it would seem that the first example would allow you to have a Set relation from Project to a ProjectAssociation that doesn’t require you to load the Association in order to ensure uniqueness. In the second example, that seems impossible. Is that true?

  21. Ok, did anyone get a NonUniqueObjectException on ProductItem because it already exists?
    I also noticed that Hibernate doesn’t call the ProductItem.equals, nor the ProductItemPk.equals.

    Why is this Many-To-Many core-feature implementation so crappy for so long?!?
    All solutions found in forums eventually fail.
    It took me days now to solve this without success, basically meaning that the framework isn’t not helping, instead it’s creating more work to do the job.
    Hibernate should be ashamed!

    My exception:
    [2011-12-30 16:48:18,534 AccessController] ERROR Failed to create new Product
    org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session
    I suppose object here if referring to an instance of ProductItem…

  22. Hi, I have three entities: employee, qualification, institute. There is a ManyToMany between qualification and institute (each qualification can be obtained at multiple institutes while each institute provides many qualifications), so there is a qualification_institute. However I need to store for each employee which of his/her qualification has been obtained from which institute, hence the qualification_institute join table has a simple primary key (besides the two foreign keys) and I created a fifth table to join employee with qualification_institute. I don’t want to work with EmployeeQualificationInstitute classes, instead I’d like to have these relations stored inside Employee class as a Map field. How should I annotate that field?

    Thank you and regards.

    • Hi,

      I haven’t been working with hibernate for a while… but it looks like you need a one-to-many relation between employee and qualification_institute.

      Would not an attribute of the type Set in your Employee entity object marked as one-to-many address your needs?

      Hope this is of any help

      Regards,
      Gio

      • I do have a Set at the moment in the Employee class, however I would like to replace it to a Map so I wouldn’t need to keep a separate EmployeeQualificationInstitute class. I already managed to get rid of QualificationInstitute class, I just don’t want to keep entity classes for the join tables. And yes, it’s only two class files, but in the long run it’s 2 out of 5, meaning 40% less code in that layer.

      • Hi,

        Sorry I don’t understand what should be the key in the map. Would you mind to provide the snippet of the Set of actual code, and what should be the signature of the Map you desire?

        Cheers, Gio

  23. Hi Gio,

    Sorry, my bad; the Map signature containing the classes got truncated. So to make it clear:
    - a qualification can be obtained from multiple institutes, so class Qualification already contains a Set Institute
    - an institute can provide multiple qualifications, so class Institute already contains a Set Qualification
    - as an employee can get each qualification from one particular institute, instead of a Set QualificationInstitute I’d like to have a Map Qualification, Institute : the Qualification would be the key, and the held value of type Institute would tell where the employee got that qualification from.

    I hope I’m not talking gibberish here :)

    Regards,
    Dr H

  24. Is there any specific reason why in ProjectAssociation the @ManyToOne relationships were modeled as read-only, that is @JoinColumn(… , insertable = false, updatable = false) which is the same as @PrimaryKeyJoinColumn? After all, shouldn’t link entities be insertable and updatable by default, if not at all times?

    Kawu

  25. Thanks for the article. I feel like I’m close but I’m still having problems. I coped the classes from Boris’ article and wrote a test case to try the following:
    Create a product and two items, persist them to the DB
    create ProductItems for the single product and each of the two items, add them to product and merge.

    Then I check that the ProductItems are present in the DB.
    Next, I want to remove one of the ProductItems, and to do so from the Product (as though it was done in another layer of the application).

    Here is my test case code:

    @Test
    public void testProductItemManyToMany() {
    EntityManager manager = factory.createEntityManager();

    Product product = new Product();
    product.setName(“product”);

    Item item1 = new Item();
    item1.setName(“item1″);

    Item item2 = new Item();
    item2.setName(“item1″);

    product = merge(manager, product);
    item1 = merge(manager, item1);
    item2 = merge(manager, item2);

    manager.close();

    Product productCopy = new Product();
    productCopy.setId(product.getId());
    productCopy.setName(product.getName());

    ProductItem pi1 = new ProductItem();
    pi1.setProduct(product);
    pi1.setItem(item1);

    ProductItem pi2 = new ProductItem();
    pi2.setProduct(product);
    pi2.setItem(item2);

    productCopy.setProductItems(ImmutableList.of(pi1, pi2));

    manager = factory.createEntityManager();
    merge(manager, productCopy);
    ExtraAsserts.assertContentsAnyOrder(manager.createQuery(“from ProductItem”).getResultList(), pi1, pi2);
    manager.close();

    productCopy = new Product();
    productCopy.setId(product.getId());
    productCopy.setName(product.getName());
    productCopy.setProductItems(ImmutableList.of(pi1));

    manager = factory.createEntityManager();
    merge(manager, productCopy);
    manager.flush();

    ExtraAsserts.assertContentsAnyOrder(manager.createQuery(“from ProductItem”).getResultList(), pi1);
    manager.close();
    }

    private T merge(EntityManager manager, T entity) {
    EntityTransaction tx = manager.getTransaction();
    tx.begin();
    entity = manager.merge(entity);
    tx.commit();
    return entity;
    }

    With the original code from Boris’ article, the ProductItems were not inserted, I tried adding different Cascade values, finally trying the ones you suggested, a mix of javax.persistance and org.hibernate annotations. In all cases with the cascade on, the last merge causes the query:

    Hibernate: select product0_.product_id as product1_24_1_, product0_.name as name24_1_, productite1_.product_id as product2_3_, productite1_.item_id as item1_3_, productite1_.item_id as item1_25_0_, productite1_.product_id as product2_25_0_ from product product0_ left outer join product_item productite1_ on product0_.product_id=productite1_.product_id where product0_.product_id=?

    to repeat until the stack overflows.

    Due to restrictions on the server framework, I’m limited to JPA 1.0 and thus to Hibernate 3.3.x (with EntityManager 3.4.0 and Annotations 3.4.0).

    Please advise.

    • same error: stack overflow (always the same select) jpa2, hibernate4

    • to avoid the stackoverflow i had to add a fetchtype lazy in the mapping of join entity :
      i have recipe recipeIngredient ingredient.
      I dont use the PK entity, seems to work with 2 @id annotions

      @Entity
      @Table(name = “recipe_ingredient”)
      @EqualsAndHashCode(of = { “recipe”, “ingredient”, “amount” }, callSuper = false)
      public class RecipeIngredient implements Serializable {

      @Getter
      @Setter
      @NotEmpty
      @Column(nullable = false)
      private String amount; //my extra column

      @Id
      @Getter
      @Setter
      @NotNull
      @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
      @JoinColumn(name = “ingredient_id”)
      private Ingredient ingredient;

      @Id
      @Getter
      @Setter
      @NotNull
      @ManyToOne(fetch = FetchType.LAZY) //the fix !
      @JoinColumn(name = “recipe_id”)
      private Recipe recipe;

      public RecipeIngredient() {
      }

      public RecipeIngredient(Ingredient ingredient, String amount) {
      setIngredient(ingredient);
      this.amount = amount;
      }

      hopes this help !

  26. Hello,

    I’d like to thank you so much for this very usefull show case, I’ve just try it with my own example business and it works fine.
    unfortunately, I don’t use an other ORM and JPA provider.
    Good luck;

    BR
    Abdellatif

  27. Thanks for a solution. This is useful way to mass dissociate the many-to-many linked records. E.g:

    ——————- ItemsGroupEntity.java ——————–

    @Entity
    public class ItemsGroupEntity {

    @ManyToMany(targetEntity = ItemEntity.class, mappedBy = “groups”, cascade = {CascadeType.PERSIST})
    protected Set items;
    }

    ——————- ItemEntity.java ———————–

    @Entity
    public class ItemEntity {

    @ManyToMany(targetEntity = ItemsGroupEntity.class, cascade = {CascadeType.PERSIST})
    @JoinTable(name = “Group2Item”, joinColumns = {
    @JoinColumn(name = “item_id”)
    }, inverseJoinColumns = {
    @JoinColumn(name = “group_id”)
    })
    protected Set groups;
    }

    —————— ItemGroupAssociation.java —————-

    @Entity
    @Table(name=”Group2Item”)
    public class ItemGroupAssociation {

    @EmbeddedId
    protected CompositeKey compositeKey;

    @Embeddable
    public static class CompositeKey implements Serializable {
    @ManyToOne
    @JoinColumn(name = “item_id”)
    protected ItemEntity item;

    @ManyToOne()
    @JoinColumn(name = “group_id”)
    protected ItemsGroupEntity group;
    }
    }

    ————— hibernate.cfg.xml —————–

    This mapping allows to exec HQL queries(now Hibernate knows the “Group2Item” table as entity of ItemGroupAssociation type):

    org.hibernate.Session session;

    session.createQuery(“delete from ” + ItemGroupAssociation.class.getSimpleName() + ” where compositeKey.item.id IN (:ids)”).setParameterList(“ids”, new Long[]{1L, 2L, 3L, …}).executeUpdate();

    session.createQuery(“delete from ” + ItemGroupAssociation.class.getSimpleName() + ” where compositeKey.group.id IN (:ids)”).setParameterList(“ids”, this.model.targets).executeUpdate();

    … instead of dissociating entities one after another inside loop.

    • Woops, the hibernate.cfg.xml text is eaten by forum engine… Well, it’s most interesting part is:

      <mapping class=”entity.ItemEntity”/>
      <mapping class=”entity.ItemsGroupEntity”/>
      <mapping class=”entity.ItemGroupAssociation”/>
      <mapping class=”entity.ItemGroupAssociation$CompositeKey”/>

  28. I must thank you for the efforts you have put in writing this site.
    I am hoping to view the same high-grade blog posts
    by you in the future as well. In truth, your creative writing abilities has inspired me to get my
    very own site now ;)

  29. Hi, I m getting Error

    Exception in thread “main” java.lang.NullPointerException
    at SampleJavaProject.Entity.PersonBankAssociation.setPerson(PersonBankAssociation.java:97)
    at SampleJavaProject.HibernateTransaction.PersonBankApp.main(PersonBankApp.java:57)

  30. Hi, this works for me. Thanks for your effort!
    But now when i try to build a Criteria Query like:

    List queried_accounts = HibernateUtils.criteriaList(
    session.createCriteria(Account.class).
    createCriteria(“accountAttribute”).
    createCriteria(“aa_pk.attribut”).
    add(Restrictions.isNotNull(“name”))
    );
    … i get an exception:
    org.hibernate.exception.SQLGrammarException: could not extract ResultSet

    Entity
    @Table(name = “account”)
    public class Account {

    private Long accountId;
    private List accountAttribute = new LinkedList();
    private Person person;
    private Zielsystem zielsystem;

    public Account() {
    }
    // ….
    }

    @Entity
    @Table(name=”account_attribut”)
    @AssociationOverrides({
    @AssociationOverride(name=”aa_pk.account”, joinColumns = @JoinColumn(name=”account_id”)),
    @AssociationOverride(name=”aa_pk.attribut”, joinColumns = @JoinColumn(name=”attribut_id”))
    })
    public class AccountAttribut {
    //…
    }

    it seems, that i cant use the defined “aa_pk.attribut” property name in the query. What im doing wrong?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Yet another Java blog

mysoftwarequality

Breaking people's balls since February 1970

Yet another Java blog

Jess & the City

Cynical ramblings of a 30ish-year-old Latin girl living in Dublin

Follow

Get every new post delivered to your Inbox.

Join 62 other followers

%d bloggers like this: