diff --git a/pom.xml b/pom.xml index 6a6275432..8a1565932 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ 3.0.2 - 5.6.9.Final + 6.1.1.Final 4.3 8.0.23 42.2.19 @@ -44,7 +44,7 @@ - + spring-data-envers spring-data-jpa spring-data-jpa-distribution diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 59eb8eab8..540834bb6 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -62,8 +62,8 @@ - org.hibernate - hibernate-envers-jakarta + org.hibernate.orm + hibernate-envers ${hibernate.envers} diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index ba9220cc2..3ad9c39a2 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -145,19 +145,26 @@ - ${hibernate.groupId} - hibernate-core-jakarta + ${hibernate.groupId}.orm + hibernate-core ${hibernate} true - ${hibernate.groupId} - hibernate-jpamodelgen-jakarta + ${hibernate.groupId}.orm + hibernate-jpamodelgen ${hibernate} provided + + jakarta.xml.bind + jakarta.xml.bind-api + ${jaxb} + provided + + jakarta.annotation jakarta.annotation-api diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java index 918ec58af..38eba8d73 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java @@ -16,14 +16,15 @@ package org.springframework.data.jpa.provider; import jakarta.persistence.EntityManager; -import org.hibernate.SessionFactory; -import org.hibernate.TypeHelper; -import org.hibernate.jpa.TypedParameterValue; -import org.hibernate.type.Type; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.TypedParameterValue; +import org.hibernate.type.BasicTypeRegistry; import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.lang.Nullable; /** * {@link org.springframework.data.repository.query.ParameterAccessor} based on an {@link Parameters} instance. In @@ -33,11 +34,13 @@ import org.springframework.data.repository.query.ParametersParameterAccessor; * @author Wonchul Heo * @author Jens Schauder * @author Cedomir Igaly + * @author Robert Wilson + * @author Oliver Drotbohm * @since 2.7 */ class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAccessor { - private final TypeHelper typeHelper; + private final BasicTypeRegistry typeHelper; /** * Creates a new {@link ParametersParameterAccessor}. @@ -50,21 +53,29 @@ class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAcce super(parameters, values); - this.typeHelper = em.getEntityManagerFactory().unwrap(SessionFactory.class).getTypeHelper(); + this.typeHelper = em.getEntityManagerFactory() + .unwrap(SessionFactoryImplementor.class) + .getTypeConfiguration() + .getBasicTypeRegistry(); } @Override + @Nullable + @SuppressWarnings("unchecked") public Object getValue(Parameter parameter) { - Object value = super.getValue(parameter.getIndex()); + var value = super.getValue(parameter.getIndex()); + if (value != null) { return value; } - Type type = typeHelper.basic(parameter.getType()); + var type = typeHelper.getRegisteredType(parameter.getType()); + if (type == null) { return null; } - return new TypedParameterValue(type, null); + + return new TypedParameterValue<>(type, null); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java index 3c6a3556f..2056db61d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java @@ -16,6 +16,7 @@ package org.springframework.data.jpa.provider; import org.hibernate.query.Query; +import org.hibernate.query.spi.SqmQuery; import org.springframework.lang.Nullable; /** @@ -41,8 +42,20 @@ public abstract class HibernateUtils { @Nullable public static String getHibernateQuery(Object query) { + try { + + // Try the new Hibernate implementation first + if (query instanceof SqmQuery) { + return ((SqmQuery) query).getSqmStatement().toHqlString(); + } + + // Couple of cases in which this still breaks, see HHH-15389 + } catch (RuntimeException o_O) {} + + // Try the old way, as it still works in some cases (haven't investigated in which exactly) + if (query instanceof Query) { - return ((Query) query).getQueryString(); + return ((Query) query).getQueryString(); } else { throw new IllegalArgumentException("Don't know how to extract the query string from " + query); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java index f2d90b0c8..f3ef2f047 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java @@ -24,7 +24,9 @@ import jakarta.persistence.metamodel.IdentifiableType; import jakarta.persistence.metamodel.Metamodel; import jakarta.persistence.metamodel.SingularAttribute; +import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.NoSuchElementException; import java.util.Set; @@ -186,6 +188,8 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer } }; + private static final Collection ALL = List.of(HIBERNATE, ECLIPSELINK, GENERIC_JPA); + static ConcurrentReferenceHashMap, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>(); private final Iterable entityManagerClassNames; private final Iterable metamodelClassNames; @@ -233,7 +237,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer return cachedProvider; } - for (PersistenceProvider provider : values()) { + for (PersistenceProvider provider : ALL) { for (String entityManagerClassName : provider.entityManagerClassNames) { if (isEntityManagerOfType(em, entityManagerClassName)) { return cacheAndReturn(entityManagerType, provider); @@ -317,9 +321,9 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "jakarta.persistence.EntityManager"; String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager"; // needed as Spring only exposes that interface via the EM proxy - String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.jpa.HibernateEntityManager"; + String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.engine.spi.SessionImplementor"; - String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.internal.MetamodelImpl"; + String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.model.domain.JpaMetamodel"; String ECLIPSELINK_JPA_METAMODEL_TYPE = "org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl"; } @@ -337,7 +341,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer */ private static class HibernateScrollableResultsIterator implements CloseableIterator { - private final @Nullable ScrollableResults scrollableResults; + private final @Nullable ScrollableResults scrollableResults; /** * Creates a new {@link HibernateScrollableResultsIterator} for the given {@link Query}. @@ -346,7 +350,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer */ HibernateScrollableResultsIterator(Query jpaQuery) { - org.hibernate.query.Query query = jpaQuery.unwrap(org.hibernate.query.Query.class); + org.hibernate.query.Query query = jpaQuery.unwrap(org.hibernate.query.Query.class); this.scrollableResults = query.setReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly())// .scroll(ScrollMode.FORWARD_ONLY); } @@ -359,7 +363,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer } // Cast needed for Hibernate 6 compatibility - Object[] row = (Object[]) scrollableResults.get(); + Object[] row = scrollableResults.get(); return row.length == 1 ? row[0] : row; } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java index b4dda054b..820123828 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java @@ -15,8 +15,6 @@ */ package org.springframework.data.jpa.repository; -import java.sql.Types; - import org.hibernate.dialect.HSQLDialect; import org.springframework.orm.jpa.vendor.Database; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; @@ -24,26 +22,22 @@ import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; /** * Fix for missing type declarations for HSQL. * - * @see https://www.codesmell.org/blog/2008/12/hibernate-hsql-native-queries-and-booleans/ + * @see https://www.codesmell.org/blog/2008/12/hibernate-hsql-native-queries-and-booleans/ * @author Oliver Gierke + * @deprecated since 3.0 without replacement as it's not needed anymore. */ +@Deprecated public class CustomHsqlHibernateJpaVendorAdaptor extends HibernateJpaVendorAdapter { @Override protected Class determineDatabaseDialectClass(Database database) { - - if (Database.HSQL.equals(database)) { - return CustomHsqlDialect.class; - } - return super.determineDatabaseDialectClass(database); } - public static class CustomHsqlDialect extends HSQLDialect { - - public CustomHsqlDialect() { - registerColumnType(Types.BOOLEAN, "boolean"); - registerHibernateType(Types.BOOLEAN, "boolean"); - } - } + /** + * @deprecated since 3.0 without replacement as it's not needed anymore. + */ + @Deprecated + public static class CustomHsqlDialect extends HSQLDialect {} } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java index 67368194f..224a3e251 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java @@ -18,8 +18,6 @@ package org.springframework.data.jpa.repository; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.jpa.support.EntityManagerTestUtils.*; -import java.util.List; - import jakarta.persistence.EntityManager; import jakarta.persistence.Persistence; import jakarta.persistence.PersistenceUtil; @@ -28,12 +26,14 @@ import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; +import java.util.List; + import org.assertj.core.api.SoftAssertions; import org.junit.Assume; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -94,6 +94,7 @@ public class EntityGraphRepositoryMethodsIntegrationTests { } @Test // DATAJPA-612 + @Disabled // HHH-15391 void shouldRespectConfiguredJpaEntityGraph() { Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java index 6d89b1def..3241a237f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java @@ -16,8 +16,11 @@ package org.springframework.data.jpa.repository; import static org.assertj.core.api.Assertions.*; +import static org.junit.Assume.*; import static org.springframework.data.domain.Sort.Direction.*; +import jakarta.persistence.EntityManager; + import java.util.Arrays; import java.util.List; @@ -34,6 +37,7 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.sample.RoleRepository; import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.data.repository.query.QueryLookupStrategy; @@ -55,6 +59,9 @@ public class UserRepositoryFinderTests { @Autowired UserRepository userRepository; @Autowired RoleRepository roleRepository; + @Autowired EntityManager em; + + PersistenceProvider provider; private User dave; private User carter; @@ -73,6 +80,8 @@ public class UserRepositoryFinderTests { dave = userRepository.save(new User("Dave", "Matthews", "dave@dmband.com", singer)); carter = userRepository.save(new User("Carter", "Beauford", "carter@dmband.com", singer, drummer)); oliver = userRepository.save(new User("Oliver August", "Matthews", "oliver@dmband.com")); + + provider = PersistenceProvider.fromEntityManager(em); } @AfterEach @@ -226,6 +235,9 @@ public class UserRepositoryFinderTests { @Test // DATAJPA-1519 void escapingInLikeSpels() { + // HHH-15392 + assumeFalse(provider.equals(PersistenceProvider.HIBERNATE)); + User extra = new User("extra", "Matt_ew", "extra"); userRepository.save(extra); @@ -236,6 +248,9 @@ public class UserRepositoryFinderTests { @Test // DATAJPA-1522 void escapingInLikeSpelsInThePresenceOfEscapeCharacters() { + // HHH-15392 + assumeFalse(provider.equals(PersistenceProvider.HIBERNATE)); + User withEscapeCharacter = userRepository.save(new User("extra", "Matt\\xew", "extra1")); userRepository.save(new User("extra", "Matt\\_ew", "extra2")); @@ -245,6 +260,9 @@ public class UserRepositoryFinderTests { @Test // DATAJPA-1522 void escapingInLikeSpelsInThePresenceOfEscapedWildcards() { + // HHH-15392 + assumeFalse(provider.equals(PersistenceProvider.HIBERNATE)); + userRepository.save(new User("extra", "Matt\\xew", "extra1")); User withEscapedWildcard = userRepository.save(new User("extra", "Matt\\_ew", "extra2")); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 25dcfcdf6..877c8c905 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -2670,7 +2670,7 @@ public class UserRepositoryTests { @Test // DATAJPA-1233 void handlesCountQueriesWithLessParametersMoreThanOneIndexed() { - repository.findAllOrderedBySpecialNameMultipleParamsIndexed("Oliver", "x", PageRequest.of(2, 3)); + repository.findAllOrderedBySpecialNameMultipleParamsIndexed("x", "Oliver", PageRequest.of(2, 3)); } // DATAJPA-928 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java index f42756621..ef48b68fc 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java @@ -17,19 +17,22 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; -import java.lang.reflect.Method; -import java.util.List; - import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.TypedQuery; +import java.lang.reflect.Method; +import java.util.List; + +import org.hibernate.query.spi.SqmQuery; +import org.hibernate.query.sqm.tree.expression.SqmDistinct; +import org.hibernate.query.sqm.tree.expression.SqmFunction; +import org.hibernate.query.sqm.tree.select.SqmSelectClause; +import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.User; -import org.springframework.data.jpa.provider.HibernateUtils; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; @@ -68,10 +71,20 @@ public class JpaCountQueryCreatorIntegrationTests { TypedQuery query = entityManager.createQuery(creator.createQuery()); - assertThat(HibernateUtils.getHibernateQuery(query)).startsWith("select distinct count(distinct"); + SqmQuery sqmQuery = ((SqmQuery) query); + SqmSelectStatement select = (SqmSelectStatement) sqmQuery.getSqmStatement(); + + // Verify distinct (should this even be there for a count query?) + SqmSelectClause clause = select.getQuerySpec().getSelectClause(); + assertThat(clause.isDistinct()).isTrue(); + + // Verify count(distinct(…)) + SqmFunction function = ((SqmFunction) clause.getSelectionItems().get(0)); + assertThat(function.getFunctionName()).isEqualTo("count"); + assertThat(function.getArguments().get(0)).isInstanceOf(SqmDistinct.class); } interface SomeRepository extends Repository { - void findDistinctByRolesIn(List roles); + List findDistinctByRolesIn(List roles); } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessorTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessorTests.java index 059fc78ec..4577e8145 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessorTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessorTests.java @@ -4,14 +4,13 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.lang.reflect.Method; - import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; -import org.hibernate.jpa.TypedParameterValue; -import org.hibernate.type.StandardBasicTypes; +import java.lang.reflect.Method; + +import org.hibernate.query.TypedParameterValue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -44,7 +43,8 @@ class JpaParametersParameterAccessorTests { Method withNativeQuery = SampleRepository.class.getMethod("withNativeQuery", Integer.class); Object[] values = { null }; JpaParameters parameters = new JpaParameters(withNativeQuery); - JpaParametersParameterAccessor accessor = PersistenceProvider.GENERIC_JPA.getParameterAccessor(parameters, values, em); + JpaParametersParameterAccessor accessor = PersistenceProvider.GENERIC_JPA.getParameterAccessor(parameters, values, + em); bind(parameters, accessor); @@ -62,10 +62,10 @@ class JpaParametersParameterAccessorTests { bind(parameters, accessor); - ArgumentCaptor captor = ArgumentCaptor.forClass(TypedParameterValue.class); + ArgumentCaptor> captor = ArgumentCaptor.forClass(TypedParameterValue.class); verify(query).setParameter(eq(1), captor.capture()); - TypedParameterValue captorValue = captor.getValue(); - assertThat(captorValue.getType()).isEqualTo(StandardBasicTypes.INTEGER); + TypedParameterValue captorValue = captor.getValue(); + assertThat(captorValue.getType().getBindableJavaType()).isEqualTo(Integer.class); assertThat(captorValue.getValue()).isNull(); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java index 04b9e53b5..c8a1ddead 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java @@ -17,16 +17,15 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; -import java.lang.reflect.Method; - import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.ParameterExpression; +import java.lang.reflect.Method; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.repository.query.DefaultParameters; import org.springframework.data.repository.query.Parameters; @@ -60,7 +59,7 @@ class ParameterExpressionProviderTests { ParameterMetadataProvider provider = new ParameterMetadataProvider(builder, accessor, EscapeCharacter.DEFAULT); ParameterExpression expression = provider.next(part, Comparable.class).getExpression(); - assertThat(expression.getParameterType()).isEqualTo(int.class); + assertThat(expression.getParameterType()).isEqualTo(Integer.class); } interface SampleRepository { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java index e0177b213..980cef7b4 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java @@ -20,6 +20,11 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; import static org.springframework.test.util.ReflectionTestUtils.*; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; +import jakarta.persistence.TemporalType; + import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; @@ -27,14 +32,9 @@ import java.util.Date; import java.util.Iterator; import java.util.List; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.Query; -import jakarta.persistence.TemporalType; - import org.hibernate.Version; -import org.hibernate.query.internal.QueryImpl; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.data.domain.Page; @@ -65,6 +65,7 @@ import org.springframework.util.Assert; public class PartTreeJpaQueryIntegrationTests { private static String PROPERTY = "h.target." + getQueryProperty(); + private static Class HIBERNATE_NATIVE_QUERY = org.hibernate.query.Query.class; @PersistenceContext EntityManager entityManager; @@ -82,7 +83,7 @@ public class PartTreeJpaQueryIntegrationTests { PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { "Matthews", PageRequest.of(0, 1) })); - jpaQuery.createQuery((getAccessor(queryMethod, new Object[] { "Matthews", PageRequest.of(0, 1) }))); + jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { "Matthews", PageRequest.of(0, 1) })); } @Test @@ -100,18 +101,19 @@ public class PartTreeJpaQueryIntegrationTests { } @Test // DATAJPA-121 + @Disabled // HHH-15389 void recreatesQueryIfNullValueIsGiven() throws Exception { JpaQueryMethod queryMethod = getQueryMethod("findByFirstname", String.class, Pageable.class); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); - Query query = jpaQuery.createQuery((getAccessor(queryMethod, new Object[] { "Matthews", PageRequest.of(0, 1) }))); + Query query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { "Matthews", PageRequest.of(0, 1) })); - assertThat(HibernateUtils.getHibernateQuery(query.unwrap(QueryImpl.class))).endsWith("firstname=:param0"); + assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("firstname=:param0"); - query = jpaQuery.createQuery((getAccessor(queryMethod, new Object[] { null, PageRequest.of(0, 1) }))); + query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { null, PageRequest.of(0, 1) })); - assertThat(HibernateUtils.getHibernateQuery(query.unwrap(QueryImpl.class))).endsWith("firstname is null"); + assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("firstname is null"); } @Test // DATAJPA-920 @@ -120,42 +122,45 @@ public class PartTreeJpaQueryIntegrationTests { JpaQueryMethod queryMethod = getQueryMethod("existsByFirstname", String.class); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); - Query query = jpaQuery.createQuery((getAccessor(queryMethod, new Object[] { "Matthews" }))); + Query query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { "Matthews" })); assertThat(query.getMaxResults()).isEqualTo(1); } @Test // DATAJPA-920 + @Disabled // HHH-15389 void shouldSelectAliasedIdForExistsProjectionQueries() throws Exception { JpaQueryMethod queryMethod = getQueryMethod("existsByFirstname", String.class); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); - Query query = jpaQuery.createQuery((getAccessor(queryMethod, new Object[] { "Matthews" }))); + Query query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] { "Matthews" })); - assertThat(HibernateUtils.getHibernateQuery(query.unwrap(QueryImpl.class))).contains(".id from User as"); + assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).contains(".id from User as"); } @Test // DATAJPA-1074 + @Disabled // HHH-15389 void isEmptyCollection() throws Exception { JpaQueryMethod queryMethod = getQueryMethod("findByRolesIsEmpty"); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); - Query query = jpaQuery.createQuery((getAccessor(queryMethod, new Object[] {}))); + Query query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] {})); - assertThat(HibernateUtils.getHibernateQuery(query.unwrap(QueryImpl.class))).endsWith("roles is empty"); + assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("roles is empty"); } @Test // DATAJPA-1074 + @Disabled // HHH-15389 void isNotEmptyCollection() throws Exception { JpaQueryMethod queryMethod = getQueryMethod("findByRolesIsNotEmpty"); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); - Query query = jpaQuery.createQuery((getAccessor(queryMethod, new Object[] {}))); + Query query = jpaQuery.createQuery(getAccessor(queryMethod, new Object[] {})); - assertThat(HibernateUtils.getHibernateQuery(query.unwrap(QueryImpl.class))).endsWith("roles is not empty"); + assertThat(HibernateUtils.getHibernateQuery(query.unwrap(HIBERNATE_NATIVE_QUERY))).endsWith("roles is not empty"); } @Test // DATAJPA-1074 @@ -236,7 +241,7 @@ public class PartTreeJpaQueryIntegrationTests { JpaQueryMethod queryMethod = getQueryMethod(methodName, parameterTypes); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); - jpaQuery.createQuery((getAccessor(queryMethod, values))); + jpaQuery.createQuery(getAccessor(queryMethod, values)); } private JpaQueryMethod getQueryMethod(String methodName, Class... parameterTypes) throws Exception { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index f9fd6b986..51659d968 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -582,8 +582,8 @@ public interface UserRepository // DATAJPA-1233 @Query( - value = "SELECT u FROM User u WHERE ?2 = 'x' ORDER BY CASE WHEN (u.firstname >= ?1) THEN 0 ELSE 1 END, u.firstname") - Page findAllOrderedBySpecialNameMultipleParamsIndexed(String name, String other, Pageable page); + value = "SELECT u FROM User u WHERE ?1 = 'x' ORDER BY CASE WHEN (u.firstname >= ?2) THEN 0 ELSE 1 END, u.firstname") + Page findAllOrderedBySpecialNameMultipleParamsIndexed(String other, String name, Pageable page); // DATAJPA-928 Page findByNativeNamedQueryWithPageable(Pageable pageable); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java index 5ac718189..5776d82b0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java @@ -17,27 +17,18 @@ package org.springframework.data.jpa.repository.support; import static org.assertj.core.api.Assertions.*; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - import jakarta.persistence.EntityManager; -import javax.sql.DataSource; +import jakarta.persistence.EntityManagerFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.Primary; -import org.springframework.orm.jpa.JpaVendorAdapter; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.stereotype.Component; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -62,59 +53,35 @@ public class EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests { assertThat(target.primaryEm).isNotNull(); } - /** - * Annotation to demarcate test components. - * - * @author Oliver Gierke - */ - @Component - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.TYPE) - private static @interface TestComponent { - - } - @Configuration + @Import(EntityManagerInjectionTarget.class) @ImportResource("classpath:infrastructure.xml") - @ComponentScan(includeFilters = @Filter(TestComponent.class), useDefaultFilters = false) static class Config { - @Autowired DataSource dataSource; - @Autowired JpaVendorAdapter vendorAdapter; + @Autowired @Qualifier("entityManagerFactory") EntityManagerFactory emf; @Bean public static EntityManagerBeanDefinitionRegistrarPostProcessor processor() { return new EntityManagerBeanDefinitionRegistrarPostProcessor(); } - private LocalContainerEntityManagerFactoryBean emf() { - - LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean(); - factoryBean.setPersistenceUnitName("spring-data-jpa"); - factoryBean.setDataSource(dataSource); - factoryBean.setJpaVendorAdapter(vendorAdapter); - - return factoryBean; + @Bean + EntityManagerFactory firstEmf() { + return emf; } @Bean - LocalContainerEntityManagerFactoryBean firstEmf() { - return emf(); - } - - @Bean - LocalContainerEntityManagerFactoryBean secondEmf() { - return emf(); + EntityManagerFactory secondEmf() { + return emf; } @Primary @Bean - LocalContainerEntityManagerFactoryBean thirdEmf() { - return emf(); + EntityManagerFactory thirdEmf() { + return emf; } } - @TestComponent static class EntityManagerInjectionTarget { private final EntityManager firstEm; diff --git a/spring-data-jpa/src/test/resources/hibernate.xml b/spring-data-jpa/src/test/resources/hibernate.xml index fafd2e120..05420a9be 100644 --- a/spring-data-jpa/src/test/resources/hibernate.xml +++ b/spring-data-jpa/src/test/resources/hibernate.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> - + diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 1c71bcf5d..7d4b34656 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -1,6 +1,30 @@ [[new-features]] = New & Noteworthy +[[new-features.3-0]] +== What's New in Spring Data JPA 3.0 +* Upgrade to Hibernate 6. +See <> for what to consider when upgrading. +* Support for null handling definitions via `Sort`. + +[[new-features.3-0.hibernate-6]] +=== Upgrading to Hibernate 6 +Spring Data 3.0 upgrades its Hibernate baseline to Hibernate 6. +As quite a few things have changed in that version, a couple of things that have worked before might need some tweaks. + +* _Using JPA named queries with pagination_ -- Pagination requires Spring Data to derive a count query from the originally declared one loading the actual content of the Page. +For queries declared as JPA named queries we have relied on provider-specific API to obtain the original source query and tweak it accordingly. +On Hibernate 6, in certain arrangements that query extraction might fail. +We recommend to either rather declare the queries on the repository methods directly using `@Query`. +* _Using positional parameters with pagination_ -- When using positional parameters with pagination queries you need to make sure that the parameter indexes still start with 1, even with a potential `ORDER BY` clause removed from the query. +This is because, the count query derived from the original one will have that clause removed from the query and Hibernate 6 rejects queries parameter indexes not starting at 1. +We generally recommend to use named parameters anyway. +* _Applying JPA entity graphs_ -- Under certain model conditions, the application of entity graphs might fail on Hibernate 6. +See https://hibernate.atlassian.net/browse/HHH-15391[this ticket] for details. +We generally recommend to rather use <> instead of entity graphs. +* _Using `… like … escape ?#{escapeCharacter()}` in queries_ -- If you have customized the global default escape character (via `@EnableJpaRepositories(escapeCharacter = '…')`) the application of that through the corresponding SpEL expression currently fails. +See https://hibernate.atlassian.net/browse/HHH-15392[this ticket] for details. + [[new-features.2-5-0]] == What's New in Spring Data JPA 2.5