Hello,
I have a stackoverflow error in the following test case.
This test case define a bidirectionnal relation OneToMany between A and B where at least one B is Required in the collection A.getB().
The goal of the test case is to delete some B retreived from database.
My conclusion: it does not work when retreiving B directly - see
deleteFromB() - but it works when getting B from the retreived A - see
deleteFromA().
In class MainAB.java, if I use
deleteFromA() method, the deletion of "B"s works but
deleteFromB() causes a stackoverflow.
I think it is a bug on Hibernate or Hibernate Validator but I am not sure, perhaps it is normal with this mapping ?
If someone can guide me, do I have to post a bug on Hibernate ?
Best regards,
Vincent
Test case classes and filesVersion hibernate déclarée dans le pom.xml => 3.6.5 final
Version hibernate validator déclarée dans le pom.xml => 4.1.0 final
Code:
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>HibernateTest</artifactId>
<name>Test Hibernate</name>
<description>Small project to practice some tests with Hibernate</description>
<inceptionYear>2011</inceptionYear>
<properties>
<org.hibernate.version>3.6.5.Final</org.hibernate.version>
<branch></branch>
</properties>
<scm>
<connection>scm:cvs:pserver:cvs@xxxxx:/cvs:HibernateTest</connection>
</scm>
<issueManagement>
<system>Bugzilla</system>
<url>http://xxxxx/bugzilla/</url>
</issueManagement>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins></plugins>
<pluginManagement>
<plugins></plugins>
</pluginManagement>
</build>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.16</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${org.hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${org.hibernate.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>4.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.1.0.Final</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<groupId>fr.test</groupId>
<version>1.0</version>
</project>
Code:
package fr.test.hibernate.bo;
import java.io.Serializable;
import java.util.Calendar;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
@MappedSuperclass
public abstract class BusinessObject implements Serializable {
private static final long serialVersionUID = 4179574695380922867L;
/**
* Technical primary key
*/
protected Long id = null;
protected Calendar timeStamp = null;
public BusinessObject() {
}
@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setId(long id) {
this.id = new Long(id);
}
@Version
@Column(name = "OBJ_OPT_LOCK", nullable = false)
public Calendar getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(Calendar ts) {
timeStamp = ts;
}
}
Code:
package fr.test.hibernate.bo;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import fr.test.hibernate.validator.Required;
@Entity
@Table(name = "A")
public class A extends BusinessObject {
private static final long serialVersionUID = 87345767169183337L;
private Set<B> b;
private String name;
public A() {
super();
b = new HashSet<B>();
}
public A(String name) {
this();
this.name = name;
}
@OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REFRESH, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, targetEntity = B.class, mappedBy = "a")
@Cascade({ org.hibernate.annotations.CascadeType.ALL })
@Required(i18nName = "B")
@LazyCollection(LazyCollectionOption.EXTRA)
public Set<B> getB() {
return b;
}
public void setB(Set<B> b) {
this.b = b;
}
@Basic
@Column(name = "name", nullable = false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Code:
package fr.test.hibernate.bo;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.NaturalId;
import fr.test.hibernate.validator.Required;
@Entity
@Table(name = "B")
public class B extends BusinessObject {
private static final long serialVersionUID = -5733931756299476473L;
private A a;
private String name;
public B() {
super();
}
public B(String name) {
this();
this.name = name;
}
@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.REFRESH, CascadeType.PERSIST, CascadeType.MERGE }, targetEntity = A.class)
@JoinColumn(name = "ID_A", nullable = false)
@Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE })
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
@Basic
@Column(name = "name", nullable = false)
@NaturalId(mutable = true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Transient
public void detach() {
if (getA() != null) {
getA().getB().remove(this);
}
}
}
Code:
package fr.test.hibernate.validator;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
* Designates a property that is required to have a value. It cannot be null or
* empty.
*/
@Documented
@Constraint(validatedBy = RequiredValidator.class)
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface Required {
String i18nName();
String message() default "Required => {i18nName}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Code:
package fr.test.hibernate.validator;
import java.io.Serializable;
import java.util.Collection;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class RequiredValidator implements ConstraintValidator<Required, Object>, Serializable {
@Override
public void initialize(Required parameters) {
}
/** @return true if the value is not null or an empty String */
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintContext) {
if (value == null) {
return false;
}
if (value instanceof String) {
String stringValue = (String) value;
if (stringValue.trim().length() == 0) {
return false;
}
}
if (value instanceof Collection) {
return ((Collection<?>) value).size() > 0;
}
return true;
}
}
Code:
package fr.test.hibernate;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
public class ExportSchema {
public static void main(String[] args) {
Configuration configuration = new Configuration().configure();
SchemaExport schemaExport = new SchemaExport(configuration);
schemaExport.create(true, true);
}
}
Code:
package fr.test.hibernate;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
Code:
package fr.test.hibernate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import fr.test.hibernate.bo.A;
import fr.test.hibernate.bo.B;
import fr.test.hibernate.bo.BusinessObject;
public class MainAB {
public static void main(String[] args) {
ExportSchema.main(null);
System.err.println("<!-- Save Begin -->");
for (int i = 0; i < 10; i++) {
A a = new A("a" + i);
B b = new B("b" + i);
B bprime = new B("b" + (i + 100));
a.getB().add(b);
a.getB().add(bprime);
b.setA(a);
bprime.setA(a);
save(a);
}
System.err.println("<!-- Save End -->");
// deleteFromA();
deleteFromB();
}
public static void save(BusinessObject boToSave) {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
if (boToSave != null) {
session.saveOrUpdate(boToSave);
}
session.flush();
transaction.commit();
} catch (HibernateException e) {
transaction.rollback();
e.printStackTrace();
throw e;
} finally {
session.close();
}
}
@SuppressWarnings({ "finally", "unchecked" })
public static void deleteFromA() {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
Criteria crit = session.createCriteria(A.class);
List<A> objectsA = crit.list();
Set<B> objectsB = new HashSet<B>();
Set<B> objectsBToDelete = new HashSet<B>();
for (A a : objectsA) {
for (B b : a.getB()) {
objectsB.add(b);
}
}
for (B b : objectsB) {
// we do not detach and delete each "b" to be able to validate the @Required mapping on a.getB()
if (b.getA().getB().size() > 1) {
b.detach();
objectsBToDelete.add(b);
}
}
session.flush();
// delete B
for (B b : objectsBToDelete) {
session.delete(b);
}
session.flush();
transaction.commit();
} catch (HibernateException e) {
transaction.rollback();
e.printStackTrace();
throw e;
} finally {
session.close();
}
}
@SuppressWarnings({ "finally", "unchecked" })
public static void deleteFromB() {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
Criteria crit = session.createCriteria(B.class);
List<B> objectsB = crit.list();
Set<B> objectsBToDelete = new HashSet<B>();
for (B b : objectsB) {
// we do not detach and delete each "b" to be able to validate the @Required mapping on a.getB()
if (b.getA().getB().size() > 1) {
b.detach();
objectsBToDelete.add(b);
}
}
session.flush();
// delete B
for (B b : objectsBToDelete) {
session.delete(b);
}
session.flush();
transaction.commit();
} catch (HibernateException e) {
transaction.rollback();
e.printStackTrace();
throw e;
} finally {
session.close();
}
}
}
ehcache.xml
Code:
<ehcache>
<diskStore path="user.dir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
/>
<cache name="fr.test.hibernate.A"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="false"
/>
<cache name="fr.test.hibernate.B"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="false"
/>
</ehcache>
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Config -->
<!-- http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#session-configuration -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.pool_size">10</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">false</property>
<property name="hibernate.max_fetch_depth">1</property>
<property name="hibernate.connection.autocommit">false</property>
<!--
<property name="hibernate.jdbc.batch_versioned_data">true</property>
<property name="hibernate.jdbc.use_streams_for_binary">true</property>
<property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</property>
-->
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.query.substitutions">yes 'Y', no 'N'</property>
<property name="hibernate.connection.url">jdbc:mysql://dbservername:3306/testhibernate</property>
<property name="hibernate.connection.username">user</property>
<property name="hibernate.connection.password">pwd</property>
<!-- mapping files -->
<mapping class="fr.test.hibernate.bo.A"/>
<mapping class="fr.test.hibernate.bo.B"/>
</session-factory>
</hibernate-configuration>
Code:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
alter table B drop foreign key FK42F32685F3
drop table if exists A
drop table if exists B
create table A (ID bigint not null auto_increment, OBJ_OPT_LOCK datetime not null, name varchar(255) not null, primary key (ID)) ENGINE=InnoDB
create table B (ID bigint not null auto_increment, OBJ_OPT_LOCK datetime not null, name varchar(255) not null, ID_A bigint not null, primary key (ID), unique (name)) ENGINE=InnoDB
alter table B add index FK42F32685F3 (ID_A), add constraint FK42F32685F3 foreign key (ID_A) references A (ID)
<!-- Save Begin -->
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into A (OBJ_OPT_LOCK, name) values (?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
Hibernate: insert into B (OBJ_OPT_LOCK, ID_A, name) values (?, ?, ?)
<!-- Save End -->
Hibernate: select this_.ID as ID3_0_, this_.OBJ_OPT_LOCK as OBJ2_3_0_, this_.ID_A as ID4_3_0_, this_.name as name3_0_ from B this_
Hibernate: select a0_.ID as ID2_0_, a0_.OBJ_OPT_LOCK as OBJ2_2_0_, a0_.name as name2_0_ from A a0_ where a0_.ID=?
Hibernate: select count(ID) from B where ID_A =?
Hibernate: select 1 from B where ID_A =? and ID =?
Exception in thread "main" java.lang.StackOverflowError
at java.util.LinkedHashMap$Entry.<init>(Unknown Source)
at java.util.LinkedHashMap.createEntry(Unknown Source)
at java.util.LinkedHashMap.addEntry(Unknown Source)
at java.util.HashMap.put(Unknown Source)
at org.hibernate.util.IdentityMap.put(IdentityMap.java:170)
at org.hibernate.engine.StatefulPersistenceContext.addChildParent(StatefulPersistenceContext.java:1629)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:390)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:154)
at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:127)
[...]
at org.hibernate.collection.PersistentSet.size(PersistentSet.java:162)
at fr.test.hibernate.validator.RequiredValidator.isValid(RequiredValidator.java:27)
at org.hibernate.validator.engine.ConstraintTree.validateSingleConstraint(ConstraintTree.java:153)
at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:140)
at org.hibernate.validator.metadata.MetaConstraint.validateConstraint(MetaConstraint.java:121)
at org.hibernate.validator.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:327)
at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForRedefinedDefaultGroup(ValidatorImpl.java:273)
at org.hibernate.validator.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:256)
at org.hibernate.validator.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:210)
at org.hibernate.validator.engine.ValidatorImpl.validate(ValidatorImpl.java:119)
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:136)
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreUpdate(BeanValidationEventListener.java:102)
at org.hibernate.action.EntityUpdateAction.preUpdate(EntityUpdateAction.java:237)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:86)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:127)