diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java index 2cab523720b2..a97a230d2062 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationEventListener.java @@ -14,6 +14,7 @@ import org.hibernate.boot.internal.ClassLoaderAccessImpl; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.PreCollectionUpdateEvent; import org.hibernate.event.spi.PreCollectionUpdateEventListener; import org.hibernate.event.spi.PreDeleteEvent; @@ -59,8 +60,9 @@ public class BeanValidationEventListener private Validator validator; private GroupsPerOperation groupsPerOperation; - public BeanValidationEventListener( - ValidatorFactory factory, Map settings, ClassLoaderService classLoaderService) { + private SessionFactoryImplementor sessionFactory; + + public BeanValidationEventListener(ValidatorFactory factory, Map settings, ClassLoaderService classLoaderService) { traversableResolver = new HibernateTraversableResolver(); validator = factory.usingContext() .traversableResolver( traversableResolver ) @@ -70,17 +72,15 @@ public BeanValidationEventListener( @Override public void sessionFactoryCreated(SessionFactory factory) { - SessionFactoryImplementor implementor = factory.unwrap( SessionFactoryImplementor.class ); - implementor - .getMappingMetamodel() - .forEachEntityDescriptor( entityPersister -> traversableResolver.addPersister( entityPersister, implementor ) ); + sessionFactory = factory.unwrap( SessionFactoryImplementor.class ); + sessionFactory.getMappingMetamodel() + .forEachEntityDescriptor( entityPersister -> traversableResolver.addPersister( entityPersister, sessionFactory ) ); } public boolean onPreInsert(PreInsertEvent event) { validate( event.getEntity(), event.getPersister(), - event.getFactory(), GroupsPerOperation.Operation.INSERT ); return false; @@ -90,7 +90,6 @@ public boolean onPreUpdate(PreUpdateEvent event) { validate( event.getEntity(), event.getPersister(), - event.getFactory(), GroupsPerOperation.Operation.UPDATE ); return false; @@ -100,7 +99,6 @@ public boolean onPreDelete(PreDeleteEvent event) { validate( event.getEntity(), event.getPersister(), - event.getFactory(), GroupsPerOperation.Operation.DELETE ); return false; @@ -111,7 +109,6 @@ public boolean onPreUpsert(PreUpsertEvent event) { validate( event.getEntity(), event.getPersister(), - event.getFactory(), GroupsPerOperation.Operation.UPSERT ); return false; @@ -122,16 +119,24 @@ public void onPreUpdateCollection(PreCollectionUpdateEvent event) { final Object entity = castNonNull( event.getCollection().getOwner() ); validate( entity, - event.getSession().getEntityPersister( event.getAffectedOwnerEntityName(), entity ), - event.getFactory(), + getEntityPersister( event.getSession(), event.getAffectedOwnerEntityName(), entity ), GroupsPerOperation.Operation.UPDATE ); } + private EntityPersister getEntityPersister(SharedSessionContractImplementor session, String entityName, Object entity) { + if ( session != null ) { + return session.getEntityPersister( entityName, entity ); + } + return entityName == null + ? sessionFactory.getMappingMetamodel().getEntityDescriptor( entity.getClass().getName() ) + : sessionFactory.getMappingMetamodel().getEntityDescriptor( entityName ) + .getSubclassEntityPersister( entity, sessionFactory ); + } + private void validate( T object, EntityPersister persister, - SessionFactoryImplementor sessionFactory, GroupsPerOperation.Operation operation) { if ( object == null || persister.getRepresentationStrategy().getMode() != RepresentationMode.POJO ) { return; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CollectionActionsValidationStatelessTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CollectionActionsValidationStatelessTest.java new file mode 100644 index 000000000000..aff5004cd14d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/beanvalidation/CollectionActionsValidationStatelessTest.java @@ -0,0 +1,109 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.annotations.beanvalidation; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.constraints.Size; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SessionFactory +@DomainModel(annotatedClasses = { + CollectionActionsValidationStatelessTest.Author.class, + CollectionActionsValidationStatelessTest.Book.class, +}) +@ServiceRegistry(settings = @Setting(name = AvailableSettings.JAKARTA_VALIDATION_MODE, value = "auto")) +@Jira("https://hibernate.atlassian.net/browse/HHH-19843") +public class CollectionActionsValidationStatelessTest { + + @Test + void smoke(SessionFactoryScope scope) { + scope.inStatelessTransaction( session -> { + final ConstraintViolationException e = assertThrows( ConstraintViolationException.class, () -> { + ArrayList books = new ArrayList<>(); + Author author = new Author( 1L, "first", "last", books ); + Book book = new Book( 10L, "", author ); + books.add( book ); + + session.upsertMultiple( List.of( author ) ); + } ); + assertThat( e.getConstraintViolations() ).hasSize( 1 ); + } ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + } + + @Table(name = "author") + @Entity + static class Author { + + public Author() { + } + + public Author(long id, String firstName, String lastName, List books) { + this.firstName = firstName; + this.lastName = lastName; + this.books = books; + this.id = id; + } + + @Id + Long id; + + String firstName; + + String lastName; + + @OneToMany + @JoinColumn(name = "bookId") + @Size(min = 10) + List books; + + } + + @Table(name = "book") + @Entity + static class Book { + + public Book() { + } + + public Book(long id, String title, Author author) { + this.id = id; + this.title = title; + this.author = author; + } + + @Id + Long id; + + String title; + + @ManyToOne + Author author; + } +}