【问题标题】:Importing and normalising XML with Hibernate使用 Hibernate 导入和规范化 XML
【发布时间】:2010-11-10 02:49:35
【问题描述】:

当使用 Hibernate 将 xml 导入数据库时​​,有没有办法解析由逗号分隔的值组成的属性以填充相关表?

在这个(有点混淆的)示例中,我有一个 xml 文件,其中的每一行代表一个人。 Person 有一个 Hobbies 属性,其中包含一个以逗号分隔的值列表。人与爱好的关系是多对多的。实际上,我有大量数据要处理。

将每个人导入到 PEOPLE 表时,我想将每个爱好添加到 HOBBIES 表(忽略重复项),然后添加到 PEOPLE_HOBBIES 表的映射。

我已经设置了具有双向关联的映射文件,并且 Hibernate 似乎可以按照我的预期构建表(详细信息如下),但是我看不出我可以使用什么机制来提取/填充 HOBBIES和 PEOPLE_HOBBIES,同时处理 PEOPLE。

感谢所有帮助和/或 RTFM 参考。

这是我正在处理的文件(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>

Person.hbm.xml 是(省略 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>

Hobby.hbm.xml 是:

<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>

这是 Person 类,在 setHobbies() 方法中,我使用 Hobby 实例填充 hobbiesSet:

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;
    }
}

这是我用来处理文件的代码:

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;
    }
}

这是 PEOPLE 表中的结果内容。

PEOPLE_ID           |NAME        |HOBBIES              
-------------------------------------------------------
1                   |Dave        |drinking, walking    
2                   |Geoff       |football, ballet     
3                   |Anne        |walking, karate      
4                   |Frank       |karate, cross-stitch 

...这些是空的 HOBBIES 和 PEOPLE_HOBBIES 表:

爱好:

HOBBY
----------------------

0 rows selected

PEOPLE_HOBBIES:

PEOPLE_ID           |HOBBY
---------------------------------------

0 rows selected

【问题讨论】:

  • re '在 setHobbies() 方法中我用 Hobby 实例填充 hobbiesSet' 我没有看到我只看到 public void setHobbies(final String hobbies) { this.hobbies = hobbies; }

标签: java xml hibernate


【解决方案1】:

当 Hibernate 读取 hobbies 属性时,它只是将其作为文本直接存储到 Person 表中。此时它无法知道 hobbiesSet,因为您填充集合的唯一时间是对象再次从数据库中读回时。但由于该集合从未填充到数据库中,因此它不起作用。

您配置 hobbies 和 hobbiesSet 的方式令人困惑,我不建议像这样混合 hobbies 和 hobbiesSet。我强烈建议您自己将 XML 读入对象模型,包括拆分 hobbies 字符串,然后使用 hobbies 集合以正常方式将手动构建的对象持久化到 Hibernate。

【讨论】:

  • Hibernate 3 支持 XML 映射,所以对我来说支持这种处理似乎是合理的。我知道我可以自己动手,但这违背了使用 ORM 的意义。 setHobbies() 实现是我调查映射如何工作的一部分,我认为这不是处理设置属性的好方法,但正如你所说,在处理 XML 时不会调用它(没有在我尝试之前知道)所以并不真正相关。我将删除方法内容以避免混淆。
  • 当您在不改变其结构的情况下存储 XML 时,XML 持久性模式非常适合。但是您正在更改结构,因此将其存储在一个结构中并尝试在另一个结构中检索它是没有意义的。
【解决方案2】:

我找到了部分解决方案,并认为值得在这里记录。 不幸的是,如果列表属性中有重复的键,您需要对元素进行合并而不是保存,而 EntityMode.DOM4J 尚不支持这一点。这是来自 org.hibernate.type.CollectionType.replaceElements() 的评论:

// TODO: 不适用于 EntityMode.DOM4J!

你可以在 SAXReader 中添加一个 ElementHandler 来处理每个元素并动态地将属性转换为子元素,这是我的实现:

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;
    }
});

并且后面的循环修改如下:

while (iter.hasNext()) {
    Object peopleObj = iter.next();
    dom4jSession.merge(Person.class.getName(), peopleObj);
}

一旦我更新了映射文件以处理子元素并重命名域对象中的相关方法,它就会保留相关数据(只要爱好 natch 中没有重复项) .

更新了 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>

更新 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>

【讨论】:

    【解决方案3】:

    您可能会考虑将您的 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>
    

    最好是:

    <People>
      <Person Id="1" Name="Dave">
        <Hobbies>
          <Hobby>drinking</Hobby>
          <Hobby>walking</Hobby>
        </Hobbies>
      </Person>
    
      ...
    </People>
    

    您可以使用 XSLT 脚本执行此操作 - 有关示例,请参阅 XSLT - Best way to split and render comma separated text as HTML

    这样应该可以更轻松地以您想要的方式导入 Hibernate。

    【讨论】:

    • 我的“部分解决方案”在内存中有效地做到了这一点。看来这是一个 Hibernate 问题,对 EntityMode.DOM4J 的支持还没有完成。
    • 我将支持“对 EntityMode.DOM4J 的支持尚未完成”。几年前,我们在尝试使用 Hibernate JIRA 时遇到了 HHH-422、HHH-1039、HHH-1265 和 HHH-1999 问题。所有这些问题今天仍然存在。
    • EntityMode.DOM4J 在 Hibernate >=4 中不再可用。是否有可用的 EntityModes 解决方法?
    【解决方案4】:

    不久前我们尝试在同一个应用程序中使用 Hibernate 的 DOM4J 和 POJO 实体模式。也许现在它已经成熟了,但我们在 DOM4J 实体模式方面遇到了问题。

    我建议将 Hibernate 与 POJO 结合使用,并使用 XStream 或原始 DOM4J 之类的东西对 POJO 进行 XML 序列化。

    【讨论】:

      猜你喜欢
      • 2011-09-10
      • 2011-03-17
      • 1970-01-01
      • 2011-01-25
      • 2021-06-13
      • 1970-01-01
      • 1970-01-01
      • 2016-02-27
      • 1970-01-01
      相关资源
      最近更新 更多