NucleusUserException: Cannot find mapping for field - java

I am running into a NucleusUserException while querying an entity from DB. I have tried a long time and couldn't figure out what caused this problem. I am hoping some of you can point me into the right direction. Any help will be greatly appreciated
Entity Class
#PersistenceCapable(detachable = "true")
public class Position implements IsSerializable, Serializable {
#PrimaryKey
#Persistent
protected String key;
#Persistent
protected Double quantity;
// getter and setter omitted
}
JDO MetaData
<jdo xmlns="http://java.sun.com/xml/ns/jdo/jdo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/jdo/jdo
http://java.sun.com/xml/ns/jdo/jdo_3_0.xsd">
<package name="org.sly.main.shared.data.finance.trading">
<class name="Position" detachable="true" cacheable="false">
<version strategy="version-number" />
<field name="key" persistence-modifier="persistent"
value-strategy="increment" primary-key="true">
<column length="32" jdbc-type="VARCHAR" />
</field>
<field name="quantity" persistence-modifier="persistent" />
</class>
</package>
</jdo>
The code that i used to retrieved
PersistenceManager pm = pmf.getPersistenceManager();
Extent e = pm.getExtent(Position.class, true);
Query query = pm.newQuery(e);
query.setFilter("key == my_key");
query.declareParameters("String my_key");
Object[] params = { "1234" };
List<Position> managerList = (List<Position>) query.executeWithArray(params);
The Exception i Got is below:
Cannot find mapping for field org.sly.main.shared.data.finance.trading.Position.key in table `POSITION` [`POSITION`.`KEY`, `POSITION`.`QUANTITY`]
org.datanucleus.exceptions.NucleusUserException: Cannot find mapping for field org.sly.main.shared.data.finance.trading.Position.key in table `POSITION` [`POSITION`.`KEY`, `POSITION`.`QUANTITY`]
at org.datanucleus.store.rdbms.table.AbstractClassTable.addApplicationIdUsingClassTableId(AbstractClassTable.java:233)
at org.datanucleus.store.rdbms.table.ClassTable.initializePK(ClassTable.java:1031)
at org.datanucleus.store.rdbms.table.ClassTable.preInitialize(ClassTable.java:246)
at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.addClassTable(RDBMSStoreManager.java:3146)
at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.addClassTables(RDBMSStoreManager.java:2937)
at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.addClassTablesAndValidate(RDBMSStoreManager.java:3210)
at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.run(RDBMSStoreManager.java:2869)
at org.datanucleus.store.rdbms.AbstractSchemaTransaction.execute(AbstractSchemaTransaction.java:122)
at org.datanucleus.store.rdbms.RDBMSStoreManager.addClasses(RDBMSStoreManager.java:1606)
at org.datanucleus.store.AbstractStoreManager.addClass(AbstractStoreManager.java:954)
at org.datanucleus.store.AbstractStoreManager.getSubClassesForClass(AbstractStoreManager.java:1693)
at org.datanucleus.store.rdbms.sql.DiscriminatorStatementGenerator.getStatement(DiscriminatorStatementGenerator.java:306)
at org.datanucleus.store.rdbms.scostore.JoinListStore.getIteratorStatement(JoinListStore.java:964)
at org.datanucleus.store.rdbms.scostore.JoinListStore.listIterator(JoinListStore.java:691)
at org.datanucleus.store.rdbms.scostore.AbstractListStore.listIterator(AbstractListStore.java:92)
at org.datanucleus.store.rdbms.scostore.AbstractListStore.iterator(AbstractListStore.java:82)
at org.datanucleus.store.types.backed.ArrayList.loadFromStore(ArrayList.java:294)
at org.datanucleus.store.types.backed.ArrayList.initialise(ArrayList.java:243)
at org.datanucleus.store.types.SCOUtils.createSCOWrapper(SCOUtils.java:256)
at org.datanucleus.store.types.SCOUtils.newSCOInstance(SCOUtils.java:142)
at org.datanucleus.store.rdbms.mapping.java.AbstractContainerMapping.replaceFieldWithWrapper(AbstractContainerMapping.java:399)
at org.datanucleus.store.rdbms.mapping.java.AbstractContainerMapping.postFetch(AbstractContainerMapping.java:417)
at org.datanucleus.store.rdbms.request.FetchRequest.execute(FetchRequest.java:420)
at org.datanucleus.store.rdbms.RDBMSPersistenceHandler.fetchObject(RDBMSPersistenceHandler.java:324)
at org.datanucleus.state.AbstractStateManager.loadFieldsFromDatastore(AbstractStateManager.java:1122)
at org.datanucleus.state.JDOStateManager.loadUnloadedFieldsInFetchPlan(JDOStateManager.java:3000)
at org.datanucleus.state.JDOStateManager.isLoaded(JDOStateManager.java:3214)
Not sure will this help but this is run on AWS Elastic Mapreduce Hadoop cluster

Related

org.hibernate.MappingException class not found while looking for property

Please help me,i really need help...
I create a composite-id in hibernate.Here are things i have
PurchasedTestId.java
package jp.go.mhlw.vaccine.draft;
import java.io.Serializable;
public class PurchasedTestId implements Serializable {
private static final long serialVersionUID = 1L;
private Long testId;
private Long customerId;
// an easy initializing constructor
public PurchasedTestId(Long testId, Long customerId) {
this.testId = testId;
this.customerId = customerId;
}
// generate setters and getters here
}
And here is my vaccin.hbm.xml file
<class name="jp.go.mhlw.vaccine.draft.PurchasedTestttt" table="PurchasedTesttt">
<composite-id name="purchasedTestId" class="jp.go.mhlw.vaccine.draft.PurchasedTestId">
<key-property name="testId" >
<column name="testId" ></column>
</key-property>
<key-property name="customerId" column="customerId" />
</composite-id>
<property name="name" column="name" type="string" />
</class>
I am using Ant build (using bulld.xml file) to generate Domain class and DB shema,only class PurchasedTestttt will be generated in my case,I've created the class PurchasedTestId before.
Whenever i start to run tools it throws
org.hibernate.MappingException: class jp.go.mhlw.vaccine.draft.PurchasedTestId not found while looking for property: testId
But in my vaccin.hbm.xml file i can hold the control key and click on
jp.go.mhlw.vaccine.draft.PurchasedTestId
And it immediately jumps to PurchasedTestId.java file with same package name.Obviously the PurchasedTestId class is in my classpath.I've been searching alot for 2 days but i could not solve my problem.Please help me figure out what it is.I am so tired
Please help me.
You don't have to specify the class of the composite-id in the hbm.xml file; you have to set the name of the property in your PurchasedTestttt class.
E.g. it has to look like:
Class PurchasedTestttt:
public class PurchasedTestttt {
PurchasedTestId purchasedTestId;
public PurchasedTestId getPurchasedTestId() {
return purchasedTestId;
}
public void setPurchasedTestId(PurchasedTestId purchasedTestId) {
this.purchasedTestId = purchasedTestId;
}
....
}
*.hbm.xml:
<class name=”entities.PurchasedTestttt”>
<composite-id name=”purchasedTestId”>
<key-property name=”testId” column=”TEST_ID” />
<key-property name=”customerId” column=”CUSTOMER_ID” />
</composite-id>
...
</class>
It is important that the class you use for the composite-id has properties with the same name as specified in *.hbm.xml, but Hibernate does not need to know the class you used for that.

Hibernate trouble getting composite key to work

