Skip to content

Commit 453f879

Browse files
committed
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
1 parent b3a18c0 commit 453f879

18 files changed

+183
-127
lines changed

Diff for: pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<!-- AspectJ maven plugin can't handle 17 yet -->
3232

3333
<eclipselink>3.0.2</eclipselink>
34-
<hibernate>5.6.9.Final</hibernate>
34+
<hibernate>6.1.1.Final</hibernate>
3535
<jsqlparser>4.3</jsqlparser>
3636
<mysql-connector-java>8.0.23</mysql-connector-java>
3737
<postgresql>42.2.19</postgresql>
@@ -44,7 +44,7 @@
4444

4545
</properties>
4646

47-
<modules>
47+
<modules>
4848
<module>spring-data-envers</module>
4949
<module>spring-data-jpa</module>
5050
<module>spring-data-jpa-distribution</module>

Diff for: spring-data-envers/pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@
6262

6363
<!-- Hibernate -->
6464
<dependency>
65-
<groupId>org.hibernate</groupId>
66-
<artifactId>hibernate-envers-jakarta</artifactId>
65+
<groupId>org.hibernate.orm</groupId>
66+
<artifactId>hibernate-envers</artifactId>
6767
<version>${hibernate.envers}</version>
6868
</dependency>
6969

Diff for: spring-data-jpa/pom.xml

+11-4
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,26 @@
145145
</dependency>
146146

147147
<dependency>
148-
<groupId>${hibernate.groupId}</groupId>
149-
<artifactId>hibernate-core-jakarta</artifactId>
148+
<groupId>${hibernate.groupId}.orm</groupId>
149+
<artifactId>hibernate-core</artifactId>
150150
<version>${hibernate}</version>
151151
<optional>true</optional>
152152
</dependency>
153153

154154
<dependency>
155-
<groupId>${hibernate.groupId}</groupId>
156-
<artifactId>hibernate-jpamodelgen-jakarta</artifactId>
155+
<groupId>${hibernate.groupId}.orm</groupId>
156+
<artifactId>hibernate-jpamodelgen</artifactId>
157157
<version>${hibernate}</version>
158158
<scope>provided</scope>
159159
</dependency>
160160

161+
<dependency>
162+
<groupId>jakarta.xml.bind</groupId>
163+
<artifactId>jakarta.xml.bind-api</artifactId>
164+
<version>${jaxb}</version>
165+
<scope>provided</scope>
166+
</dependency>
167+
161168
<dependency>
162169
<groupId>jakarta.annotation</groupId>
163170
<artifactId>jakarta.annotation-api</artifactId>

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateJpaParametersParameterAccessor.java

+20-9
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
package org.springframework.data.jpa.provider;
1717

1818
import jakarta.persistence.EntityManager;
19-
import org.hibernate.SessionFactory;
20-
import org.hibernate.TypeHelper;
21-
import org.hibernate.jpa.TypedParameterValue;
22-
import org.hibernate.type.Type;
19+
20+
import org.hibernate.engine.spi.SessionFactoryImplementor;
21+
import org.hibernate.query.TypedParameterValue;
22+
import org.hibernate.type.BasicTypeRegistry;
2323
import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
2424
import org.springframework.data.repository.query.Parameter;
2525
import org.springframework.data.repository.query.Parameters;
2626
import org.springframework.data.repository.query.ParametersParameterAccessor;
27+
import org.springframework.lang.Nullable;
2728

