Search code examples
javahibernatejpajava-ee-6

JPA 2 Hibernate mapping with composite key in primary key using @IdClass with 3 tier structure


This question is very similar to: JPA (Hibernate, EclipseLink) mapping: why doesn't this code work (chain of 2 relationships using JPA 2.0, @EmbeddedId composite PK-FK)?

Actually my only (from meaningful that I spotted) difference is that I use @IdClass and that I most probably won't be able to switch to a different provider than hibernate.

but anyway here is the code (removed parts that where unimportant):

PermissionContextType.java:

@Entity
@IdClass(PermissionContextTypePk.class)
public class PermissionContextType{
   @Id    
   private String id;    

   @Id
   @JoinColumn (name = "PROJECT", referencedColumnName = "ID")
   @ManyToOne ()
   private Project project;

   public static class PermissionContextTypePk implements Serializable{
       public String project;
       public String id;
       // ... eq and hashCode here ...
   }

}

PermissionContext.java:

@Entity
@IdClass(PermissionContextPk.class)
public class PermissionContext{
   @Id
   private String id;

   @Id
   @JoinColumns ({
            @JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
            @JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "ID")
        })
   @ManyToOne
   private PermissionContextType permissionContextType;

   public static class PermissionContextPk implements Serializable{
      public String id;
      public PermissionContextTypePk permissionContextType;
      // ... eq and hashCode here ...
   }
}

Permission.java:

@Entity
@IdClass(PermissionPk.class)
public class Permission{

   @Id
   private String id;

   @Id
   @JoinColumns ({
            @JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
            @JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE"),
            @JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID")
        })
   @ManyToOne
   private PermissionContext permissionContext;

   public static class PermissionPk implements Serializable{
      public String id;        
      public PermissionContextPk permissionContext;
      // ... eq and hashCode here ...
   }
}

and what I get is:

 org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a @MapsId: PermissionContext 
    Caused by: org.hibernate.AssertionFailure: org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a @MapsId: PermissionContext

does anybody know if this is a hibernate bug and I should post it on their issue tracking system (and pray that I would be able to update to given hibernate version) or is there something fundamentally wrong with my way of binding the entities?

I've checked it with the hibernate implementation on EAP 6.1 (4.2.0) as well as on wildfly (don't really know which one.)


Solution

  • Ok, so this is what I found so far :

    Thanks fr my friend : https://hibernate.atlassian.net/browse/HHH-5764 which most probably is the reason for this behaviour.

    And I found a workaround :

    Permission.java:

    @Entity
    @IdClass(PermissionPk.class)
    public class Permission{
    
       @Id
       private String id;
    
    // for the next 3 fields there are no public acessors, so the public API of the class was not changed !
       @Id
       @Column(name = "PROJECT")
       private String projectId;
    
       @Id
       @Column(name = "PERMISSIONCONTEXTTYPE")
       private String permissionContextTypeId;
    
       @Id
       @Column(name = "PERMISSIONCONTEXT")
       private String permissionContextId;
    
    
       @JoinColumns ({
                @JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT", updatable = false, insertable = false),
                @JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE", updatable = false, insertable = false),
                @JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID", updatable = false, insertable = false)
            })
       @ManyToOne
       private PermissionContext permissionContext;
    
       public static class PermissionPk implements Serializable{      
    // previously they where private as well, but removed public constructor for the sake of simplicity of the question - so no changes where necesary in public API of the class !
          private String id;        
          private String projectId;        
          private String permissionContextTypeId;
          private String permissionContextId;
    
       public PermissionPk () {}
    
        public PermissionPk (String aId, PermissionContextPk aPermissionContext) {
            this.id = aId;
            permissionContextId = aPermissionContext.id;
            permissionContextTypeId = aPermissionContext.permissionContextType.id;
            projectId = aPermissionContext.permissionContextType.project;
        }  
    ... eq and hashCode here ...
       }
    }
    

    The good thing about this workaround is that it does not change the public API of the class in any way (the only change was that I needed to make fields in Pk's of context and contexttype visible to the PermissionPk - they where private before with only a public constructor [but again simplified for the question]), nor did it change the jpql queries, and at the same time workaround is scalable (to any tier amount - as long as every even pk does not contain another pk), so if the bug will be resolved it will be easy to remove the workaround.

    I would still gladly accept any comments on either my workaround or the question in itself.