Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public void doSecondPass(java.util.Map persistentClasses) throws MappingExceptio
+ manyToOne.getReferencedEntityName()
);
}
manyToOne.setPropertyName( path );
BinderHelper.createSyntheticPropertyReference( columns, ref, null, manyToOne, false, buildingContext );
TableBinder.bindFk( ref, null, columns, manyToOne, unique, buildingContext );
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public Type getType() throws MappingException {
getReferencedEntityName(),
referenceToPrimaryKey,
getReferencedPropertyName(),
getPropertyName(),
isLazy(),
isUnwrapProxy(),
isIgnoreNotFound(),
Expand Down
11 changes: 11 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public abstract class ToOne extends SimpleValue implements Fetchable {
private FetchMode fetchMode;
protected String referencedPropertyName;
private String referencedEntityName;
private String propertyName;
private boolean embedded;
private boolean lazy = true;
protected boolean unwrapProxy;
Expand Down Expand Up @@ -59,6 +60,16 @@ public void setReferencedEntityName(String referencedEntityName) {
null : referencedEntityName.intern();
}

public String getPropertyName() {
return propertyName;
}

public void setPropertyName(String propertyName) {
this.propertyName = propertyName==null ?
null : propertyName.intern();
}

@Override
public void setTypeUsingReflection(String className, String propertyName) throws MappingException {
if (referencedEntityName == null) {
final ClassLoaderService cls = getMetadata().getMetadataBuildingOptions()
Expand Down
41 changes: 38 additions & 3 deletions hibernate-core/src/main/java/org/hibernate/type/ManyToOneType.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@
import org.hibernate.MappingException;
import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.EntityUniqueKey;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Loadable;

/**
* A many-to-one association to an entity.
*
* @author Gavin King
*/
public class ManyToOneType extends EntityType {
private final String propertyName;
private final boolean ignoreNotFound;
private boolean isLogicalOneToOne;

Expand All @@ -50,12 +54,12 @@ public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName) {
* @param lazy Should the association be handled lazily
*/
public ManyToOneType(TypeFactory.TypeScope scope, String referencedEntityName, boolean lazy) {
this( scope, referencedEntityName, true, null, lazy, true, false, false );
this( scope, referencedEntityName, true, null, null, lazy, true, false, false );
}


/**
* @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, boolean, boolean, boolean, boolean ) } instead.
* @deprecated Use {@link #ManyToOneType(TypeFactory.TypeScope, String, boolean, String, String, boolean, boolean, boolean, boolean ) } instead.
*/
@Deprecated
public ManyToOneType(
Expand All @@ -67,27 +71,36 @@ public ManyToOneType(
boolean isEmbeddedInXML,
boolean ignoreNotFound,
boolean isLogicalOneToOne) {
this( scope, referencedEntityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne );
this( scope, referencedEntityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, null, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne );
}

public ManyToOneType(
TypeFactory.TypeScope scope,
String referencedEntityName,
boolean referenceToPrimaryKey,
String uniqueKeyPropertyName,
String propertyName,
boolean lazy,
boolean unwrapProxy,
boolean ignoreNotFound,
boolean isLogicalOneToOne) {
super( scope, referencedEntityName, referenceToPrimaryKey, uniqueKeyPropertyName, !lazy, unwrapProxy );
this.propertyName = propertyName;
this.ignoreNotFound = ignoreNotFound;
this.isLogicalOneToOne = isLogicalOneToOne;
}

@Override
protected boolean isNullable() {
return ignoreNotFound;
}

@Override
public String getPropertyName() {
return propertyName;
}

@Override
public boolean isAlwaysDirtyChecked() {
// always need to dirty-check, even when non-updateable;
// this ensures that when the association is updated,
Expand Down Expand Up @@ -209,6 +222,28 @@ public boolean isModified(
.isDirty( old, getIdentifier( current, session ), session );
}

@Override
public Object resolve(Object value, SessionImplementor session, Object owner) throws HibernateException {
Object resolvedValue = super.resolve( value, session, owner );
if ( isLogicalOneToOne && value != null && getPropertyName() != null ) {
EntityEntry entry = session.getPersistenceContext().getEntry( owner );
if ( entry != null ) {
final Loadable ownerPersister = (Loadable) session.getFactory().getEntityPersister( entry.getEntityName() );
EntityUniqueKey entityKey = new EntityUniqueKey(
ownerPersister.getEntityName(),
getPropertyName(),
value,
this,
ownerPersister.getEntityMode(),
session.getFactory()
);
session.getPersistenceContext().addEntity( entityKey, owner );
}
}
return resolvedValue;
}

@Override
public Serializable disassemble(
Object value,
SessionImplementor session,
Expand Down
26 changes: 26 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,35 @@ public EntityType manyToOne(
);
}

/**
* @deprecated Use {@link #manyToOne(String, boolean, String, String, boolean, boolean, boolean, boolean)} instead.
*/
@Deprecated
public EntityType manyToOne(
String persistentClass,
boolean referenceToPrimaryKey,
String uniqueKeyPropertyName,
boolean lazy,
boolean unwrapProxy,
boolean ignoreNotFound,
boolean isLogicalOneToOne) {
return manyToOne(
persistentClass,
referenceToPrimaryKey,
uniqueKeyPropertyName,
null,
lazy,
unwrapProxy,
ignoreNotFound,
isLogicalOneToOne
);
}

public EntityType manyToOne(
String persistentClass,
boolean referenceToPrimaryKey,
String uniqueKeyPropertyName,
String propertyName,
boolean lazy,
boolean unwrapProxy,
boolean ignoreNotFound,
Expand All @@ -299,6 +324,7 @@ public EntityType manyToOne(
persistentClass,
referenceToPrimaryKey,
uniqueKeyPropertyName,
propertyName,
lazy,
unwrapProxy,
ignoreNotFound,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2014, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.onetoone.bidirectional;

import org.hibernate.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.engine.internal.StatisticalLoggingSessionEventListener;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Test;

import javax.persistence.*;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.assertEquals;

/**
* Test cases for fetch joining a bi-directional one-to-one mapping.
*
* @author Christian Beikov
*/
public class BiDirectionalOneToOneFetchTest extends BaseCoreFunctionalTestCase {

final AtomicInteger queryExecutionCount = new AtomicInteger();

@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {
EntityA.class,
EntityB.class
};
}

@After
public void delete() {
Session session = openSession();
session.getTransaction().begin();
{
session.createQuery( "delete from EntityA" ).executeUpdate();
session.createQuery( "delete from EntityB" ).executeUpdate();
}
session.getTransaction().commit();
session.close();
}

public SessionBuilder sessionBuilder() {
return sessionFactory()
.withOptions()
.eventListeners(
new StatisticalLoggingSessionEventListener() {
@Override
public void jdbcExecuteStatementStart() {
super.jdbcExecuteStatementStart();
queryExecutionCount.getAndIncrement();
}
}
);
}

@Test
@TestForIssue( jiraKey = "HHH-3930" )
public void testEagerFetchBidirectionalOneToOneWithDirectFetching() {
Session session = sessionBuilder().openSession();
session.getTransaction().begin();
{
EntityA a = new EntityA( 1L, new EntityB( 2L ) );

session.persist( a );
session.flush();
session.clear();

queryExecutionCount.set( 0 );
session.get( EntityA.class, 1L );

assertEquals(
"Join fetching inverse one-to-one didn't use the object already present in the result set!",
1,
queryExecutionCount.get()
);
}
session.getTransaction().commit();
session.close();
}

@Test
@TestForIssue( jiraKey = "HHH-3930" )
public void testFetchBidirectionalOneToOneWithOneJoinFetch() {
Session session = sessionBuilder().openSession();
session.getTransaction().begin();
{
EntityA a = new EntityA( 1L, new EntityB( 2L ) );

session.persist( a );
session.flush();
session.clear();

queryExecutionCount.set( 0 );
session.createQuery(
"from EntityA a join fetch a.b"
).list();

assertEquals(
"Join fetching inverse one-to-one didn't use the object already present in the result set!",
1,
queryExecutionCount.get()
);
}
session.getTransaction().commit();
session.close();
}

@Test
@TestForIssue( jiraKey = "HHH-3930" )
public void testFetchBidirectionalOneToOneWithCircularJoinFetch() {
Session session = sessionBuilder().openSession();
session.getTransaction().begin();
{
EntityA a = new EntityA( 1L, new EntityB( 2L ) );

session.persist( a );
session.flush();
session.clear();

queryExecutionCount.set( 0 );
session.createQuery(
"from EntityA a join fetch a.b b join fetch b.a"
).list();

assertEquals(
"Join fetching inverse one-to-one didn't use the object already present in the result set!",
1,
queryExecutionCount.get()
);
}
session.getTransaction().commit();
session.close();
}

@Entity(name = "EntityA")
public static class EntityA {

@Id
private Long id;

@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "b_id")
private EntityB b;

public EntityA() {
}

public EntityA(Long id, EntityB b) {
this.id = id;
this.b = b;
this.b.a = this;
}
}

@Entity(name = "EntityB")
public static class EntityB {

@Id
private Long id;

@OneToOne(mappedBy = "b", fetch = FetchType.EAGER)
private EntityA a;

public EntityB() {
}

public EntityB(Long id) {
this.id = id;
}
}

}