2829
/**
2930
* {@link org.springframework.data.repository.query.ParameterAccessor} based on an {@link Parameters} instance. In
@@ -33,11 +34,13 @@
3334
* @author Wonchul Heo
3435
* @author Jens Schauder
3536
* @author Cedomir Igaly
37+
* @author Robert Wilson
38+
* @author Oliver Drotbohm
3639
* @since 2.7
3740
*/
3841
class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAccessor {
3942

40-
private final TypeHelper typeHelper;
43+
private final BasicTypeRegistry typeHelper;
4144

4245
/**
4346
* Creates a new {@link ParametersParameterAccessor}.
@@ -50,21 +53,29 @@ class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAcce
5053

5154
super(parameters, values);
5255

53-
this.typeHelper = em.getEntityManagerFactory().unwrap(SessionFactory.class).getTypeHelper();
56+
this.typeHelper = em.getEntityManagerFactory()
57+
.unwrap(SessionFactoryImplementor.class)
58+
.getTypeConfiguration()
59+
.getBasicTypeRegistry();
5460
}
5561

5662
@Override
63+
@Nullable
64+
@SuppressWarnings("unchecked")
5765
public Object getValue(Parameter parameter) {
5866

59-
Object value = super.getValue(parameter.getIndex());
67+
var value = super.getValue(parameter.getIndex());
68+
6069
if (value != null) {
6170
return value;
6271
}
6372

64-
Type type = typeHelper.basic(parameter.getType());
73+
var type = typeHelper.getRegisteredType(parameter.getType());
74+
6575
if (type == null) {
6676
return null;
6777
}
68-
return new TypedParameterValue(type, null);
78+
79+
return new TypedParameterValue<>(type, null);
6980
}
7081
}

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.provider;
1717

1818
import org.hibernate.query.Query;
19+
import org.hibernate.query.spi.SqmQuery;
1920
import org.springframework.lang.Nullable;
2021

2122
/**
@@ -41,8 +42,20 @@ private HibernateUtils() {}
4142
@Nullable
4243
public static String getHibernateQuery(Object query) {
4344

45+
try {
46+
47+
// Try the new Hibernate implementation first
48+
if (query instanceof SqmQuery) {
49+
return ((SqmQuery) query).getSqmStatement().toHqlString();
50+
}
51+
52+
// Couple of cases in which this still breaks, see HHH-15389
53+
} catch (RuntimeException o_O) {}
54+
55+
// Try the old way, as it still works in some cases (haven't investigated in which exactly)
56+
4457
if (query instanceof Query) {
45-
return ((Query) query).getQueryString();
58+
return ((Query<?>) query).getQueryString();
4659
} else {
4760
throw new IllegalArgumentException("Don't know how to extract the query string from " + query);
4861
}

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import jakarta.persistence.metamodel.Metamodel;
2525
import jakarta.persistence.metamodel.SingularAttribute;
2626

27+
import java.util.Collection;
2728
import java.util.Collections;
29+
import java.util.List;
2830
import java.util.NoSuchElementException;
2931
import java.util.Set;
3032

@@ -186,6 +188,8 @@ public String getCommentHintKey() {
186188
}
187189
};
188190

191+
private static final Collection<PersistenceProvider> ALL = List.of(HIBERNATE, ECLIPSELINK, GENERIC_JPA);
192+
189193
static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
190194
private final Iterable<String> entityManagerClassNames;
191195
private final Iterable<String> metamodelClassNames;
@@ -233,7 +237,7 @@ public static PersistenceProvider fromEntityManager(EntityManager em) {
233237
return cachedProvider;
234238
}
235239

236-
for (PersistenceProvider provider : values()) {
240+
for (PersistenceProvider provider : ALL) {
237241
for (String entityManagerClassName : provider.entityManagerClassNames) {
238242
if (isEntityManagerOfType(em, entityManagerClassName)) {
239243
return cacheAndReturn(entityManagerType, provider);
@@ -317,9 +321,9 @@ interface Constants {
317321
String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "jakarta.persistence.EntityManager";
318322
String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager";
319323
// needed as Spring only exposes that interface via the EM proxy
320-
String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.jpa.HibernateEntityManager";
324+
String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.engine.spi.SessionImplementor";
321325

322-
String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.internal.MetamodelImpl";
326+
String HIBERNATE_JPA_METAMODEL_TYPE = "org.hibernate.metamodel.model.domain.JpaMetamodel";
323327
String ECLIPSELINK_JPA_METAMODEL_TYPE = "org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl";
324328
}
325329

@@ -337,7 +341,7 @@ public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
337341
*/
338342
private static class HibernateScrollableResultsIterator implements CloseableIterator<Object> {
339343

340-
private final @Nullable ScrollableResults scrollableResults;
344+
private final @Nullable ScrollableResults<Object[]> scrollableResults;
341345

342346
/**
343347
* Creates a new {@link HibernateScrollableResultsIterator} for the given {@link Query}.
@@ -346,7 +350,7 @@ private static class HibernateScrollableResultsIterator implements CloseableIter
346350
*/
347351
HibernateScrollableResultsIterator(Query jpaQuery) {
348352

349-
org.hibernate.query.Query<?> query = jpaQuery.unwrap(org.hibernate.query.Query.class);
353+
org.hibernate.query.Query<Object[]> query = jpaQuery.unwrap(org.hibernate.query.Query.class);
350354
this.scrollableResults = query.setReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly())//
351355
.scroll(ScrollMode.FORWARD_ONLY);
352356
}
@@ -359,7 +363,7 @@ public Object next() {
359363
}
360364

361365
// Cast needed for Hibernate 6 compatibility
362-
Object[] row = (Object[]) scrollableResults.get();
366+
Object[] row = scrollableResults.get();
363367

364368
return row.length == 1 ? row[0] : row;
365369
}

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/CustomHsqlHibernateJpaVendorAdaptor.java

