Here is a test case that reproduces the problem:
Java source:
Code:
import net.sf.hibernate.*;
import net.sf.hibernate.type.Type;
import net.sf.hibernate.cfg.Configuration;
import java.io.Serializable;
import java.util.Iterator;
public class HBug {
public static abstract class Updater {
private final String info;
public Updater(String info) {
this.info = info;
}
public abstract void update(Session session) throws HibernateException;
public String toString() {
return info;
}
}
public static class MyInterceptor implements Interceptor {
public boolean onSave(Object entity, Serializable id,
Object[] state, String[] propertyNames, Type[] types) {
System.out.println("*** onSave() invoked for " + entity);
return false;
}
public void onDelete(Object entity, Serializable id,
Object[] state, String[] propertyNames, Type[] types) {
System.out.println("*** onDelete() invoked for " + entity);
}
public boolean onFlushDirty(Object entity, Serializable id,
Object[] currentState, Object[] previousState, String[] propertyNames,
Type[] types) {
System.out.println("*** onFlushDirty() invoked for " + entity);
return false;
}
public boolean onLoad(Object entity, Serializable id,
Object[] state, String[] propertyNames, Type[] types) {
System.out.println("*** onLoad() invoked for " + entity);
return false;
}
public void preFlush(Iterator entities) {
}
public void postFlush(Iterator entities) {
}
public Boolean isUnsaved(Object entity) {
return null;
}
public int[] findDirty(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) {
return null;
}
public Object instantiate(Class clazz, Serializable id)
throws CallbackException {
return null;
}
}
public static class Asset {
private Long id;
private Tag tag;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Tag getTag() { return tag; }
public void setTag(Tag tag) { this.tag = tag; }
}
public static class Tag {
private Long id;
private Asset asset;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Asset getAsset() { return asset; }
public void setAsset(Asset asset) { this.asset = asset; }
}
private static final Class[] PERSISTENT_CLASSES = {
Asset.class,
Tag.class,
};
private static final MyInterceptor INTERCEPTOR = new MyInterceptor();
private static SessionFactory sessionFactory;
private static Asset asset;
private static Tag tag;
public static void main(String[] args) throws Exception {
// Configure Hibernate
Configuration config = new Configuration();
for (int i = 0; i < PERSISTENT_CLASSES.length; i++)
config.addClass(PERSISTENT_CLASSES[i]);
sessionFactory = config.buildSessionFactory();
// Initialize database
update(new Updater("initializing database") {
public void update(Session session) throws HibernateException {
asset = new Asset();
tag = new Tag();
session.save(asset);
session.save(tag);
}
});
// Read back tag and asset
update(new Updater("reading objects from database") {
public void update(Session session) throws HibernateException {
asset = (Asset)session.createQuery(
"from HBug$Asset as asset where asset.id=:id")
.setParameter("id", asset.getId()).uniqueResult();
tag = (Tag)session.createQuery(
"from HBug$Tag as tag where tag.id=:id")
.setParameter("id", tag.getId()).uniqueResult();
}
});
// Assign tag to asset
System.out.println("assigning tag to database");
asset.setTag(tag);
// Write changes back to database
update(new Updater("saving modified objects to database") {
public void update(Session session) throws HibernateException {
session.replicate(tag, ReplicationMode.OVERWRITE);
}
});
// Done
System.out.println("done");
}
private static void update(Updater updater) throws HibernateException {
System.out.println(">>> creating session for: " + updater);
Session session = sessionFactory.openSession(INTERCEPTOR);
Transaction tx = null;
boolean success = false;
try {
tx = session.beginTransaction();
updater.update(session);
tx.commit();
success = true;
} finally {
if (!success && (tx != null))
tx.rollback();
System.out.println("<<< closing session for: " + updater);
session.close();
}
}
}
HBug$Asset.hbm.xml:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="HBug$Asset" dynamic-update="false"
dynamic-insert="false" select-before-update="false"
optimistic-lock="version" table="Asset">
<id name="id" column="id" type="java.lang.Long">
<generator class="native"/>
</id>
<one-to-one name="tag" class="HBug$Tag" cascade="save-update"
outer-join="auto" constrained="false" property-ref="asset"/>
</class>
</hibernate-mapping>
HBug$Tag.hbm.xml:
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="HBug$Tag" dynamic-update="false"
dynamic-insert="false" select-before-update="false"
optimistic-lock="version" table="Tag">
<id name="id" column="id" type="java.lang.Long">
<generator class="native"/>
</id>
<many-to-one name="asset" class="HBug$Asset" cascade="save-update"
outer-join="auto" update="true" insert="true" access="property"
column="asset" unique="true"/>
</class>
</hibernate-mapping>
MySQL schema:
Code:
#
# Table structure for table `Asset`
#
CREATE TABLE `Asset` (
`id` bigint(20) NOT NULL auto_increment,
PRIMARY KEY (`id`)
) TYPE=MyISAM AUTO_INCREMENT=6 ;
# --------------------------------------------------------
#
# Table structure for table `Tag`
#
CREATE TABLE `Tag` (
`id` bigint(20) NOT NULL auto_increment,
`asset` bigint(20) default NULL,
PRIMARY KEY (`id`)
) TYPE=MyISAM AUTO_INCREMENT=6 ;
My hibernate.properties:
Code:
hibernate.query.substitutions true 1, false 0, yes 'Y', no 'N'
hibernate.dialect net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class com.mysql.jdbc.Driver
hibernate.connection.url jdbc:mysql://localhost/hbug
hibernate.connection.username awarix
hibernate.connection.password awarix
hibernate.connection.pool_size 1
hibernate.proxool.pool_alias pool1
hibernate.jdbc.batch_size 0
hibernate.jdbc.use_streams_for_binary true
hibernate.max_fetch_depth 1
hibernate.cache.use_query_cache true
hibernate.cache.provider_class net.sf.hibernate.cache.HashtableCacheProvider
This is the output I get when I run this test program.
Note that there are no Interceptor calls to onFlushDirty()
during the last Session, even though the database does
get updated properly:
Code:
>>> creating session for: initializing database
*** onSave() invoked for HBug$Asset@331059
*** onSave() invoked for HBug$Tag@da6bf4
<<< closing session for: initializing database
>>> creating session for: reading objects from database
*** onLoad() invoked for HBug$Asset@1326484
*** onLoad() invoked for HBug$Tag@16d2633
<<< closing session for: reading objects from database
assigning tag to database
>>> creating session for: saving modified objects to database
<<< closing session for: saving modified objects to database
done