Skip to content

Commit 33862d9

Browse files
committed
Merge branch '6.0.x'
2 parents c504ac5 + bbcc788 commit 33862d9

File tree

4 files changed

+91
-58
lines changed

4 files changed

+91
-58
lines changed

framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,6 @@ You can use this option for full JPA capabilities in a Spring-based application
8888
This includes web containers such as Tomcat, stand-alone applications, and
8989
integration tests with sophisticated persistence requirements.
9090

91-
NOTE: If you want to specifically configure a Hibernate setup, an immediate alternative
92-
is to set up a native Hibernate `LocalSessionFactoryBean` instead of a plain JPA
93-
`LocalContainerEntityManagerFactoryBean`, letting it interact with JPA access code
94-
as well as native Hibernate access code.
95-
See xref:data-access/orm/jpa.adoc#orm-jpa-hibernate[Native Hibernate setup for JPA interaction] for details.
96-
9791
The `LocalContainerEntityManagerFactoryBean` gives full control over
9892
`EntityManagerFactory` configuration and is appropriate for environments where
9993
fine-grained customization is required. The `LocalContainerEntityManagerFactoryBean`
@@ -187,6 +181,7 @@ and automatic propagation of the weaver to all weaver-aware beans:
187181
[source,xml,indent=0,subs="verbatim,quotes"]
188182
----
189183
<context:load-time-weaver/>
184+
190185
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
191186
...
192187
</bean>
@@ -425,20 +420,20 @@ Kotlin::
425420
----
426421
======
427422

428-
The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults to
429-
`PersistenceContextType.TRANSACTION`. You can use this default to receive a shared
423+
The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults
424+
to `PersistenceContextType.TRANSACTION`. You can use this default to receive a shared
430425
`EntityManager` proxy. The alternative, `PersistenceContextType.EXTENDED`, is a completely
431426
different affair. This results in a so-called extended `EntityManager`, which is not
432427
thread-safe and, hence, must not be used in a concurrently accessed component, such as a
433-
Spring-managed singleton bean. Extended `EntityManager` instances are only supposed to be used#
428+
Spring-managed singleton bean. Extended `EntityManager` instances are only supposed to be used
434429
in stateful components that, for example, reside in a session, with the lifecycle of the
435430
`EntityManager` not tied to a current transaction but rather being completely up to the
436431
application.
437432

438433
.Method- and field-level Injection
439434
****
440-
You can apply annotations that indicate dependency injections (such as `@PersistenceUnit` and
441-
`@PersistenceContext`) on field or methods inside a class -- hence the expressions
435+
You can apply annotations that indicate dependency injections (such as `@PersistenceUnit`
436+
and `@PersistenceContext`) on field or methods inside a class -- hence the expressions
442437
"`method-level injection`" and "`field-level injection`". Field-level annotations are
443438
concise and easier to use while method-level annotations allow for further processing of the
444439
injected dependency. In both cases, the member visibility (public, protected, or private)
@@ -460,12 +455,53 @@ No import of any Spring class is required. Moreover, as the JPA annotations are
460455
the injections are applied automatically by the Spring container. This is appealing from
461456
a non-invasiveness perspective and can feel more natural to JPA developers.
462457

458+
[[orm-jpa-dao-autowired]]
459+
=== Implementing DAOs Based on `@Autowired` (typically with constructor-based injection)
460+
461+
`@PersistenceUnit` and `@PersistenceContext` can only be declared on methods and fields.
462+
What about providing JPA resources via constructors and other `@Autowired` injection points?
463+
464+
`EntityManagerFactory` can easily be injected via constructors and `@Autowired` fields/methods
465+
as long as the target is defined as a bean, e.g. via `LocalContainerEntityManagerFactoryBean`.
466+
The injection point matches the original `EntityManagerFactory` definition by type as-is.
467+
468+
However, an `@PersistenceContext`-style shared `EntityManager` reference is not available for
469+
regular dependency injection out of the box. In order to make it available for type-based
470+
matching as required by `@Autowired`, consider defining a `SharedEntityManagerBean` as a
471+
companion for your `EntityManagerFactory` definition:
472+
473+
[source,xml,indent=0,subs="verbatim,quotes"]
474+
----
475+
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
476+
...
477+
</bean>
478+
479+
<bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
480+
<property name="entityManagerFactory" ref="emf"/>
481+
</bean>
482+
----
483+
484+
Alternatively, you may define an `@Bean` method based on `SharedEntityManagerCreator`:
485+
486+
[source,java,indent=0,subs="verbatim,quotes"]
487+
----
488+
@Bean("em")
489+
public static EntityManager sharedEntityManager(EntityManagerFactory emf) {
490+
return SharedEntityManagerCreator.createSharedEntityManager(emf);
491+
}
492+
----
493+
494+
In case of multiple persistence units, each `EntityManagerFactory` definition needs to be
495+
accompanied by a corresponding `EntityManager` bean definition, ideally with qualifiers
496+
that match with the distinct `EntityManagerFactory` definition in order to distinguish
497+
the persistence units via `@Autowired @Qualifier("...")`.
498+
463499

464500
[[orm-jpa-tx]]
465-
== Spring-driven JPA transactions
501+
== Spring-driven JPA Transactions
466502

467-
NOTE: We strongly encourage you to read xref:data-access/transaction/declarative.adoc[Declarative Transaction Management], if you have not
468-
already done so, to get more detailed coverage of Spring's declarative transaction support.
503+
NOTE: We strongly encourage you to read xref:data-access/transaction/declarative.adoc[Declarative Transaction Management],
504+
if you have not already done so, to get more detailed coverage of Spring's declarative transaction support.
469505

470506
The recommended strategy for JPA is local transactions through JPA's native transaction
471507
support. Spring's `JpaTransactionManager` provides many capabilities known from local
@@ -478,11 +514,6 @@ to JDBC access code that accesses the same `DataSource`, provided that the regis
478514
Spring provides dialects for the EclipseLink and Hibernate JPA implementations.
479515
See the xref:data-access/orm/jpa.adoc#orm-jpa-dialect[next section] for details on the `JpaDialect` mechanism.
480516

481-
NOTE: As an immediate alternative, Spring's native `HibernateTransactionManager` is capable
482-
of interacting with JPA access code, adapting to several Hibernate specifics and providing
483-
JDBC interaction. This makes particular sense in combination with `LocalSessionFactoryBean`
484-
setup. See xref:data-access/orm/jpa.adoc#orm-jpa-hibernate[Native Hibernate Setup for JPA Interaction] for details.
485-
486517

487518
[[orm-jpa-dialect]]
488519
== Understanding `JpaDialect` and `JpaVendorAdapter`

spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -177,9 +177,9 @@
177177
* <li>Only one cache may be specified</li>
178178
* <li>No other cache-related operation can be combined</li>
179179
* </ol>
180-
* This is effectively a hint and the actual cache provider that you are
181-
* using may not support it in a synchronized fashion. Check your provider
182-
* documentation for more details on the actual semantics.
180+
* This is effectively a hint and the chosen cache provider might not actually
181+
* support it in a synchronized fashion. Check your provider documentation for
182+
* more details on the actual semantics.
183183
* @since 4.3
184184
* @see org.springframework.cache.Cache#get(Object, Callable)
185185
*/

spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public void afterPropertiesSet() {
214214
@Override
215215
public void afterSingletonsInstantiated() {
216216
if (getCacheResolver() == null) {
217-
// Lazily initialize cache resolver via default cache manager...
217+
// Lazily initialize cache resolver via default cache manager
218218
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
219219
try {
220220
setCacheManager(this.beanFactory.getBean(CacheManager.class));
@@ -307,22 +307,22 @@ else if (StringUtils.hasText(operation.getCacheManager())) {
307307
}
308308

309309
/**
310-
* Return a bean with the specified name and type. Used to resolve services that
311-
* are referenced by name in a {@link CacheOperation}.
312-
* @param beanName the name of the bean, as defined by the operation
313-
* @param expectedType type for the bean
314-
* @return the bean matching that name
310+
* Retrieve a bean with the specified name and type.
311+
* Used to resolve services that are referenced by name in a {@link CacheOperation}.
312+
* @param name the name of the bean, as defined by the cache operation
313+
* @param serviceType the type expected by the operation's service reference
314+
* @return the bean matching the expected type, qualified by the given name
315315
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist
316316
* @see CacheOperation#getKeyGenerator()
317317
* @see CacheOperation#getCacheManager()
318318
* @see CacheOperation#getCacheResolver()
319319
*/
320-
protected <T> T getBean(String beanName, Class<T> expectedType) {
320+
protected <T> T getBean(String name, Class<T> serviceType) {
321321
if (this.beanFactory == null) {
322322
throw new IllegalStateException(
323-
"BeanFactory must be set on cache aspect for " + expectedType.getSimpleName() + " retrieval");
323+
"BeanFactory must be set on cache aspect for " + serviceType.getSimpleName() + " retrieval");
324324
}
325-
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName);
325+
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, serviceType, name);
326326
}
327327

328328
/**
@@ -388,12 +388,11 @@ private Object execute(final CacheOperationInvoker invoker, Method method, Cache
388388
}
389389
}
390390
else {
391-
// No caching required, only call the underlying method
391+
// No caching required, just call the underlying method
392392
return invokeOperation(invoker);
393393
}
394394
}
395395

396-
397396
// Process any early evictions
398397
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
399398
CacheOperationExpressionEvaluator.NO_RESULT);
@@ -640,21 +639,21 @@ private boolean determineSyncFlag(Method method) {
640639
if (syncEnabled) {
641640
if (this.contexts.size() > 1) {
642641
throw new IllegalStateException(
643-
"@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
642+
"A sync=true operation cannot be combined with other cache operations on '" + method + "'");
644643
}
645644
if (cacheOperationContexts.size() > 1) {
646645
throw new IllegalStateException(
647-
"Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
646+
"Only one sync=true operation is allowed on '" + method + "'");
648647
}
649648
CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
650-
CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
649+
CacheOperation operation = cacheOperationContext.getOperation();
651650
if (cacheOperationContext.getCaches().size() > 1) {
652651
throw new IllegalStateException(
653-
"@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
652+
"A sync=true operation is restricted to a single cache on '" + operation + "'");
654653
}
655-
if (StringUtils.hasText(operation.getUnless())) {
654+
if (operation instanceof CacheableOperation cacheable && StringUtils.hasText(cacheable.getUnless())) {
656655
throw new IllegalStateException(
657-
"@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
656+
"A sync=true operation does not support the unless attribute on '" + operation + "'");
658657
}
659658
return true;
660659
}
@@ -881,13 +880,13 @@ public int compareTo(CacheOperationCacheKey other) {
881880
}
882881
}
883882

883+
884884
/**
885885
* Internal holder class for recording that a cache method was invoked.
886886
*/
887887
private static class InvocationAwareResult {
888888

889889
boolean invoked;
890-
891890
}
892891

893892
}

spring-context/src/test/java/org/springframework/cache/interceptor/CacheSyncFailureTests.java

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -48,8 +48,9 @@ public class CacheSyncFailureTests {
4848

4949
private SimpleService simpleService;
5050

51+
5152
@BeforeEach
52-
public void setUp() {
53+
public void setup() {
5354
this.context = new AnnotationConfigApplicationContext(Config.class);
5455
this.simpleService = this.context.getBean(SimpleService.class);
5556
}
@@ -61,39 +62,40 @@ public void closeContext() {
6162
}
6263
}
6364

65+
6466
@Test
6567
public void unlessSync() {
66-
assertThatIllegalStateException().isThrownBy(() ->
67-
this.simpleService.unlessSync("key"))
68-
.withMessageContaining("@Cacheable(sync=true) does not support unless attribute");
68+
assertThatIllegalStateException()
69+
.isThrownBy(() -> this.simpleService.unlessSync("key"))
70+
.withMessageContaining("A sync=true operation does not support the unless attribute");
6971
}
7072

7173
@Test
7274
public void severalCachesSync() {
73-
assertThatIllegalStateException().isThrownBy(() ->
74-
this.simpleService.severalCachesSync("key"))
75-
.withMessageContaining("@Cacheable(sync=true) only allows a single cache");
75+
assertThatIllegalStateException()
76+
.isThrownBy(() -> this.simpleService.severalCachesSync("key"))
77+
.withMessageContaining("A sync=true operation is restricted to a single cache");
7678
}
7779

7880
@Test
7981
public void severalCachesWithResolvedSync() {
80-
assertThatIllegalStateException().isThrownBy(() ->
81-
this.simpleService.severalCachesWithResolvedSync("key"))
82-
.withMessageContaining("@Cacheable(sync=true) only allows a single cache");
82+
assertThatIllegalStateException()
83+
.isThrownBy(() -> this.simpleService.severalCachesWithResolvedSync("key"))
84+
.withMessageContaining("A sync=true operation is restricted to a single cache");
8385
}
8486

8587
@Test
8688
public void syncWithAnotherOperation() {
87-
assertThatIllegalStateException().isThrownBy(() ->
88-
this.simpleService.syncWithAnotherOperation("key"))
89-
.withMessageContaining("@Cacheable(sync=true) cannot be combined with other cache operations");
89+
assertThatIllegalStateException()
90+
.isThrownBy(() -> this.simpleService.syncWithAnotherOperation("key"))
91+
.withMessageContaining("A sync=true operation cannot be combined with other cache operations");
9092
}
9193

9294
@Test
9395
public void syncWithTwoGetOperations() {
94-
assertThatIllegalStateException().isThrownBy(() ->
95-
this.simpleService.syncWithTwoGetOperations("key"))
96-
.withMessageContaining("Only one @Cacheable(sync=true) entry is allowed");
96+
assertThatIllegalStateException()
97+
.isThrownBy(() -> this.simpleService.syncWithTwoGetOperations("key"))
98+
.withMessageContaining("Only one sync=true operation is allowed");
9799
}
98100

99101

@@ -131,6 +133,7 @@ public Object syncWithTwoGetOperations(Object arg1) {
131133
}
132134
}
133135

136+
134137
@Configuration
135138
@EnableCaching
136139
static class Config implements CachingConfigurer {

0 commit comments

Comments
 (0)