Skip to content

Commit 596debe

Browse files
committed
make @check work when applied at the field level
This was a bug! Also add checkConstraint member to @table to allow check constraints on secondary tables Also clean up some Javadoc of some related annotations
1 parent bd8bf15 commit 596debe

File tree

8 files changed

+116
-44
lines changed

8 files changed

+116
-44
lines changed

documentation/src/test/java/org/hibernate/userguide/schema/CheckTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212

1313
import org.hibernate.annotations.Check;
1414
import org.hibernate.annotations.NaturalId;
15-
import org.hibernate.dialect.PostgreSQL81Dialect;
15+
import org.hibernate.dialect.H2Dialect;
16+
import org.hibernate.dialect.PostgreSQLDialect;
1617
import org.hibernate.exception.ConstraintViolationException;
1718
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
1819

@@ -26,7 +27,8 @@
2627
/**
2728
* @author Vlad Mihalcea
2829
*/
29-
@RequiresDialect(PostgreSQL81Dialect.class)
30+
@RequiresDialect(PostgreSQLDialect.class)
31+
@RequiresDialect(H2Dialect.class)
3032
public class CheckTest extends BaseEntityManagerFunctionalTestCase {
3133

3234
@Override
@@ -81,16 +83,14 @@ public void test() {
8183
}
8284

8385
@Entity(name = "Person")
84-
@Check(constraints = "code > 0")
8586
public static class Person {
8687

8788
@Id
8889
private Long id;
8990

9091
private String name;
9192

92-
// This one does not work! Only the entity-level annotation works.
93-
// @Check(constraints = "code > 0")
93+
@Check(constraints = "code > 0")
9494
private Long code;
9595

9696
public Long getId() {

hibernate-core/src/main/java/org/hibernate/annotations/Check.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515
import static java.lang.annotation.RetentionPolicy.RUNTIME;
1616

1717
/**
18-
* Arbitrary SQL CHECK constraints which can be defined at the class, property or collection level.
18+
* Specifies a {@code check} constraint to be included in the generated DDL.
19+
* <ul>
20+
* <li>When a field or property is annotated, the check constraint is added to the column definition.
21+
* <li>When an entity class is annotated, the check constraint is added to the primary table.
22+
* </ul>
1923
*
2024
* @author Emmanuel Bernard
2125
*/
2226
@Target({TYPE, METHOD, FIELD})
2327
@Retention(RUNTIME)
2428
public @interface Check {
2529
/**
26-
* The check constraints string.
30+
* The check constraint, written in native SQL.
2731
*/
2832
String constraints();
2933
}

hibernate-core/src/main/java/org/hibernate/annotations/ForeignKey.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,24 @@
1515
import static java.lang.annotation.RetentionPolicy.RUNTIME;
1616

1717
/**
18-
* Define the foreign key name.
18+
* Specifies a foreign key name.
1919
*
20-
* @deprecated Prefer the JPA 2.1 introduced {@link jakarta.persistence.ForeignKey} instead.
20+
* @deprecated use the JPA 2.1 {@link jakarta.persistence.ForeignKey} annotation
2121
*/
2222
@Target({FIELD, METHOD, TYPE})
2323
@Retention(RUNTIME)
2424
@Deprecated
2525
public @interface ForeignKey {
2626
/**
27-
* Name of the foreign key. Used in OneToMany, ManyToOne, and OneToOne
28-
* relationships. Used for the owning side in ManyToMany relationships
27+
* Name of the foreign key of a {@code OneToMany}, {@code ManyToOne}, or
28+
* {@code OneToOne} association. May also be applied to the owning side a
29+
* {@code ManyToMany} association.
2930
*/
3031
String name();
3132

3233
/**
33-
* Used for the non-owning side of a ManyToMany relationship. Ignored
34-
* in other relationships
34+
* Used for the non-owning side of a {@code ManyToMany} association.
35+
* Ignored for other association cardinalities.
3536
*/
3637
String inverseName() default "";
3738
}

hibernate-core/src/main/java/org/hibernate/annotations/Index.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import static java.lang.annotation.RetentionPolicy.RUNTIME;
1414

1515
/**
16-
* Define a DB index.
16+
* Defines an index of a database table.
1717
*
1818
* @author Emmanuel Bernard
1919
*

hibernate-core/src/main/java/org/hibernate/annotations/Table.java

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,82 +14,111 @@
1414
import static java.lang.annotation.RetentionPolicy.RUNTIME;
1515

1616
/**
17-
* Complementary information to a table either primary or secondary.
17+
* Complementary information for a table declared using the {@link jakarta.persistence.Table},
18+
* or {@link jakarta.persistence.SecondaryTable} annotation. Usually used only for secondary
19+
* tables.
1820
*
1921
* @author Emmanuel Bernard
22+
*
23+
* @see jakarta.persistence.Table
24+
* @see jakarta.persistence.SecondaryTable
2025
*/
21-
@Target({TYPE})
26+
@Target(TYPE)
2227
@Retention(RUNTIME)
2328
@Repeatable(Tables.class)
2429
public @interface Table {
2530
/**
26-
* name of the targeted table.
31+
* The name of the targeted table.
2732
*/
2833
String appliesTo();
2934

3035
/**
3136
* Indexes.
37+
*
38+
* @deprecated use {@link jakarta.persistence.Table#indexes()} or
39+
* {@link jakarta.persistence.SecondaryTable#indexes()}
3240
*/
41+
@Deprecated
3342
Index[] indexes() default {};
3443

3544
/**
36-
* Define a table comment.
45+
* A check constraint, written in native SQL.
46+
*
47+
* @see Check
48+
*/
49+
String checkConstraint() default "";
50+
51+
/**
52+
* Specifies comment to add to the generated DDL for the table.
3753
*
3854
* @see Comment
3955
*/
4056
String comment() default "";
4157

4258
/**
43-
* Defines the Foreign Key name of a secondary table pointing back to the primary table.
59+
* Specifies a foreign key of a secondary table, which points back to the primary table.
60+
*
61+
* @deprecated use {@link jakarta.persistence.SecondaryTable#foreignKey()}
4462
*/
63+
@Deprecated
4564
ForeignKey foreignKey() default @ForeignKey( name="" );
4665

4766
/**
48-
* If set to JOIN, the default, Hibernate will use an inner join to retrieve a
49-
* secondary table defined by a class or its superclasses and an outer join for a
50-
* secondary table defined by a subclass.
51-
* If set to select then Hibernate will use a
52-
* sequential select for a secondary table defined on a subclass, which will be issued only if a row
53-
* turns out to represent an instance of the subclass. Inner joins will still be used to retrieve a
54-
* secondary defined by the class and its superclasses.
55-
*
56-
* <b>Only applies to secondary tables</b>
67+
* Defines a fetching strategy for the secondary table.
68+
* <ul>
69+
* <li>If set to {@link FetchMode#JOIN}, the default, Hibernate will use an inner join to
70+
* retrieve a secondary table defined by a class or its superclasses and an outer join for
71+
* a secondary table defined by a subclass.
72+
* <li>If set to {@link FetchMode#SELECT} then Hibernate will use a sequential select for
73+
* a secondary table defined on a subclass, which will be issued only if a row turns out
74+
* to represent an instance of the subclass. Inner joins will still be used to retrieve a
75+
* secondary table defined by the class and its superclasses.
76+
* </ul>
77+
* <p>
78+
* <em>Only applies to secondary tables.</em>
5779
*/
5880
FetchMode fetch() default FetchMode.JOIN;
5981

6082
/**
61-
* If true, Hibernate will not try to insert or update the properties defined by this join.
62-
*
63-
* <b>Only applies to secondary tables</b>
83+
* If enabled, Hibernate will never insert or update the columns of the secondary table.
84+
* <p>
85+
* <em>Only applies to secondary tables.</em>
6486
*/
6587
boolean inverse() default false;
6688

6789
/**
68-
* If enabled, Hibernate will insert a row only if the properties defined by this join are non-null
69-
* and will always use an outer join to retrieve the properties.
70-
*
71-
* <b>Only applies to secondary tables</b>
90+
* If enabled, Hibernate will insert a row only if the columns of the secondary table
91+
* would not all be null, and will always use an outer join to read the columns. Thus,
92+
* by default, Hibernate avoids creating a row of null values.
93+
* <p>
94+
* <em>Only applies to secondary tables.<p></em>
7295
*/
7396
boolean optional() default true;
7497

7598
/**
7699
* Defines a custom SQL insert statement.
100+
* <p>
101+
* <em>Only applies to secondary tables.</em>
77102
*
78-
* <b>Only applies to secondary tables</b>
103+
* @see SQLInsert
79104
*/
80105
SQLInsert sqlInsert() default @SQLInsert(sql="");
81106

82107
/**
83108
* Defines a custom SQL update statement.
109+
* <p>
110+
* <em>Only applies to secondary tables.</em>
84111
*
85-
* <b>Only applies to secondary tables</b>
112+
* @see SQLUpdate
86113
*/
87114
SQLUpdate sqlUpdate() default @SQLUpdate(sql="");
88115

89116
/**
90117
* Defines a custom SQL delete statement.
118+
* <p>
119+
* <em>Only applies to secondary tables.</em>
91120
*
92-
* <b>Only applies to secondary tables</b>
121+
* @see SQLDelete
93122
*/
94123
SQLDelete sqlDelete() default @SQLDelete(sql="");
95124
}

hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedColumn.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.AnnotationException;
1212
import org.hibernate.AssertionFailure;
1313
import org.hibernate.MappingException;
14+
import org.hibernate.annotations.Check;
1415
import org.hibernate.annotations.ColumnDefault;
1516
import org.hibernate.annotations.GeneratedColumn;
1617
import org.hibernate.annotations.ColumnTransformer;
@@ -73,6 +74,7 @@ public class AnnotatedColumn {
7374
private String generatedAs;
7475

7576
private String comment;
77+
private String checkConstraint;
7678

7779
public void setTable(Table table) {
7880
this.table = table;
@@ -193,10 +195,18 @@ public String getDefaultValue() {
193195
return defaultValue;
194196
}
195197

198+
public String getCheckConstraint() {
199+
return checkConstraint;
200+
}
201+
196202
public void setDefaultValue(String defaultValue) {
197203
this.defaultValue = defaultValue;
198204
}
199205

206+
public void setCheckConstraint(String checkConstraint) {
207+
this.checkConstraint = checkConstraint;
208+
}
209+
200210
public String getComment() {
201211
return comment;
202212
}
@@ -229,6 +239,9 @@ public void bind() {
229239
if ( defaultValue != null ) {
230240
mappingColumn.setDefaultValue( defaultValue );
231241
}
242+
if ( checkConstraint !=null ) {
243+
mappingColumn.setCheckConstraint( checkConstraint );
244+
}
232245
if ( StringHelper.isNotEmpty( comment ) ) {
233246
mappingColumn.setComment( comment );
234247
}
@@ -266,6 +279,7 @@ protected void initMappingColumn(
266279
this.mappingColumn.setNullable( nullable );
267280
this.mappingColumn.setSqlType( sqlType );
268281
this.mappingColumn.setUnique( unique );
282+
this.mappingColumn.setCheckConstraint( checkConstraint );
269283

270284
if ( writeExpression != null ) {
271285
final int numberOfJdbcParams = StringHelper.count( writeExpression, '?' );
@@ -645,6 +659,7 @@ public static AnnotatedColumn[] buildColumnFromAnnotation(
645659
column.setBuildingContext( context );
646660
column.applyColumnDefault( inferredData, length );
647661
column.applyGeneratedAs( inferredData, length );
662+
column.applyCheckConstraint( inferredData, length );
648663
column.extractDataFromPropertyData(inferredData);
649664
column.bind();
650665
columns[index] = column;
@@ -685,7 +700,25 @@ private void applyGeneratedAs(PropertyData inferredData, int length) {
685700
}
686701
else {
687702
LOG.trace(
688-
"Could not perform @ColumnGeneratedAlways lookup as 'PropertyData' did not give access to XProperty"
703+
"Could not perform @GeneratedColumn lookup as 'PropertyData' did not give access to XProperty"
704+
);
705+
}
706+
}
707+
708+
private void applyCheckConstraint(PropertyData inferredData, int length) {
709+
final XProperty xProperty = inferredData.getProperty();
710+
if ( xProperty != null ) {
711+
Check columnDefaultAnn = xProperty.getAnnotation( Check.class );
712+
if ( columnDefaultAnn != null ) {
713+
if (length!=1) {
714+
throw new MappingException("@Check may only be applied to single-column mappings (use a table-level @Check)");
715+
}
716+
setCheckConstraint( columnDefaultAnn.constraints() );
717+
}
718+
}
719+
else {
720+
LOG.trace(
721+
"Could not perform @Check lookup as 'PropertyData' did not give access to XProperty"
689722
);
690723
}
691724
}
@@ -761,6 +794,7 @@ private static AnnotatedColumn[] buildImplicitColumn(
761794
column.setImplicit( implicit );
762795
column.applyColumnDefault( inferredData, 1 );
763796
column.applyGeneratedAs( inferredData, 1 );
797+
column.applyCheckConstraint( inferredData, 1 );
764798
column.extractDataFromPropertyData( inferredData );
765799
column.bind();
766800

hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -894,18 +894,19 @@ else if ( idColumns.size() != columns.length ) {
894894
* @param column the referenced column.
895895
*/
896896
public void overrideFromReferencedColumnIfNecessary(Column column) {
897-
if (getMappingColumn() != null) {
897+
Column mappingColumn = getMappingColumn();
898+
if (mappingColumn != null) {
898899
// columnDefinition can also be specified using @JoinColumn, hence we have to check
899900
// whether it is set or not
900901
if ( StringHelper.isEmpty( sqlType ) ) {
901902
sqlType = column.getSqlType();
902-
getMappingColumn().setSqlType( sqlType );
903+
mappingColumn.setSqlType( sqlType );
903904
}
904905

905906
// these properties can only be applied on the referenced column - we can just take them over
906-
getMappingColumn().setLength(column.getLength());
907-
getMappingColumn().setPrecision(column.getPrecision());
908-
getMappingColumn().setScale(column.getScale());
907+
mappingColumn.setLength( column.getLength() );
908+
mappingColumn.setPrecision( column.getPrecision() );
909+
mappingColumn.setScale( column.getScale() );
909910
}
910911
}
911912

hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,9 @@ public void processComplementaryTableDefinitions(org.hibernate.annotations.Table
12301230
if ( !BinderHelper.isEmptyAnnotationValue( table.comment() ) ) {
12311231
hibTable.setComment( table.comment() );
12321232
}
1233+
if ( !BinderHelper.isEmptyAnnotationValue( table.checkConstraint() ) ) {
1234+
hibTable.addCheckConstraint( table.checkConstraint() );
1235+
}
12331236
TableBinder.addIndexes( hibTable, table.indexes(), context );
12341237
}
12351238

0 commit comments

Comments
 (0)