views:

34

answers:

2

Ok so I'm having bit of a problem with my Hibernate mappings and getting the desired behavior.

Basically what I have is the following Hibernate mapping:

<hibernate-mapping>
    <class name="com.package.Person" table="PERSON" schema="MYSCHEMA" lazy="false">
        <id name="personId" column="PERSON_ID" type="java.lang.Long">
            <generator class="sequence">
                <param name="sequence">PERSON_ID_SEQ</param>
            </generator>
        </id>

        <property name="firstName" type="string" column="FIRST_NAME">
        <property name="lastName" type="string" column="LAST_NAME">
        <property name="age" type="int" column="AGE">

        <set name="skills" table="PERSON_SKILL" cascade="all-delete-orphan">
            <key>
                <column name="PERSON_ID" precision="12" scale="0" not-null="true"/>
            </key>
            <many-to-many column="SKILL_ID" unique="true" class="com.package.Skill"/>
        </set>
    </class>
</hibernate-mapping>


<hibernate-mapping>
    <class name="com.package.Skill" table="SKILL" schema="MYSCHEMA">
        <id name="skillId" column="SKILL_ID" type="java.lang.Long">
            <generator class="sequence">
                <param name="sequence">SKILL_ID_SEQ</param>
            </generator>
        </id>
        <property name="description" type="string" column="DESCRIPTION">
    </class>
</hibernate-mapping>

So lets assume that I have already populated the Skill table with some skills in it. Now when I create a new Person I want to associate them with a set of skills that already exist in the skill table by just setting the ID of the skill. For example:

Person p = new Person();
p.setFirstName("John");
p.setLastName("Doe");
p.setAge(55);

//Skill with id=2 is already in the skill table
Skill s = new Skill()
s.setSkillId(2L);

p.setSkills(new HashSet<Skill>(Arrays.asList(s)));
PersonDao.saveOrUpdate(p);

If I try to do that however I get an error saying:

WARN (org.slf4j.impl.JCLLoggerAdapter:357) - SQL Error: 1407, SQLState: 72000
ERROR (org.slf4j.impl.JCLLoggerAdapter:454) - ORA-01407: cannot update ("MYSCHEMA"."SKILL"."DESCRIPTION") to NULL

ERROR (org.slf4j.impl.JCLLoggerAdapter:532) - Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update

The reason I am getting this error I think is because Hibernate sees that the Skill with Id 2 has 'updated' its description to null (since I never set it) and tries to update it. But I don't want Hibernate to update this. What I want it to do is insert the new Person p and insert a record into the join table, PERSON_SKILL, that matches p with the skill in the SKILL table with id=2 without touching the SKILL table.

Is there anyway to achieve this behavior?

+1  A: 

Instead of creating the Skill object yourself:

//Skill with id=2 is already in the skill table
Skill s = new Skill()
s.setSkillId(2L);
p.setSkills(new HashSet<Skill>(Arrays.asList(s)));

You should be retrieving it from the Hibernate Session:

Skill s = (Skill) session.get(Skill.class, 2L);
p.setSkills(new HashSet<Skill>(Arrays.asList(s)));

This way the Session thinks that the skill contained in p.skills is persistent, and not transient.

matt b
The skill is not transient (this would result in an insert), it has a representation in the database, it has an identifier value.
Pascal Thivent
What would it be labeled then - "detached"? I think the solution is the same either way.
matt b
Yes, it is detached.
Pascal Thivent
@Pascal Thivent: Yeah how is his implementation any different from your answer of loading the desired Skill and adding it to the set of skills for the Person (seems to be the same)? Is there some difference I am missing because matt b's solution is how I would interpret yours.
tkeE2036
@tkeE2036: it's the same (except I didn't provide the code).
Pascal Thivent
+1  A: 

This may be possible if you don't cascade all-delete-orphan which is explicitely telling hibernate to cascade the changes.

But the right way would be IMO to load load the desired Skill entity from the database and to add it to the set of skills of the Person.

Pascal Thivent
Ok so removing the all-delete-orphan worked but what is considered best practice here? Retrieving the Skill first and associating it with my Person or removing the cascade attribute?
tkeE2036
Well, I you still want to benefit from cascading in some situations, cascading nothing is not a good solution :) So I'd load the skill from the database first.
Pascal Thivent