Skip to content

Commit 7f3193d

Browse files
committed
improvements to Nullability and CascadingActions
includes a bugfix to nullability checking of composite collection elements
1 parent 17c3914 commit 7f3193d

File tree

5 files changed

+132
-121
lines changed

5 files changed

+132
-121
lines changed

hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hibernate.engine.internal.ForeignKeys;
1010
import org.hibernate.engine.internal.NonNullableTransientDependencies;
1111
import org.hibernate.engine.internal.Nullability;
12+
import org.hibernate.engine.internal.Nullability.NullabilityCheckType;
1213
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
1314
import org.hibernate.engine.spi.CollectionKey;
1415
import org.hibernate.engine.spi.EntityEntry;
@@ -121,7 +122,8 @@ protected final void nullifyTransientReferencesIfNotAlready() {
121122
if ( !areTransientReferencesNullified ) {
122123
new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() )
123124
.nullifyTransientReferences( getState() );
124-
new Nullability( getSession() ).checkNullability( getState(), getPersister(), false );
125+
new Nullability( getSession(), NullabilityCheckType.CREATE )
126+
.checkNullability( getState(), getPersister() );
125127
areTransientReferencesNullified = true;
126128
}
127129
}

hibernate-core/src/main/java/org/hibernate/engine/internal/Nullability.java

+94-95
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.hibernate.type.Type;
2020

2121
import static org.hibernate.engine.spi.CascadingActions.getLoadedElementsIterator;
22+
import static org.hibernate.internal.util.StringHelper.qualify;
2223

2324
/**
2425
* Implements the algorithm for validating property values for illegal null values
@@ -28,76 +29,83 @@
2829
public final class Nullability {
2930
private final SharedSessionContractImplementor session;
3031
private final boolean checkNullability;
32+
private NullabilityCheckType checkType;
3133

32-
/**
33-
* Constructs a Nullability
34-
*
35-
* @param session The session
36-
*/
34+
public enum NullabilityCheckType {
35+
CREATE,
36+
UPDATE,
37+
DELETE
38+
}
39+
40+
public Nullability(SharedSessionContractImplementor session, NullabilityCheckType checkType) {
41+
this.session = session;
42+
this.checkNullability = session.getFactory().getSessionFactoryOptions().isCheckNullability();
43+
this.checkType = checkType;
44+
}
45+
46+
@Deprecated(forRemoval = true, since = "7")
3747
public Nullability(SharedSessionContractImplementor session) {
3848
this.session = session;
3949
this.checkNullability = session.getFactory().getSessionFactoryOptions().isCheckNullability();
4050
}
51+
4152
/**
42-
* Check nullability of the class persister properties
53+
* Check nullability of the entity properties
4354
*
4455
* @param values entity properties
4556
* @param persister class persister
4657
* @param isUpdate whether it is intended to be updated or saved
4758
*
4859
* @throws PropertyValueException Break the nullability of one property
4960
* @throws HibernateException error while getting Component values
61+
*
62+
* @deprecated Use {@link #checkNullability(Object[], EntityPersister)}
5063
*/
64+
@Deprecated(forRemoval = true, since = "7")
5165
public void checkNullability(
5266
final Object[] values,
5367
final EntityPersister persister,
5468
final boolean isUpdate) {
55-
checkNullability( values, persister, isUpdate ? NullabilityCheckType.UPDATE : NullabilityCheckType.CREATE );
69+
checkType = isUpdate ? NullabilityCheckType.UPDATE : NullabilityCheckType.CREATE;
70+
checkNullability( values, persister );
5671
}
5772

