I created a simple case for testing and I don't get the expected behavior. I am interested why hibernate chooses to update the parent table (Country) although I provide update="false" attribute to the many-to-one relationship (in the Address.hbm.xml)?
The DDLs:
CREATE TABLE Country (
`id` smallint NOT NULL auto_increment,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) TYPE=InnoDB AUTO_INCREMENT=0;
CREATE TABLE Address (
`id` bigint NOT NULL auto_increment,
`firstName` varchar(100) NOT NULL,
`lastName` varchar(100) NOT NULL,
`countryId` smallint NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (countryId)
REFERENCES Country(id)
ON UPDATE CASCADE ON DELETE RESTRICT
) TYPE=InnoDB AUTO_INCREMENT=0;
The hibernate xmls:
<class name="Country" table="Country" lazy="false">
<id name="id" column="id" type="integer">
<generator class="native" />
</id>
<property name="name" type="string" not-null="true" length="100"/>
</class>
<class name="Address" table="Address" >
<id name="id" column="id" type="long" unsaved-value="-1">
<generator class="native" />
</id>
<property name="addressFirstName" column="firstName" type="string" not-null="true" length="100"/>
<property name="addressLastName" column="lastName" type="string" not-null="true" length="100"/>
<!-- Country is parent table, Address is child table (holds the FK countryId).
From MySQL:
CASCADE: Update the row from the parent table and automatically update the matching rows in the child table.
RESTRICT: Rejects the delete operation for the parent table.
From hibernate:
1. cascade (optional): specifies which operations should be cascaded from the parent object to the associated object.
2. update, insert (optional - defaults to true): specifies that the mapped columns should be included in SQL UPDATE
and/or INSERT statements. Setting both to false allows a pure "derived" association whose value is initialized
from another property that maps to the same column(s), or by a trigger or other application.
-->
<many-to-one name="country" class="Country" column="countryId"
not-null="true" cascade="persist,merge,save-update" update="false" />
<many-to-one name="state" class="State" column="stateId"
cascade="persist,merge,save-update" update="false" />
</class>
My Country table has one row (id=1):
INSERT INTO Country (name) VALUES ("United States");
If I do following:
Country country = getCountryFrom("US");
Address address = new Address();
address.setAddressFirstName("Deksa");
address.setAddressLastName("Jakim");
country.setName("Slovenia");
address.setCountry(country);
address.setId(-1);
saveAddress(address);
session.saveOrUpdate(address);
I expect that I have one new row inserted in the Address table, but I also get an update on the Country table - the only row, the name of the country becomes Slovenia, instead of staying United States (expected behavior <= update="false"):
Hibernate: select country0_.id as id6_, country0_.name as name6_ from Country country0_ where country0_.stringId='US'
Hibernate: insert into Address (firstName, lastName, countryId) values (?, ?, ?)
Hibernate: update Country set name=? where id=?
So why does hibernate update the parent table when instructed not to?
Kind regards,
DespotEdit: Shortly after writing this post I realized that I am confusing the DB's cascade with the hibernate's cascade and that solved all my problems.
The thing is the following: Hibernate clearly says:update, insert (optional - defaults to true): specifies that the mapped columns should be included in SQL UPDATE and/or INSERT statements. Setting both to false allows a pure "derived" association whose value is initialized from another property that maps to the same column(s), or by 开发者_StackOverflow社区a trigger or other application.
. This means that when using update="false" on the country property (on the countryId), I say to hibernate you cannot do UPDATE Address SET firstName="x", lastName="y", countryId=123 where condition; Only do an update without the countryId.
Than I saw that the DB's ON UPDATE CASCADE refers to the parent table (Country) - if the Country was updated, than update the child table (the countryId column in Address table). Contrary to what I thought hibernate's update would do, I realized that by providing cascade="save-update", I say to hibebernate, whatever values you have for the Country object in the Address object update them in it's own Country table. So the solution was to have cascade="persist,merge" without the save-update part!Setting update = "false"
on country
property doesn't allow you to update the corresponding countryId
field, i.e. to choose another Country
for particular Address
.
If you want to disable chaning of the name of existing Country
, you need to specify update = "false"
on the corresponding property:
<class name="Country" table="Country" lazy="false">
...
<property name="name" type="string" not-null="true" length="100" update = "false" />
</class>
精彩评论