Upgrade to Hibernate and Hibernate Envers 6.
TypedQuery inspection through its String representations seems to be a bit flaky in Hibernate 6 still [0]. Tweaked the code to extract a query string from a query object to try the new way first but fall back to the old way, as this seems to work under some conditions, too. Adapted the test case in which we could rather inspect the new SqmQuery API for test result verification. Re-bootstrapping the EntityManagerFactory for the same persistence unit causes the second bootstrap to fail als apparently foreign key names are randomized and the second bootstrap doesn't create a new constraint but tries to work with a new name. Tweaked the offending test case to reuse the existing EMF declaration as it actually only tests the qualified wiring into clients. Applying an entity graph is causing a StackOverflow in current Hibernate 6. Filed an issue [1] and disabled the test case for now. Dial back on the flip to use String as parameter type for like expression escape characters as Eclipselink rejects that. The JPA spec chapter 4.2.10 allows both Character and String to be used. Filed [2] with Hibernate to ask for reintroduction of the support for characters and commented out the test cases for now. Deprecated CustomHsqlHibernateJpaVendorAdapter as it's not needed on Hibernate 6 anymore. Rewrote HibernateJpaParametersParameterAccessor to use Hibernate 6 API. Add Hibernate 6 upgrade information to the reference docs. Related ticket: #2423. [0] https://hibernate.atlassian.net/browse/HHH-15389 [1] https://hibernate.atlassian.net/browse/HHH-15391 [2] https://hibernate.atlassian.net/browse/HHH-15392
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -31,7 +31,7 @@
|
||||
<!-- AspectJ maven plugin can't handle 17 yet -->
|
||||
|
||||
<eclipselink>3.0.2</eclipselink>
|
||||
<hibernate>5.6.9.Final</hibernate>
|
||||
<hibernate>6.1.1.Final</hibernate>
|
||||
<jsqlparser>4.3</jsqlparser>
|
||||
<mysql-connector-java>8.0.23</mysql-connector-java>
|
||||
<postgresql>42.2.19</postgresql>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<modules>
|
||||
<module>spring-data-envers</module>
|
||||
<module>spring-data-jpa</module>
|
||||
<module>spring-data-jpa-distribution</module>
|
||||
|
||||
@@ -62,8 +62,8 @@
|
||||
|
||||
<!-- Hibernate -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-envers-jakarta</artifactId>
|
||||
<groupId>org.hibernate.orm</groupId>
|
||||
<artifactId>hibernate-envers</artifactId>
|
||||
<version>${hibernate.envers}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -145,19 +145,26 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${hibernate.groupId}</groupId>
|
||||
<artifactId>hibernate-core-jakarta</artifactId>
|
||||
<groupId>${hibernate.groupId}.orm</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${hibernate.groupId}</groupId>
|
||||
<artifactId>hibernate-jpamodelgen-jakarta</artifactId>
|
||||
<groupId>${hibernate.groupId}.orm</groupId>
|
||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||
<version>${hibernate}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
<version>${jaxb}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<PersistenceProvider> ALL = List.of(HIBERNATE, ECLIPSELINK, GENERIC_JPA);
|
||||
|
||||
static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
|
||||
private final Iterable<String> entityManagerClassNames;
|
||||
private final Iterable<String> 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<Object> {
|
||||
|
||||
private final @Nullable ScrollableResults scrollableResults;
|
||||
private final @Nullable ScrollableResults<Object[]> 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<Object[]> 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;
|
||||
}
|
||||
|
||||
@@ -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 <a href="https://www.codesmell.org/blog/2008/12/hibernate-hsql-native-queries-and-booleans/">https://www.codesmell.org/blog/2008/12/hibernate-hsql-native-queries-and-booleans/</a>
|
||||
* @see <a href=
|
||||
* "https://www.codesmell.org/blog/2008/12/hibernate-hsql-native-queries-and-booleans/">https://www.codesmell.org/blog/2008/12/hibernate-hsql-native-queries-and-booleans/</a>
|
||||
* @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 {}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<? extends Object> 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<User, Integer> {
|
||||
void findDistinctByRolesIn(List<Role> roles);
|
||||
List<User> findDistinctByRolesIn(List<Role> roles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TypedParameterValue> captor = ArgumentCaptor.forClass(TypedParameterValue.class);
|
||||
ArgumentCaptor<TypedParameterValue<?>> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<? extends Comparable> expression = provider.next(part, Comparable.class).getExpression();
|
||||
|
||||
assertThat(expression.getParameterType()).isEqualTo(int.class);
|
||||
assertThat(expression.getParameterType()).isEqualTo(Integer.class);
|
||||
}
|
||||
|
||||
interface SampleRepository {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<User> 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<User> findAllOrderedBySpecialNameMultipleParamsIndexed(String other, String name, Pageable page);
|
||||
|
||||
// DATAJPA-928
|
||||
Page<User> findByNativeNamedQueryWithPageable(Pageable pageable);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
<bean id="vendorAdaptor" class="org.springframework.data.jpa.repository.CustomHsqlHibernateJpaVendorAdaptor" parent="abstractVendorAdaptor" />
|
||||
<bean id="vendorAdaptor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" parent="abstractVendorAdaptor" />
|
||||
|
||||
<util:properties id="jpaProperties" />
|
||||
|
||||
|
||||
@@ -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 <<new-features.3-0.hibernate-6, this section>> 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 <<projections, interface or DTO projections>> 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user