+9-15
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,29 @@
1515
*/
1616
package org.springframework.data.jpa.repository;
1717

18-
import java.sql.Types;
19-
2018
import org.hibernate.dialect.HSQLDialect;
2119
import org.springframework.orm.jpa.vendor.Database;
2220
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
2321

2422
/**
2523
* Fix for missing type declarations for HSQL.
2624
*
27-
* @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>
25+
* @see <a href=
26+
* "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>
2827
* @author Oliver Gierke
28+
* @deprecated since 3.0 without replacement as it's not needed anymore.
2929
*/
30+
@Deprecated
3031
public class CustomHsqlHibernateJpaVendorAdaptor extends HibernateJpaVendorAdapter {
3132

3233
@Override
3334
protected Class<?> determineDatabaseDialectClass(Database database) {
34-
35-
if (Database.HSQL.equals(database)) {
36-
return CustomHsqlDialect.class;
37-
}
38-
3935
return super.determineDatabaseDialectClass(database);
4036
}
4137

42-
public static class CustomHsqlDialect extends HSQLDialect {
43-
44-
public CustomHsqlDialect() {
45-
registerColumnType(Types.BOOLEAN, "boolean");
46-
registerHibernateType(Types.BOOLEAN, "boolean");
47-
}
48-
}
38+
/**
39+
* @deprecated since 3.0 without replacement as it's not needed anymore.
40+
*/
41+
@Deprecated
42+
public static class CustomHsqlDialect extends HSQLDialect {}
4943
}

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.springframework.data.jpa.support.EntityManagerTestUtils.*;
2020

21-
import java.util.List;
22-
2321
import jakarta.persistence.EntityManager;
2422
import jakarta.persistence.Persistence;
2523
import jakarta.persistence.PersistenceUtil;
@@ -28,12 +26,14 @@
2826
import jakarta.persistence.criteria.Predicate;
2927
import jakarta.persistence.criteria.Root;
3028

29+
import java.util.List;
30+
3131
import org.assertj.core.api.SoftAssertions;
3232
import org.junit.Assume;
3333
import org.junit.jupiter.api.BeforeEach;
34+
import org.junit.jupiter.api.Disabled;
3435
import org.junit.jupiter.api.Test;
3536
import org.junit.jupiter.api.extension.ExtendWith;
36-
3737
import org.springframework.beans.factory.annotation.Autowired;
3838
import org.springframework.data.domain.Page;
3939
import org.springframework.data.domain.PageRequest;
@@ -94,6 +94,7 @@ void setup() {
9494
}
9595

