首页 > 解决方案 > 尝试使用 Erwin Vervaet 的框架存储时间集合并获取 ClassCastException

问题描述

我正在尝试使用Erwin Vervaet 的 Bitemporal框架与 Hibernate 一起存储时间集合,而不是像他的示例中那样存储时间属性。(这里有框架的介绍)

我正在尝试存储随时间变化的地址集合,即一个人可以同时拥有多个地址,并且这组地址可以改变。

映射有效,我的意思是表是在数据库中创建的,但出现以下运行时错误:

java.lang.ClassCastException: com.ervacon.bitemporal.AddressSet cannot be cast to java.util.Collection

我不明白这个错误。我知道当 Hibernate 尝试构建addresses包时正在执行转换Person,但为什么它会得到一个ClassCastException?如果我注释掉映射value中的entity-name="AddressSet"映射,我没有错误,但地址没有保存。所以问题出在我相信的那个映射上。

我也不明白我想要实现的目标是否可以用这个框架来完成。

你能帮助我吗?

我如何修改 Vervaet 的示例:我添加了 AddressSet 类并修改了 Hibernate 映射、Person 和测试类

涉及的类如下:

/*
 * (c) Copyright Ervacon 2016.
 * All Rights Reserved.
 */
package com.ervacon.bitemporal;

import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedList;

public class Person implements Serializable {

    private Long id;
    private String name;
    private Collection<BitemporalWrapper<AddressSet>> addresses = new LinkedList<>();
    private Collection<BitemporalWrapper<Boolean>> alive = new LinkedList<>();

    /**
     * For Hibernate.
     */
    @SuppressWarnings("unused")
    private Person() {
    }

    public Person(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Name is required");
        }
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public WrappedBitemporalProperty<AddressSet> addresses() {
        return new WrappedBitemporalProperty<>(addresses);
    }

    public WrappedBitemporalProperty<Boolean> alive() {
        return new WrappedBitemporalProperty<>(alive);
    }

    @Override
    public String toString() {
        return getName();
    }
}

地址

/*
 * (c) Copyright Ervacon 2016.
 * All Rights Reserved.
 */
package com.ervacon.bitemporal;

import java.io.Serializable;

public class Address implements Serializable {

    private String line1;
    private String line2;
    private String line3;

    private long id;

    /**
     * For Hibernate.
     Address.java*/
    @SuppressWarnings("unused")
    private Address() {
    }

    public Address(String line1, String line2, String line3) {
        this.line1 = line1;
        this.line2 = line2;
        this.line3 = line3;
    }

    public String getLine1() {
        return line1;
    }

    public String getLine2() {
        return line2;
    }

    public String getLine3() {
        return line3;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof Address) {
            Address other = (Address) obj;
            return other.line1.equals(this.line1)
                    && other.line2.equals(this.line2)
                    && other.line3.equals(this.line3);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return this.line1.hashCode() + this.line2.hashCode() + this.line3.hashCode();
    }

    @Override
    public String toString() {
        return this.line1 + " " + line2 + " " + line3;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }


}

地址集

package com.ervacon.bitemporal;

import java.io.Serializable;
import java.util.Set;

public class AddressSet implements Serializable {

    private List<Address> addressSet;

    private long id;

    private AddressSet() {
    }

    public AddressSet(List<Address> a) {
        this.addressSet = a;
    }

    public List<Address> getAddressSet() {
        return addressSet;
    }

    public void setAddressSet(List<Address> addressSet) {
        this.addressSet = addressSet;
    }

}

映射是:

<?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 default-access="field">

    <class name="com.ervacon.bitemporal.BitemporalWrapper" entity-name="AddressSet">
        <id name="id" type="long">
            <generator class="native"/>
        </id>

        <bag name="value">
            <key column="addressSet_id"/>
            <one-to-many class="com.ervacon.bitemporal.Address"/>
        </bag>

        <property name="validityInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
            <column name="validityStart"/>
            <column name="validityEnd"/>
        </property>
        <property name="recordInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
            <column name="recordStart"/>
            <column name="recordEnd"/>
        </property>
    </class>


    <class name="com.ervacon.bitemporal.Person">
        <id name="id" type="long">
            <generator class="native"/>
        </id>

        <property name="name"/>
        <!-- <bag name="address" cascade="all-delete-orphan">
            <key column="person_id" not-null="true" update="false"/>
            <one-to-many entity-name="Address"/>
        </bag>
         -->

        <bag name="addresses" cascade="all-delete-orphan">
            <key column="person_id" not-null="true" update="false"/>
            <one-to-many entity-name="AddressSet"/>
        </bag>

        <bag name="alive" cascade="all-delete-orphan">
            <key column="person_id" not-null="true" update="false"/>
            <one-to-many entity-name="Alive"/>
        </bag>
    </class>

    <class name="com.ervacon.bitemporal.Address">
        <id name="id" type="long">
            <generator class="native"/>
        </id>

        <property name="line1"/>
        <property name="line2"/>
        <property name="line3"/>
    </class>


    <class name="com.ervacon.bitemporal.BitemporalWrapper" entity-name="Alive">
        <id name="id" type="long">
            <generator class="native"/>
        </id>

        <property name="value" type="boolean"/>

        <property name="validityInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
            <column name="validityStart"/>
            <column name="validityEnd"/>
        </property>
        <property name="recordInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
            <column name="recordStart"/>
            <column name="recordEnd"/>
        </property>
    </class>
</hibernate-mapping>

考试

package com.ervacon.bitemporal;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.junit.Before;
import org.junit.Test;

public class HibernateTest {

    private SessionFactory sessionFactory;

    @Before
    public void setUp() {
        System.err.println("Before");
        try {
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (HibernateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //@After
    public void tearDown() throws Exception {
        sessionFactory.close();
        TimeUtils.clearReference();
    }

    @Test
    public void testPersistence() {
        System.err.println("Testing");
        Session session = sessionFactory.openSession();
        session.beginTransaction();

        TimeUtils.setReference(TimeUtils.day(4, 4, 1975));
        Person johnDoe = new Person("John Doe");
        johnDoe.alive().set(
                true,
                TimeUtils.from(TimeUtils.day(3, 4, 1975)));


        AddressSet addressSet = new AddressSet(new ArrayList<>());
        List<Address> addressList1 = new ArrayList<>();
        addressList1.add(new Address("Address1.1", "", ""));
        addressSet.setAddressSet(addressList1);

        johnDoe.addresses().set(
                addressSet,
                TimeUtils.from(TimeUtils.day(3, 4, 1975)));

        AddressSet addressSet2 = new AddressSet(new ArrayList<>());
        List<Address> addressList2 = new ArrayList<>();
        addressList2.add(new Address("Address2.1", "", ""));
        addressSet2.setAddressSet(addressList2);

        johnDoe.addresses().set(
                addressSet2,
                TimeUtils.from(TimeUtils.day(3, 4, 1976)));

        try {
            session.save(johnDoe);

            session.getTransaction().commit();
            session.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
}

完整的例外是

java.lang.ClassCastException: com.ervacon.bitemporal.AddressSet cannot be cast to java.util.Collection
    at org.hibernate.type.BagType.wrap(BagType.java:35)
    at org.hibernate.event.internal.WrapVisitor.processArrayOrNewCollection(WrapVisitor.java:91)
    at org.hibernate.event.internal.WrapVisitor.processCollection(WrapVisitor.java:56)
    at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:104)
    at org.hibernate.event.internal.WrapVisitor.processValue(WrapVisitor.java:108)
    at org.hibernate.event.internal.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:59)
    at org.hibernate.event.internal.AbstractSaveEventListener.visitCollectionsBeforeSave(AbstractSaveEventListener.java:354)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:260)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:97)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
    at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:651)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:643)
    at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:218)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:391)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:316)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:155)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:424)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:356)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:319)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:155)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:104)
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:281)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:113)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
    at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
    at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
    at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:682)
    at org.hibernate.internal.SessionImpl.save(SessionImpl.java:674)
    at org.hibernate.internal.SessionImpl.save(SessionImpl.java:669)
    at com.ervacon.bitemporal.HibernateTest.testPersistence(HibernateTest.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

编辑:更改SetListin AddressSet,因为在使用 Tijkijiki 的解决方案后,当 Hibernate 构建包时出现错误。在此之后,测试通过了。

但是我AddressSet在测试中添加了两组(现在自原始发布以来已对其进行了修改),现在我得到了这个错误,有人可以解释我为什么吗?AddressSet 对象对我来说似乎不同。

ERROR: HHH000346: Error during managed flush [Found shared references to a collection: AddressSet.value.addressSet]
org.hibernate.HibernateException: Found shared references to a collection: AddressSet.value.addressSet
    at org.hibernate.engine.internal.Collections.processReachableCollection(Collections.java:182)
    at org.hibernate.event.internal.FlushVisitor.processCollection(FlushVisitor.java:42)
    at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:104)
    at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:65)
    at org.hibernate.event.internal.AbstractVisitor.processValues(AbstractVisitor.java:44)
    at org.hibernate.event.internal.AbstractVisitor.processComponent(AbstractVisitor.java:85)
    at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:110)
    at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:65)
    at org.hibernate.event.internal.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:59)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:155)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:491)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
    at com.ervacon.bitemporal.HibernateTest.testPersistence(HibernateTest.java:77)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

