Skip to content

Fix caching of OneToOneType from second level cache. #3590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions hibernate-core/src/main/java/org/hibernate/type/OneToOneType.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.Mapping;
Expand Down Expand Up @@ -149,12 +151,19 @@ public boolean isOneToOne() {

@Override
public boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) {
return false;
if ( isSame( old, current ) ) {
return false;
}

Object oldid = getIdentifier( old, session );
Object newid = getIdentifier( current, session );

return getIdentifierType( session ).isDirty( oldid, newid, session );
}

@Override
public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) {
return false;
return isDirty(old, current, session);
}

@Override
Expand Down Expand Up @@ -188,25 +197,36 @@ public boolean useLHSPrimaryKey() {

@Override
public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
return null;
if (value == null) {
return null;
}

Object id = ForeignKeys.getEntityIdentifierIfNotUnsaved( getAssociatedEntityName(), value, session );

if ( id == null ) {
throw new AssertionFailure(
"cannot cache a reference to an object with a null id: " +
getAssociatedEntityName()
);
}

return getIdentifierType( session ).disassemble( id, session, owner );
}

@Override
public Object assemble(Serializable oid, SharedSessionContractImplementor session, Object owner) throws HibernateException {
//this should be a call to resolve(), not resolveIdentifier(),
//'cos it might be a property-ref, and we did not cache the
//referenced value
return resolve( session.getContextEntityIdentifier(owner), session, owner );
//the owner of the association is not the owner of the id
Serializable id = ( Serializable ) getIdentifierType( session ).assemble( oid, session, null );

if ( id == null ) {
return null;
}

return resolveIdentifier( id, session );
}

/**
* We don't need to dirty check one-to-one because of how
* assemble/disassemble is implemented and because a one-to-one
* association is never dirty
*/
@Override
public boolean isAlwaysDirtyChecked() {
//TODO: this is kinda inconsistent with CollectionType
return false;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">


<hibernate-mapping package="org.hibernate.test.onetoone.cache">
<class name="DetailsByFK">
<cache usage="read-write"/>
<id name="id" unsaved-value="0">
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<one-to-one name="person" class="PersonByFK" constrained="true"/>
<property name="data"/>
</class>

<class name="DetailsByRef">
<cache usage="read-write"/>
<id name="id" unsaved-value="0">
<generator class="native"/>
</id>
<many-to-one name="person" class="PersonByRef" column="personId" unique="true"/>
<property name="data"/>
</class>
</hibernate-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.onetoone.cache;
import java.io.Serializable;

public abstract class Details implements Serializable {
private int id;
private String data;
private Person person;

public int getId() {
return id;
}

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

public String getData() {
return data;
}

public void setData(String data) {
this.data = data;
}

public Person getPerson() {
return person;
}

protected void setPerson(Person person) {
this.person = person;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.onetoone.cache;

public class DetailsByFK extends Details {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.onetoone.cache;

public class DetailsByRef extends Details {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.hibernate.test.onetoone.cache;

import static org.junit.Assert.assertEquals;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cache.spi.CacheImplementor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;

public class OneToOneCacheTest extends BaseCoreFunctionalTestCase {
@Override
public String[] getMappings() {
return new String[] {
"onetoone/cache/Details.hbm.xml",
"onetoone/cache/Person.hbm.xml",
};
}

@Override
protected void configure(Configuration configuration) {
configuration.setProperty(AvailableSettings.USE_SECOND_LEVEL_CACHE, "true");
configuration.setProperty(AvailableSettings.GENERATE_STATISTICS, "true");
}

private <TPerson extends Person, TDetails extends Details> void OneToOneTest(Class<TPerson> personClass,
Class<TDetails> detailsClass) throws Exception {

// Initialize the database with data.
List<Serializable> ids = createPersonsAndDetails(personClass, detailsClass);

// Clear the second level cache and the statistics.
SessionFactoryImplementor sfi = sessionFactory();
CacheImplementor cache = sfi.getCache();
StatisticsImplementor statistics = sfi.getStatistics();

cache.evictEntityData(personClass);
cache.evictEntityData(detailsClass);
cache.evictQueryRegions();

statistics.clear();

// Fill the empty caches with data.
this.getPersons(personClass, ids);

// Verify that no data was retrieved from the cache.
assertEquals("Second level cache hit count", 0, statistics.getSecondLevelCacheHitCount());

statistics.clear();

this.getPersons(personClass, ids);

// Verify that all data was retrieved from the cache.
assertEquals("Second level cache miss count", 0, statistics.getSecondLevelCacheMissCount());
}

private <TPerson extends Person, TDetails extends Details> List<Serializable> createPersonsAndDetails(Class<TPerson> personClass,
Class<TDetails> detailsClass) throws Exception {
Session s = openSession();
Transaction tx = s.beginTransaction();

Constructor<TPerson> ctorPerson = personClass.getConstructor();
Constructor<TDetails> ctorDetails = detailsClass.getConstructor();
List<Serializable> ids = new ArrayList<Serializable>();

for (int i = 0; i < 6; i++) {
Person person = ctorPerson.newInstance();

if (i % 2 == 0) {
Details details = ctorDetails.newInstance();

details.setData(String.format("%s%d", detailsClass.getName(), i));
person.setDetails(details);
}

person.setName(String.format("%s%d", personClass.getName(), i));

ids.add(s.save(person));
}

tx.commit();
s.close();

return ids;
}

private <TPerson extends Person> List<TPerson> getPersons(Class<TPerson> personClass, List<Serializable> ids) {
Session s = openSession();
Transaction tx = s.beginTransaction();
List<TPerson> people = new ArrayList<TPerson>();

for (Serializable id : ids) {
people.add(s.get(personClass, id));
}

tx.commit();
s.close();

return people;
}

@Test
public void OneToOneCacheByForeignKey() throws Exception {
OneToOneTest(PersonByFK.class, DetailsByFK.class);
}

@Test
public void OneToOneCacheByRef() throws Exception {
OneToOneTest(PersonByRef.class, DetailsByRef.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="org.hibernate.test.onetoone.cache">
<class name="PersonByFK" batch-size="3">
<cache usage="read-write"/>
<id name="id" unsaved-value="0">
<generator class="native"/>
</id>
<property name="name"/>
<one-to-one name="details" class="DetailsByFK" fetch="join" cascade="all"/>
</class>

<class name="PersonByRef" batch-size="3">
<cache usage="read-write"/>
<id name="id" unsaved-value="0">
<generator class="native"/>
</id>
<property name="name"/>
<one-to-one name="details" class="DetailsByRef" property-ref="person" fetch="join" cascade="all"/>
</class>
</hibernate-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.onetoone.cache;
import java.io.Serializable;

public abstract class Person implements Serializable {
private int id;
private String name;
private Details details;

public int getId() {
return id;
}

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

public Details getDetails() {
return details;
}

public void setDetails(Details details) {
if (details != null) {
details.setPerson(this);
}

this.details = details;
}


public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.onetoone.cache;

public class PersonByFK extends Person {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.onetoone.cache;

public class PersonByRef extends Person {}