Issue
I have the following entity:
@Entity
@AttributeOverrides({
@AttributeOverride(name="id", column=@Column(name="person_id))
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
@Table(name="people")
public class Person {
@Column(name="name")
private String name;
@ManyToMany(cascade=CascadeType.ALL)
@JoinColumn(name="friend_person_id", nullable=true)
private List<Person> friends;
@ManyToMany(cascade=CascadeType.ALL)
@JoinColumn(name="enemy_person_id", nullable=true)
private List<Person> enemies;
// constructor, getters/setters, etc.
}
Simply, a Person
has a name and a list of friends (who are also all people) as well as a list of enemies (more people). Hence Person
has 2 many to many relationships with itself: 1 for friends, and 1 for enemies. However, it is possible for a person to have no friends and/or no enemies. Hence I want these fields to be nullable.
Here is my PersonDAO
, which exposes public methods that can be used for persisting people to the DB:
// Inside PersonDAO
public void savePerson(Person person) {
Session session = getDaoUtils().newSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
// Execute the query.
session.saveOrUpdate(person);
transaction.commit();
} catch(Throwable throwable) {
transaction.rollback();
throw new RuntimeException(throwable);
} finally {
session.close();
}
}
Finally, I have a little test script for testing this all out:
public class Tester {
public static void main(String[] args) {
Person tom = new Person("Tom", null, null); // Tom has no friends/enemies.
Person bob = new Person("Bob", null, null); // Bob also has no friends/enemies.
List<Person> friends = new ArrayList<Person>();
List<Person> enemies = new ArrayList<Person>();
friends.add(tom);
enemies.add(bob);
// Randy's friend is Tom. Randy's enemy is Bob.
Person randy = new Person("Randy", friends, enemies);
PersonDAO personDAO = new PersonDAO();
personDAO.savePerson(randy);
}
}
When I run this I get the following exception (against an H2 DB):
INFO: HHH000010: On release of batch it still contained JDBC statements
Exception in thread "main" java.lang.RuntimeException: org.hibernate.exception.ConstraintViolationException: could not execute statement
at net.myuser.myapp.common.dal.dao.PersonDAO.savePerson(PersonDAO.java:38)
at net.myuser.myapp.tools.personloader.Tester.main(Tester.java:25)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:129)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:136)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:58)
at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1256)
at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:58)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:377)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:369)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:292)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:339)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1234)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:404)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
at net.myuser.myapp.common.dal.dao.PersonDAO.savePerson(PersonDAO.java:30)
... 1 more
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "FRIENDS_PERSON_ID"; SQL statement:
/* insert collection row net.myuser.myapp.common.dal.dto.Person.enemies */ insert into persons_persons (persons_person_id, enemies_word_id) values (?, ?) [23502-173]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:331)
at org.h2.message.DbException.get(DbException.java:171)
at org.h2.message.DbException.get(DbException.java:148)
at org.h2.table.Column.validateConvertUpdateSequence(Column.java:295)
at org.h2.table.Table.validateConvertUpdateSequence(Table.java:699)
at org.h2.command.dml.Insert.insertRows(Insert.java:123)
at org.h2.command.dml.Insert.update(Insert.java:86)
at org.h2.command.CommandContainer.update(CommandContainer.java:79)
at org.h2.command.Command.executeUpdate(Command.java:235)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:154)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:140)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:133)
... 14 more
So it looks like Hibernate is attempting to store both many to many relationships into 1 table? Either way, I can't figure out what it's doing. After I run this, I see persons_persons
table in my database, but both its friends_person_id
and enemies_person_id
columns are NOT NULLABLE so it's disregarding my nullable=true
setting.
How can I annotate my entity correctly?
Solution
@Entity
@Table(name = "people")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long Id;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "friends", joinColumns = @JoinColumn(name = "person_id"), inverseJoinColumns = @JoinColumn(name = "friend_id"))
private List<Person> friends;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "enemies", joinColumns = @JoinColumn(name = "person_id"), inverseJoinColumns = @JoinColumn(name = "enemy_id"))
private List<Person> enemies;
public Person() {
friends = new ArrayList<>();
enemies = new ArrayList<>();
}
/**
*
* Optionally force client classes through your add/remove methods if mutual
* relationship should be maintained.
*
* @return
*/
public List<Person> getFriends() {
return Collections.unmodifiableList(friends);
}
/**
* Optionally force client classes through your add/remove methods if mutual
* relationship should be maintained.
*
* @return
*/
public List<Person> getEnemies() {
return Collections.unmodifiableList(enemies);
}
/**
* Ensures mutual relationship set correctly.
*
* @param person
*/
public void addFriend(Person person) {
friends.add(person);
person.friends.add(this);
}
/**
* Ensures mutual relationship set correctly.
*
* @param person
*/
public void addEnemy(Person person) {
enemies.add(person);
person.enemies.add(this);
}
}
Answered By - Alan Hay
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)