Search code examples
javahibernatehibernate-mapping

How to solve hibernate composite key exception (expecting IdClass mapping)?


This question has been asked here before but none of the solutions are working for me. I am getting following error due to composite key in my SQL Server 2008 table.

java.lang.ExceptionInInitializerError
    at HB.Main.<clinit>(Main.java:30)
Caused by: java.lang.IllegalArgumentException: expecting IdClass mapping
    at org.hibernate.metamodel.internal.AttributeFactory$3.resolveMember(AttributeFactory.java:977)
    at org.hibernate.metamodel.internal.AttributeFactory$5.resolveMember(AttributeFactory.java:1035)
    at org.hibernate.metamodel.internal.AttributeFactory.determineAttributeMetadata(AttributeFactory.java:450)
    at org.hibernate.metamodel.internal.AttributeFactory.buildIdAttribute(AttributeFactory.java:139)
    at org.hibernate.metamodel.internal.MetadataContext.buildIdClassAttributes(MetadataContext.java:388)
    at org.hibernate.metamodel.internal.MetadataContext.applyIdMetadata(MetadataContext.java:318)
    at org.hibernate.metamodel.internal.MetadataContext.wrapUp(MetadataContext.java:221)
    at org.hibernate.metamodel.internal.MetamodelImpl.initialize(MetamodelImpl.java:274)
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:305)
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:462)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:708)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:724)
    at HB.Main.<clinit>(Main.java:26)
Exception in thread "main" 

my table looks like this

enter image description here

The classes and mapping is as follows: Entity Class

package HB;

import java.util.Objects;

public class EmMonthlyPollTablesEntity
{
    private int monthlyPollId;
    private int tableId;

    public int getMonthlyPollId()
    {
        return monthlyPollId;
    }

    public void setMonthlyPollId(int monthlyPollId)
    {
        this.monthlyPollId = monthlyPollId;
    }

    public int getTableId()
    {
        return tableId;
    }

    public void setTableId(int tableId)
    {
        this.tableId = tableId;
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        EmMonthlyPollTablesEntity that = (EmMonthlyPollTablesEntity) o;
        return monthlyPollId == that.monthlyPollId && tableId == that.tableId;
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(monthlyPollId, tableId);
    }
}

ID Class

package HB;

import java.io.Serializable;
import java.util.Objects;

public class EmMonthlyPollTablesEntityPK implements Serializable
{
    private int monthlyPollId;
    private int tableId;

    public EmMonthlyPollTablesEntityPK()
    {
    }

    public EmMonthlyPollTablesEntityPK(int monthlyPollId, int tableId)
    {
        this.monthlyPollId = monthlyPollId;
        this.tableId = tableId;
    }

    public int getMonthlyPollId()
    {
        return monthlyPollId;
    }

    public void setMonthlyPollId(int monthlyPollId)
    {
        this.monthlyPollId = monthlyPollId;
    }

    public int getTableId()
    {
        return tableId;
    }

    public void setTableId(int tableId)
    {
        this.tableId = tableId;
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        EmMonthlyPollTablesEntityPK that = (EmMonthlyPollTablesEntityPK) o;
        return monthlyPollId == that.monthlyPollId && tableId == that.tableId;
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(monthlyPollId, tableId);
    }
}

Main Class

package HB;

import org.hibernate.HibernateException;
import org.hibernate.Metamodel;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;

import javax.persistence.metamodel.EntityType;

public class Main
{
    private static final SessionFactory ourSessionFactory;

