如何在Hibernate中处理Identity与Equality?

时间:2013-11-15 15:40:05

标签: java xml hibernate

注意:我开发了这个SSSCE来说明我的问题。实际问题要大得多(个人"记录"有更多字段和更多数据,XML数据文件有30K记录。)

给出以下XML片段:

<?xml version="1.0" encoding="UTF-8"?>
<manufacturers>

    <manufacturer>
        <name>BMW</name>
        <manufacturing-countries>
            <country code="DE" iso="DEU">Germany</country>
        </manufacturing-countries>
        <using-countries>
            <country code="DE" iso="DEU">Germany</country>
            <country code="JP" iso="JPN">Japan</country>
            <country code="US" iso="USA">United States</country>
            <country code="UK" iso="GBR">Germany</country>
        </using-countries>
    </manufacturer>

    <manufacturer>
        <name>Honda</name>
        <manufacturing-countries>
            <country code="JP" iso="JPN">Japan</country>
            <country code="US" iso="USA">United States</country>
        </manufacturing-countries>
        <using-countries>
            <country code="JP" iso="JPN">Japan</country>
            <country code="US" iso="USA">United States</country>
            <country code="UK" iso="GBR">Germany</country>
        </using-countries>
    </manufacturer>

</manufacturers>

我有一个基于JAX-B的解析器,它将此XML读入以下对象 (请注意,对象设计为不可变的;它们使用构建器模式来构建实际对象):

// @Immutable
public class Country {
    private String code;
    private String name;
    private String isoCode;

    private Country() {
        // private, no arg constructor
    }

    // getters elided

    // builder elided
}    

// @Immutable
public class Manufacturer {

    private String name;
    // list of countries where manufacturer builds thier product(s)
    private List<Country> manufacturingCountries;
    // list of countries who use manufacturer's product(s)
    private List<Country> usingCountries;

    private Manufacturer() {
        // private no-arg constructor
    }
    // getters elided

    // builder elided
}

因此,在我的代码中,我可以轻松地从XML文件中加载制造商列表:

List<Manufacturer> manufacturers = ManufacturerReader.readManufacturersFromXml(xmlFilePath);

我需要转身并将此信息放入数据库中。到目前为止,我已经获得了这个Hibernate映射文件:

hibernate.hbm.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field">
    <class name="Country"
           table="COUNTRIES">

        <id
            name="code"
            length="2"
            column="CODE">
            <generator class="assigned"/>
        </id>

        <property
            name="name"
            length="40"
            column="NAME">
        </property>

        <property
            name="isoCode"
            length="3"
            column="ISO_CODE">
        </property>

    </class>

    <class name="Manufacturer"
           table="MANUFACTURERS">

        <id
            type="string"
            column="ID">
            <generator class="uuid"/>
        </id>

        <property
            name="name"
            length="255"
            column="NAME">
        </property>

        <list
            name="manufacturingCountries"
            table="MANUF_MANUF_COUNTRY"
            cascade="save-update">
            <key column="MANUFACTURER_ID"/>
            <list-index column="POSITION"/>
            <many-to-many class="Country" column="COUNTRY_CODE"/>
        </list>

        <list
            name="usingCountries"
            table="MANUF_USING_COUNTRY"
            cascade="save-update">
            <key column="MANUFACTURER_ID"/>
            <list-index column="POSITION"/>
            <many-to-many class="Country" column="COUNTRY_CODE"/>
        </list>       

    </class>
</hibernate-mapping>

鉴于解析器并不关心相等性,所以创建的Country对象在各个制造商之间和之间重复(例如,有三个&#34; US&#34; Country对象,两个&# 34; JP&#34;国家对象等)

当我尝试使用Hibernate保存对象图时,由于重复(等式)Country对象,我得到一个NonUniqueObjectException。

Exception in thread "main" org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [Country#JP]
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:190)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:143)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:685)
    at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:677)
    at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:252)
    at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
    at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
    at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
    at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:425)
    at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:362)
    at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:338)
    at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
    at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
    at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:475)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:353)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:143)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:713)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:701)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:697)
    at Main.main(Main.java:28)

缺少&#34;手动&#34;从XML Parser读取后,在内存中对这些对象进行重复数据删除,如何解决此问题?

假设:

  • 必须在我的&#34;保存到数据库逻辑&#34;其中包括 hibernate映射文件。
  • 无法更改国家/地区或制造商 对象(它们位于上游,API已设置。)
  • 无法改变 XML阅读器/解析器(再次提供上游,并设置API。)
  • 无法更改XML架构(同样,上游提供了API,API也是 集。)

修改1:

我想出了一个简单的代码来实际传输数据:

import java.util.List;
import org.hibernate.Session;
import org.hibernate.Transaction;

public class Main {
    public static void main(String[] args) {

        List<Manufacturer> manuf = ManufacturerXmlReader.readManufacturersFromXml("manufacturers.xml");

        Session session = HibernateUtil.getSessionFactory().openSession();

        Transaction tx = session.beginTransaction();

        for (Manufacturer m : manuf) {
            session.save(m);
        }

        tx.commit();
        session.close();
        HibernateUtil.shutdown();
    }
}

编辑2:

session.save()更改为session.merge()时,我会

Exception in thread "main" org.hibernate.HibernateException: The class has no identifier property: Manufacturer
    at org.hibernate.tuple.entity.AbstractEntityTuplizer.getIdentifier(AbstractEntityTuplizer.java:220)
    at org.hibernate.persister.entity.AbstractEntityPersister.getIdentifier(AbstractEntityPersister.java:3876)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:233)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:84)
    at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:867)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:851)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:855)
    at Main.main(Main.java:16)

编辑3:

如果我更改Hibernate映射文件,为Manufacturer ID元素提供name属性,如:

<class name="Manufacturer"
       table="MANUFACTURERS">

    <id
        name="id"
        type="string"
        column="ID">
        <generator class="uuid"/>
    </id>

我得到以下内容:

Initial SessionFactory creation failed.org.hibernate.PropertyNotFoundException: field [id] not found on Manufacturer
Exception in thread "main" java.lang.ExceptionInInitializerError
    at HibernateUtil.<clinit>(HibernateUtil.java:21)
    at Main.main(Main.java:11)
Caused by: org.hibernate.PropertyNotFoundException: field [id] not found on Manufacturer
    at org.hibernate.property.DirectPropertyAccessor.getField(DirectPropertyAccessor.java:182)
    at org.hibernate.property.DirectPropertyAccessor.getField(DirectPropertyAccessor.java:174)
    at org.hibernate.property.DirectPropertyAccessor.getGetter(DirectPropertyAccessor.java:197)
    at org.hibernate.tuple.PropertyFactory.getGetter(PropertyFactory.java:191)
    at org.hibernate.tuple.PropertyFactory.buildIdentifierProperty(PropertyFactory.java:67)
    at org.hibernate.tuple.entity.EntityMetamodel.<init>(EntityMetamodel.java:135)
    at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:485)
    at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:133)
    at org.hibernate.persister.PersisterFactory.createClassPersister(PersisterFactory.java:84)
    at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:286)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1872)
    at HibernateUtil.<clinit>(HibernateUtil.java:17)
    ... 1 more

如果我将name属性/字段更改为

之类的ID字段
    <id
        name="name"
        length="255"
        column="NAME">
        <generator class="assigned"/>
    </id>

然后我明白了:

org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [Country#JP]
    at org.hibernate.impl.SessionFactoryImpl$2.handleEntityNotFound(SessionFactoryImpl.java:435)
    at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:233)
    at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:285)
    at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
    at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
    at org.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1038)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:630)
    at org.hibernate.type.EntityType.resolve(EntityType.java:438)
    at org.hibernate.type.EntityType.replace(EntityType.java:298)
    at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:508)
    at org.hibernate.type.CollectionType.replace(CollectionType.java:575)
    at org.hibernate.type.AbstractType.replace(AbstractType.java:176)
    at org.hibernate.type.TypeHelper.replaceAssociations(TypeHelper.java:262)
    at org.hibernate.event.def.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:589)
    at org.hibernate.event.def.DefaultMergeEventListener.mergeTransientEntity(DefaultMergeEventListener.java:389)
    at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:303)
    at org.hibernate.event.def.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:464)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:255)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:84)
    at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:867)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:851)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:855)
    at Main.main(Main.java:16)
Exception in thread "main" org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [Country#JP]
    at org.hibernate.impl.SessionFactoryImpl$2.handleEntityNotFound(SessionFactoryImpl.java:435)
    at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:233)
    at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:285)
    at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
    at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
    at org.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1038)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:630)
    at org.hibernate.type.EntityType.resolve(EntityType.java:438)
    at org.hibernate.type.EntityType.replace(EntityType.java:298)
    at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:508)
    at org.hibernate.type.CollectionType.replace(CollectionType.java:575)
    at org.hibernate.type.AbstractType.replace(AbstractType.java:176)
    at org.hibernate.type.TypeHelper.replaceAssociations(TypeHelper.java:262)
    at org.hibernate.event.def.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:589)
    at org.hibernate.event.def.DefaultMergeEventListener.mergeTransientEntity(DefaultMergeEventListener.java:389)
    at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:303)
    at org.hibernate.event.def.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:464)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:255)
    at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:84)
    at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:867)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:851)
    at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:855)
    at Main.main(Main.java:16)

3 个答案:

答案 0 :(得分:1)

你可以使用Hibernate合并,我认为它非常适合你的情况,看看这个问题:Hibernate : Downside of merge() over update()

您可以使用与瞬态对象的合并(应该是来自XML的对象),以及第二个对象,它是附加到会话的对象(您从Hibernate配置的会话中获得的对象)。如果您之前未在会话中显式加载它,则调用merge将加载它(实际上Hibernate会检查1级缓存)。

然后Hibernate获取瞬态对象,检查与附加对象的差异并将更改应用于附加对象;请注意,瞬态字段未合并。

Merge返回附加的对象,因此您可以在Hibernate会话中安全地继续对其进行更改。

所以在你的代码中:

Manufacturer manufacturerDB = session.merge(manufacturerFromXML);

其中manufacturerDB将代表您的制造商数据库,其中您合并了来自XML值的值。

如果您不想修改它,另一个简单的解决方案是遍历您的制造商,然后在国家/地区并用countryDB值替换每个国家/地区值(在manufacturerXML中)(来自Hibernate by session.load( ...),您不必担心多次调用相同的ID,因为Hibernate具有1级缓存,同一个国家/地区的会话是单个值,这要归功于缓存,因此您不会有NonUnique ...异常)它会起作用。

答案 1 :(得分:0)

直接回答您的问题是代理键

您可以考虑在实施中使用代理键...

答案 2 :(得分:0)

似乎解决这个问题的唯一方法是拥有Hibernate可以完全管理的开放数据对象。我的解决方案必须是:

  • 打开上游对象并更改它们以添加ID字段。
  • 使用可以使用Hibernate的对象扩展或重新建模域。
  • 编写代码以自行遍历对象图/树/列表
  • 编写代码以删除上游数据中的重复

我读过的所有内容都表明ID字段是必要的,至少在数据库方面是这样,你不能在对象中没有ID字段的对象上使用 merge

除非有人能指出我更好的答案,否则我必须将此作为已接受的答案,尽管很糟糕。