I have a class called WebAsset:
public class WebAsset {
private Long id;
private String url;
private int status;
//more fields that are not relevent
}
I need to be able to show relationships between WebAsset, so I created a table for the relationship and a composite key class.
public class WebAssetReferencePK {
private Long sourceAssetId;
private Long targetAssetId;
}
public class WebAssetReference {
private WebAssetReferencePK wpk;
private Long updateTime;
}
We are forced to use an older version of Hibernate so we need to use xml files instead of annotaions. Here is the mapping for the reference class:
<class name="ca.gc.cra.www.crawler.valueobject.WebAssetReference" table="webassetreference">
<composite-id name="webAssetReferencePK" class="ca.gc.cra.www.crawler.valueobject.WebAssetReferencePK">
<key-property name="sourceAsset" type="java.lang.Long" column="sourceAssetId" />
<key-property name="targetAsset" type="java.lang.Long" column="targetAssetId" />
</composite-id>
<property name="updateTime" type="java.lang.Long" column="updatetime" not-null="true" />
</class>
In the composite key I get what I expect in the database with 2 ids related to each other. But when I try to query with HQL or Criteria it doesn't work since there is no direct relation between the PK class and WebAsset and I need to be able to do a join between WebAsset and WebAssetReference. If I try to change the composite key types from java.lang.Long to WebAsset then hibernate stores the whole object in the WebAssetReference table instead of just the ids.
An example of what I am trying to do is if I have a sourceAssetId I want to return all the targetAssetIds with the same source, but I don't want the ids themselves I want the WebAsset that is the primary key for each targetAssetId.
I have been searching around for the answer but every example I can find are just simple examples that don't relate.
Update 1: With continued searching I finally found the answer. Instead of key-property I need to use key-many-to-one. I haven't tried a join yet but everything else looks right so this should be the answer.
Update 2: Can't get the query to work with HQL. Here is th SQL of what I am trying to do:
select * from webasset as wa join webassetreference as war on war.targetassetid=wa.webasset_id where war.sourceassetid=?
Here is the HQL that is not working:
FROM WebAsset JOIN WebAssetReference WebAssetReference.WebAssetReferencePK.targetAsset=WebAsset WHERE WebAssetReference.WebAssetReferencePK.sourceAsset = :sourceAsset
I get the following error with HQL: ERROR - line 1:89: unexpected token: .
I'll keep trying but I can't seem to figure out the HQL.
I discovered how to do this. In the case I have above it will not work since I have 2 columns joining to the same table. However if I use the same WebAsset class above and instead use this class:
public class TreeNode implements Comparable<TreeNode>{
private String nodeUrl;
private Long id;
private Boolean folder;
private transient WebAsset nodeAsset = null;
}
With this .hbm.xml file:
<class name="ca.gc.cra.www.crawler.valueobject.TreeNode" table="TreeNode">
<id name="id" type="java.lang.Long" column="treenode_id" >
<generator class="identity"/>
</id>
<many-to-one name="nodeAsset" class="ca.gc.cra.www.crawler.valueobject.WebAsset" column="nodeAsset_id" lazy="false" not-null="false" cascade="none" unique="true" />
<property name="folder" type="java.lang.Boolean" column="folder" not-null="true" />
<property name="nodeUrl" length="512" type="java.lang.String" column="nodeUrl" not-null="true" />
<set name="children" table="TreeNode" inverse="false" lazy="true" >
<key column="parentnode_id"/>
<one-to-many class="ca.gc.cra.www.crawler.valueobject.TreeNode" />
</set>
</class>
You can then use this code to retrieve the join:
Session session = HibernateUtil.getSession();
try {
String hql = "FROM TreeNode tn JOIN tn.nodeAsset WHERE tn.id=5";
Query query = session.createQuery(hql);
List result = query.list();
System.out.println("done");
} catch (HibernateException e) {
e.printStackTrace();
throw new Exception("Query failed", e);
} finally {
session.flush();
session.close();
}
Hibernate can then perform the join correctly. The result will be a List containing an Object array for each entry. The Object contains the 2 classes that are part of the join. You have to cast the Object with (Object[]) to access the elements and then cast each on to the appropriate class.
I would recommend against this approach because Hibernate will attempt to load all connected classes as well. With the example above I was getting 1 row from TreeNode yet it generated 19 select statements. I even attempted to set the connected classes to lazy load and it still generated all the selects.

Hibernate mappings for a single foreign key column to multiple tables