9696
@Test // DATAJPA-612
97+
@Disabled // HHH-15391
9798
void shouldRespectConfiguredJpaEntityGraph() {
9899

99100
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java

+18
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
package org.springframework.data.jpa.repository;
1717

1818
import static org.assertj.core.api.Assertions.*;
19+
import static org.junit.Assume.*;
1920
import static org.springframework.data.domain.Sort.Direction.*;
2021

22+
import jakarta.persistence.EntityManager;
23+
2124
import java.util.Arrays;
2225
import java.util.List;
2326

@@ -34,6 +37,7 @@
3437
import org.springframework.data.domain.Sort;
3538
import org.springframework.data.jpa.domain.sample.Role;
3639
import org.springframework.data.jpa.domain.sample.User;
40+
import org.springframework.data.jpa.provider.PersistenceProvider;
3741
import org.springframework.data.jpa.repository.sample.RoleRepository;
3842
import org.springframework.data.jpa.repository.sample.UserRepository;
3943
import org.springframework.data.repository.query.QueryLookupStrategy;
@@ -55,6 +59,9 @@ public class UserRepositoryFinderTests {
5559

5660
@Autowired UserRepository userRepository;
5761
@Autowired RoleRepository roleRepository;
62+
@Autowired EntityManager em;
63+
64+
PersistenceProvider provider;
5865

5966
private User dave;
6067
private User carter;
@@ -73,6 +80,8 @@ void setUp() {
7380
dave = userRepository.save(new User("Dave", "Matthews", "[email protected]", singer));
7481
carter = userRepository.save(new User("Carter", "Beauford", "[email protected]", singer, drummer));
7582
oliver = userRepository.save(new User("Oliver August", "Matthews", "[email protected]"));
83+
84+
provider = PersistenceProvider.fromEntityManager(em);
7685
}
7786

7887
@AfterEach
@@ -226,6 +235,9 @@ void parametersForContainsGetProperlyEscaped() {
226235
@Test // DATAJPA-1519
227236
void escapingInLikeSpels() {
228237

238+
// HHH-15392
239+
assumeFalse(provider.equals(PersistenceProvider.HIBERNATE));
240+
229241
User extra = new User("extra", "Matt_ew", "extra");
230242

231243
userRepository.save(extra);
@@ -236,6 +248,9 @@ void escapingInLikeSpels() {
236248
@Test // DATAJPA-1522
237249
void escapingInLikeSpelsInThePresenceOfEscapeCharacters() {
238250

251+
// HHH-15392
252+
assumeFalse(provider.equals(PersistenceProvider.HIBERNATE));
253+
239254
User withEscapeCharacter = userRepository.save(new User("extra", "Matt\\xew", "extra1"));
240255
userRepository.save(new User("extra", "Matt\\_ew", "extra2"));
241256

@@ -245,6 +260,9 @@ void escapingInLikeSpelsInThePresenceOfEscapeCharacters() {
245260
@Test // DATAJPA-1522
246261
void escapingInLikeSpelsInThePresenceOfEscapedWildcards() {
247262

263+
// HHH-15392
264+
assumeFalse(provider.equals(PersistenceProvider.HIBERNATE));
265+
248266
userRepository.save(new User("extra", "Matt\\xew", "extra1"));
249267
User withEscapedWildcard = userRepository.save(new User("extra", "Matt\\_ew", "extra2"));
250268

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2670,7 +2670,7 @@ void handlesCountQueriesWithLessParametersMoreThanOne() {
26702670

26712671
@Test // DATAJPA-1233
26722672
void handlesCountQueriesWithLessParametersMoreThanOneIndexed() {
2673-
repository.findAllOrderedBySpecialNameMultipleParamsIndexed("Oliver", "x", PageRequest.of(2, 3));
2673+
repository.findAllOrderedBySpecialNameMultipleParamsIndexed("x", "Oliver", PageRequest.of(2, 3));
26742674
}
26752675

26762676
// DATAJPA-928

0 commit comments

Comments
 (0)