HHH-15770 let you use @ColumnDefault on associations

this is very useful in combination with @OnDelete(action=SET_NULL)
This commit is contained in:
Gavin 2022-11-27 11:11:46 +01:00 committed by Gavin King
parent 76f92bd901
commit 759b68b022
8 changed files with 211 additions and 61 deletions

View File

@ -282,6 +282,7 @@ public class AnnotatedColumn {
mappingColumn.setSqlType( sqlType );
mappingColumn.setUnique( unique );
mappingColumn.setCheckConstraint( checkConstraint );
mappingColumn.setDefaultValue( defaultValue );
if ( writeExpression != null ) {
final int numberOfJdbcParams = StringHelper.count( writeExpression, '?' );
@ -766,7 +767,7 @@ public class AnnotatedColumn {
: database.getJdbcEnvironment().getIdentifierHelper().toIdentifier( column.name() ).render();
}
private void applyColumnDefault(PropertyData inferredData, int length) {
void applyColumnDefault(PropertyData inferredData, int length) {
final XProperty property = inferredData.getProperty();
if ( property != null ) {
final ColumnDefault columnDefault =
@ -783,7 +784,7 @@ public class AnnotatedColumn {
}
}
private void applyGeneratedAs(PropertyData inferredData, int length) {
void applyGeneratedAs(PropertyData inferredData, int length) {
final XProperty property = inferredData.getProperty();
if ( property != null ) {
final GeneratedColumn generatedColumn =

View File

@ -76,15 +76,15 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
String mappedBy,
AnnotatedJoinColumns parent,
PropertyHolder propertyHolder,
String propertyName) {
final String path = qualify( propertyHolder.getPath(), propertyName );
PropertyData inferredData) {
final String path = qualify( propertyHolder.getPath(), inferredData.getPropertyName() );
final JoinColumn[] overrides = propertyHolder.getOverriddenJoinColumn( path );
if ( overrides != null ) {
//TODO: relax this restriction
throw new AnnotationException("Property '" + path
+ "' overrides mapping specified using '@JoinColumnOrFormula'");
throw new AnnotationException( "Property '" + path
+ "' overrides mapping specified using '@JoinColumnOrFormula'" );
}
return buildJoinColumn( joinColumn, null, mappedBy, parent, propertyHolder, propertyName, "" );
return buildJoinColumn( joinColumn, null, mappedBy, parent, propertyHolder, inferredData, "" );
}
public static AnnotatedJoinColumn buildJoinFormula(
@ -108,19 +108,18 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
String mappedBy,
AnnotatedJoinColumns parent,
PropertyHolder propertyHolder,
String propertyName,
PropertyData inferredData,
String defaultColumnSuffix) {
if ( joinColumn != null ) {
if ( !isEmptyOrNullAnnotationValue( mappedBy ) ) {
throw new AnnotationException(
"Association '" + getRelativePath( propertyHolder, propertyName )
+ "' is 'mappedBy' a different entity and may not explicitly specify the '@JoinColumn'"
);
throw new AnnotationException( "Association '"
+ getRelativePath( propertyHolder, inferredData.getPropertyName() )
+ "' is 'mappedBy' a different entity and may not explicitly specify the '@JoinColumn'" );
}
return explicitJoinColumn( joinColumn, comment, parent, propertyName, defaultColumnSuffix );
return explicitJoinColumn( joinColumn, comment, parent, inferredData, defaultColumnSuffix );
}
else {
return implicitJoinColumn( parent, propertyName, defaultColumnSuffix );
return implicitJoinColumn( parent, inferredData, defaultColumnSuffix );
}
}
@ -128,7 +127,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
JoinColumn joinColumn,
Comment comment,
AnnotatedJoinColumns parent,
String propertyName,
PropertyData inferredData,
String defaultColumnSuffix) {
final AnnotatedJoinColumn column = new AnnotatedJoinColumn();
column.setComment( comment != null ? comment.value() : null );
@ -136,19 +135,20 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
// column.setJoins( joins );
// column.setPropertyHolder( propertyHolder );
if ( isEmpty( column.getLogicalColumnName() ) && isNotEmpty( defaultColumnSuffix ) ) {
column.setLogicalColumnName( propertyName + defaultColumnSuffix );
column.setLogicalColumnName( inferredData.getPropertyName() + defaultColumnSuffix );
}
// column.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
column.setImplicit( false );
column.setParent( parent );
column.applyJoinAnnotation( joinColumn, null );
column.applyColumnDefault( inferredData, parent.getColumns().size() );
column.bind();
return column;
}
private static AnnotatedJoinColumn implicitJoinColumn(
AnnotatedJoinColumns parent,
String propertyName,
PropertyData inferredData,
String defaultColumnSuffix) {
final AnnotatedJoinColumn column = new AnnotatedJoinColumn();
// column.setContext( context );
@ -157,13 +157,14 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
// column.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
// property name + suffix is an "explicit" column name
if ( isNotEmpty( defaultColumnSuffix ) ) {
column.setLogicalColumnName( propertyName + defaultColumnSuffix );
column.setLogicalColumnName( inferredData.getPropertyName() + defaultColumnSuffix );
column.setImplicit( false );
}
else {
column.setImplicit( true );
}
column.setParent( parent );
column.applyColumnDefault( inferredData, parent.getColumns().size() );
column.bind();
return column;
}
@ -401,7 +402,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
static AnnotatedJoinColumn buildImplicitJoinTableJoinColumn(
AnnotatedJoinColumns parent,
PropertyHolder propertyHolder,
String propertyName) {
PropertyData inferredData) {
final AnnotatedJoinColumn column = new AnnotatedJoinColumn();
column.setImplicit( true );
column.setNullable( false ); //I break the spec, but it's for good
@ -417,7 +418,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
static AnnotatedJoinColumn buildExplicitJoinTableJoinColumn(
AnnotatedJoinColumns parent,
PropertyHolder propertyHolder,
String propertyName,
PropertyData inferredData,
JoinColumn joinColumn) {
final AnnotatedJoinColumn column = new AnnotatedJoinColumn();
column.setImplicit( true );
@ -428,7 +429,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
column.setNullable( false ); //I break the spec, but it's for good
//done after the annotation to override it
column.setParent( parent );
column.applyJoinAnnotation( joinColumn, propertyName );
column.applyJoinAnnotation( joinColumn, inferredData.getPropertyName() );
column.bind();
return column;
}

View File

@ -67,13 +67,13 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
String mappedBy,
Map<String, Join> joins,
PropertyHolder propertyHolder,
String propertyName,
PropertyData inferredData,
MetadataBuildingContext context) {
final AnnotatedJoinColumns parent = new AnnotatedJoinColumns();
parent.setBuildingContext( context );
parent.setJoins( joins );
parent.setPropertyHolder( propertyHolder );
parent.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
parent.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) );
parent.setMappedBy( mappedBy );
for ( JoinColumnOrFormula columnOrFormula : joinColumnOrFormulas ) {
final JoinFormula formula = columnOrFormula.formula();
@ -82,23 +82,23 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
AnnotatedJoinColumn.buildJoinFormula( formula, parent );
}
else {
AnnotatedJoinColumn.buildJoinColumn( column, mappedBy, parent, propertyHolder, propertyName );
AnnotatedJoinColumn.buildJoinColumn( column, mappedBy, parent, propertyHolder, inferredData );
}
}
return parent;
}
static AnnotatedJoinColumns buildJoinColumnsWithFormula(
String propertyName,
JoinFormula joinFormula,
Map<String, Join> secondaryTables,
PropertyHolder propertyHolder,
PropertyData inferredData,
MetadataBuildingContext context) {
final AnnotatedJoinColumns joinColumns = new AnnotatedJoinColumns();
joinColumns.setBuildingContext( context );
joinColumns.setJoins( secondaryTables );
joinColumns.setPropertyHolder( propertyHolder );
joinColumns.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
joinColumns.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) );
AnnotatedJoinColumn.buildJoinFormula( joinFormula, joinColumns );
return joinColumns;
}
@ -109,7 +109,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
String mappedBy,
Map<String, Join> joins,
PropertyHolder propertyHolder,
String propertyName,
PropertyData inferredData,
MetadataBuildingContext buildingContext) {
return buildJoinColumnsWithDefaultColumnSuffix(
joinColumns,
@ -117,7 +117,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
mappedBy,
joins,
propertyHolder,
propertyName,
inferredData,
"",
buildingContext
);
@ -129,9 +129,10 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
String mappedBy,
Map<String, Join> joins,
PropertyHolder propertyHolder,
String propertyName,
PropertyData inferredData,
String defaultColumnSuffix,
MetadataBuildingContext context) {
final String propertyName = inferredData.getPropertyName();
final String path = qualify( propertyHolder.getPath(), propertyName );
final JoinColumn[] overriddes = propertyHolder.getOverriddenJoinColumn( path );
final JoinColumn[] actualColumns = overriddes == null ? joinColumns : overriddes;
@ -148,7 +149,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
mappedBy,
parent,
propertyHolder,
propertyName,
inferredData,
defaultColumnSuffix
);
}
@ -161,7 +162,7 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
mappedBy,
parent,
propertyHolder,
propertyName,
inferredData,
defaultColumnSuffix
);
}
@ -173,21 +174,21 @@ public class AnnotatedJoinColumns extends AnnotatedColumns {
JoinColumn[] joinColumns,
Map<String, Join> secondaryTables,
PropertyHolder propertyHolder,
String propertyName,
PropertyData inferredData,
String mappedBy,
MetadataBuildingContext context) {
final AnnotatedJoinColumns parent = new AnnotatedJoinColumns();
parent.setBuildingContext( context );
parent.setJoins( secondaryTables );
parent.setPropertyHolder( propertyHolder );
parent.setPropertyName( getRelativePath( propertyHolder, propertyName ) );
parent.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) );
parent.setMappedBy( mappedBy );
if ( joinColumns == null ) {
AnnotatedJoinColumn.buildImplicitJoinTableJoinColumn( parent, propertyHolder, propertyName );
AnnotatedJoinColumn.buildImplicitJoinTableJoinColumn( parent, propertyHolder, inferredData );
}
else {
for ( JoinColumn joinColumn : joinColumns ) {
AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn( parent, propertyHolder, propertyName, joinColumn );
AnnotatedJoinColumn.buildExplicitJoinTableJoinColumn( parent, propertyHolder, inferredData, joinColumn );
}
}
return parent;