I have three tables similar to these:
vehicles
- id1
- id2
- vehicle_type_id
- vehicle_detail_id
- primary key(id1, id2)
buses
- id
...
cars
- id
...
vehicleTypeId decides the type of the vehicle and also from which table the vehicleDetails are to be fetched. Join is to be taken on vehicleDetailId of vehicle and id of a particular vehicleType.
I created classes this way:
class Vehicle{
VehiclePk pk;
Long vehicleTypeId;
Long vehicleDetailId;
}
class Bus extends Vehicle{
Long id;
}
class Car extends Vehicle{
Long id;
}
class VehiclePk {
private Long id1;
private Long id2;
#Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof VehiclePk) {
VehiclePk p = (VehiclePk)obj;
return id1.equals(p.id1) && id2.equals(p.id2);
}
return false;
}
#Override
public int hashCode() {
return (id1 + id2).hashCode();
}
}
I tried combining and elements in the hibernate mapping file, using discriminator:
<?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 schema="test_db">
<class name="Vehicle" table="vehicles" discriminator-value="0">
<composite-id name="pk" class="VehiclePK">
<key-property name="id1" column="id1" type="long"/>
<key-property name="id2" column="id2" type="long"/>
</composite-id>
<discriminator column="vehicle_type_id" type="long"/>
<property name="vehicleDetailId" column="vehicle_detail_id" type="long"/>
<subclass name="Bus" discriminator-value="1">
<join table="vehicles" >
<key column="vehicle_detail_id" />
...
</join>
</subclass>
<subclass name="Car" discriminator-value="2">
<join table="vehicles" >
<key column="vehicle_detail_id" />
...
</join>
</subclass>
</class>
There is something wrong with the mapping file, as the DB is not getting initialized. Please let me know if I have missed something. Thanks.
UPDATE : It gives ArrayIndexOutOfBoundsException: 1
Stacktrace :
...
at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1206)
at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1026)
at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4421)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4734)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:799)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:779)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:601)
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:943)
at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:778)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:504)
at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1385)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:306)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:142)
at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1389)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1653)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1662)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1642)
at java.lang.Thread.run(Thread.java:662)
This is a typical error message when the number of columns is not correct. In this case, you have only one primary key column in your join. Joins are primary-key related tables, so they need to have the same primary keys. In your case, the vehicles have two ids, while the buses and cars have only one.
The join element specifies a new table with the key column as both primary key and foreign key.
Consider making it a many-to-one which has a different type per subclass. (Instead of the join).
It would also map to a one-to-any property.

Hibernate : Table doesn't get updated

I'm having trouble with my hibernate transaction. After execution , table has no values in it and is not updated at all. All my other updates work properly.
Here's the mapping
<class name="LastDownloadedMessage" table="t_imap_lastmsguid">
<id name="id" column="id" ><generator class="increment"/></id>
<property name="lastDownloadedMessageUid"><column name="last_msg_uid" /></property>
<property name="lastUidNext"><column name="next_msg_uid" /></property>
<property name="folder"><column name="folder_name" /></property>
<property name="cred"><column name="credential" /></property>
</class>
This is the POJO object :
public class LastDownloadedMessage {
Integer id;
private String lastDownloadedMessageUid;
private String lastUidNext;
private String folder;
private String cred;
//GETTERS AND SETTERS HERE
public LastDownloadedMessage() {
super();
}
public LastDownloadedMessage(String lastDownloadedMessageUid,
String lastUidNext) {
super();
this.lastDownloadedMessageUid = lastDownloadedMessageUid;
this.lastUidNext = lastUidNext;
}
}
and this is the function which is doing the update.
Session ssn=HibernateSessionFactory.openSession();
Transaction txn = ssn.beginTransaction();
Query query = ssn.createQuery("update LastDownloadedMessage e set e.lastDownloadedMessageUid = :luid , e.lastUidNext = :nextUid where e.folder =:folder and e.cred = :cred");
query.setParameter("luid",last_downloaded_msg_uid);
query.setParameter("nextUid", uid_next);
query.setParameter("folder", folder);
query.setParameter("cred", credential);
int result = query.executeUpate();
txn.commit();
ssn.flush();
ssn.close();
The function appears to execute properly with no errors . What could be the issue ?
Based on the comments, it seems to me you are confusing UPDATE with INSERT.
If result is zero, it means your WHERE clause didn't match anything, so there's nothing to update.
Also, why are you trying to issue an UPDATE/INSERT on a known (mapped) entity? All you have to do is set your values on your object (LastDownloadedMessage) and then execute a persist on your EntityManager. Browse around the web for EntityManager.persist.

Importing and normalising XML with Hibernate

