1、描述问题
构建独立项目并被springboot项目引用,在使用JPA的情况下,研究无法扫描到独立项目中定义的Entity的问题。即使在在独立项目中加入@EntityScan也无法成功扫描entity。
现状:主SpringBoot项目中,通过自定义EntityManagerFactory实现对配置文件中的basePackages进行扫描,据了解这样实现是为了通过配置文件修改扫描entity范围。(后续发现有问题)
@Value("${spring.datasource.entity-packages}")
private String[] entityPackages;
@Primary
@Bean(
name = {"entityManagerFactoryPrimary"}
)
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(@Qualifier("primaryDataSource") DataSource primaryDataSource, EntityManagerFactoryBuilder builder) {
logger.info("entitypackage:{}", Arrays.toString(this.entityPackages));
return builder.dataSource(primaryDataSource).properties(this.getVendorProperties()).packages(this.entityPackages).persistenceUnit("primaryPersistenceUnit").build();
}
问题就出在此,由于通过配置类自定义了EntityManagerFactory,并且entityPackages并没有可扩展的方法,因此若需要引入其他独立项目并希望能扫描到其中的entity时,则无法被正确扫描出来。
2、分析过程
想到的第一个想法是,希望不调整已有项目的代码结构,通过外部的设置实现entity的扫描。因此分析了一下LocalContainerEntityManagerFactoryBean的构建过程,看能够在其中修改或加入我们所需要的独立项目中的entity路径。
图1 (LocalContainerEntityManagerFactoryBean的继承结构)
LocalContainerEntityManagerFactoryBean的继承关系还是比较复杂的,但目前的问题是entity的扫描问题,因此我们先找到利用entityPackages的地方,能看出实际上packagesToScan这个值是赋给了internalPersistenceUnitManager这个对象(其他属性也类似),因此我们可以基本确定,在构建entityFactory的过程中,该对象将起到重要作用,同时我们能看到有该类中有声明了两个类似的对象——PersistenceUnitManager,从字面上能看出这个属性是管理我们持久化单元的对象。
@Nullable
private PersistenceUnitManager persistenceUnitManager;
private final DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager();
......
public void setPackagesToScan(String... packagesToScan) {
this.internalPersistenceUnitManager.setPackagesToScan(packagesToScan);
}
我们从图一看出,LocalContainerEntityManagerFactoryBean类实现了InitializingBean接口,该接口是bean在构建过程中,在属性赋值完成之后被调用的勾子方法。
@Override
public void afterPropertiesSet() throws PersistenceException {
PersistenceUnitManager managerToUse = this.persistenceUnitManager;
if (this.persistenceUnitManager == null) {
this.internalPersistenceUnitManager.afterPropertiesSet();
managerToUse = this.internalPersistenceUnitManager;
}
this.persistenceUnitInfo = determinePersistenceUnitInfo(managerToUse);
JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter();
if (jpaVendorAdapter != null && this.persistenceUnitInfo instanceof SmartPersistenceUnitInfo) {
String rootPackage = jpaVendorAdapter.getPersistenceProviderRootPackage();
if (rootPackage != null) {
((SmartPersistenceUnitInfo) this.persistenceUnitInfo).setPersistenceProviderPackageName(rootPackage);
}
}
super.afterPropertiesSet();
}
图2 (核心方法1)
在afterPropertiesSet方法中,首先是判断是否有给PersistenceUnitManager赋值,若没有则使用默认的PersistenceUnitManager对象(实际上我们也没有自定义PersistenceUnitManager),此时我们留意到了internalPersistenceUnitManager也实现了InitializingBean接口,且进行了调用。
@Override
public void afterPropertiesSet() {
if (this.loadTimeWeaver == null && InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) {
this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(this.resourcePatternResolver.getClassLoader());
}
preparePersistenceUnitInfos();
}
/**
* Prepare the PersistenceUnitInfos according to the configuration
* of this manager: scanning for {@code persistence.xml} files,
* parsing all matching files, configuring and post-processing them.
* <p>PersistenceUnitInfos cannot be obtained before this preparation
* method has been invoked.
* @see #obtainDefaultPersistenceUnitInfo()
* @see #obtainPersistenceUnitInfo(String)
*/
public void preparePersistenceUnitInfos() {
this.persistenceUnitInfoNames.clear();
this.persistenceUnitInfos.clear();
List<SpringPersistenceUnitInfo> puis = readPersistenceUnitInfos();
for (SpringPersistenceUnitInfo pui : puis) {
if (pui.getPersistenceUnitRootUrl() == null) {
pui.setPersistenceUnitRootUrl(determineDefaultPersistenceUnitRootUrl());
}
if (pui.getJtaDataSource() == null && this.defaultJtaDataSource != null) {
pui.setJtaDataSource(this.defaultJtaDataSource);
}
if (pui.getNonJtaDataSource() == null && this.defaultDataSource != null) {
pui.setNonJtaDataSource(this.defaultDataSource);
}
if (this.sharedCacheMode != null) {
pui.setSharedCacheMode(this.sharedCacheMode);
}
if (this.validationMode != null) {
pui.setValidationMode(this.validationMode);
}
if (this.loadTimeWeaver != null) {
pui.init(this.loadTimeWeaver);
}
else {
pui.init(this.resourcePatternResolver.getClassLoader());
}
postProcessPersistenceUnitInfo(pui);
String name = pui.getPersistenceUnitName();
if (!this.persistenceUnitInfoNames.add(name) && !isPersistenceUnitOverrideAllowed()) {
StringBuilder msg = new StringBuilder();
msg.append("Conflicting persistence unit definitions for name '").append(name).append("': ");
msg.append(pui.getPersistenceUnitRootUrl()).append(", ");
msg.append(this.persistenceUnitInfos.get(name).getPersistenceUnitRootUrl());
throw new IllegalStateException(msg.toString());
}
this.persistenceUnitInfos.put(name, pui);
}
}
图3 (internalPersistenceUnitManager.afterPropertitesSet)
在internalPersistenceUnitManager.afterPropertitesSet方法中,调用了preparePersistenceUnitInfos方法,再往下看,能够看到一个核心方法——readPersistenceUnitInfos
通过下图的代码,我们了解到实际上这个方法是需要处理两个属性:packagesToScan以及orm.xml,来对infos进行初始化。
/**
* Perform Spring-based scanning for entity classes.
* @see #setPackagesToScan
*/
private SpringPersistenceUnitInfo buildDefaultPersistenceUnitInfo() {
SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo();
if (this.defaultPersistenceUnitName != null) {
scannedUnit.setPersistenceUnitName(this.defaultPersistenceUnitName);
}
scannedUnit.setExcludeUnlistedClasses(true);
if (this.packagesToScan != null) {
for (String pkg : this.packagesToScan) {
scanPackage(scannedUnit, pkg);
}
}
if (this.mappingResources != null) {
for (String mappingFileName : this.mappingResources) {
scannedUnit.addMappingFileName(mappingFileName);
}
}
else {
Resource ormXml = getOrmXmlForDefaultPersistenceUnit();
if (ormXml != null) {
scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE);
if (scannedUnit.getPersistenceUnitRootUrl() == null) {
try {
scannedUnit.setPersistenceUnitRootUrl(
PersistenceUnitReader.determinePersistenceUnitRootUrl(ormXml));
}
catch (IOException ex) {
logger.debug("Failed to determine persistence unit root URL from orm.xml location", ex);
}
}
}
}
return scannedUnit;
}
总结:至此我们基本能一定程度的了解entityFactory如何使用packagesToScan这个属性,同时我们需要进一步思考,如何改写该属性,实现扫描独立项目中的entity包