views:

272

answers:

2

Using Hibernate + annotations, I'm trying to do the following:

Two entities, Entity1 and Entity2.

  • Entity1 contains a simple generated value primary key.
  • Entity2 primary key is composed by a simple generated value + the id of entity one (with a many to one relationship)

Unfortunately, I can't make it work.

Here is an excerpt of the code:

@Entity
public class Entity1 {

 @Id @GeneratedValue
 private Long id;

 private String name;

    ...
}

@Entity
public class Entity2 {

 @EmbeddedId
 private Entity2PK pk = new Entity2PK();

 private String miscData;

    ...
}

@Embeddable
public class Entity2PK implements Serializable {

 @GeneratedValue
 private Long id;

 @ManyToOne
 private Entity1 entity;
}

void test() {

    Entity1 e1 = new Entity1();
    e1.setName("nameE1");
    Entity2 e2 = new Entity2();

    e2.setEntity1(e1);
    e2.setMiscData("test");

    Transaction transaction = session.getTransaction();
    try {
     transaction.begin();

     session.save(e1);
     session.save(e2);

     transaction.commit();
    } catch (Exception e) {
     transaction.rollback();
    } finally {
     session.close();
    }
}

When I run the test method I get the following errors:

Hibernate: insert into Entity1 (id, name) values (null, ?)
Hibernate: call identity()
Hibernate: insert into Entity2 (miscData, entity_id, id) values (?, ?, ?)
07-Jun-2010 10:51:11 org.hibernate.util.JDBCExceptionReporter logExceptions
WARNING: SQL Error: 0, SQLState: null
07-Jun-2010 10:51:11 org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: failed batch
07-Jun-2010 10:51:11 org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
 at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
 at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
 at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
 at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:254)
 at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
 at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:167)
 at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
 at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
 at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1001)
 at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:339)
 at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
 at test.App.main(App.java:32)
Caused by: java.sql.BatchUpdateException: failed batch
 at org.hsqldb.jdbc.jdbcStatement.executeBatch(Unknown Source)
 at org.hsqldb.jdbc.jdbcPreparedStatement.executeBatch(Unknown Source)
 at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
 at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:247)
 ... 8 more

Note that I use HSQLDB.

Any ideas about what is wrong ?

A: 

You can use foreign key in embedded id as a separate read-only column:

@Entity 
public class Entity2 { 

    @EmbeddedId 
    private Entity2PK pk = new Entity2PK(); 

    @ManyToOne 
    @JoinColumn(name = "entity1_id")
    private Entity1 entity; 

    ... 
} 

@Embeddable 
public class Entity2PK implements Serializable { 

    @GeneratedValue 
    private Long id; 

    @Column(name = "entity1_id", insertable = false, updateable = false)
    private Long entity1Id;

}
axtavt
I tried your solution but unfortunately can't make it work : Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: test.Entity2 column: entity1_id (should be mapped with insert="false" update="false")
David
@David: Note the `insertable = false, updateable = false` in `Entity2PK`.
axtavt
A: 

Do as follows whether you want this kind of behavior (You nedd to use property access instead of field one)

Parent.class (Notice a property of Type MutableInt and addChild as a way to set up both sides)

@Entity
public class Parent implements Serializable {

    private MutableInt id = new MutableInt();

    private List<Child> childList = new ArrayList();

    @OneToMany(mappedBy="parent")
    @JoinColumn(name="PARENT_ID", insertable=false, updatable=false)
    @Cascade(CascadeType.SAVE_UPDATE)
    public List<Child> getChildList() {
        return childList;
    }

    public void setChildList(List<Child> childList) {
        this.childList = childList;
    }

    @Id
    @GeneratedValue
    public Integer getId() {
        return id.intValue();
    }

    public void setId(Integer id) {
        this.id.setValue(id);
    }

    @Transient
    public MutableInt getIdAsMutableInt() {
        return id;
    }

    /**
     * Add convenience method 
     * 
     * A way to set up both sides (You have a bi-directional relationship, right ???)
     */
    public void addChild(Child child) {
        if(child.getChildId() == null)
            child.setChildId(new Child.ChildId());

        child.getChildId().setParentIdAsMutableInt(id);

        getChildList().add(child);

        child.setParent(this);
    }

}

Child.class (Notice static inner class )

@Entity
public class Child implements Serializable {

    private ChildId childId;

    private Parent parent;

    @EmbeddedId
    public ChildId getChildId() {
        return childId;
    }

    public void setChildId(ChildId childId) {
        this.childId = childId;
    }

    @ManyToOne
    @JoinColumn(name="PARENT_ID", insertable=false, updatable=false)
    public Parent getParent() {
        return parent;
    }

    public void setParent(Parent parent) {
        this.parent = parent;
    }

    /**
      * Composite primary key class MUST override equals and hashCode
      */
    @Embeddable
    public static class ChildId implements Serializable {

        private MutableInt parentId = new MutableInt();

        private Integer chId;

        public void setParentIdAsMutableInt(MutableInt parentId) {
            this.parentId = parentId;
        }

        @GeneratedValue
        public Integer getChId() {
            return chId;
        }

        public void setChId(Integer chId) {
            this.chId = chId;
        }

        @Column(name="PARENT_ID")
        public Integer getParentId() {
            return parentId.intValue();
        }

        public void setParentId(Integer parentId) {
            this.parentId.setValue(parentId);
        }

        @Override
        public boolean equals(Object o) {
            if(!(o instanceof ChildId))
                return false;

            final ChildId other = (ChildId) o;
            return new EqualsBuilder()
                       .append(getChId(), other.getChId())
                       .append(getParentId(), other.getParentId()).isEquals();
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 11 * hash + getParentId().hashCode();
            hash = 11 * hash + getChId().hashCode();
            return hash;
        }

    }

}

And to test

Session session = configuration.buildSessionFactory().openSession();
session.beginTransaction();

Parent parent = new Parent();
parent.addChild(new Child());

session.save(parent);

session.getTransaction().commit();

You need a MutableInt because of Integer is immutable instance. But your MutableInt field is encapsulated by a Integer property. See carefully

Arthur Ronald F D Garcia
Thanks for your solution but I find it tedious. Why can't it be as simple as the code I've made ?
David
@David If you really want this kind of behavior, you should do as shown. Keep in mind: if your @Entity needs a composite primary key, such as Child, you **should** provide a composite primary key class and **must** override equals and hashCode - See ChildId class. add convenience method - see Parent class - Takes care of setting up both sides of The bi-directional relationship.
Arthur Ronald F D Garcia