58-
public enum NullabilityCheckType {
59-
CREATE,
60-
UPDATE,
61-
DELETE
62-
}
63-
64-
public void checkNullability(
65-
final Object[] values,
66-
final EntityPersister persister,
67-
final NullabilityCheckType checkType) {
73+
/**
74+
* Check nullability of the entity properties
75+
*
76+
* @param values entity properties
77+
* @param persister class persister
78+
*
79+
* @throws PropertyValueException Break the nullability of one property
80+
* @throws HibernateException error while getting Component values
81+
*/
82+
public void checkNullability(final Object[] values, final EntityPersister persister) {
6883

69-
/*
70-
* Typically when Bean Validation is on, we don't want to validate null values
71-
* at the Hibernate Core level. Hence the checkNullability setting.
72-
*/
84+
// Typically when Bean Validation is on, we don't want to validate null values
85+
// at the Hibernate Core level. Hence, the checkNullability setting.
7386
if ( checkNullability ) {
74-
/*
75-
* Algorithm
76-
* Check for any level one nullability breaks
77-
* Look at non-null components to
78-
* recursively check next level of nullability breaks
79-
* Look at Collections containing components to
80-
* recursively check next level of nullability breaks
81-
*
82-
*
83-
* In the previous implementation, not-null stuffs where checked
84-
* filtering by level one only updatable
85-
* or insertable columns. So setting a subcomponent as update="false"
86-
* has no effect on not-null check if the main component had good checkability
87-
* In this implementation, we keep this feature.
88-
* However, I never see any documentation mentioning that, but it's for
89-
* sure a limitation.
90-
*/
87+
// Algorithm:
88+
// Check for any level one nullability breaks
89+
// Look at non-null components to
90+
// recursively check next level of nullability breaks
91+
// Look at Collections containing components to
92+
// recursively check next level of nullability breaks
93+
//
94+
// In the previous implementation, not-null stuffs where checked
95+
// filtering by level one only updatable
96+
// or insertable columns. So setting a subcomponent as update="false"
97+
// has no effect on not-null check if the main component had good checkability
98+
// In this implementation, we keep this feature.
99+
// However, I never see any documentation mentioning that, but it's for
100+
// sure a limitation.
91101

92102
final boolean[] nullability = persister.getPropertyNullability();
93103
final boolean[] checkability = checkType == NullabilityCheckType.CREATE
94104
? persister.getPropertyInsertability()
95105
: persister.getPropertyUpdateability();
96106
final Type[] propertyTypes = persister.getPropertyTypes();
97107
final Generator[] generators = persister.getEntityMetamodel().getGenerators();
98-
99108
for ( int i = 0; i < values.length; i++ ) {
100-
101109
if ( checkability[i]
102110
&& values[i] != LazyPropertyInitializer.UNFETCHED_PROPERTY
103111
&& !generated( generators[i] ) ) {
@@ -118,13 +126,12 @@ else if ( value != null ) {
118126
throw new PropertyValueException(
119127
"not-null property references a null or transient value",
120128
persister.getEntityName(),
121-
buildPropertyPath( persister.getPropertyNames()[i], breakProperties )
129+
qualify( persister.getPropertyNames()[i], breakProperties )
122130
);
123131
}
124132

125133
}
126134
}
127-
128135
}
129136
}
130137
}
@@ -134,98 +141,90 @@ private static boolean generated(Generator generator) {
134141
}
135142

