I am having some trouble solving a problem where Hibernate complaints about "not-null property references a null or transient value" during flush after a delete.
Basically I can not decide wether my model is wrong or if I am using Hibernate incorretly. It surely is not a Hibernate bug :-)
Our product uses Hibernate upon a model of roughly 40 entines which are thightly coupled. We use a variant of the thread-local pattern by allocating a Hibernate session per API-call within a dynamic proxy. JBossCache is our 2nd level cache.
To simplify the scenario for this posting I have put together two entities -- user and authorization. Users can authorize other users.
Mapping file for users:
Code:
<hibernate-mapping>
<class name="com.foo.PWVUser" table="wvUser" lazy="true">
<id name="id" type="string" length="32" unsaved-value="null">
<generator class="uuid.hex"/>
</id>
<cache usage="transactional"/>
<map name="reactiveAuthorizations" inverse="true" cascade="all-delete-orphan" lazy="true">
<cache usage="transactional"/>
<key column="wvUserId"/>
<index column="authorizedWVUserId" type="string"/>
<one-to-many class="com.foo.PReactiveAuthorization"/>
</map>
</class>
</hibernate-mapping>
Mapping for authorizations:
Code:
<hibernate-mapping>
<class name="com.foo.PReactiveAuthorization" table="reactiveAuth" lazy="true">
<id name="id" type="string" length="32" unsaved-value="null">
<generator class="uuid.hex"/>
</id>
<cache usage="transactional"/>
<many-to-one name="authorizingWVUser" not-null="true" class="com.foo.PWVUser">
<meta attribute="field-description">Authorizing user.</meta>
<column name="wvUserId" index="reactWvUserId"/>
</many-to-one>
<many-to-one name="authorizedWVUser" column="authorizedWVUserId" not-null="true" class="com.foo.PWVUser"/>
<meta attribute="field-description">Authorized user.</meta>
</many-to-one>
</class>
</hibernate-mapping>
User class:
Code:
ublic class PWVUser {
private String id;
private Map reactiveAuthorizations;
public PWVUser() {}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public Map getReactiveAuthorizations() {
return this.reactiveAuthorizations;
}
public void setReactiveAuthorizations(Map reactiveAuthorizations) {
this.reactiveAuthorizations = reactiveAuthorizations;
}
}
Authorization class:
Code:
public class PReactiveAuthorization {
private String id;
private PWVUser authorizingWVUser;
private PWVUser authorizedWVUser;
public PReactiveAuthorization() {}
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public PWVUser getAuthorizingWVUser() {
return this.authorizingWVUser;
}
public void setAuthorizingWVUser(PWVUser authorizingWVUser) {
this.authorizingWVUser = authorizingWVUser;
}
public PWVUser getAuthorizedWVUser() {
return this.authorizedWVUser;
}
public void setAuthorizedWVUser(PWVUser authorizedWVUser) {
this.authorizedWVUser = authorizedWVUser;
}
}
If a user B wants to authorize a user A, an authorization is created and added to the collection of authorizations on user B.
The problem with not-null field arises when I try to delete the authorized user (user A). Since I do not have a any references of authorizations given upon a user (should I?), I simply do a query to locate all users authorizing the user I am trying to delete, and then update their collection of authorizations:
Code:
List reactivlyAuthorizingUsers = session.createQuery("select auth.authorizingWVUser from " + PReactiveAuthorization.class.getName() + " auth where auth.authorizedWVUser.id = :wvUserId")
.setParameter("wvUserId", userA.getId())
.list();
for (Iterator i = reactivlyAuthorizingUsers.iterator(); i.hasNext();) {
((PWVUser) i.next()).getReactiveAuthorizations().remove(userA.getId());
}
When I then do a delete on the userA and flush the session, Hibernate throws a PropertyValueException compliainging about PReactiveAuthorization.authorizedWVUser is null.
I guess that this has something to do with link-owners, but I can not figure out what it is.
Could I use any other relational mapping (ternary?), or should I manage two collections on the user; One for authorizations given to other users and one for authorizations given upon a user. Other suggestions?
System setup:
Hibernate: 2.1.4
Oracle: 9.2.0.1.0
Tomcat: 5.0.19
JVM: Sun 1.4.2_03
Cache: JBoss TreeCache 1.01
Simple test that fails:
Code:
public class LinkOwnerBugOnDeleteTest extends TestCase {
private net.sf.hibernate.SessionFactory factory;
public LinkOwnerBugOnDeleteTest(String s) throws Exception {
super(s);
SessionFactory.initializeHibernate();
factory = SessionFactory.getFactory();
}
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testDeleteBug() throws Exception {
// create user A and B
PWVUser userA = new PWVUser();
userA.setUserRef("userA");
PWVUser userB = new PWVUser();
userB.setUserRef("userB");
userB.setReactiveAuthorizations(new HashMap(0));
Session session;
Transaction t = null;
try {
session = factory.openSession();
session.setFlushMode(FlushMode.NEVER);
session.save(userA);
session.save(userB);
t = session.beginTransaction();
session.flush();
t.commit();
session.close();
} finally {
if (t != null && !t.wasCommitted()) {
t.rollback();
}
t = null;
}
Serializable userAId = userA.getId();
Serializable userBId = userB.getId();
// add authorization for user A on user B
try {
session = factory.openSession();
session.setFlushMode(FlushMode.NEVER);
userA = (PWVUser) session.load(PWVUser.class, userAId);
userB = (PWVUser) session.load(PWVUser.class, userBId);
PReactiveAuthorization pra = new PReactiveAuthorization();
pra.setAuthorizedWVUser(userA);
pra.setAuthorizingWVUser(userB);
userB.getReactiveAuthorizations().put(userA.getId(), pra);
t = session.beginTransaction();
session.flush();
t.commit();
session.close();
} finally {
if (t != null && !t.wasCommitted()) {
t.rollback();
}
t = null;
}
// delete user A
try {
session = factory.openSession();
session.setFlushMode(FlushMode.COMMIT);
userA = (PWVUser) session.load(PWVUser.class, userAId);
// delete authorization made upon userA
List reactivlyAuthorizingUsers = session.createQuery("select auth.authorizingWVUser from " + PReactiveAuthorization.class.getName() + " auth where auth.authorizedWVUser.id = :wvUserId")
.setParameter("wvUserId", userA.getId())
.list();
for (Iterator i = reactivlyAuthorizingUsers.iterator(); i.hasNext();) {
PWVUser otherUser = (PWVUser) i.next();
Map ras = otherUser.getReactiveAuthorizations();
ras.remove(userA.getId());
}
session.delete(userA);
t = session.beginTransaction();
session.flush();
t.commit();
session.close();
} finally {
if (t != null && !t.wasCommitted()) {
t.rollback();
}
t = null;
}
}
}
Thanks for your time :-)