我正在尝试使用Erwin Vervaet的Bitemporal framework与Hibernate存储一个时态集合,而不是像他的示例中那样存储时态属性。
(演示了here框架)
我正在尝试存储随时间变化的地址的集合,即一个人可以同时具有多个地址,并且这组地址可以更改。
映射有效,我的意思是表是在数据库中创建的,但是出现以下运行时错误:
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和test类
涉及的类如下:
人
/*
* (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();
}
}
完整的异常(exception)是
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)
编辑:将
Set
更改为List
中的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)
最佳答案
问题确实是您的映射, 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
因此,我对Bitemporal框架进行了一些研究,我认为这个问题是here,它基本上是在新记录间隔内增加了附加值,但是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 { .. }
现在测试通过。如果您有兴趣,我可以分享我的更改。