标签: javahibernatetemporal

解决方案


问题真的是你的映射, BitemporalWrapper<AddressSet>应该映射valueAddressSet

<class name="com.ervacon.bitemporal.Person">
    <id name="id" type="long">
        <generator class="native"/>
    </id>

    <property name="name"/>
    <bag name="addresses" cascade="all-delete-orphan">
        <key column="person_id" not-null="true" update="false"/>
        <one-to-many entity-name="AddressSet"/>
    </bag>
    <bag name="alive" cascade="all-delete-orphan">
        <key column="person_id" not-null="true" update="false"/>
        <one-to-many entity-name="Alive"/>
    </bag>
</class>

<class name="com.ervacon.bitemporal.BitemporalWrapper" entity-name="AddressSet">
    <id name="id" type="long">
        <generator class="native"/>
    </id>

    <component name="value" class="com.ervacon.bitemporal.AddressSet">
        <bag name="addressSet" cascade="all-delete-orphan">
            <key column="addressSet_id" not-null="true" update="false"/>
            <one-to-many entity-name="Address"/>
        </bag>
    </component>

    <property name="validityInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
        <column name="validityStart"/>
        <column name="validityEnd"/>
    </property>
    <property name="recordInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
        <column name="recordStart"/>
        <column name="recordEnd"/>
    </property>
</class>

<class name="com.ervacon.bitemporal.Address" entity-name="Address">
    <id name="id" type="long">
        <generator class="native"/>
    </id>

    <property name="line1"/>
    <property name="line2"/>
    <property name="line3"/>
</class>

<class name="com.ervacon.bitemporal.BitemporalWrapper" entity-name="Alive">
    <id name="id" type="long">
        <generator class="native"/>
    </id>

    <property name="value" type="boolean"/>

    <property name="validityInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
        <column name="validityStart"/>
        <column name="validityEnd"/>
    </property>
    <property name="recordInterval" type="com.ervacon.bitemporal.support.PersistentInterval">
        <column name="recordStart"/>
        <column name="recordEnd"/>
    </property>
</class>

在搜索解决方案时,我发现获取版本化实体是在 java 中完成的,而不是在 query中。所以我个人不会走这条路。


编辑:从这个答案

当您尝试持久化多个实体实例共享相同的集合引用(即集合标识与集合相等性对比)时,Hibernate 会显示此错误。

所以我对双时态框架进行了一些研究,我认为,问题就在这里,基本上是用新的记录间隔添加了附加值,但值没有被复制,这在大多数情况下都不是问题。但是由于我们的对象在两个 BitemporalWrappers 中使用并且它具有相同的集合引用(因为它的对象相同),所以会引发异常。

为了证明我的理论(老实说,这对我来说有点薄冰;我不确定我的理解是否正确)我对框架进行了以下更改:V实现类型Serializabe,因此我可以在克隆 BitemporalWrapper 时使用commons-lang3 SerializationUtils并返回副本V value

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>



public class BitemporalWrapper<V extends Serializable> implements Bitemporal, Serializable {

...
    @Override
    public Bitemporal copyWith(Interval validityInterval) {
        // force record interval to be 'from now'
        return new BitemporalWrapper<>(SerializationUtils.clone(value), validityInterval);
    }
...
}

public class WrappedBitemporalProperty<V extends Serializable> extends BitemporalProperty<V, BitemporalWrapper<V>> { ...  }

public class BitemporalProperty<V extends Serializable, T extends Bitemporal> implements Serializable { .. }

现在测试通过了。如果您有兴趣,我可以分享我的更改。


推荐阅读