    static
    {
        try
        {
            Configuration configuration = new Configuration();
            configuration.setProperty("hibernate.connection.username", "sa");
            configuration.setProperty("hibernate.connection.password", "");

            configuration.configure();

            ourSessionFactory = configuration.buildSessionFactory();
        }
        catch (Throwable ex)
        {
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static Session getSession() throws HibernateException
    {
        return ourSessionFactory.openSession();
    }

    public static void main(final String[] args) throws Exception
    {
        final Session session = getSession();
        try
        {

            System.out.println("querying all the managed entities...");
            final Metamodel metamodel = session.getSessionFactory().getMetamodel();
            for (EntityType<?> entityType : metamodel.getEntities())
            {
                try
                {
                    final String entityName = entityType.getName();
                    final Query query = session.createQuery("from " + entityName);
                    System.out.println("executing: " + query.getQueryString());
                    for (Object o : query.list())
                    {
                        try
                        {
                            System.out.println("  " + o);
                        }

                        catch (Exception ex)
                        {
                            ex.printStackTrace();
                            ;
                        }
                    }
                }
                catch (Exception ex)
                {
                    ex.printStackTrace();
                    ;
                }
            }

        }
        finally
        {
            session.close();
    }
}

}

Mapping

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="HB.EmMonthlyPollTablesEntity" table="EM_MONTHLY_POLL_TABLES" schema="dbo" catalog="HB2">
        <composite-id mapped="true" class="HB.EmMonthlyPollTablesEntityPK">
            <key-property name="monthlyPollId">
                <column name="MONTHLY_POLL_ID" sql-type="int"/>
            </key-property>
            <key-property name="tableId">
                <column name="TABLE_ID" sql-type="int"/>
            </key-property>
        </composite-id>
    </class>
</hibernate-mapping>

Entire Intellij Idea Project and table script can be downloaded from here


Solution

  • There is a bug in Hibernate composite id's defined via hbm.xml cause java.lang.IllegalArgumentException in JPA deployment

    Root cause

    In JPA the composite id must be embeddable. Because the EmMonthlyPollTablesEntityPK class is not embeddable and that is not a component the JPA environment will never support that.

    Solution 1: Workaround

    Disable metamodel population in hibernate.cfg.xml

    <hibernate-configuration>
        <session-factory>
            <property name="hibernate.ejb.metamodel.population">disabled</property>
            <mapping class="..."/>
    <!-- ... -->
        </session-factory>
    </hibernate-configuration>
    

    Solution 2: Drop HBM Mappings and use annotations

    As I know HBM mappings are deprecated and will be removed in a future version of Hibernate.

    In this case you can choose between using Composite Id, IdClass and EmbeddedId approaches. The following example uses EmbeddedId

    Entity class

    @Entity
    @Table(name = "EM_MONTHLY_POLL_TABLES")
    public class EmMonthlyPollTablesEntity implements Serializable {
    
        @EmbeddedId
        private EmMonthlyPollTablesEntityPK id;
    
        @Column(name = "NAME")
        private String name;
    
        public EmMonthlyPollTablesEntityPK getId() {
            return id;
        }
    
        public void setId(EmMonthlyPollTablesEntityPK id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof EmMonthlyPollTablesEntity)) return false;
            EmMonthlyPollTablesEntity that = (EmMonthlyPollTablesEntity) o;
            return id.equals(that.id) &&
                    Objects.equals(name, that.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(id, name);
        }
    }
    

    PK class

    @Embeddable
    public class EmMonthlyPollTablesEntityPK implements Serializable {
    
        @Column(name = "MONTHLY_POLL_ID", nullable = false)
        private int monthlyPollId;
    
        @Column(name = "TABLE_ID", nullable = false)
        private int tableId;
    
        public EmMonthlyPollTablesEntityPK() {
        }
    
        public EmMonthlyPollTablesEntityPK(int monthlyPollId, int tableId) {
            this.monthlyPollId = monthlyPollId;
            this.tableId = tableId;
        }
    
        public int getMonthlyPollId() {
            return monthlyPollId;
        }
    
        public void setMonthlyPollId(int monthlyPollId) {
            this.monthlyPollId = monthlyPollId;
        }
    
        public int getTableId() {
            return tableId;
        }
    
        public void setTableId(int tableId) {
            this.tableId = tableId;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            EmMonthlyPollTablesEntityPK that = (EmMonthlyPollTablesEntityPK) o;
            return monthlyPollId == that.monthlyPollId && tableId == that.tableId;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(monthlyPollId, tableId);
        }
    }
    

    Delegating PK's getters and setters are available of course. In this case you don't have to disable metamodel population and you can drop HBM mapping resource from hibernate.cfg.xml

    Although I know migrating a huge legacy codebase to JPA annotations and XML mappings is very exhausting I'm sure that is the way to go.