Skip to content

Commit b72591b

Browse files
committed
HHH-19205 Do not recreate the validator on each BeanValidationEventListener#validate call
1 parent 0bc1dff commit b72591b

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;
@@ -29,11 +30,9 @@
2930

3031
import jakarta.validation.ConstraintViolation;
3132
import jakarta.validation.ConstraintViolationException;
32-
import jakarta.validation.TraversableResolver;
3333
import jakarta.validation.Validator;
3434
import jakarta.validation.ValidatorFactory;
3535

36-
import static jakarta.validation.Validation.buildDefaultValidatorFactory;
3736
import static org.hibernate.internal.util.collections.CollectionHelper.setOfSize;
3837

3938
/**
@@ -44,40 +43,34 @@
4443
*/
4544
//FIXME review exception model
4645
public class BeanValidationEventListener
47-
implements PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener, PreUpsertEventListener {
46+
implements PreInsertEventListener, PreUpdateEventListener, PreDeleteEventListener, PreUpsertEventListener,
47+
SessionFactoryObserver {
4848

4949
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
5050
MethodHandles.lookup(),
5151
CoreMessageLogger.class,
5252
BeanValidationEventListener.class.getName()
5353
);
5454

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

71-
public void initialize(Map<String,Object> settings, ClassLoaderService classLoaderService) {
72-
if ( !initialized ) {
73-
init( buildDefaultValidatorFactory(), settings, classLoaderService );
74-
}
59+
public BeanValidationEventListener(
60+
ValidatorFactory factory, Map<String, Object> settings, ClassLoaderService classLoaderService) {
61+
traversableResolver = new HibernateTraversableResolver();
62+
validator = factory.usingContext()
63+
.traversableResolver( traversableResolver )
64+
.getValidator();
65+
groupsPerOperation = GroupsPerOperation.from( settings, new ClassLoaderAccessImpl( classLoaderService ) );
7566
}
7667

77-
private void init(ValidatorFactory factory, Map<String,Object> settings, ClassLoaderService classLoaderService) {
78-
this.factory = factory;
79-
groupsPerOperation = GroupsPerOperation.from( settings, new ClassLoaderAccessImpl( classLoaderService ) );
80-
initialized = true;
68+
@Override
69+
public void sessionFactoryCreated(SessionFactory factory) {
70+
SessionFactoryImplementor implementor = factory.unwrap( SessionFactoryImplementor.class );
71+
implementor
72+
.getMappingMetamodel()
73+
.forEachEntityDescriptor( entityPersister -> traversableResolver.addPersister( entityPersister, implementor ) );
8174
}
8275

8376
public boolean onPreInsert(PreInsertEvent event) {
@@ -129,10 +122,6 @@ private <T> void validate(
129122
if ( object == null || persister.getRepresentationStrategy().getMode() != RepresentationMode.POJO ) {
130123
return;
131124
}
132-
TraversableResolver tr = new HibernateTraversableResolver( persister, associationsPerEntityPersister, sessionFactory );
133-
Validator validator = factory.usingContext()
134-
.traversableResolver( tr )
135-
.getValidator();
136125
final Class<?>[] groups = groupsPerOperation.get( operation );
137126
if ( groups.length > 0 ) {
138127
final Set<ConstraintViolation<T>> constraintViolations = validator.validate( object, groups );
@@ -153,7 +142,7 @@ private <T> void validate(
153142
builder.append( toString( groups ) );
154143
builder.append( "\nList of constraint violations:[\n" );
155144
for ( ConstraintViolation<?> violation : constraintViolations ) {
156-
builder.append( "\t" ).append( violation.toString() ).append("\n");
145+
builder.append( "\t" ).append( violation.toString() ).append( "\n" );
157146
}
158147
builder.append( "]" );
159148

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
@@ -34,6 +34,7 @@
3434
import org.hibernate.engine.config.spi.ConfigurationService;
3535
import org.hibernate.engine.config.spi.StandardConverters;
3636
import org.hibernate.engine.jdbc.spi.JdbcServices;
37+
import org.hibernate.engine.spi.SessionFactoryImplementor;
3738
import org.hibernate.event.service.spi.EventListenerRegistry;
3839
import org.hibernate.event.spi.EventType;
3940
import org.hibernate.internal.CoreMessageLogger;
@@ -115,7 +116,7 @@ else if ( validationModes.contains( ValidationMode.DDL ) ) {
115116
public static void applyCallbackListeners(ValidatorFactory validatorFactory, ActivationContext context) {
116117
if ( isValidationEnabled( context ) ) {
117118
disableNullabilityChecking( context );
118-
setupListener( validatorFactory, context.getServiceRegistry() );
119+
setupListener( validatorFactory, context.getServiceRegistry(), context.getSessionFactory() );
119120
}
120121
}
121122

@@ -141,7 +142,7 @@ private static boolean isCheckNullabilityExplicit(ActivationContext context) {
141142
.getSettings().get( CHECK_NULLABILITY ) == null;
142143
}
143144

144-
private static void setupListener(ValidatorFactory validatorFactory, SessionFactoryServiceRegistry serviceRegistry) {
145+
private static void setupListener(ValidatorFactory validatorFactory, SessionFactoryServiceRegistry serviceRegistry, SessionFactoryImplementor sessionFactory) {
145146
final ClassLoaderService classLoaderService = serviceRegistry.requireService( ClassLoaderService.class );
146147
final ConfigurationService cfgService = serviceRegistry.requireService( ConfigurationService.class );
147148
final BeanValidationEventListener listener =
@@ -152,7 +153,7 @@ private static void setupListener(ValidatorFactory validatorFactory, SessionFact
152153
listenerRegistry.appendListeners( EventType.PRE_UPDATE, listener );
153154
listenerRegistry.appendListeners( EventType.PRE_DELETE, listener );
154155
listenerRegistry.appendListeners( EventType.PRE_UPSERT, listener );
155-
listener.initialize( cfgService.getSettings(), classLoaderService );
156+
sessionFactory.addObserver( listener );
156157
}
157158

158159
private static boolean isConstraintBasedValidationEnabled(ActivationContext context) {

0 commit comments

Comments
 (0)