简体   繁体   中英

Do i have a Hibernate cache or mapping error?

I'm using following Frameworks:

  • Hibernate 4.2.0.Final
  • Spring 3.2.2.RELEASE
  • Spring Data Jpa 1.1.0.RELEASE
  • HSQL 2.2.9
  • TestNG 6.1.1

I've got 2 Entities:

Entity A:

@Entity
@Table( name = "A" )
public class A {

    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long id;

    @OneToMany( fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "a" )
    private Set<B> b = new HashSet<B>();

    //... getter and setter

}

and Entity B:

@Entity
@Table( name = "B" )
public class B {

    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long id;

    @ManyToOne
    @JoinColumn( name = "a_id" )
    private A a;

    //... getter and setter

}

And i made Spring Data JPARepositories for them:

@Repository
public interface ARepository
        extends JpaRepository<A, Long> {

}

@Repository
public interface BRepository
        extends JpaRepository<B, Long> {

}

Then i've wrote a test to see if the mapping works:

@TransactionConfiguration( defaultRollback = true )
@ContextConfiguration
public class ARepositoryTest
        extends AbstractTransactionalTestNGSpringContextTests {

    @Inject
    @Setter
    private ARepository aRepository;

    @Inject
    @Setter
    private BRepository bRepository;


    @Test( groups = { "integration" } )
    public void testSaveWithFeed() {

        A a = new A();
        aRepository.saveAndFlush( a );

        B b = new B();
        b.setA( a );
        bRepository.saveAndFlush( b );

        A findOne = aRepository.findOne( a.getId() );
        Assert.assertEquals( 1, findOne.getB().size() );

    }
}

But the test fails:

Hibernate: insert into A (id) values (default)
Hibernate: insert into B (id, a_id) values (default, ?)
FAILED: testSaveWithA
java.lang.AssertionError: expected [1] but found [0]

And i don't see why. Is there something missing in the mapping, or is do i have to clear a hibernate cache? I see that there is no new select-query so hibernate is caching the A object - but shouldn't it update the reference from A to the Bs in it's cache??

As additional info here's my persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    xmlns="http://java.sun.com/xml/ns/persistence">    
    <persistence-unit name="local" transaction-type="RESOURCE_LOCAL" >
    </persistence-unit>
</persistence>

My ARepositoryTest-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:jpa="http://www.springframework.org/schema/data/jpa"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jdbc="http://www.springframework.org/schema/jdbc"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/data/jpa 
     http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
     http://www.springframework.org/schema/jdbc
 http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd"> 

    <context:annotation-config /> 
    <context:component-scan base-package="my.package"/>

    <import resource="classpath:/SpringBeans.xml"/>

</beans>

and my SpringBeans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:jpa="http://www.springframework.org/schema/data/jpa"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jdbc="http://www.springframework.org/schema/jdbc"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/data/jpa 
     http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
     http://www.springframework.org/schema/jdbc
 http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd"> 

    <context:annotation-config /> 

    <jpa:repositories base-package="my.package.dao" />

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <property name="persistenceUnitName" value="local"/>
      <property name="dataSource" ref="dataSource" />
      <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="true"/>
                <property name="generateDdl" value="true" />
                <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect"/>
                <property name="database" value="HSQL" />               
            </bean>
        </property>
        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.show_sql" value="true" />
            </map>
        </property>
    </bean>

    <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:file:${user.home}/data;shutdown=true" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>

    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> 

</beans>

edit: added version numbers, and xml files


edit: for JB Nizet answer If i add to Class A

public void addB( B b ) {

    this.b.add( b );
}

and adapt the testcase

@Test( groups = { "integration" } )
public void testSaveWithA() {

    A a = new A();
    aRepository.save( a );

    B b = new B();
    a.addB( b );
    aRepository.saveAndFlush( a );

    A findOne = aRepository.findOne( a.getId() );
    Assert.assertEquals( findOne.getB().size(), 1 );

}

the test passes But within the hsql file there is no link between A and B:

INSERT INTO A VALUES(1)
INSERT INTO B VALUES(1,NULL)

So it won't be able to select it correct in an other transaction.

If i expand the new mMethod in A

public void addB( B b ) {

    this.b.add( b );
    b.setA( this );
}

An exception occures when flushing or committing

java.lang.StackOverflowError
    at java.util.HashMap$KeyIterator.<init>(HashMap.java:926)
    at java.util.HashMap$KeyIterator.<init>(HashMap.java:926)
    at java.util.HashMap.newKeyIterator(HashMap.java:940)
    at java.util.HashMap$KeySet.iterator(HashMap.java:974)
    at java.util.HashSet.iterator(HashSet.java:170)
    at java.util.AbstractSet.hashCode(AbstractSet.java:122)
    at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:429)
    at my.package.A.hashCode(A.java:17)
    at my.package.B.hashCode(B.java:13)
    at java.util.AbstractSet.hashCode(AbstractSet.java:126)
    at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:429)
    at my.package.A.hashCode(A.java:17)
    at my.package.B.hashCode(B.java:13)
    ...

Linenumbers A.java:17 and B.java:13 is where the @Data annotation of Lombok is placed generating my getters and setters.

Resolved by removing the dependency to lombok for hashCode and equals / by using Lomboks @EqualsAndHashCode( exclude = { "b" } )

Your test runs in a single transaction, using a single session. So when you're executing aRepository.findOne(a.getId()) , Hibernate returns the A that is already in its first level cache. And since you forgot to add the B to the set of Bs in A, this set is still empty.

It's your responsibility to maintain the coherence of the object graph. If you do b.setA(a) , you should also do a.getBs().add(b) . The best way is to encapsulate these two operations into a method addB(B b) in A, which adds the B to the set and initializes Ba .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM