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 04bf58a0e805..42735bf68f4d 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 @@ -13,6 +13,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; @@ -51,8 +52,9 @@ public class BeanValidationEventListener private final Validator validator; private final 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() @@ -63,9 +65,9 @@ public BeanValidationEventListener( @Override public void sessionFactoryCreated(SessionFactory factory) { - var 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) { @@ -110,11 +112,21 @@ public void onPreUpdateCollection(PreCollectionUpdateEvent event) { final Object entity = castNonNull( event.getCollection().getOwner() ); validate( entity, - event.getSession().getEntityPersister( event.getAffectedOwnerEntityName(), entity ), + 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, 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; + } +}