Skip to content

Commit beb224e

Browse files
committed
Add support for JPA 3.2 PersistenceConfiguration
See gh-35662
1 parent cfb268f commit beb224e

10 files changed

Lines changed: 421 additions & 153 deletions

spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@
7171
* making {@code EntityManager} available for dependency injection as well.
7272
*
7373
* <p>Encapsulates the common functionality between the different JPA bootstrap
74-
* contracts (standalone as well as container).
74+
* contracts: standalone as well as container. Note that as of 7.0, the JPA 3.2
75+
* {@link LocalEntityManagerFactoryBean#setPersistenceConfiguration PersistenceConfiguration}
76+
* mechanism is supported as well, allowing for much richer standalone bootstrap options.
7577
*
7678
* <p>Implements support for standard JPA configuration conventions as well as
7779
* Spring's customizable {@link JpaVendorAdapter} mechanism, and controls the

spring-orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import javax.sql.DataSource;
2222

2323
import jakarta.persistence.EntityManagerFactory;
24+
import jakarta.persistence.PersistenceConfiguration;
2425
import jakarta.persistence.PersistenceException;
2526
import jakarta.persistence.SharedCacheMode;
2627
import jakarta.persistence.ValidationMode;
@@ -166,6 +167,22 @@ public void setPersistenceUnitRootLocation(String defaultPersistenceUnitRootLoca
166167
this.internalPersistenceUnitManager.setDefaultPersistenceUnitRootLocation(defaultPersistenceUnitRootLocation);
167168
}
168169

170+
/**
171+
* Set a local JPA 3.2 {@link PersistenceConfiguration} to use for this
172+
* persistence unit.
173+
* <p>Note: {@link PersistenceConfiguration} includes a persistence unit name,
174+
* so this effectively overrides the {@link #setPersistenceUnitName} method.
175+
* In contrast, all other settings will be merged with the settings in the
176+
* {@code PersistenceConfiguration} instance.
177+
* @since 7.0
178+
* @see DefaultPersistenceUnitManager#setPersistenceConfiguration
179+
*/
180+
public void setPersistenceConfiguration(PersistenceConfiguration configuration) {
181+
Assert.notNull(configuration, "PersistenceConfiguration must not be null");
182+
super.setPersistenceUnitName(configuration.name());
183+
this.internalPersistenceUnitManager.setPersistenceConfiguration(configuration);
184+
}
185+
169186
/**
170187
* Set the {@link PersistenceManagedTypes} to use to build the list of managed types
171188
* as an alternative to entity scanning.
@@ -424,15 +441,16 @@ protected EntityManagerFactory createNativeEntityManagerFactory() throws Persist
424441
* Determine the PersistenceUnitInfo to use for the EntityManagerFactory
425442
* created by this bean.
426443
* <p>The default implementation reads in all persistence unit infos from
427-
* {@code persistence.xml}, as defined in the JPA specification.
428-
* If no entity manager name was specified, it takes the first info in the
429-
* array as returned by the reader. Otherwise, it checks for a matching name.
444+
* {@code persistence.xml}, as defined in the JPA specification, selecting a unit
445+
* by name. If no persistence unit name was specified, it takes the default one
446+
* if configured, or otherwise the first persistence unit as found by the reader.
430447
* @param persistenceUnitManager the PersistenceUnitManager to obtain from
431448
* @return the chosen PersistenceUnitInfo
432449
*/
433450
protected PersistenceUnitInfo determinePersistenceUnitInfo(PersistenceUnitManager persistenceUnitManager) {
434-
if (getPersistenceUnitName() != null) {
435-
return persistenceUnitManager.obtainPersistenceUnitInfo(getPersistenceUnitName());
451+
String persistenceUnitName = getPersistenceUnitName();
452+
if (persistenceUnitName != null) {
453+
return persistenceUnitManager.obtainPersistenceUnitInfo(persistenceUnitName);
436454
}
437455
else {
438456
return persistenceUnitManager.obtainDefaultPersistenceUnitInfo();

spring-orm/src/main/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBean.java

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@
2020

2121
import jakarta.persistence.EntityManagerFactory;
2222
import jakarta.persistence.Persistence;
23+
import jakarta.persistence.PersistenceConfiguration;
2324
import jakarta.persistence.PersistenceException;
2425
import jakarta.persistence.spi.PersistenceProvider;
2526
import org.jspecify.annotations.Nullable;
2627

28+
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
29+
import org.springframework.util.Assert;
30+
2731
/**
2832
* {@link org.springframework.beans.factory.FactoryBean} that creates a JPA
2933
* {@link jakarta.persistence.EntityManagerFactory} according to JPA's standard
@@ -62,6 +66,80 @@ public class LocalEntityManagerFactoryBean extends AbstractEntityManagerFactoryB
6266

6367
private static final String DATASOURCE_PROPERTY = "jakarta.persistence.dataSource";
6468

69+
private @Nullable PersistenceConfiguration configuration;
70+
71+
72+
/**
73+
* Create a {@code LocalEntityManagerFactoryBean}.
74+
* <p>As of 7.0, This uses "default" for the default persistence unit name.
75+
* @see #setPersistenceUnitName
76+
* @see #setPersistenceConfiguration
77+
*/
78+
public LocalEntityManagerFactoryBean() {
79+
setPersistenceUnitName(DefaultPersistenceUnitManager.ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME);
80+
}
81+
82+
/**
83+
* Create a {@code LocalEntityManagerFactoryBean} for the given persistence unit.
84+
* @param persistenceUnitName the name of the persistence unit
85+
* @since 7.0
86+
*/
87+
public LocalEntityManagerFactoryBean(String persistenceUnitName) {
88+
setPersistenceUnitName(persistenceUnitName);
89+
}
90+
91+
/**
92+
* Create a {@code LocalEntityManagerFactoryBean} for the given persistence unit.
93+
* @param configuration the configuration for the persistence unit
94+
* @since 7.0
95+
*/
96+
public LocalEntityManagerFactoryBean(PersistenceConfiguration configuration) {
97+
setPersistenceConfiguration(configuration);
98+
}
99+
100+
101+
/**
102+
* Set a local JPA 3.2 {@link PersistenceConfiguration} to use for creating
103+
* the EntityManagerFactory. This can be a provider-specific subclass such as
104+
* {@link org.hibernate.jpa.HibernatePersistenceConfiguration}, exposing a
105+
* complete programmatic persistence unit configuration which replaces
106+
* {@code persistence.xml} (including provider-specific classpath scanning).
107+
* <p>Note: {@link PersistenceConfiguration} includes a persistence unit name,
108+
* so this effectively overrides the {@link #setPersistenceUnitName} method.
109+
* In contrast, locally specified JPA properties ({@link #setJpaProperties})
110+
* will get merged into the given {@code PersistenceConfiguration} instance.
111+
* @since 7.0
112+
* @see #getPersistenceConfiguration()
113+
* @see #getPersistenceUnitName()
114+
*/
115+
public void setPersistenceConfiguration(PersistenceConfiguration configuration) {
116+
Assert.notNull(configuration, "PersistenceConfiguration must not be null");
117+
this.configuration = configuration;
118+
setPersistenceUnitName(configuration.name());
119+
}
120+
121+
/**
122+
* Set a local JPA 3.2 {@link PersistenceConfiguration} to use for creating
123+
* the EntityManagerFactory. If none is in use yet, a new plain
124+
* {@link PersistenceConfiguration} for the configured persistence unit name
125+
* will be created and returned.
126+
* @since 7.0
127+
* @see #setPersistenceConfiguration
128+
* @see #setPersistenceUnitName
129+
*/
130+
public PersistenceConfiguration getPersistenceConfiguration() {
131+
if (this.configuration == null) {
132+
this.configuration = new PersistenceConfiguration(getPersistenceUnitName());
133+
}
134+
return this.configuration;
135+
}
136+
137+
@Override
138+
public void setPersistenceUnitName(@Nullable String persistenceUnitName) {
139+
Assert.state(this.configuration == null || this.configuration.name().equals(persistenceUnitName),
140+
"Cannot change setPersistenceUnitName when PersistenceConfiguration has been set");
141+
super.setPersistenceUnitName(persistenceUnitName);
142+
}
65143

66144
/**
67145
* Specify the JDBC DataSource that the JPA persistence provider is supposed
@@ -104,10 +182,17 @@ protected EntityManagerFactory createNativeEntityManagerFactory() throws Persist
104182
if (logger.isDebugEnabled()) {
105183
logger.debug("Building JPA EntityManagerFactory for persistence unit '" + getPersistenceUnitName() + "'");
106184
}
185+
186+
if (this.configuration != null) {
187+
this.configuration.properties(getJpaPropertyMap());
188+
}
189+
107190
PersistenceProvider provider = getPersistenceProvider();
108191
if (provider != null) {
109192
// Create EntityManagerFactory directly through PersistenceProvider.
110-
EntityManagerFactory emf = provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap());
193+
EntityManagerFactory emf = (this.configuration != null ?
194+
provider.createEntityManagerFactory(this.configuration) :
195+
provider.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()));
111196
if (emf == null) {
112197
throw new IllegalStateException(
113198
"PersistenceProvider [" + provider + "] did not return an EntityManagerFactory for name '" +
@@ -117,7 +202,9 @@ protected EntityManagerFactory createNativeEntityManagerFactory() throws Persist
117202
}
118203
else {
119204
// Let JPA perform its standard PersistenceProvider autodetection.
120-
return Persistence.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap());
205+
return (this.configuration != null ?
206+
Persistence.createEntityManagerFactory(this.configuration) :
207+
Persistence.createEntityManagerFactory(getPersistenceUnitName(), getJpaPropertyMap()));
121208
}
122209
}
123210

spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import javax.sql.DataSource;
2929

30+
import jakarta.persistence.PersistenceConfiguration;
3031
import jakarta.persistence.PersistenceException;
3132
import jakarta.persistence.SharedCacheMode;
3233
import jakarta.persistence.ValidationMode;
@@ -108,6 +109,8 @@ public class DefaultPersistenceUnitManager
108109

109110
private @Nullable String defaultPersistenceUnitName = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME;
110111

112+
private @Nullable PersistenceConfiguration persistenceConfiguration;
113+
111114
private @Nullable PersistenceManagedTypes managedTypes;
112115

113116
private String @Nullable [] packagesToScan;
@@ -179,9 +182,19 @@ public void setDefaultPersistenceUnitName(String defaultPersistenceUnitName) {
179182
this.defaultPersistenceUnitName = defaultPersistenceUnitName;
180183
}
181184

185+
/**
186+
* Set a JPA 3.2 {@link PersistenceConfiguration} to apply to the default
187+
* persistence unit. The contained configuration will be merged with the
188+
* common settings specified on this {@code DefaultPersistenceUnitManager}.
189+
* @since 7.0
190+
*/
191+
public void setPersistenceConfiguration(PersistenceConfiguration configuration) {
192+
this.persistenceConfiguration = configuration;
193+
}
194+
182195
/**
183196
* Set the {@link PersistenceManagedTypes} to use to build the list of managed types
184-
* as an alternative to entity scanning.
197+
* for the default persistence unit, as an alternative to entity scanning.
185198
* @param managedTypes the managed types
186199
* @since 6.0
187200
*/
@@ -492,7 +505,8 @@ public void preparePersistenceUnitInfos() {
492505
private List<SpringPersistenceUnitInfo> readPersistenceUnitInfos() {
493506
List<SpringPersistenceUnitInfo> infos = new ArrayList<>(1);
494507
String defaultName = this.defaultPersistenceUnitName;
495-
boolean buildDefaultUnit = (this.managedTypes != null || this.packagesToScan != null || this.mappingResources != null);
508+
boolean buildDefaultUnit = (this.persistenceConfiguration != null || this.managedTypes != null ||
509+
this.packagesToScan != null || this.mappingResources != null);
496510
boolean foundDefaultUnit = false;
497511

498512
PersistenceUnitReader reader = new PersistenceUnitReader(this.resourcePatternResolver, this.dataSourceLookup);
@@ -507,8 +521,9 @@ private List<SpringPersistenceUnitInfo> readPersistenceUnitInfos() {
507521
if (buildDefaultUnit) {
508522
if (foundDefaultUnit) {
509523
if (logger.isWarnEnabled()) {
510-
logger.warn("Found explicit default persistence unit with name '" + defaultName + "' in persistence.xml - " +
511-
"overriding local default persistence unit settings ('managedTypes', 'packagesToScan' or 'mappingResources')");
524+
logger.warn("Found explicit default persistence unit with name '" + defaultName +
525+
"' in persistence.xml - overriding local default persistence unit settings " +
526+
"(`persistenceConfiguration`, 'managedTypes', 'packagesToScan' or 'mappingResources')");
512527
}
513528
}
514529
else {
@@ -523,33 +538,37 @@ private List<SpringPersistenceUnitInfo> readPersistenceUnitInfos() {
523538
* @see #setPackagesToScan
524539
*/
525540
private SpringPersistenceUnitInfo buildDefaultPersistenceUnitInfo() {
526-
SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo();
541+
SpringPersistenceUnitInfo defaultUnit = new SpringPersistenceUnitInfo();
527542
if (this.defaultPersistenceUnitName != null) {
528-
scannedUnit.setPersistenceUnitName(this.defaultPersistenceUnitName);
543+
defaultUnit.setPersistenceUnitName(this.defaultPersistenceUnitName);
544+
}
545+
defaultUnit.setExcludeUnlistedClasses(true);
546+
547+
if (this.persistenceConfiguration != null) {
548+
defaultUnit.apply(this.persistenceConfiguration, this.dataSourceLookup);
529549
}
530-
scannedUnit.setExcludeUnlistedClasses(true);
531550

532551
if (this.managedTypes != null) {
533-
applyManagedTypes(scannedUnit, this.managedTypes);
552+
defaultUnit.apply(this.managedTypes);
534553
}
535554
else if (this.packagesToScan != null) {
536555
PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(
537556
this.resourcePatternResolver, this.managedClassNameFilter);
538-
applyManagedTypes(scannedUnit, scanner.scan(this.packagesToScan));
557+
defaultUnit.apply(scanner.scan(this.packagesToScan));
539558
}
540559

541560
if (this.mappingResources != null) {
542561
for (String mappingFileName : this.mappingResources) {
543-
scannedUnit.addMappingFileName(mappingFileName);
562+
defaultUnit.addMappingFileName(mappingFileName);
544563
}
545564
}
546565
else {
547566
Resource ormXml = getOrmXmlForDefaultPersistenceUnit();
548567
if (ormXml != null) {
549-
scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
550-
if (scannedUnit.getPersistenceUnitRootUrl() == null) {
568+
defaultUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
569+
if (defaultUnit.getPersistenceUnitRootUrl() == null) {
551570
try {
552-
scannedUnit.setPersistenceUnitRootUrl(
571+
defaultUnit.setPersistenceUnitRootUrl(
553572
PersistenceUnitReader.determinePersistenceUnitRootUrl(ormXml));
554573
}
555574
catch (IOException ex) {
@@ -559,16 +578,7 @@ else if (this.packagesToScan != null) {
559578
}
560579
}
561580

562-
return scannedUnit;
563-
}
564-
565-
private void applyManagedTypes(SpringPersistenceUnitInfo scannedUnit, PersistenceManagedTypes managedTypes) {
566-
managedTypes.getManagedClassNames().forEach(scannedUnit::addManagedClassName);
567-
managedTypes.getManagedPackages().forEach(scannedUnit::addManagedPackage);
568-
URL persistenceUnitRootUrl = managedTypes.getPersistenceUnitRootUrl();
569-
if (scannedUnit.getPersistenceUnitRootUrl() == null && persistenceUnitRootUrl != null) {
570-
scannedUnit.setPersistenceUnitRootUrl(persistenceUnitRootUrl);
571-
}
581+
return defaultUnit;
572582
}
573583

574584
/**

spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,12 @@ public class MutablePersistenceUnitInfo {
6060

6161
private @Nullable String persistenceProviderClassName;
6262

63-
private @Nullable String scopeAnnotationName;
64-
65-
private final List<String> qualifierAnnotationNames = new ArrayList<>();
66-
6763
private @Nullable PersistenceUnitTransactionType transactionType;
6864

69-
private @Nullable DataSource nonJtaDataSource;
70-
7165
private @Nullable DataSource jtaDataSource;
7266

67+
private @Nullable DataSource nonJtaDataSource;
68+
7369
private final List<String> mappingFileNames = new ArrayList<>();
7470

7571
private final List<URL> jarFileUrls = new ArrayList<>();
@@ -109,22 +105,6 @@ public void setPersistenceProviderClassName(@Nullable String persistenceProvider
109105
return this.persistenceProviderClassName;
110106
}
111107

112-
public void setScopeAnnotationName(@Nullable String scopeAnnotationName) {
113-
this.scopeAnnotationName = scopeAnnotationName;
114-
}
115-
116-
public @Nullable String getScopeAnnotationName() {
117-
return this.scopeAnnotationName;
118-
}
119-
120-
public void addQualifierAnnotationName(String qualifierAnnotationName) {
121-
this.qualifierAnnotationNames.add(qualifierAnnotationName);
122-
}
123-
124-
public List<String> getQualifierAnnotationNames() {
125-
return this.qualifierAnnotationNames;
126-
}
127-
128108
public void setTransactionType(PersistenceUnitTransactionType transactionType) {
129109
this.transactionType = transactionType;
130110
}

0 commit comments

Comments
 (0)