HHH-13725 Work on circular fetch detection

This commit is contained in:
Andrea Boriero 2019-11-18 17:48:48 +00:00 committed by Steve Ebersole
parent 461e559184
commit aa3ff4507d
11 changed files with 387 additions and 210 deletions

View File

@ -299,7 +299,8 @@ public class MetamodelSelectBuilderProcess {
final Fetch biDirectionalFetch = circularFetchDetector.findBiDirectionalFetch(
fetchParent,
fetchable
fetchable,
creationState
);
if ( biDirectionalFetch != null ) {

View File

@ -13,8 +13,10 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.NavigablePath;
@ -24,6 +26,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAliasStemHelper;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupBuilder;
@ -36,7 +39,6 @@ import org.hibernate.sql.results.internal.domain.entity.EntityFetch;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.sql.results.spi.Fetch;
import org.hibernate.sql.results.spi.FetchParent;
import org.hibernate.sql.results.spi.FetchableContainer;
/**
* @author Steve Ebersole
@ -46,7 +48,6 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
private final String sqlAliasStem;
private final boolean isNullable;
private final ForeignKeyDescriptor foreignKeyDescriptor;
private String mappedBy;
public SingularAssociationAttributeMapping(
String name,
@ -70,7 +71,6 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
this.sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromAttributeName( name );
this.isNullable = isNullable;
this.foreignKeyDescriptor = foreignKeyDescriptor;
this.mappedBy = null;
}
@Override
@ -226,17 +226,40 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
}
@Override
public boolean isCircular(FetchParent fetchParent) {
NavigablePath navigablePath = fetchParent.getNavigablePath();
if ( navigablePath.getParent() == null ) {
public boolean isCircular(FetchParent fetchParent, SqlAstProcessingState creationState) {
final NavigablePath navigablePath = fetchParent.getNavigablePath();
final NavigablePath parent = navigablePath.getParent();
if ( parent == null ) {
return false;
}
else {
NavigablePath parentParentNavigablePath = navigablePath.getParent();
if ( parentParentNavigablePath.getLocalName().equals( getMappedTypeDescriptor().getEntityName() ) ) {
final String entityName = getMappedTypeDescriptor().getEntityName();
if ( parent.getLocalName().equals( entityName ) ) {
return true;
}
else {
NavigablePath parentOfParent = parent.getParent();
if ( parentOfParent == null ) {
return false;
}
else {
if ( parentOfParent.getParent() != null ) {
return false;
}
else {
final EntityPersister entityDescriptor = creationState.getSqlAstCreationState()
.getCreationContext()
.getDomainModel()
.findEntityDescriptor( parentOfParent.getFullPath() );
final String parentEntityName = entityDescriptor.findSubPart( parent.getLocalName() ).getJavaTypeDescriptor()
.getJavaType()
.getName();
if ( parentEntityName.equals( entityName ) ) {
return true;
}
}
}
return false;
}
}

View File

@ -172,7 +172,8 @@ public class StandardSqmSelectTranslator
public void accept(Fetchable fetchable) {
final Fetch biDirectionalFetch = circularFetchDetector.findBiDirectionalFetch(
fetchParent,
fetchable
fetchable,
getSqlAstCreationState().getCurrentProcessingState()
);
if ( biDirectionalFetch != null ) {

View File

@ -293,6 +293,30 @@ public abstract class AbstractSqlAstWalker
// }
renderTableReference( tableGroup.getPrimaryTableReference() );
renderTableReferenceJoins( tableGroup );
// if ( tableGroup.getGroupAlias() != null ) {
// sqlAppender.appendSql( CLOSE_PARENTHESIS );
// sqlAppender.appendSql( AS_KEYWORD );
// sqlAppender.appendSql( tableGroup.getGroupAlias() );
// }
processTableGroupJoins( tableGroup );
}
protected void renderTableGroup(TableGroup tableGroup, Predicate predicate) {
// NOTE : commented out blocks render the TableGroup as a CTE
// if ( tableGroup.getGroupAlias() != null ) {
// sqlAppender.appendSql( OPEN_PARENTHESIS );
// }
renderTableReference( tableGroup.getPrimaryTableReference() );
appendSql( " on " );
predicate.accept( this );
renderTableReferenceJoins( tableGroup );
// if ( tableGroup.getGroupAlias() != null ) {
@ -355,17 +379,11 @@ public abstract class AbstractSqlAstWalker
appendSql( tableGroupJoin.getJoinType().getText() );
appendSql( " join " );
renderTableGroup( joinedGroup );
clauseStack.push( Clause.WHERE );
try {
if ( tableGroupJoin.getPredicate() != null && !tableGroupJoin.getPredicate().isEmpty() ) {
appendSql( " on " );
tableGroupJoin.getPredicate().accept( this );
}
if ( tableGroupJoin.getPredicate() != null && !tableGroupJoin.getPredicate().isEmpty() ) {
renderTableGroup( joinedGroup, tableGroupJoin.getPredicate() );
}
finally {
clauseStack.pop();
else {
renderTableGroup( joinedGroup );
}
}
}

View File

@ -8,11 +8,15 @@ package org.hibernate.sql.results.internal.domain;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.spi.AssemblerCreationState;
import org.hibernate.sql.results.spi.BiDirectionalFetch;
import org.hibernate.sql.results.spi.DomainResultAssembler;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.sql.results.spi.Fetch;
import org.hibernate.sql.results.spi.FetchParent;
import org.hibernate.sql.results.spi.FetchParentAccess;
@ -23,30 +27,23 @@ import org.hibernate.sql.results.spi.RowProcessingState;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* Implementation of BiDirectionalFetch for the `oa` case - the bi-dir fetch
* refers to another fetch (`a`)
*
* @author Steve Ebersole
* @author Andrea Boriero
*/
public class BiDirectionalFetchImpl implements BiDirectionalFetch {
public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
private final NavigablePath navigablePath;
private Fetchable fetchable;
private NavigablePath referencedNavigablePath;
private final FetchParent fetchParent;
private final Fetch referencedFetch;
/**
* Create the bi-dir fetch
*
* @param navigablePath `Person(p).address.owner.address`
* @param fetchParent The parent for the `oa` fetch is `o`
* @param referencedFetch `RootBiDirectionalFetchImpl(a)` (because `a` is itself also a bi-dir fetch referring to the `p root)
*/
public BiDirectionalFetchImpl(
NavigablePath navigablePath,
FetchParent fetchParent,
Fetch referencedFetch) {
this.navigablePath = navigablePath;
Fetchable fetchable,
NavigablePath referencedNavigablePath) {
this.fetchParent = fetchParent;
this.referencedFetch = referencedFetch;
this.navigablePath = navigablePath;
this.fetchable = fetchable;
this.referencedNavigablePath = referencedNavigablePath;
}
@Override
@ -56,7 +53,7 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch {
@Override
public NavigablePath getReferencedPath() {
return referencedFetch.getNavigablePath();
return referencedNavigablePath;
}
@Override
@ -66,7 +63,7 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch {
@Override
public Fetchable getFetchedMapping() {
return referencedFetch.getFetchedMapping();
return fetchable;
}
@Override
@ -81,10 +78,42 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch {
AssemblerCreationState creationState) {
return new CircularFetchAssembler(
getReferencedPath(),
referencedFetch.getFetchedMapping().getJavaTypeDescriptor()
fetchable.getJavaTypeDescriptor()
);
}
@Override
public String getFetchableName() {
return fetchable.getFetchableName();
}
@Override
public String getPartName() {
return fetchable.getFetchableName();
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return fetchable.getJavaTypeDescriptor();
}
@Override
public FetchStrategy getMappedFetchStrategy() {
throw new UnsupportedOperationException();
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
LockMode lockMode,
String resultVariable,
DomainResultCreationState creationState) {
throw new UnsupportedOperationException();
}
private static class CircularFetchAssembler implements DomainResultAssembler {
private final NavigablePath circularPath;
private final JavaTypeDescriptor javaTypeDescriptor;
@ -98,7 +127,13 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch {
@Override
public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) {
return rowProcessingState.resolveInitializer( circularPath ).getInitializedInstance();
Initializer initializer = rowProcessingState.resolveInitializer( circularPath );
if ( initializer.getInitializedInstance() == null ) {
initializer.resolveKey( rowProcessingState );
initializer.resolveInstance( rowProcessingState );
initializer.initializeInstance( rowProcessingState );
}
return initializer.getInitializedInstance();
}
@Override

View File

@ -1,146 +0,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
*/
package org.hibernate.sql.results.internal.domain;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.spi.AssemblerCreationState;
import org.hibernate.sql.results.spi.BiDirectionalFetch;
import org.hibernate.sql.results.spi.DomainResultAssembler;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.sql.results.spi.Fetch;
import org.hibernate.sql.results.spi.FetchParent;
import org.hibernate.sql.results.spi.FetchParentAccess;
import org.hibernate.sql.results.spi.Fetchable;
import org.hibernate.sql.results.spi.Initializer;
import org.hibernate.sql.results.spi.JdbcValuesSourceProcessingOptions;
import org.hibernate.sql.results.spi.RowProcessingState;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* @author Andrea Boriero
*/
public class RootBiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
private final NavigablePath navigablePath;
private Fetchable fetchable;
private NavigablePath referencedNavigablePath;
private final FetchParent fetchParent;
public RootBiDirectionalFetchImpl(
NavigablePath navigablePath,
FetchParent fetchParent,
Fetchable fetchable,
NavigablePath referencedNavigablePath) {
this.fetchParent = fetchParent;
this.navigablePath = navigablePath;
this.fetchable = fetchable;
this.referencedNavigablePath = referencedNavigablePath;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public NavigablePath getReferencedPath() {
return referencedNavigablePath;
}
@Override
public FetchParent getFetchParent() {
return fetchParent;
}
@Override
public Fetchable getFetchedMapping() {
return null;
}
@Override
public boolean isNullable() {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public DomainResultAssembler createAssembler(
FetchParentAccess parentAccess,
Consumer<Initializer> collector,
AssemblerCreationState creationState) {
return new CircularFetchAssembler(
getReferencedPath(),
fetchable.getJavaTypeDescriptor()
);
}
@Override
public String getFetchableName() {
// An entity itself is not fetchable
return null;
}
@Override
public String getPartName() {
return navigablePath.getLocalName();
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return fetchable.getJavaTypeDescriptor();
}
@Override
public FetchStrategy getMappedFetchStrategy() {
throw new UnsupportedOperationException();
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
LockMode lockMode,
String resultVariable,
DomainResultCreationState creationState) {
throw new UnsupportedOperationException();
}
private static class CircularFetchAssembler implements DomainResultAssembler {
private final NavigablePath circularPath;
private final JavaTypeDescriptor javaTypeDescriptor;
public CircularFetchAssembler(
NavigablePath circularPath,
JavaTypeDescriptor javaTypeDescriptor) {
this.circularPath = circularPath;
this.javaTypeDescriptor = javaTypeDescriptor;
}
@Override
public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) {
Initializer initializer = rowProcessingState.resolveInitializer( circularPath );
if ( initializer.getInitializedInstance() == null ) {
initializer.resolveKey( rowProcessingState );
initializer.resolveInstance( rowProcessingState );
initializer.initializeInstance( rowProcessingState );
}
return initializer.getInitializedInstance();
}
@Override
public JavaTypeDescriptor getAssembledJavaTypeDescriptor() {
return javaTypeDescriptor;
}
}
}

View File

@ -8,8 +8,8 @@ package org.hibernate.sql.results.spi;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.results.internal.domain.BiDirectionalFetchImpl;
import org.hibernate.sql.results.internal.domain.RootBiDirectionalFetchImpl;
/**
* Maintains state while processing a Fetch graph to be able to detect
@ -19,35 +19,26 @@ import org.hibernate.sql.results.internal.domain.RootBiDirectionalFetchImpl;
*/
public class CircularFetchDetector {
public Fetch findBiDirectionalFetch(FetchParent fetchParent, Fetchable fetchable) {
if ( ! fetchable.isCircular( fetchParent ) ) {
public Fetch findBiDirectionalFetch(FetchParent fetchParent, Fetchable fetchable, SqlAstProcessingState creationState) {
if ( !fetchable.isCircular( fetchParent, creationState ) ) {
return null;
}
if ( fetchParent instanceof Fetch ) {
final Fetch fetchParentAsFetch = (Fetch) fetchParent;
final NavigablePath parentParentPath = fetchParent.getNavigablePath().getParent();
assert fetchParent.getNavigablePath().getParent() != null;
assert fetchParentAsFetch.getFetchParent().getNavigablePath().equals( parentParentPath );
final NavigablePath navigablePath = fetchParent.getNavigablePath();
if ( navigablePath.getParent().getParent() == null ) {
return new BiDirectionalFetchImpl(
fetchParent.getNavigablePath().append( fetchable.getFetchableName() ),
navigablePath,
fetchParent,
fetchParentAsFetch
fetchable,
fetchParent.getNavigablePath().getParent()
);
}
else {
// note : the "`fetchParentAsFetch` is `RootBiDirectionalFetchImpl`" case would
// be handled in the `Fetch` block since `RootBiDirectionalFetchImpl` is a Fetch
return new RootBiDirectionalFetchImpl(
new NavigablePath( fetchable.getJavaTypeDescriptor().getJavaType().getName() ),
return new BiDirectionalFetchImpl(
navigablePath.append( fetchable.getFetchableName() ),
fetchParent,
fetchable,
fetchParent.getNavigablePath()
navigablePath.getParent()
);
}
}

View File

@ -11,6 +11,7 @@ import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
/**
* @author Steve Ebersole
@ -34,7 +35,7 @@ public interface Fetchable extends ModelPart {
String resultVariable,
DomainResultCreationState creationState);
default boolean isCircular(FetchParent fetchParent){
default boolean isCircular(FetchParent fetchParent, SqlAstProcessingState creationState){
return false;
}

View File

@ -4,7 +4,7 @@
* 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.orm.test.crud.onetoone;
package org.hibernate.orm.test.sql.exec.onetoone;
import javax.persistence.Basic;
import javax.persistence.Entity;

View File

@ -16,7 +16,6 @@ import javax.persistence.Table;
import org.hibernate.Hibernate;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@ -42,7 +41,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
)
@ServiceRegistry
@SessionFactory(generateStatistics = true)
@FailureExpected
public class EntityWithBidirectionalOneToOneJoinTableTest {
@BeforeEach

View File

@ -0,0 +1,255 @@
/*
* 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.orm.test.sql.exec.onetoone.bidirectional;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.Hibernate;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.hamcrest.CoreMatchers;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Andrea Boriero
*/
@DomainModel(
annotatedClasses = {
EntityWithOneBidirectionalOneToOneJoinTableTest.Parent.class,
EntityWithOneBidirectionalOneToOneJoinTableTest.Child.class,
}
)
@ServiceRegistry
@SessionFactory(generateStatistics = true)
public class EntityWithOneBidirectionalOneToOneJoinTableTest {
@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Parent parent = new Parent( 1, "Hibernate" );
Child child = new Child( 2, parent );
child.setName( "Acme" );
session.save( parent );
session.save( child );
} );
}
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete from Parent" ).executeUpdate();
session.createQuery( "delete from Child" ).executeUpdate();
} );
}
@Test
public void testGetParent(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final Parent parent = session.get( Parent.class, 1 );
Child child = parent.getChild();
assertThat( child, CoreMatchers.notNullValue() );
assertTrue(
Hibernate.isInitialized( child ),
"The child eager OneToOne association is not initialized"
);
assertThat( child.getName(), equalTo( "Acme" ) );
assertThat( child.getParent(), CoreMatchers.notNullValue() );
} );
}
@Test
public void testGetChild(SessionFactoryScope scope) {
scope.inTransaction( session -> {
final Child child = session.get( Child.class, 2 );
Parent parent = child.getParent();
assertThat( parent, CoreMatchers.notNullValue() );
assertTrue(
Hibernate.isInitialized( parent ),
"The parent eager OneToOne association is not initialized"
);
assertThat( parent.getDescription(), CoreMatchers.notNullValue() );
Child child1 = parent.getChild();
assertThat( child1, CoreMatchers.notNullValue() );
assertTrue(
Hibernate.isInitialized( child1 ),
"The child eager OneToOne association is not initialized"
);
} );
}
@Test
public void testHqlSelectChild(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final String queryString = "SELECT c FROM Child c JOIN c.parent d WHERE d.id = :id";
final Child child = session.createQuery( queryString, Child.class )
.setParameter( "id", 1 )
.getSingleResult();
assertThat( child.getParent(), CoreMatchers.notNullValue() );
String description = child.getParent().getDescription();
assertThat( description, CoreMatchers.notNullValue() );
}
);
}
@Test
public void testHqlSelectParent(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final Parent parent = session.createQuery(
"SELECT p FROM Parent p JOIN p.child WHERE p.id = :id",
Parent.class
)
.setParameter( "id", 1 )
.getSingleResult();
Child child = parent.getChild();
assertThat( child, CoreMatchers.notNullValue() );
assertTrue(
Hibernate.isInitialized( child ),
"the child have to be initialized"
);
String name = child.getName();
assertThat( name, CoreMatchers.notNullValue() );
}
);
scope.inTransaction(
session -> {
final Parent parent = session.createQuery(
"SELECT p FROM Parent p JOIN p.child WHERE p.id = :id",
Parent.class
)
.setParameter( "id", 1 )
.getSingleResult();
Child child = parent.getChild();
assertThat( child, CoreMatchers.notNullValue() );
assertTrue(
Hibernate.isInitialized( child ),
"The child have to be initialized"
);
String name = child.getName();
assertThat( name, CoreMatchers.notNullValue() );
}
);
}
@Entity(name = "Parent")
@Table(name = "PARENT")
public static class Parent {
private Integer id;
private String description;
private Child child;
Parent() {
}
public Parent(Integer id, String description) {
this.id = id;
this.description = description;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@OneToOne
@JoinTable(name = "PARENT_CHILD", inverseJoinColumns = @JoinColumn(name = "child_id"), joinColumns = @JoinColumn(name = "parent_id"))
public Child getChild() {
return child;
}
public void setChild(Child other) {
this.child = other;
}
}
@Entity(name = "Child")
@Table(name = "CHILD")
public static class Child {
private Integer id;
private String name;
private Parent parent;
Child() {
}
Child(Integer id, Parent parent) {
this.id = id;
this.parent = parent;
this.parent.setChild( this );
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@OneToOne(mappedBy = "child")
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
}