Skip to content

Commit 58246ec

Browse files
committed
Merge branch '6.2.x'
2 parents 07ae185 + d8f8e76 commit 58246ec

File tree

4 files changed

+119
-51
lines changed

4 files changed

+119
-51
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/ListableBeanFactory.java

+13-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -146,8 +146,6 @@ public interface ListableBeanFactory extends BeanFactory {
146146
* <p>Does not consider any hierarchy this factory may participate in.
147147
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
148148
* to include beans in ancestor factories too.
149-
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
150-
* by other means than bean definitions.
151149
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
152150
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
153151
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -177,8 +175,6 @@ public interface ListableBeanFactory extends BeanFactory {
177175
* <p>Does not consider any hierarchy this factory may participate in.
178176
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
179177
* to include beans in ancestor factories too.
180-
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
181-
* by other means than bean definitions.
182178
* <p>Bean names returned by this method should always return bean names <i>in the
183179
* order of definition</i> in the backend configuration, as far as possible.
184180
* @param type the generically typed class or interface to match
@@ -211,8 +207,6 @@ public interface ListableBeanFactory extends BeanFactory {
211207
* <p>Does not consider any hierarchy this factory may participate in.
212208
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
213209
* to include beans in ancestor factories too.
214-
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
215-
* by other means than bean definitions.
216210
* <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
217211
* be it singletons, prototypes, or FactoryBeans. In most implementations, the
218212
* result will be the same as for {@code getBeanNamesForType(type, true, true)}.
@@ -240,8 +234,6 @@ public interface ListableBeanFactory extends BeanFactory {
240234
* <p>Does not consider any hierarchy this factory may participate in.
241235
* Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
242236
* to include beans in ancestor factories too.
243-
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
244-
* by other means than bean definitions.
245237
* <p>Bean names returned by this method should always return bean names <i>in the
246238
* order of definition</i> in the backend configuration, as far as possible.
247239
* @param type the class or interface to match, or {@code null} for all bean names
@@ -266,21 +258,24 @@ public interface ListableBeanFactory extends BeanFactory {
266258
* subclasses), judging from either bean definitions or the value of
267259
* {@code getObjectType} in the case of FactoryBeans.
268260
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
269-
* check nested beans which might match the specified type as well.
261+
* check nested beans which might match the specified type as well. Also, it
262+
* <b>suppresses exceptions for beans that are currently in creation in a circular
263+
* reference scenario:</b> typically, references back to the caller of this method.
270264
* <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
271265
* will get initialized. If the object created by the FactoryBean doesn't match,
272266
* the raw FactoryBean itself will be matched against the type.
273267
* <p>Does not consider any hierarchy this factory may participate in.
274268
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
275269
* to include beans in ancestor factories too.
276-
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
277-
* by other means than bean definitions.
278270
* <p>This version of getBeansOfType matches all kinds of beans, be it
279271
* singletons, prototypes, or FactoryBeans. In most implementations, the
280272
* result will be the same as for {@code getBeansOfType(type, true, true)}.
281273
* <p>The Map returned by this method should always return bean names and
282274
* corresponding bean instances <i>in the order of definition</i> in the
283275
* backend configuration, as far as possible.
276+
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
277+
* calls for specific bean names in preference to this Map-based retrieval method.</b>
278+
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
284279
* @param type the class or interface to match, or {@code null} for all concrete beans
285280
* @return a Map with the matching beans, containing the bean names as
286281
* keys and the corresponding bean instances as values
@@ -296,7 +291,9 @@ public interface ListableBeanFactory extends BeanFactory {
296291
* subclasses), judging from either bean definitions or the value of
297292
* {@code getObjectType} in the case of FactoryBeans.
298293
* <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
299-
* check nested beans which might match the specified type as well.
294+
* check nested beans which might match the specified type as well. Also, it
295+
* <b>suppresses exceptions for beans that are currently in creation in a circular
296+
* reference scenario:</b> typically, references back to the caller of this method.
300297
* <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
301298
* which means that FactoryBeans will get initialized. If the object created by the
302299
* FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
@@ -305,11 +302,12 @@ public interface ListableBeanFactory extends BeanFactory {
305302
* <p>Does not consider any hierarchy this factory may participate in.
306303
* Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
307304
* to include beans in ancestor factories too.
308-
* <p>Note: Does <i>not</i> ignore singleton beans that have been registered
309-
* by other means than bean definitions.
310305
* <p>The Map returned by this method should always return bean names and
311306
* corresponding bean instances <i>in the order of definition</i> in the
312307
* backend configuration, as far as possible.
308+
* <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
309+
* calls for specific bean names in preference to this Map-based retrieval method.</b>
310+
* Aside from lazy instantiation benefits, this also avoids any exception suppression.
313311
* @param type the class or interface to match, or {@code null} for all concrete beans
314312
* @param includeNonSingletons whether to include prototype or scoped beans too
315313
* or just singletons (also applies to FactoryBeans)

spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java

+31-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -249,14 +249,18 @@ public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean r
249249
* @return the corresponding DataAccessException instance
250250
*/
251251
protected DataAccessException convertHibernateAccessException(HibernateException ex) {
252-
if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException jdbcEx) {
252+
return convertHibernateAccessException(ex, ex);
253+
}
254+
255+
private DataAccessException convertHibernateAccessException(HibernateException ex, HibernateException exToCheck) {
256+
if (this.jdbcExceptionTranslator != null && exToCheck instanceof JDBCException jdbcEx) {
253257
DataAccessException dae = this.jdbcExceptionTranslator.translate(
254258
"Hibernate operation: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
255259
if (dae != null) {
256260
return dae;
257261
}
258262
}
259-
if (this.transactionExceptionTranslator != null && ex instanceof org.hibernate.TransactionException) {
263+
if (this.transactionExceptionTranslator != null && exToCheck instanceof org.hibernate.TransactionException) {
260264
if (ex.getCause() instanceof SQLException sqlEx) {
261265
DataAccessException dae = this.transactionExceptionTranslator.translate(
262266
"Hibernate transaction: " + ex.getMessage(), null, sqlEx);
@@ -266,74 +270,77 @@ protected DataAccessException convertHibernateAccessException(HibernateException
266270
}
267271
}
268272

269-
if (ex instanceof JDBCConnectionException) {
273+
if (exToCheck instanceof JDBCConnectionException) {
270274
return new DataAccessResourceFailureException(ex.getMessage(), ex);
271275
}
272-
if (ex instanceof SQLGrammarException hibEx) {
276+
if (exToCheck instanceof SQLGrammarException hibEx) {
273277
return new InvalidDataAccessResourceUsageException(ex.getMessage() + "; SQL [" + hibEx.getSQL() + "]", ex);
274278
}
275-
if (ex instanceof QueryTimeoutException hibEx) {
279+
if (exToCheck instanceof QueryTimeoutException hibEx) {
276280
return new org.springframework.dao.QueryTimeoutException(ex.getMessage() + "; SQL [" + hibEx.getSQL() + "]", ex);
277281
}
278-
if (ex instanceof LockAcquisitionException hibEx) {
282+
if (exToCheck instanceof LockAcquisitionException hibEx) {
279283
return new CannotAcquireLockException(ex.getMessage() + "; SQL [" + hibEx.getSQL() + "]", ex);
280284
}
281-
if (ex instanceof PessimisticLockException hibEx) {
285+
if (exToCheck instanceof PessimisticLockException hibEx) {
282286
return new PessimisticLockingFailureException(ex.getMessage() + "; SQL [" + hibEx.getSQL() + "]", ex);
283287
}
284-
if (ex instanceof ConstraintViolationException hibEx) {
288+
if (exToCheck instanceof ConstraintViolationException hibEx) {
285289
return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + hibEx.getSQL() +
286290
"]; constraint [" + hibEx.getConstraintName() + "]", ex);
287291
}
288-
if (ex instanceof DataException hibEx) {
292+
if (exToCheck instanceof DataException hibEx) {
289293
return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + hibEx.getSQL() + "]", ex);
290294
}
291295
// end of JDBCException subclass handling
292296

293-
if (ex instanceof QueryException) {
297+
if (exToCheck instanceof QueryException) {
294298
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
295299
}
296-
if (ex instanceof NonUniqueResultException) {
300+
if (exToCheck instanceof NonUniqueResultException) {
297301
return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);
298302
}
299-
if (ex instanceof NonUniqueObjectException) {
303+
if (exToCheck instanceof NonUniqueObjectException) {
300304
return new DuplicateKeyException(ex.getMessage(), ex);
301305
}
302-
if (ex instanceof PropertyValueException) {
306+
if (exToCheck instanceof PropertyValueException) {
303307
return new DataIntegrityViolationException(ex.getMessage(), ex);
304308
}
305-
if (ex instanceof PersistentObjectException) {
309+
if (exToCheck instanceof PersistentObjectException) {
306310
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
307311
}
308-
if (ex instanceof TransientObjectException) {
312+
if (exToCheck instanceof TransientObjectException) {
309313
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
310314
}
311-
if (ex instanceof ObjectDeletedException) {
315+
if (exToCheck instanceof ObjectDeletedException) {
312316
return new InvalidDataAccessApiUsageException(ex.getMessage(), ex);
313317
}
314-
if (ex instanceof UnresolvableObjectException hibEx) {
318+
if (exToCheck instanceof UnresolvableObjectException hibEx) {
315319
return new ObjectRetrievalFailureException(hibEx.getEntityName(), getIdentifier(hibEx), ex.getMessage(), ex);
316320
}
317-
if (ex instanceof WrongClassException hibEx) {
321+
if (exToCheck instanceof WrongClassException hibEx) {
318322
return new ObjectRetrievalFailureException(hibEx.getEntityName(), getIdentifier(hibEx), ex.getMessage(), ex);
319323
}
320-
if (ex instanceof StaleObjectStateException hibEx) {
324+
if (exToCheck instanceof StaleObjectStateException hibEx) {
321325
return new ObjectOptimisticLockingFailureException(hibEx.getEntityName(), getIdentifier(hibEx), ex.getMessage(), ex);
322326
}
323-
if (ex instanceof StaleStateException) {
327+
if (exToCheck instanceof StaleStateException) {
324328
return new ObjectOptimisticLockingFailureException(ex.getMessage(), ex);
325329
}
326-
if (ex instanceof OptimisticEntityLockException) {
330+
if (exToCheck instanceof OptimisticEntityLockException) {
327331
return new ObjectOptimisticLockingFailureException(ex.getMessage(), ex);
328332
}
329-
if (ex instanceof PessimisticEntityLockException) {
333+
if (exToCheck instanceof PessimisticEntityLockException) {
330334
if (ex.getCause() instanceof LockAcquisitionException) {
331335
return new CannotAcquireLockException(ex.getMessage(), ex.getCause());
332336
}
333337
return new PessimisticLockingFailureException(ex.getMessage(), ex);
334338
}
335339

336-
// fallback
340+
// Fallback: check potentially more specific cause, otherwise JpaSystemException
341+
if (exToCheck.getCause() instanceof HibernateException causeToCheck) {
342+
return convertHibernateAccessException(ex, causeToCheck);
343+
}
337344
return new JpaSystemException(ex);
338345
}
339346

spring-orm/src/test/java/org/springframework/orm/jpa/DefaultJpaDialectTests.java

+17-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import jakarta.persistence.EntityManager;
2020
import jakarta.persistence.EntityTransaction;
2121
import jakarta.persistence.OptimisticLockException;
22+
import jakarta.persistence.PersistenceException;
2223
import org.junit.jupiter.api.Test;
2324

2425
import org.springframework.transaction.TransactionDefinition;
@@ -33,33 +34,37 @@
3334
/**
3435
* @author Costin Leau
3536
* @author Phillip Webb
37+
* @author Juergen Hoeller
3638
*/
3739
class DefaultJpaDialectTests {
3840

39-
private JpaDialect dialect = new DefaultJpaDialect();
41+
private final JpaDialect dialect = new DefaultJpaDialect();
4042

41-
@Test
42-
void testDefaultTransactionDefinition() {
43-
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
44-
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
45-
assertThatExceptionOfType(TransactionException.class).isThrownBy(() ->
46-
dialect.beginTransaction(null, definition));
47-
}
4843

4944
@Test
5045
void testDefaultBeginTransaction() throws Exception {
5146
TransactionDefinition definition = new DefaultTransactionDefinition();
5247
EntityManager entityManager = mock();
5348
EntityTransaction entityTx = mock();
54-
5549
given(entityManager.getTransaction()).willReturn(entityTx);
5650

5751
dialect.beginTransaction(entityManager, definition);
5852
}
5953

54+
@Test
55+
void testCustomIsolationLevel() {
56+
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
57+
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
58+
59+
assertThatExceptionOfType(TransactionException.class).isThrownBy(() ->
60+
dialect.beginTransaction(null, definition));
61+
}
62+
6063
@Test
6164
void testTranslateException() {
62-
OptimisticLockException ex = new OptimisticLockException();
63-
assertThat(dialect.translateExceptionIfPossible(ex).getCause()).isEqualTo(EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex).getCause());
65+
PersistenceException ex = new OptimisticLockException();
66+
assertThat(dialect.translateExceptionIfPossible(ex))
67+
.isInstanceOf(JpaOptimisticLockingFailureException.class).hasCause(ex);
6468
}
69+
6570
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.orm.jpa.hibernate;
18+
19+
import jakarta.persistence.OptimisticLockException;
20+
import jakarta.persistence.PersistenceException;
21+
import org.hibernate.HibernateException;
22+
import org.hibernate.dialect.lock.OptimisticEntityLockException;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.orm.ObjectOptimisticLockingFailureException;
26+
import org.springframework.orm.jpa.JpaDialect;
27+
import org.springframework.orm.jpa.JpaOptimisticLockingFailureException;
28+
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* @author Juergen Hoeller
34+
*/
35+
class HibernateJpaDialectTests {
36+
37+
private final JpaDialect dialect = new HibernateJpaDialect();
38+
39+
40+
@Test
41+
void testTranslateException() {
42+
// Plain JPA exception
43+
PersistenceException ex = new OptimisticLockException();
44+
assertThat(dialect.translateExceptionIfPossible(ex))
45+
.isInstanceOf(JpaOptimisticLockingFailureException.class).hasCause(ex);
46+
47+
// Hibernate-specific exception
48+
ex = new OptimisticEntityLockException("", "");
49+
assertThat(dialect.translateExceptionIfPossible(ex))
50+
.isInstanceOf(ObjectOptimisticLockingFailureException.class).hasCause(ex);
51+
52+
// Nested Hibernate-specific exception
53+
ex = new HibernateException(new OptimisticEntityLockException("", ""));
54+
assertThat(dialect.translateExceptionIfPossible(ex))
55+
.isInstanceOf(ObjectOptimisticLockingFailureException.class).hasCause(ex);
56+
}
57+
58+
}

0 commit comments

Comments
 (0)