Hi all.
I'm not sure but may be it is bug.
In my test application I try to make inheritance hierarchy usind "Table per subclass" method.
So in test app I have different locations: streets, buildings, rooms.
And I create next database structure for it:
Code:
DROP TABLE IF EXISTS locations_third;
create table locations_third (
id int unsigned NOT NULL auto_increment,
name varchar(50) NOT NULL,
primary key (id)
);
DROP TABLE IF EXISTS streets_third;
create table streets_third (
id int unsigned NOT NULL auto_increment,
prestig int,
primary key (id)
);
DROP TABLE IF EXISTS buildings_third;
create table buildings_third (
id int unsigned NOT NULL auto_increment,
street_id int NOT NULL,
num_stores int,
primary key (id)
);
DROP TABLE IF EXISTS rooms_third;
create table rooms_third (
id int unsigned NOT NULL auto_increment,
building_id int NOT NULL,
level int,
primary key (id)
);
insert into locations_third (id, name) values
(1, 'dzerjinka'), (2, 'tverskaya'), (3, 'dolgoozernaya'),
(4, 'dom N 19'), (5, 'DK Aluminka'), (6, 'dom N 33'),
(7, 'Hotel One *****'), (8, 'Cazino Gold Pavlin'),
(9, 'dom 2A'), (10, 'dom 4'),
(11, 'parikmaherskaya'), (12, 'my flat'), (13, 'hol'), (14, 'kino'),
(15, 'magazin'), (16, 'hol'), (17, 'restoran'), (18, 'pocker'), (19, 'flat 119(my)'), (20, 'flat 1');
insert into streets_third (id, prestig) values
(1, 103),
(2, 110),
(3, 106);
insert into buildings_third (id, num_stores, street_id) values
(4, 105, 1),
(5, 104, 1),
(6, 109, 1),
(7, 107, 2),
(8, 103, 2),
(9, 107, 3),
(10, 103, 3);
insert into rooms_third (id, level, building_id) values
(11, 101, 4),
(12, 105, 4),
(13, 101, 5),
(14, 102, 5),
(15, 101, 6),
(16, 101, 7),
(17, 102, 7),
(18, 101, 8),
(19, 107, 9),
(20, 101, 10);
Hibernate mapping file is:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="at.db.polimorph.third.Location" table="locations_third">
<id name="id" column="id" type="integer">
<generator class="native"/>
</id>
<property name="name" column="name" type="string"/>
<joined-subclass name="at.db.polimorph.third.Street" table="streets_third">
<key column="id"/>
<property name="prestigiousness" column="prestig" type="integer"/>
<set name="buildings" cascade="all">
<key column="street_id"/>
<one-to-many class="at.db.polimorph.third.Building" not-found="ignore"/>
</set>
</joined-subclass>
<joined-subclass name="at.db.polimorph.third.Building" table="buildings_third">
<key column="id"/>
<property name="numberOfStoreys" column="num_stores" type="integer"/>
<many-to-one name="street" class="at.db.polimorph.third.Street" column="street_id" not-found="ignore"/>
<set name="rooms" cascade="all">
<key column="building_id"/>
<one-to-many class="at.db.polimorph.third.Room" not-found="ignore"/>
</set>
</joined-subclass>
<joined-subclass name="at.db.polimorph.third.Room" table="rooms_third">
<key column="id"/>
<property name="level" column="level" type="integer"/>
<many-to-one name="building" class="at.db.polimorph.third.Building" column="building_id" not-found="ignore"/>
</joined-subclass>
</class>
</hibernate-mapping>
And java code is:
Code:
package at.db.polimorph.third;
public abstract class Location {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Code:
package at.db.polimorph.third;
import java.util.Set;
public class Street extends Location {
private Set<Building> buildings;
private Integer prestigiousness;
public Set<Building> getBuildings() {
return buildings;
}
public void setBuildings(Set<Building> buildings) {
this.buildings = buildings;
}
public Integer getPrestigiousness() {
return prestigiousness;
}
public void setPrestigiousness(Integer prestigiousness) {
this.prestigiousness = prestigiousness;
}
public void addBuilding(Building building) {
buildings.add(building);
building.setStreet(this);
}
public String toString() {
return "STREET: " + getName() + " prestigiousness[" + prestigiousness +"]";
}
}
Code:
package at.db.polimorph.third;
import java.util.Set;
public class Building extends Location {
private Street street;
private Set<Room> rooms;
private Integer numberOfStoreys;
public Street getStreet() {
return street;
}
public void setStreet(Street street) {
this.street = street;
}
public Set<Room> getRooms() {
return rooms;
}
public void setRooms(Set<Room> rooms) {
this.rooms = rooms;
}
public Integer getNumberOfStoreys() {
return numberOfStoreys;
}
public void setNumberOfStoreys(Integer numberOfStoreys) {
this.numberOfStoreys = numberOfStoreys;
}
public void addRoom(Room room) {
rooms.add(room);
room.setBuilding(this);
}
public String toString() {
return "BUILDING: " + getName() + " numberOfStoreys[" + numberOfStoreys +"]";
}
}
Code:
package at.db.polimorph.third;
public class Room extends Location {
private Building building;
private Integer level;
public Building getBuilding() {
return building;
}
public void setBuilding(Building building) {
this.building = building;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public String toString() {
return "ROOM: " + getName() + " level[" + level +"]";
}
}
So this code works fine:
I can get locations
List<Location> locslist = sn.createCriteria(Location.class).list();
I can get streets:
List<Street> streets = sn.createCriteria(Street.class).list();
And so on.
I can create new Building and add it to some Street:
Code:
Street street = (Street) sn.get(Location.class, 1);
Building building = new Building();
building.setName("house number 156");
building.setNumberOfStoreys(25);
street.addBuilding(building);
sn.save(street);
BUT when I try to do
silly thing, I have problems.
I add new Building so:
Code:
Street street = (Street) sn.get(Location.class, 1);
Building building = new Building();
building.setName("house number 157");
building.setNumberOfStoreys(18);
street.getBuildings().add(building);
sn.save(street);
Note that I used:
street.getBuildings().add(building);
Instead of:
street.addBuilding(building);
So now building does not have street.
And when I try to commit transaction i get Exception:
Code:
Hibernate: insert into locations_third (name) values (?)
Hibernate: insert into buildings_third (num_stores, street_id, id) values (?, ?, ?)
2008.03.27-19:16:03,861 ERROR JDBCExceptionReporter : Column 'street_id' cannot be null
org.hibernate.exception.ConstraintViolationException: could not insert: [at.db.polimorph.third.Building]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2267)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2640)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:48)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:94)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
at org.hibernate.engine.CascadingAction$1.cascade(CascadingAction.java:218)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:131)
at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:122)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at at.TestPolimorph_ThirdCase.main(TestPolimorph_ThirdCase.java:72)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
Caused by: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Column 'street_id' cannot be null
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:931)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2941)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715)
at com.mysql.jdbc.Connection.execSQL(Connection.java:3249)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2250)
Of course I catch it and make rollback:
sn.getTransaction().rollback();
And I expect that two inserts will be rolled back:
insert into locations_third (name) values (?)
insert into buildings_third (num_stores, street_id, id) values (?, ?, ?)
But actually first insert is not rolled back.
I use next configuration:
java 1.5
hibernate3.jar
mysql_5.0.45