View File

@ -136,7 +136,7 @@ class ColumnsBuilder {
oneToMany != null ? oneToMany.mappedBy() : "",
entityBinder.getSecondaryTables(),
propertyHolder,
inferredData.getPropertyName(),
inferredData,
buildingContext
);
}
@ -182,7 +182,7 @@ class ColumnsBuilder {
null,
entityBinder.getSecondaryTables(),
propertyHolder,
inferredData.getPropertyName(),
inferredData,
buildingContext
);
}
@ -194,7 +194,7 @@ class ColumnsBuilder {
oneToOneAnn != null ? oneToOneAnn.mappedBy() : null,
entityBinder.getSecondaryTables(),
propertyHolder,
inferredData.getPropertyName(),
inferredData,
buildingContext
);
}
@ -202,8 +202,6 @@ class ColumnsBuilder {
private AnnotatedJoinColumns buildExplicitJoinColumns(XProperty property, PropertyData inferredData) {
// process @JoinColumns before @Columns to handle collection of entities properly
final String propertyName = inferredData.getPropertyName();
final JoinColumn[] joinColumnAnnotations = getJoinColumnAnnotations( property, inferredData );
if ( joinColumnAnnotations != null ) {
return AnnotatedJoinColumns.buildJoinColumns(
@ -212,7 +210,7 @@ class ColumnsBuilder {
null,
entityBinder.getSecondaryTables(),
propertyHolder,
propertyName,
inferredData,
buildingContext
);
}
@ -224,7 +222,7 @@ class ColumnsBuilder {
null,
entityBinder.getSecondaryTables(),
propertyHolder,
propertyName,
inferredData,
buildingContext
);
}
@ -232,10 +230,10 @@ class ColumnsBuilder {
if ( property.isAnnotationPresent( JoinFormula.class) ) {
final JoinFormula joinFormula = getOverridableAnnotation( property, JoinFormula.class, buildingContext );
return AnnotatedJoinColumns.buildJoinColumnsWithFormula(
propertyName,
joinFormula,
entityBinder.getSecondaryTables(),
propertyHolder,
inferredData,
buildingContext
);
}

View File

@ -63,7 +63,7 @@ public class ToOneFkSecondPass extends FkSecondPass {
}
final PersistentClass persistentClass = buildingContext.getMetadataCollector()
.getEntityBinding( entityClassName );
Property property = persistentClass.getIdentifierProperty();
final Property property = persistentClass.getIdentifierProperty();
if ( path == null ) {
return false;
}
@ -82,7 +82,7 @@ public class ToOneFkSecondPass extends FkSecondPass {
localPath = path.substring( 3 );
}
Component component = (Component) valueIdentifier;
final Component component = (Component) valueIdentifier;
for ( Property idProperty : component.getProperties() ) {
if ( localPath.equals( idProperty.getName() ) || localPath.startsWith( idProperty.getName() + "." ) ) {
return true;

View File

@ -388,7 +388,7 @@ public abstract class CollectionBinder {
null,
entityBinder.getSecondaryTables(),
propertyHolder,
inferredData.getPropertyName(),
inferredData,
"_KEY",
context
);
@ -706,7 +706,7 @@ public abstract class CollectionBinder {
annJoins,
entityBinder.getSecondaryTables(),
propertyHolder,
inferredData.getPropertyName(),
inferredData,
mappedBy,
buildingContext
) );
@ -714,7 +714,7 @@ public abstract class CollectionBinder {
annInverseJoins,
entityBinder.getSecondaryTables(),
propertyHolder,
inferredData.getPropertyName(),
inferredData,
mappedBy,
buildingContext
) );

View File

@ -0,0 +1,137 @@
package org.hibernate.orm.test.ondelete.toone;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@DomainModel(
annotatedClasses = {
ToOneOnDeleteSetNullTest.Parent.class,
ToOneOnDeleteSetNullTest.Child.class,
ToOneOnDeleteSetNullTest.GrandChild.class
}
)
@SessionFactory
public class ToOneOnDeleteSetNullTest {
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
}
@Test
@SkipForDialect(
dialectClass = SybaseDialect.class,
matchSubTypes = true,
reason = "Sybase does not support on delete actions"
)
public void testManyToOne(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Parent parent = new Parent();
parent.id = 1L;
session.persist( parent );
Child child1 = new Child();
child1.id = 1L;
child1.parent = parent;
session.persist( child1 );
GrandChild grandChild11 = new GrandChild();
grandChild11.id = 1L;
grandChild11.parent = child1;
session.persist( grandChild11 );
Child child2 = new Child();
child2.id = 2L;
child2.parent = parent;
session.persist( child2 );
GrandChild grandChild21 = new GrandChild();
grandChild21.id = 2L;
grandChild21.parent = child2;
session.persist( grandChild21 );
GrandChild grandChild22 = new GrandChild();
grandChild22.id = 3L;
grandChild22.parent = child2;
session.persist( grandChild22 );
}
);
scope.inTransaction(
session -> {
assertNotNull( session.get(Child.class, 1L).parent );
assertNotNull( session.get(Child.class, 2L).parent );
assertNotNull( session.get(GrandChild.class, 2L).parent );
assertNotNull( session.get(GrandChild.class, 3L).parent );
}
);
scope.inTransaction(
session -> {
Parent parent = session.get( Parent.class, 1L );
session.remove( parent );
}
);
scope.inTransaction(
session -> {
assertNull( session.get(Child.class, 1L).parent );
assertNull( session.get(Child.class, 2L).parent );
assertNotNull( session.get(GrandChild.class, 2L).parent );
assertNotNull( session.get(GrandChild.class, 3L).parent );
assertNull( session.get( Parent.class, 1L ) );
}
);
}
@Entity(name = "Parent")
public static class Parent {
@Id
private Long id;
private String name;
}
@Entity(name = "Child")
public static class Child {
@Id
private Long id;
private String name;
@ManyToOne
@OnDelete(action = OnDeleteAction.SET_NULL)
private Parent parent;
}
@Entity(name = "GrandChild")
public static class GrandChild {
@Id
private Long id;
private String name;
@ManyToOne
@OnDelete(action = OnDeleteAction.SET_DEFAULT)
@ColumnDefault("null")
private Child parent;
}
}

