尝试使用 Erwin Vervaet 的框架存储临时集合并获取 ClassCastException

Trying to store a temporal collection with Erwin Vervaet's framework and getting ClassCastException

我正在尝试使用 Erwin Vervaet 的 Bitemporal framework 与 Hibernate 一起存储时态集合,而不是他的示例中的时态 属性。 (有框架介绍here

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

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

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

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

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

你能帮帮我吗?

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

涉及的classes如下:

/*
 * (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.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.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.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=16=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.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)

edit:在 AddressSet 中将 Set 更改为 List,因为在使用 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.access0(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.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.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=17=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.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)

问题确实出在你的映射上,BitemporalWrapper<AddressSet> 应该将 value 映射到 AddressSet

<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、not in a query 中完成的。所以我个人不会走这条路。


编辑:来自 this answer

Hibernate shows this error when you attempt to persist more than one entity instance sharing the same collection reference (i.e. the collection identity in contrast with collection equality).

所以我深入研究了 Bitemporal 框架,我认为问题是 here, where is basicly added additional value with new record interval, but value is not copied,在大多数情况下这不是问题。但是由于我们的对象在两个 BitemporalWrappers 中使用并且它具有相同的集合引用(因为它是同一个对象),所以抛出异常。

为了证明我的理论(老实说,这对我来说有点像薄冰;我不确定我的理解是否正确)我在框架中做了以下更改: V 的类型实现了 Serializabe,所以我可以使用 commons-lang3 SerializationUtils 并在克隆 BitemporalWrapper 时返回 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 { .. }

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