分析JPA实体工厂类如何根据entity-packages进行初始化的过程,以及@EntityScan注解不起效

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包

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值