How to create an entity with a composite primary key containing a generated value
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 ?
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
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;
}
精彩评论