When importing xml to a DB with Hibernate, is there a way to resolve an attribute consisting of comma-separated values to populate related table(s)?
In this (somewhat obfuscated) example I have an xml file, each row of which represents a Person. The Person has a Hobbies property which contains a comma-separated list of values. The Person-Hobby relationship is many to many. In reality I have gigs of data to process.
When importing each Person to the PEOPLE table, I would like to add each Hobby to the HOBBIES table (ignoring duplicates), then add a mapping to the PEOPLE_HOBBIES table.
I've set up my mapping files with bi-directional associations and Hibernate appears to construct the tables as I'd expect (details below), however I don't see what mechanism I can use for extracting/populating the HOBBIES and PEOPLE_HOBBIES while processing PEOPLE.
All help and/or RTFM references gratefully received.
This is the file I'm processing (people.xml):
<People>
<Person Id="1" Name="Dave" Hobbies="drinking, walking"/>
<Person Id="2" Name="Geoff" Hobbies="football, ballet"/>
<Person Id="3" Name="Anne" Hobbies="walking, karate"/>
<Person Id="4" Name="Frank" Hobbies="karate, cross-stitch"/>
</People>
The Person.hbm.xml is (omitting xml decl):
<?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 package="name.seller.rich.hobby">
<class name="Person" node="Person" table="PEOPLE">
<id name="id" node="#Id" column="PEOPLE_ID"/>
<property name="name" node="#Name" column="NAME" type="string"/>
<property name="hobbies" node="#Hobbies" column="HOBBIES" type="string"/>
<set name="hobbiesSet" table="PEOPLE_HOBBIES">
<key column="PEOPLE_ID"/>
<many-to-many column="HOBBY" class="Hobby"/>
</set>
</class>
</hibernate-mapping>
The Hobby.hbm.xml is:
<hibernate-mapping package="name.seller.rich.hobby">
<class name="Hobby" node="Hobby" table="HOBBIES">
<id name="hobby" column="HOBBY" type="string"/>
<set name="people" table="PEOPLE_HOBBIES" inverse="true">
<key column="HOBBY"/>
<many-to-many column="PEOPLE_ID" class="Person"/>
</set>
</class>
</hibernate-mapping>
This is the Person class, in the setHobbies() method I populate the hobbiesSet with Hobby instances:
package name.seller.rich.hobby;
import java.util.HashSet;
import java.util.Set;
public class Person {
private long id;
private String name;
private String hobbies;
private Set hobbiesSet = new HashSet();
public String getHobbies() {
return hobbies;
}
public Set getHobbiesSet() {
if (hobbiesSet == null) {
hobbiesSet = new HashSet();
}
return hobbiesSet;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setHobbies(final String hobbies) {
this.hobbies = hobbies;
}
public void setHobbiesSet(final Set hobbiesSet) {
this.hobbiesSet = hobbiesSet;
}
public void setId(final long id) {
this.id = id;
}
public void setName(final String name) {
this.name = name;
}
}
This is the code I'm using to process the file:
package name.seller.rich.hobby;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
public class DataImporter {
public static void main(final String[] args) {
File baseDir = new File("C:\\workspaces\\hobby");
DataImporter importer = new DataImporter();
Configuration config = importer.setupDb(baseDir);
if (config != null) {
importer.importContents(new File(baseDir, "people.xml"), config);
}
}
private void importContents(final File file, final Configuration config) {
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(file);
List list = document.selectNodes("//Person");
Iterator iter = list.iterator();
while (iter.hasNext()) {
Object personObj = iter.next();
dom4jSession.save(Person.class.getName(), personObj);
}
session.flush();
tx.commit();
session.close();
} catch (HibernateException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}
private Configuration setupDb(final File baseDir) throws HibernateException {
Configuration cfg = new Configuration();
cfg.addFile(new File(baseDir, "name/seller/rich/hobby/Person.hbm.xml"));
cfg.addFile(new File(baseDir, "name/seller/rich/hobby/Hobby.hbm.xml"));
SchemaExport export = new SchemaExport(cfg);
export.setOutputFile("hobbyDB.txt");
export.execute(false, true, false, false);
return cfg;
}
}
This is the resulting content in the PEOPLE table.
PEOPLE_ID |NAME |HOBBIES
-------------------------------------------------------
1 |Dave |drinking, walking
2 |Geoff |football, ballet
3 |Anne |walking, karate
4 |Frank |karate, cross-stitch
...and these are the empty HOBBIES and PEOPLE_HOBBIES tables:
HOBBIES:
HOBBY
----------------------
0 rows selected
PEOPLE_HOBBIES:
PEOPLE_ID |HOBBY
---------------------------------------
0 rows selected
You might consider pre-processing your xml into something more suitable. It is generally better to represent lists of things as elements rather than comma separated attribute values.
For example:
<People>
<Person Id="1" Name="Dave" Hobbies="drinking, walking"/>
<Person Id="2" Name="Geoff" Hobbies="football, ballet"/>
<Person Id="3" Name="Anne" Hobbies="walking, karate"/>
<Person Id="4" Name="Frank" Hobbies="karate, cross-stitch"/>
</People>
Would be better as:
<People>
<Person Id="1" Name="Dave">
<Hobbies>
<Hobby>drinking</Hobby>
<Hobby>walking</Hobby>
</Hobbies>
</Person>
...
</People>
You could do this with an XSLT script - see XSLT - Best way to split and render comma separated text as HTML for an example.
That should then make it easier to import into Hibernate in the manner you desire.
When Hibernate reads the hobbies attribute, it just stores it as text directly into the Person table. It has no way of knowing about hobbiesSet at this point, since the only time that you are populating the set is when the object is read back out of the database again. But since the set was never populated in the database, it doesn't work.
The way you've configured both hobbies and hobbiesSet is confusing, and I wouldn't recommend mixing hobbies and hobbiesSet like that. I strongly suggest you read the XML into the object model yourself, including splitting the hobbies string, and then persist the manually-constructed objects to Hibernate in the normal way, using a hobbies collection.
I have found a partial solution, and thought it would be worth recording it here.
Unfortunately if there are duplicate keys in the list-attribute, you need to do a merge rather than a save on the element, and this is not yet supported for EntityMode.DOM4J. This is the comment from org.hibernate.type.CollectionType.replaceElements():
// TODO: does not work for EntityMode.DOM4J yet!
You can add an ElementHandler to the SAXReader to process each element and dynamically turn the attributes into child elements, this is my implementation:
SAXReader saxReader = new SAXReader();
saxReader.addHandler("/People/Person", new ElementHandler() {
public void onEnd(final ElementPath elementPath) {
Element element = elementPath.getCurrent();
Attribute hobbyAttribute = element.attribute("Hobbies");
if (hobbyAttribute != null) {
String hobbies = hobbyAttribute.getValue();
Element hobbiesList = new DefaultElement("Hobbies");
element.add(hobbiesList);
String[] hobbiesArray = hobbies.split(",");
for (String hobby : hobbiesArray) {
if (hobby.trim().length() > 0) {
Element hobbyElement = new DefaultElement("Hobby");
hobbiesList.add(hobbyElement);
Element idElement = new DefaultElement("id");
hobbyElement.add(idElement);
idElement.setText(hobby.trim());
}
}
}
}
public void onStart(final ElementPath elementPath) {
//no-op;
}
});
And the later loop is modified as follows:
while (iter.hasNext()) {
Object peopleObj = iter.next();
dom4jSession.merge(Person.class.getName(), peopleObj);
}
Once I'd updated the mapping files to handle the child elements and renamed the relevant methods in the domain objects it persists the related data (as long as there are no duplicates in the hobbies natch).
Updated Hobby.hbm.xml:
<?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 package="name.seller.rich.hobby">
<class name="Hobby" node="Hobby" table="HOBBIES">
<!--id name="id" column="HOBBY_ID">
<generator class="native"/>
</id-->
<id name="id" column="HOBBY_ID" type="string"/>
<set name="people" table="PEOPLE_HOBBIES" inverse="true">
<key column="HOBBY_ID"/>
<many-to-many column="PEOPLE_ID" class="Person"/>
</set>
</class>
</hibernate-mapping>
Updated Person.hbm.xml:
<hibernate-mapping package="name.seller.rich.hobby">
<class name="Person" node="Person" table="PEOPLE">
<id name="id" node="#Id" column="PEOPLE_ID"/>
<property name="name" node="#Name" column="NAME" type="string"/>
<!-- property name="hobbies" node="#Hobbies" column="HOBBIES" type="string"/-->
<set name="hobbies" node="Hobbies" table="PEOPLE_HOBBIES" cascade="save-update,persist">
<key column="PEOPLE_ID"/>
<many-to-many column="HOBBY_ID" class="Hobby"/>
</set>
</class>
</hibernate-mapping>
We tried using Hibernate's DOM4J and POJO entity modes in the same application a while ago. Maybe it's matured more by now, but we had nothing but problems with the DOM4J entity mode.
I'd recommend using Hibernate with your POJOs and use something like XStream or raw DOM4J for doing your XML serialization to and from the POJOs.

Categories

Resources