Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to use the Bitemporal framework of Erwin Vervaet to store with Hibernate a temporal collection instead of a temporal property as in his example. (there is a presentation of the framework here)

I'm trying to store a collection of addresses which change over time, i.e. a Person can have multiple addresses at the same time, and this set of addresses can change.

The mapping works, I mean the tables are created in the db, but I get the following runtime error:

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

I don't understand the error. I understand a cast is being done when Hibernate tries to build the addresses bag of Person, but why does it get a ClassCastException? If I comment out the mapping of value in the entity-name="AddressSet" mapping, I have no error, but the addresses aren't saved. So the problem is in that mapping I believe.

I also don't understand if what I'm trying to achieve can be done with this framework.

Can you help me?

How I modified Vervaet's example: I added the AddressSet class and modified the Hibernate mapping, the Person and test class

The classes involved are the following:

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

Address

/*
 * (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;
    }


}

AddressSet

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

}

The mapping is:

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

The test

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

The full exception is

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)

edit: changed Set to List in AddressSet, because after using Tijkijiki's solution I got an error while Hibernate was building the bag. After this the test passed.

But I added two set of AddressSet to the test (which is now modified since the original posting) and now I get this error, someone can explain me why? The AddressSet objects seem different to me.

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)
like image 946
cdarwin Avatar asked May 02 '19 18:05

cdarwin


1 Answers

Problem is really your mapping, BitemporalWrapper<AddressSet> should map value to 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>

When searching for solution I found out, that getting versioned entity is done in java, not in a query. So personally I wouldnt go this way.


Edit: From 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).

So I dig little bit into Bitemporal framework and I think, that problem is here, where is basicly added additional value with new record interval, but value is not copied, which is not problem in most cases. But since our object is used in two BitemporalWrappers and it has same collection reference (because its same object), exception is thrown.

To prove my theory (to be honest, this is little bit thin ice for me; Im not sure if my understanding is correct) I have made following changes in framework: Type of V implements Serializabe, so I can use commons-lang3 SerializationUtils and returning copy of V value when clonning BitemporalWrapper :

<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 { .. }

Now test passes. I can share my changes if you were interested.

like image 90
Tijkijiki Avatar answered Nov 08 '22 15:11

Tijkijiki