Hi !
I have a problem with unidirectional one to many association.
I tried some samples from the net. There are two classes
Group and Story
each group can have one ore more stories.
The List of Story ist annotated as OneToMany with mappedBy=parent_id (column in the stories table).
Unfortunately the parent_id is not updated with the group id when a Group object is stored, and an exception occurres, because of a constraint violation (parent_id 0 does not exist as an id in groups table)
I do not know whether this is normal behavior or not. Hibernate Annotations documentation tells you, that in the dependend class (the Story class) nothing has to be annotated.
The Group class
Code:
package test.onetomany.copy;
import java.util.*;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "groups")
public class Group {
private int id;
private String name;
private List stories;
public Group() {
}
public Group(String name) {
this.name = name;
}
public void setId(int i) {
System.out.println("g.setID=" + i);
id = i;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
System.out.println("g.getID=" + id);
return id;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setStories(List l) {
stories = l;
}
@OneToMany(fetch = FetchType.EAGER, mappedBy = "parent_id", targetEntity = Story.class, cascade = { javax.persistence.CascadeType.ALL })
public List getStories() {
return stories;
}
}
The Story class
Code:
package test.onetomany.copy;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "stories")
public class Story {
private int id;
private String info;
private int parent_id;
public int getParent_id() {
return parent_id;
}
public void setParent_id(int parent_id) {
System.out.println("s.setparent_id=" + parent_id);
this.parent_id = parent_id;
}
public Story() {
}
public Story(String info) {
this.info = info;
}
public void setId(int i) {
System.out.println("s.setID=" + i);
id = i;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
System.out.println("s.getID=" + id);
return id;
}
public void setInfo(String n) {
info = n;
}
public String getInfo() {
return info;
}
}
The Test class
Code:
package test.onetomany.copy;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class Test {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
Transaction transaction = null;
try {
Session session = HibernateUtil.getCurrentSession();
transaction = session.beginTransaction();
Group sp = new Group("Group Name");
session.saveOrUpdate(sp);
transaction.commit();
session = HibernateUtil.getCurrentSession();
transaction = session.beginTransaction();
ArrayList list = new ArrayList();
list.add(new Story("Story Name 1"));
list.add(new Story("Story Name 2"));
list.add(new Story("Story Name 3"));
list.add(new Story("Story Name 4"));
sp.setStories(list);
session.saveOrUpdate(sp);
transaction.commit();
// reload
session = HibernateUtil.getCurrentSession();
transaction = session.beginTransaction();
Query q = session.createQuery("from Group");
List<Group> geometries = (List<Group>) q.list();
for (Group g : geometries) {
List<Story> stories = g.getStories();
for (Story s : stories) {
System.out.println("story info=" + s.getInfo());
}
}
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
throw e;
}
} finally {
// session.close();
}
}
}
Hibernate config looks like this,
Database is automatically generated
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory name="SessionFactory">
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
<property name="hibernate.connection.url">jdbc:postgresql://localhost:5433/postgis</property>
<property name="hibernate.connection.password">postgres</property>
<property name="hibernate.connection.username">postgres</property>
<!-- <property name="hibernate.connection.catalog">postgis</property> -->
<!-- <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property> -->
<!-- <property name="hibernate.dialect">org.hibernatespatial.postgis.PostgisDialect</property> -->
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!--<property name="hibernate.current_session_context_class">thread</property> -->
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hibernate.hbm2ddl.auto">create</property>
<mapping class="test.onetomany.copy.Group"/>
<mapping class="test.onetomany.copy.Story"/>
</session-factory>
</hibernate-configuration>
finally the code of the HibernatUtil class
Code:
package test.onetomany.copy;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static SessionFactory sessionFactory;
static {
createSessionFactory();
}
private static void createSessionFactory() {
try {
// Create the SessionFactory
String path = HibernateUtil.class.getPackage().getName().replace('.','/');
System.out.println("path=" + path);
sessionFactory = new AnnotationConfiguration().configure(path + "/hibernate.cfg.xml")
.buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
// Creating a SessionFactory ist time consuming. This should
// be done only once per database. SessionFactory is thread-safe.
public static SessionFactory getSessionFactory() {
if (sessionFactory == null)
createSessionFactory();
return sessionFactory;
}
// Version 2:
// Session is a lightweight component, but it isn't thread-safe.
// Therefore, a separate connection has to be opened for each connection.
// Hibernate 3 offers a tailor-made solution for this task:
// getCurrentSession() delivers a different session for each thread.
// Don't forget to set the property
// hibernate.current_session_context_class = thread.
// Moreover, the session is closed if the associated transaction is
// committed or rollbacked.
public static Session getCurrentSession() {
return getSessionFactory().getCurrentSession();
}
public static void closeSessionFactory() {
sessionFactory.close();
}
public static void saveOrUpdate(Object object) {
Session s = HibernateUtil.getCurrentSession();
Transaction tx = s.beginTransaction();
s.saveOrUpdate(object);
tx.commit();
}
}
Hibernate version: 3, Annotations 3.2.1
hibernate spatial postgis 1.0 M2
Mapping documents:Code between sessionFactory.openSession() and session.close():Session session = HibernateUtil.getCurrentSession();
transaction = session.beginTransaction();
Group sp = new Group("Group Name");
session.saveOrUpdate(sp);
transaction.commit();
session = HibernateUtil.getCurrentSession();
transaction = session.beginTransaction();
ArrayList list = new ArrayList();
list.add(new Story("Story Name 1"));
list.add(new Story("Story Name 2"));
list.add(new Story("Story Name 3"));
list.add(new Story("Story Name 4"));
sp.setStories(list);
session.saveOrUpdate(sp);
transaction.commit();
Full stack trace of any exception that occurs:Exception in thread "main" org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
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:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at test.onetomany.copy.Test.main(Test.java:44)
Caused by: java.sql.BatchUpdateException: Batch-Eintrag 0 insert into stories (info, parent_id, id) values (Story Name 1, 0, 2) wurde abgebrochen. Rufen Sie 'getNextException' auf, um die Ursache zu erfahren.
at org.postgresql.jdbc2.AbstractJdbc2Statement$BatchResultHandler.handleError(AbstractJdbc2Statement.java:2534)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1317)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:350)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeBatch(AbstractJdbc2Statement.java:2596)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
... 8 more
Name and version of the database you are using:Postgres 8.3
The generated SQL (show_sql=true):s.getID=0
g.getID=0
g.getID=0
Hibernate:
select
nextval ('hibernate_sequence')
g.setID=1
g.getID=1
g.getID=1
Hibernate:
insert
into
groups
(name, id)
values
(?, ?)
g.getID=1
g.getID=1
s.getID=0
Hibernate:
select
nextval ('hibernate_sequence')
s.setID=2
s.getID=0
Hibernate:
select
nextval ('hibernate_sequence')
s.setID=3
s.getID=0
Hibernate:
select
nextval ('hibernate_sequence')
s.setID=4
s.getID=0
Hibernate:
select
nextval ('hibernate_sequence')
s.setID=5
g.getID=1
s.getID=2
s.getID=3
s.getID=4
s.getID=5
s.getID=5
s.getID=2
g.getID=1
s.getID=2
s.getID=3
s.getID=4
s.getID=5
s.getID=4
s.getID=3
Hibernate:
insert
into
stories
(info, parent_id, id)
values
(?, ?, ?)
Hibernate:
insert
into
stories
(info, parent_id, id)
values
(?, ?, ?)
Hibernate:
insert
into
stories
(info, parent_id, id)
values
(?, ?, ?)
Hibernate:
insert
into
stories
(info, parent_id, id)
values
(?, ?, ?)
Note:
I do not want to introduce a backward reference for bidirectional association. I know that it works with a backward reference, but consider story is an object which can be referenced and stored by several different objects.
I ve tried to insert a ManyToOne annotation at parent_id in Story, but then hibernate wants an entity type.
currently my workaround is to update the id myself in the
getStories method of Group:
Code:
if (stories != null) {
for (Story s: (List<Story>)stories ) {
s.setParent_id(getId());
}
}
Maybe somebody has an idea, why this does not work. I think solution will be quite simple, but i dont see it now.
regards
Michael