Skip to content

Commit 5ae82c0

Browse files
marko-bekhtasebersole
authored andcommitted
HHH-19205 Do not recreate the validator on each BeanValidationEventListener#validate call
1 parent 4f05b18 commit 5ae82c0

File tree

3 files changed

+41
-54
lines changed

3 files changed

+41
-54
lines changed

hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
import java.util.HashSet;
99
import java.util.Map;
1010
import java.util.Set;
11-
import java.util.concurrent.ConcurrentHashMap;
1211

12+
import org.hibernate.SessionFactory;
13+
import org.hibernate.SessionFactoryObserver;
1314
import org.hibernate.boot.internal.ClassLoaderAccessImpl;
1415
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
1516
import org.hibernate.engine.spi.SessionFactoryImplementor;
@@ -31,11 +32,9 @@
3132

3233
import jakarta.validation.ConstraintViolation;
3334
import jakarta.validation.ConstraintViolationException;
34-
import jakarta.validation.TraversableResolver;
3535
import jakarta.validation.Validator;
3636
import jakarta.validation.ValidatorFactory;
3737

38-
import static jakarta.validation.Validation.buildDefaultValidatorFactory;
3938
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
4039
import static org.hibernate.internal.util.collections.CollectionHelper.setOfSize;
4140

@@ -47,40 +46,34 @@
4746
*/
4847
//FIXME review exception model
4948
public class BeanValidationEventListener
50-
implements PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener, PreUpsertEventListener, PreCollectionUpdateEventListener {
49+
implements PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener, PreUpsertEventListener, PreCollectionUpdateEventListener,
50+
SessionFactoryObserver {
5151

5252
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
5353
MethodHandles.lookup(),
5454
CoreMessageLogger.class,
5555
BeanValidationEventListener.class.getName()
5656
);
5757

58-
private ValidatorFactory factory;
59-
private final ConcurrentHashMap<EntityPersister, Set<String>> associationsPerEntityPersister = new ConcurrentHashMap<>();
58+
private HibernateTraversableResolver traversableResolver;
59+
private Validator validator;
6060
private GroupsPerOperation groupsPerOperation;
61-
boolean initialized;
62-
63-
/**
64-
* Constructor used in an environment where validator factory is injected (JPA2).
65-
*
66-
* @param factory The {@code ValidatorFactory} to use to create {@code Validator} instance(s)
67-
* @param settings Configured properties
68-
*/
69-
public BeanValidationEventListener(
70-
ValidatorFactory factory, Map<String,Object> settings, ClassLoaderService classLoaderService) {
71-
init( factory, settings, classLoaderService );
72-
}
7361

74-
public void initialize(Map<String,Object> settings, ClassLoaderService classLoaderService) {
75-
if ( !initialized ) {
76-
init( buildDefaultValidatorFactory(), settings, classLoaderService );
77-
}
62+
public BeanValidationEventListener(
63+
ValidatorFactory factory, Map<String, Object> settings, ClassLoaderService classLoaderService) {
64+
traversableResolver = new HibernateTraversableResolver();
65+
validator = factory.usingContext()
66+
.traversableResolver( traversableResolver )
67+
.getValidator();
68+
groupsPerOperation = GroupsPerOperation.from( settings, new ClassLoaderAccessImpl( classLoaderService ) );
7869
}
7970

80-
private void init(ValidatorFactory factory, Map<String,Object> settings, ClassLoaderService classLoaderService) {
81-
this.factory = factory;
82-
groupsPerOperation = GroupsPerOperation.from( settings, new ClassLoaderAccessImpl( classLoaderService ) );
83-
initialized = true;
71+
@Override
72+
public void sessionFactoryCreated(SessionFactory factory) {
73+
SessionFactoryImplementor implementor = factory.unwrap( SessionFactoryImplementor.class );
74+
implementor
75+
.getMappingMetamodel()
76+
.forEachEntityDescriptor( entityPersister -> traversableResolver.addPersister( entityPersister, implementor ) );
8477
}
8578

8679
public boolean onPreInsert(PreInsertEvent event) {
@@ -143,10 +136,6 @@ private <T> void validate(
143136
if ( object == null || persister.getRepresentationStrategy().getMode() != RepresentationMode.POJO ) {
144137
return;
145138
}
146-
TraversableResolver tr = new HibernateTraversableResolver( persister, associationsPerEntityPersister, sessionFactory );
147-
Validator validator = factory.usingContext()
148-
.traversableResolver( tr )
149-
.getValidator();
150139
final Class<?>[] groups = groupsPerOperation.get( operation );
151140
if ( groups.length > 0 ) {
152141
final Set<ConstraintViolation<T>> constraintViolations = validator.validate( object, groups );
@@ -167,7 +156,7 @@ private <T> void validate(
167156
builder.append( toString( groups ) );
168157
builder.append( "\nList of constraint violations:[\n" );
169158
for ( ConstraintViolation<?> violation : constraintViolations ) {
170-
builder.append( "\t" ).append( violation.toString() ).append("\n");
159+
builder.append( "\t" ).append( violation.toString() ).append( "\n" );
171160
}
172161
builder.append( "]" );
173162

hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/HibernateTraversableResolver.java

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
package org.hibernate.boot.beanvalidation;
66

77
import java.lang.annotation.ElementType;
8+
import java.util.HashMap;
89
import java.util.HashSet;
10+
import java.util.Map;
911
import java.util.Set;
10-
import java.util.concurrent.ConcurrentHashMap;
1112

1213
import org.hibernate.AssertionFailure;
1314
import org.hibernate.Hibernate;
@@ -30,32 +31,27 @@
3031
* @author Emmanuel Bernard
3132
*/
3233
public class HibernateTraversableResolver implements TraversableResolver {
33-
private Set<String> associations;
34+
private final Map<Class<?>, Set<String>> associationsPerEntityClass = new HashMap<>();
3435

35-
public HibernateTraversableResolver(
36-
EntityPersister persister,
37-
ConcurrentHashMap<EntityPersister, Set<String>> associationsPerEntityPersister,
38-
SessionFactoryImplementor factory) {
39-
associations = associationsPerEntityPersister.get( persister );
40-
if ( associations == null ) {
41-
associations = new HashSet<>();
42-
addAssociationsToTheSetForAllProperties( persister.getPropertyNames(), persister.getPropertyTypes(), "", factory );
43-
associationsPerEntityPersister.put( persister, associations );
44-
}
36+
public void addPersister(EntityPersister persister, SessionFactoryImplementor factory) {
37+
Class<?> javaTypeClass = persister.getEntityMappingType().getMappedJavaType().getJavaTypeClass();
38+
Set<String> associations = new HashSet<>();
39+
addAssociationsToTheSetForAllProperties( persister.getPropertyNames(), persister.getPropertyTypes(), "", factory, associations );
40+
associationsPerEntityClass.put( javaTypeClass, associations );
4541
}
4642

47-
private void addAssociationsToTheSetForAllProperties(
48-
String[] names, Type[] types, String prefix, SessionFactoryImplementor factory) {
43+
private static void addAssociationsToTheSetForAllProperties(
44+
String[] names, Type[] types, String prefix, SessionFactoryImplementor factory, Set<String> associations) {
4945
final int length = names.length;
5046
for( int index = 0 ; index < length; index++ ) {
51-
addAssociationsToTheSetForOneProperty( names[index], types[index], prefix, factory );
47+
addAssociationsToTheSetForOneProperty( names[index], types[index], prefix, factory, associations );
5248
}
5349
}
5450

55-
private void addAssociationsToTheSetForOneProperty(
56-
String name, Type type, String prefix, SessionFactoryImplementor factory) {
51+
private static void addAssociationsToTheSetForOneProperty(
52+
String name, Type type, String prefix, SessionFactoryImplementor factory, Set<String> associations) {
5753
if ( type instanceof CollectionType collectionType ) {
58-
addAssociationsToTheSetForOneProperty( name, collectionType.getElementType( factory ), prefix, factory );
54+
addAssociationsToTheSetForOneProperty( name, collectionType.getElementType( factory ), prefix, factory, associations );
5955
}
6056
//ToOne association
6157
else if ( type instanceof EntityType || type instanceof AnyType ) {
@@ -66,7 +62,8 @@ else if ( type instanceof ComponentType componentType ) {
6662
componentType.getPropertyNames(),
6763
componentType.getSubtypes(),
6864
( prefix.isEmpty() ? name : prefix + name ) + '.',
69-
factory
65+
factory,
66+
associations
7067
);
7168
}
7269
}
@@ -102,6 +99,6 @@ public boolean isCascadable(Object traversableObject,
10299
Class<?> rootBeanType,
103100
Path pathToTraversableObject,
104101
ElementType elementType) {
105-
return !associations.contains( getStringBasedPath( traversableProperty, pathToTraversableObject ) );
102+
return !associationsPerEntityClass.getOrDefault( rootBeanType, Set.of() ).contains( getStringBasedPath( traversableProperty, pathToTraversableObject ) );
106103
}
107104
}

hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.hibernate.engine.config.spi.ConfigurationService;
3636
import org.hibernate.engine.config.spi.StandardConverters;
3737
import org.hibernate.engine.jdbc.spi.JdbcServices;
38+
import org.hibernate.engine.spi.SessionFactoryImplementor;
3839
import org.hibernate.event.service.spi.EventListenerRegistry;
3940
import org.hibernate.event.spi.EventType;
4041
import org.hibernate.internal.CoreMessageLogger;
@@ -126,7 +127,7 @@ else if ( validationModes.contains( ValidationMode.DDL ) ) {
126127
public static void applyCallbackListeners(ValidatorFactory validatorFactory, ActivationContext context) {
127128
if ( isValidationEnabled( context ) ) {
128129
disableNullabilityChecking( context );
129-
setupListener( validatorFactory, context.getServiceRegistry() );
130+
setupListener( validatorFactory, context.getServiceRegistry(), context.getSessionFactory() );
130131
}
131132
}
132133

@@ -152,7 +153,7 @@ private static boolean isCheckNullabilityExplicit(ActivationContext context) {
152153
.getSettings().get( CHECK_NULLABILITY ) == null;
153154
}
154155

155-
private static void setupListener(ValidatorFactory validatorFactory, SessionFactoryServiceRegistry serviceRegistry) {
156+
private static void setupListener(ValidatorFactory validatorFactory, SessionFactoryServiceRegistry serviceRegistry, SessionFactoryImplementor sessionFactory) {
156157
final ClassLoaderService classLoaderService = serviceRegistry.requireService( ClassLoaderService.class );
157158
final ConfigurationService cfgService = serviceRegistry.requireService( ConfigurationService.class );
158159
final BeanValidationEventListener listener =
@@ -164,7 +165,7 @@ private static void setupListener(ValidatorFactory validatorFactory, SessionFact
164165
listenerRegistry.appendListeners( EventType.PRE_DELETE, listener );
165166
listenerRegistry.appendListeners( EventType.PRE_UPSERT, listener );
166167
listenerRegistry.appendListeners( EventType.PRE_COLLECTION_UPDATE, listener );
167-
listener.initialize( cfgService.getSettings(), classLoaderService );
168+
sessionFactory.addObserver( listener );
168169
}
169170

170171
private static boolean isConstraintBasedValidationEnabled(ActivationContext context) {

0 commit comments

Comments
 (0)