1

I have the following class diagram and I want to map it to a database (note that Person has a list with objects of class Vehicle). enter image description here

Also my database looks like: enter image description here
All tables in the database that represent a subclass of the Vehicle class have all the fields of the superclass Vehicle. Also, all the relations show a one-to-many relationship from Person to Vehicle, Car and Motorcycle.

My hibernate mapping files are the following: Person.hbm.xml

<hibernate-mapping package="...."> 
    <class name="Person" table="Persons"> 
        <id name="key" column="Person_ID"> 
            <generator class="native"/> 
        </id> 
        <list name="ownedVehicles" inverse="false" cascade="all">
            <key column="Person_ID" not-null="true" />
            <list-index column="idx"/>
            <one-to-many class="Vehicle"/>
        </list>
    </class> 
</hibernate-mapping>

Vehicle.hbm.xml

<hibernate-mapping package="..."> 
    <class name="Vehicle" table="Vehicles"  polymorphism="implicit"> 
        <id name="id" type="int" column="Vehicle_ID"> 
            <generator class="increment"/>
        </id> 
        <property name="numOfSeats"/>
        <union-subclass name="Car" table="Cars"></union-subclass>
        <union-subclass name="Motorcycle" table="Motorcycles"></union-subclass>
    </class> 
</hibernate-mapping>

The problem (error I get) is the following:

Hibernate: insert into Persons (Person_ID) values (default)
2013-06-26 15:41:52 WARN  JdbcCoordinatorImpl:424 - HHH000386: ResultSet had no statement associated with it, but was not yet registered
Hibernate: update Car set numOfSeats=? where Vehicle_ID=?
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

I get this error when I run:

Car car = new Car();
car.setNumOfSeats(5);
Person person = new Person();
person.getOwnedVehicles().add(car);
ManagePerson managePerson = new ManagePerson();
Integer personID = managePerson.store(person);

The store() function of ManagePerson actually creates a session and a transaction and then uses the save() method provided by Hibernate to persist the objects into the database.

As far as I understand Hibernate usually will do insert into Persons, then insert into Cars and finally update Cars (the update is done to save the foreign keys on Cars table that will reference the Person that owns the cars). However, here this is not the case and the insert into Cars seems to be getting skipped. I understood how Hibernate works here by trying person.getOwnedVehicles().add(vehicle); instead of person.getOwnedVehicles().add(car); on the code given above.
As you might understand, I am trying to see if Hibernate actually understands in which "subclass" table a record should go, depending on the class of the object contained in the ownedVehicle list of the Person class. For example, if the ownedVehicles has an object of class Car and one of class Motorcycle, then each of these should go to Cars and Motorcycle tables respectively.

Note: I am using Hibernate 4.2.2 and HSQLDB 2.2.9.

I would appreciate any help with this.
Thanks.

Soc
  • 283
  • 3
  • 17

2 Answers2

1

If the managePerson.store(...) method doesn't have a recursive call to the objects in "getOwnedVehicles()" such that it can then call their "store" methods then you shouldn't expect that the created "car" object would be inserted into the table.

You are in fact calling "managePerson.store" not "manageCar.store", I'd have to see the code in the .store(...) method to be sure though but I would expect that it is not doing an iteration of the Vehicles and is not doing an insert for any discovered ones (why should it unless you built it explicitly to do that?).

David Saintloth
  • 4,061
  • 2
  • 16
  • 16
  • I do not agree with what are you saying. The "cascade=all" is for doing exactly that for you. I have tested the exact same thing as I already wrote above using `person.getOwnedVehicles().add(vehicle);` instead of `person.getOwnedVehicles().add(car);` and worked like a charm. All the vehicle objects got persisted into the database. So in my opinion something else is causing the problem. Also, as you can see from my error message, Hibernate actually tries to do something with the Car object but it does an update, where it should really do a save and then an update. – Soc Jun 27 '13 at 09:30
  • By the way I can see what you mean about the store() function but Hibernate is the technology to use to take away the pain of doing such things by your own. – Soc Jun 27 '13 at 12:35
  • I see you may be right, I don't use hibernate. I invented my own persistence engine 10 years ago before there was a hibernate...it doesn't admit, (make possible) problems of this nature. If you get it to work using explicit calls to getOwnedVehicle().add(vehicle) then why is that insufficient? – David Saintloth Jun 28 '13 at 01:46
  • I am just saying that this is something that Hibernate should take care otherwise their is no good reason to use it. I am sure there might be a cheat/hack that you can use to do it but I do not really want to do that on my code. I need a clean solution. – Soc Jun 28 '13 at 12:29
1

I think it is just a matter of incorrect use of the implicit polymorphism of Hibernate. Implicit polymorphism for your case can only work by changing your list to have inverse="true". This can be done of course if your Vehicle class also 'knows' about the relationship with the Person class (e.g. by adding an 'Owner' property and the corresponding mapping).

(Have a look at this table and the case of "table per concrete-class (union-subclass)" and one-to-many associations.

If you enable logging and raise the log level to DEBUG you would see that currently Hibernate tries to update the Vehicles table with the Person_ID instead of the Car table like you meant it to. This is because of the inverse="true" and the limitations of the combination of the Table-per-concrete-class mapping strategy and implicit polymorphism (have a look at the documentation).

So, by having the Vehicle class know about its Owner and using inverse="true" you should be able to succeed in what you are trying to do. Either this or try one of the other inheritance mapping strategies (again have a look at the documentation).

ador-mg
  • 120
  • 1
  • 10
  • Thanks for the answer @ador . I tried what you suggested and couldn't make it work. I spent too much time to make it work so I finally changed it and used the Table per subclass strategy and works fine now. The change I did is the following: Replace the `` with ` `. Do the same for Motorcycle. Note that the tables representing the subclasses will now only contain columns for their fields and not of their superclass as well. Also subclasses do not need a FK anymore. – Soc Jun 28 '13 at 16:45