136143
/**
137-
* check sub elements-nullability. Returns property path that break
138-
* nullability or null if none
144+
* Check nullability of sub-elements.
145+
* Returns property path that break nullability, or null if none.
139146
*
140147
* @param propertyType type to check
141148
* @param value value to check
142149
*
143150
* @return property path
144151
* @throws HibernateException error while getting subcomponent values
145152
*/
146-
private String checkSubElementsNullability(Type propertyType, Object value) throws HibernateException {
147-
if ( propertyType instanceof AnyType ) {
148-
return checkComponentNullability( value, (AnyType) propertyType );
153+
private String checkSubElementsNullability(Type propertyType, Object value) {
154+
if ( propertyType instanceof AnyType anyType ) {
155+
return checkComponentNullability( value, anyType );
149156
}
150-
if ( propertyType instanceof ComponentType ) {
151-
return checkComponentNullability( value, (ComponentType) propertyType );
157+
else if ( propertyType instanceof ComponentType componentType ) {
158+
return checkComponentNullability( value, componentType );
152159
}
153-
154-
if ( propertyType instanceof CollectionType collectionType ) {
160+
else if ( propertyType instanceof CollectionType collectionType ) {
155161
// persistent collections may have components
156-
final Type collectionElementType = collectionType.getElementType( session.getFactory() );
157-
158-
if ( collectionElementType instanceof ComponentType || collectionElementType instanceof AnyType ) {
162+
if ( collectionType.getElementType( session.getFactory() ) instanceof CompositeType componentType ) {
159163
// check for all components values in the collection
160-
final CompositeType componentType = (CompositeType) collectionElementType;
161-
final Iterator<?> itr = getLoadedElementsIterator( session, collectionType, value );
162-
while ( itr.hasNext() ) {
163-
final Object compositeElement = itr.next();
164+
final Iterator<?> iterator = getLoadedElementsIterator( collectionType, value );
165+
while ( iterator.hasNext() ) {
166+
final Object compositeElement = iterator.next();
164167
if ( compositeElement != null ) {
165-
return checkComponentNullability( compositeElement, componentType );
168+
final String path = checkComponentNullability( compositeElement, componentType );
169+
if ( path != null ) {
170+
return path;
171+
}
166172
}
167173
}
168174
}
175+
return null;
176+
}
177+
else {
178+
return null;
169179
}
170-
171-
return null;
172180
}
173181

174182
/**
175-
* check component nullability. Returns property path that break
176-
* nullability or null if none
183+
* Check component nullability.
184+
* Returns property path that breaks nullability, or null if none.
177185
*
178-
* @param value component properties
186+
* @param composite component properties
179187
* @param compositeType component not-nullable type
180188
*
181189
* @return property path
182190
* @throws HibernateException error while getting subcomponent values
183191
*/
184-
private String checkComponentNullability(Object value, CompositeType compositeType) throws HibernateException {
192+
private String checkComponentNullability(Object composite, CompositeType compositeType) {
185193
// IMPL NOTE : we currently skip checking "any" and "many to any" mappings.
186194
//
187-
// This is not the best solution. But atm there is a mismatch between AnyType#getPropertyNullability
195+
// This is not the best solution. But atm there is a mismatch between AnyType#getPropertyNullability
188196
// and the fact that cascaded-saves for "many to any" mappings are not performed until after this nullability
189-
// check. So the nullability check fails for transient entity elements with generated identifiers because
197+
// check. So the nullability check fails for transient entity elements with generated identifiers because
190198
// the identifier is not yet generated/assigned (is null)
191199
//
192200
// The more correct fix would be to cascade saves of the many-to-any elements before the Nullability checking
193201

194202
if ( compositeType instanceof AnyType ) {
195203
return null;
196204
}
197-
198-
final boolean[] nullability = compositeType.getPropertyNullability();
199-
if ( nullability != null ) {
200-
//do the test
201-
final Object[] subValues = compositeType.getPropertyValues( value, session );
202-
final Type[] propertyTypes = compositeType.getSubtypes();
203-
for ( int i = 0; i < subValues.length; i++ ) {
204-
final Object subValue = subValues[i];
205-
if ( !nullability[i] && subValue==null ) {
206-
return compositeType.getPropertyNames()[i];
207-
}
208-
else if ( subValue != null ) {
209-
final String breakProperties = checkSubElementsNullability( propertyTypes[i], subValue );
210-
if ( breakProperties != null ) {
211-
return buildPropertyPath( compositeType.getPropertyNames()[i], breakProperties );
205+
else {
206+
final boolean[] nullability = compositeType.getPropertyNullability();
207+
if ( nullability != null ) {
208+
//do the test
209+
final Object[] values = compositeType.getPropertyValues( composite, session );
210+
final Type[] propertyTypes = compositeType.getSubtypes();
211+
for ( int i = 0; i < values.length; i++ ) {
212+
final Object value = values[i];
213+
if ( value == null ) {
214+
if ( !nullability[i] ) {
215+
return compositeType.getPropertyNames()[i];
216+
}
217+
}
218+
else {
219+
final String breakProperties = checkSubElementsNullability( propertyTypes[i], value );
220+
if ( breakProperties != null ) {
221+
return qualify( compositeType.getPropertyNames()[i], breakProperties );
222+
}
212223
}
213224
}
214225
}
226+
return null;
215227
}
216-
return null;
217-
}
218-
219-
/**
220-
* Return a well formed property path. Basically, it will return parent.child
221-
*
222-
* @param parent parent in path
223-
* @param child child in path
224-
*
225-
* @return parent-child path
226-
*/
227-
private static String buildPropertyPath(String parent, String child) {
228-
return parent + '.' + child;
229228
}
230229

231230
}

0 commit comments

Comments
 (0)