Hello,
I have a question that may have been answered elsewhere on the forums (but if so, I couldn't find the proper keywords). Basically, I'm wondering what is the best way to enforce referential integrity using Hibernate.
If I were doing pure POJOs, I might enforce referential integrity programatically. So, for example, if I had a many-to-many bidirectional association between a Person and an Event (to use the example from the tutorial), I might code them as follows:
Code:
public class Event {
...
public Set getParticipants() {
return participants;
}
public void setParticipants(Set participants) {
this.participants = participants;
}
public void addParticipant(Person participant) {
if (!getParticipants().contains(participant)) {
getParticipants().add(participant);
participant.addEvent(this);
}
}
public void removeParticipant(Person participant) {
if (getParticipants().contains(participant)) {
getParticipants().remove(participant);
participant.removeEvent(this);
}
}
}
Likewise, I would code the Person class as follows:
Code:
public class Person {
...
public Set getEvents() {
return events;
}
public void setEvents(Set events) {
this.events = events;
}
public void addEvent(Event event) {
if (!getEvents().contains(event)) {
getEvents().add(event);
event.addParticipant(this);
}
}
public void removeEvent(Event event) {
if (getEvents().contains(event)) {
getEvents().remove(event);
event.removeParticipant(this);
}
}
}
With this code, adding an event to a participant will turn around and add the participant to the event (and vice-versa) programmatically (i.e. in the object layer).
However, when I write code like the above, and then attempt to commit them in a transaction, I get a ConstraintViolationException because both the person and event objects are attempting to write a record with the same key values to the join table.
From experimentation, I know that if I
don't write the referential integrity code in Java, the transaction will commit fine, and then
after the commit, I can read the association from either side of the relationship and it will be available (i.e. the database is enforcing the referential integrity).
So...my question is, what is the proper way to go about doing this in Hibernate. Presumably, I want my object to exhibit referential integrity, but in order to have that happen (at least for a many-to-many), I need to commit them to the DB before attempting to traverse the relationships. Is this correct?
Though I don't think this is a 'bug' per se, I'm curious in feedback. For the record, here is the information on my setup
Hibernate version: 3.1
Mapping documents:Event.hbm.xml
Code:
<hibernate-mapping>
<class name="hello.Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
<set name="participants" table="PERSON_EVENT">
<key column="EVENT_ID"/>
<many-to-many column="PERSON_ID" class="hello.Person"/>
</set>
</class>
</hibernate-mapping>
Person.hbm.xml
Code:
<hibernate-mapping>
<class name="hello.Person" table="PERSON">
<id name="id" column="PERSON_ID">
<generator class="native"/>
</id>
<property name="age"/>
<property name="firstname"/>
<property name="lastname"/>
<set name="events" table="PERSON_EVENT">
<key column="PERSON_ID"/>
<many-to-many column="EVENT_ID" class="hello.Event"/>
</set>
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():Code:
Transaction tx = session.beginTransaction();
Person aPerson = (Person) session.load(Person.class, personId);
Event anEvent = (Event) session.load(Event.class, eventId);
aPerson.addEvent(anEvent);
tx.commit();
Full stack trace of any exception that occurs:Code:
Exception in thread "main" org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.ErrorCodeConverter.convert(ErrorCodeConverter.java:74)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:181)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:226)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:139)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:274)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:730)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:324)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:86)
at hello.EventManager.addPersonToEvent(EventManager.java:89)
at hello.EventManager.main(EventManager.java:33)
Caused by: java.sql.BatchUpdateException: Duplicate entry '5-5' for key 1
at com.mysql.jdbc.ServerPreparedStatement.executeBatch(ServerPreparedStatement.java:647)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:57)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:174)
Name and version of the database you are using:MySQL
The generated SQL (show_sql=true):Code:
Hibernate: insert into PERSON_EVENT (PERSON_ID, EVENT_ID) values (?, ?)
Hibernate: insert into PERSON_EVENT (EVENT_ID, PERSON_ID) values (?, ?)