View File

@ -3,7 +3,6 @@ package org.hibernate.orm.test.ondelete.toone;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@ -16,6 +15,9 @@ import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* @author Vlad Mihalcea
@ -31,21 +33,15 @@ import org.junit.jupiter.api.Test;
public class ToOneOnDeleteTest {
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session -> {
session.createQuery( "delete from Parent" ).executeUpdate();
session.createQuery( "delete from Child" ).executeUpdate();
session.createQuery( "delete from GrandChild" ).executeUpdate();
}
);
public void tearDown(SessionFactoryScope scope) {
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
}
@Test
@SkipForDialect(
dialectClass = SybaseDialect.class,
matchSubTypes = true,
reason = "HHH-13559 on-delete=\"cascade\" is not supported for unidirectional to-one associations using Sybase"
reason = "Sybase does not support on delete actions"
)
public void testManyToOne(SessionFactoryScope scope) {
scope.inTransaction(
@ -83,11 +79,27 @@ public class ToOneOnDeleteTest {
scope.inTransaction(
session -> {
Parent parent = session.get( Parent.class, 1L );
session.delete( parent );
assertNotNull( session.get(Child.class, 1L) );
assertNotNull( session.get(Child.class, 2L) );
assertNotNull( session.get(GrandChild.class, 2L) );
assertNotNull( session.get(GrandChild.class, 3L) );
}
);
scope.inTransaction(
session -> {
Parent parent = session.get( Parent.class, 1L );
session.remove( parent );
}
);
scope.inTransaction(
session -> {
assertNull( session.get(Child.class, 1L) );
assertNull( session.get(Child.class, 2L) );
assertNull( session.get(GrandChild.class, 2L) );
assertNull( session.get(GrandChild.class, 3L) );
assertNull( session.get( Parent.class, 1L ) );
}
); }
@Entity(name = "Parent")