Spring BeanPostProcessor : PersistenceAnnotationBeanPostProcessor

概述

PersistenceAnnotationBeanPostProcessorSpring提供的用于处理注解@PersistenceUnit@PersistenceContextBeanPostProcessor。用于注入相应的JPA资源:EntityManagerFactoryEntityManager (或者它们的子类变量)。

注意 : 在目前的实现中,PersistenceAnnotationBeanPostProcessor仅仅支持@PersistenceUnit@PersistenceContext上带有属性unitName或者不带任何属性(比如使用缺省persistence unit的情况)。如果这些注解使用在类上,并且使用了属性name,这些注解会被忽略,因为它们此时仅仅作为部署提示(参考Java EE规范)。

BeanPostProcessor从如下渠道获取EntityManagerFactory对象 :

  • Spring应用上下文定义的bean对象(缺省情况)

这种情况下,EntityManagerFactory通常由org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean创建。

  • JNDI

这种情况下,通常会使用jee:jndi-lookup XML配置元素,bean名称用于匹配被请求的persistence unit名称。

基于XML配置时,使用context:annotation-config或者context:component-scan 时会注册一个缺省的PersistenceAnnotationBeanPostProcessor。而基于注解的应用,比如Springboot应用,如果使用了JPA,应用也会缺省自动注册一个PersistenceAnnotationBeanPostProcessor。如果你想指定一个自定义的PersistenceAnnotationBeanPostProcessor的,需要删除或者关闭相应这样的XML配置或者注解。

Springboot JPA应用中,PersistenceAnnotationBeanPostProcessor的注册参考工具类方法AnnotationConfigUtils#registerAnnotationConfigProcessors:

     // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
		if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition();
			try {
				def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
						AnnotationConfigUtils.class.getClassLoader()));
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Cannot load optional framework class: " + 
						PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
			}
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, 
			PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

源代码分析

发现某个bean的持久化注入元数据

开发人员,或者框架自身的某个部分,可能在某些bean上通过如下方式注入了持久化有关的bean :

  • 基于成员属性的注入
 @PersistenceUnit
 EntityManagerFactory entityManagerFactory;

 @PersistenceContext
 EntityManager entityManager;
  • 基于成员属性设置方法的注入
@PersistenceUnit
public void setEntityManagerFactory(EntityManager entityManagerFactory) {
	this.entityManagerFactory= entityManagerFactory;
}

@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
	this.entityManager = entityManager;
}

对于在bean上的这些持久化注入的信息,就是由PersistenceAnnotationBeanPostProcessor来处理的,但是,PersistenceAnnotationBeanPostProcessor是怎样发现这些信息的呢 ?这一小节,我们来回答这一问题。

首先,PersistenceAnnotationBeanPostProcessor实现了接口MergedBeanDefinitionPostProcessor,该接口约定了bean创建时的生命周期回调方法postProcessMergedBeanDefinition,该方法会在每个bean创建过程中,依赖注入进行之前被调用。那么对于PersistenceAnnotationBeanPostProcessor而言,它的这个方法又做了哪些事情呢 ?

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType,
	 String beanName) {
    // 获取当前被创建的bean的持久化属性元数据
	InjectionMetadata metadata = findPersistenceMetadata(beanName, beanType, null);
	
	metadata.checkConfigMembers(beanDefinition);
}

上面方法postProcessMergedBeanDefinition主要逻辑是通过方法findPersistenceMetadata发现正在创建的bean的持久化元数据。findPersistenceMetadata实现如下 :

private InjectionMetadata findPersistenceMetadata(String beanName, final Class<?> clazz, 
	@Nullable PropertyValues pvs) {
		// Fall back to class name as cache key, for backwards compatibility with custom callers.
		String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
		// Quick check on the concurrent map first, with minimal locking.
		// 从缓存this.injectionMetadataCache中查找针对该bean的持久化元数据,看是否已经获得并缓存,
		// 返回 null 的话表示尚未获得和缓存过
		InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
		// metadata ==null 的话InjectionMetadata.needsRefresh()会返回true
		if (InjectionMetadata.needsRefresh(metadata, clazz)) {
			// 如果 metadata ==null 表明尚未获得和缓存过,
			// 此方法在postProcessMergedBeanDefinition里面执行的话显然会走到这里,
			// 而在 postProcessProperties 里面执行的话显然不会走到这里
			synchronized (this.injectionMetadataCache) {
				metadata = this.injectionMetadataCache.get(cacheKey);
				if (InjectionMetadata.needsRefresh(metadata, clazz)) {
					if (metadata != null) {
						metadata.clear(pvs);
					}
					// 根据 bean class 构建持久化元数据对象 InjectionMetadata ,
					// 所使用的 InjectedElement 实现类是 PersistenceElement,
					// 一个当前类的内嵌类定义
					metadata = buildPersistenceMetadata(clazz);
					// 缓存所构建的 InjectionMetadata 对象
					this.injectionMetadataCache.put(cacheKey, metadata);
				}
			}
		}
		return metadata;
	}    

以上方法findPersistenceMetadata构建bean的注解元数据所使用的方法实现如下 :

	// 基于类 clazz,使用反射方法获取持久化注解元数据 :
	// @PersistenceContext
	// @PersistenceUnit
	private InjectionMetadata buildPersistenceMetadata(final Class<?> clazz) {
		List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
		Class<?> targetClass = clazz;

		do {
			final LinkedList<InjectionMetadata.InjectedElement> currElements =
					new LinkedList<>();

			// 使用反射遍历 targetClass 自身声明的各个实例成员属性,看它们是否
			// 使用了注解 @PersistenceContext 或者 @PersistenceUnit , 注意,需要忽略
			// 类成员属性(也就是使用了 static 修饰符的属性定义)
			// 如果遇到了相应的属性,构造一个PersistenceElement对象,添加到currElements 
			ReflectionUtils.doWithLocalFields(targetClass, field -> {
				if (field.isAnnotationPresent(PersistenceContext.class) ||
						field.isAnnotationPresent(PersistenceUnit.class)) {
					if (Modifier.isStatic(field.getModifiers())) {
						throw new IllegalStateException(
							"Persistence annotations are not supported on static fields");
					}
					currElements.add(new PersistenceElement(field, field, null));
				}
			});

			// 使用反射遍历 targetClass 自身声明的各个实例成员方法,看它们是否
			// 使用了注解 @PersistenceContext 或者 @PersistenceUnit , 注意,
			// 1. 需要忽略类成员方法(也就是使用了 static 修饰符的方法定义)
			// 2. 使用了以上持久化注解的方法必须只有一个参数
			// 3. 也处理因为可能是用泛型导致的桥接方法
			// 如果遇到了相应的方法,构造一个PersistenceElement对象,添加到currElements 
			ReflectionUtils.doWithLocalMethods(targetClass, method -> {
				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
				if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
					return;
				}
				if ((bridgedMethod.isAnnotationPresent(PersistenceContext.class) ||
						bridgedMethod.isAnnotationPresent(PersistenceUnit.class)) &&
						method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
					if (Modifier.isStatic(method.getModifiers())) {
						throw new IllegalStateException(
							"Persistence annotations are not supported on static methods");
					}
					if (method.getParameterCount() != 1) {
						throw new IllegalStateException(
							"Persistence annotation requires a single-arg method: " + method);
					}
					PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                 // 注意,这里使用的 InjectedElement 实现类是  PersistenceElement , 这是一个
                 // PersistenceAnnotationBeanPostProcessor 自定义的专门用于获取和注入持久化属性
                 // 的 InjectedElement,它是 PersistenceAnnotationBeanPostProcessor 进行持久化
                 // 属性注入的核心
					currElements.add(new PersistenceElement(method, bridgedMethod, pd));
				}
			});

			elements.addAll(0, currElements);
			// 处理完 targetClass,开始处理 targetClass 的 父类
			targetClass = targetClass.getSuperclass();
		}
		// 直到 targetClass 变成 null 或者 Object,停止搜寻持久化元数据
		while (targetClass != null && targetClass != Object.class);

		// 将保存在 elements 中的 PersistenceElement 用于构建针对当前bean的注入元数据对象
		// InjectionMetadata
		return new InjectionMetadata(clazz, elements);
	}

从上面的代码可以看到,PersistenceAnnotationBeanPostProcessor#postProcessMergedBeanDefinition主要的作用就是提取当前创建的bean中的持久化属性元数据并保存在this.injectionMetadataCache中。而对于每个bean,持久化元数据获取的主要逻辑实现在方法buildPersistenceMetadata中。而buildPersistenceMetadata使用了InjectionMetadata包装针对一个bean的持久化属性注入元数据,其中的每一条持久化属性注入元数据元素对应一个PersistenceElement对象。而PersistenceElementPersistenceAnnotationBeanPostProcessor嵌套定义的一个内部类,继承自InjectionMetadata.InjectedElement。对目标bean相应持久化属性的注入,主要通过该实现类完成。

持久化属性的注入

从上面的分析我们知道,PersistenceAnnotationBeanPostProcessor能够获取任何一个bean上的持久化属性元数据了,也就是说,PersistenceAnnotationBeanPostProcessor知道针对某个bean的如下信息 :

  • 是否需要持久化属性注入
  • 需要注入哪些持久化属性

那么,这些属性又是如何注入的呢 ? 我们继续分析。

PersistenceAnnotationBeanPostProcessor又实现了接口InstantiationAwareBeanPostProcessor约定的生命周期回调方法postProcessProperties,该方法会在某个bean创建过程中的属性填充阶段,也可以认为是依赖注入阶段,被应用到该bean。那么我们来看PersistenceAnnotationBeanPostProcessor的这个方法又做了哪些事情:

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		// 获取当前bean的持久化属性注入元数据,根据前面的分析,该元数据已经在postProcessMergedBeanDefinition
		// 应用阶段获取和缓存,所以这里 metadata 会返回已经缓存的持久化属性注入元数据
		InjectionMetadata metadata = findPersistenceMetadata(beanName, bean.getClass(), pvs);
		try {
			// 根据持久化属性注入元数据执行属性注入
			metadata.inject(bean, beanName, pvs);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of persistence dependencies failed", ex);
		}
		return pvs;
	}      
    

InjectionMetadata#inject属性注入的过程,主要就是其中每个元素对应的属性注入的执行。而从上面的分析我们已经知道,每个bean的持久化属性注入元数据对象InjectionMetadata的每个元素都是一个PersistenceElement

/**
	 * Class representing injection information about an annotated field
	 * or setter method.
	 */
	private class PersistenceElement extends InjectionMetadata.InjectedElement {

		private final String unitName;

		@Nullable
		private PersistenceContextType type;

		private boolean synchronizedWithTransaction = false;

		@Nullable
		private Properties properties;

		public PersistenceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
			super(member, pd);
			PersistenceContext pc = ae.getAnnotation(PersistenceContext.class);
			PersistenceUnit pu = ae.getAnnotation(PersistenceUnit.class);
			Class<?> resourceType = EntityManager.class;
			if (pc != null) {
				if (pu != null) {
					throw new IllegalStateException("Member may only be annotated with either " +
							"@PersistenceContext or @PersistenceUnit, not both: " + member);
				}
				Properties properties = null;
				PersistenceProperty[] pps = pc.properties();
				if (!ObjectUtils.isEmpty(pps)) {
					properties = new Properties();
					for (PersistenceProperty pp : pps) {
						properties.setProperty(pp.name(), pp.value());
					}
				}
				this.unitName = pc.unitName();
				this.type = pc.type();
				this.synchronizedWithTransaction = 
					SynchronizationType.SYNCHRONIZED.equals(pc.synchronization());
				this.properties = properties;
			}
			else {
				resourceType = EntityManagerFactory.class;
				this.unitName = pu.unitName();
			}
			checkResourceType(resourceType);
		}

		/**
		 * Resolve the object against the application context.
		 * 从应用上下文中获取要注入的bean实例: EntityManagerFactory 对象或者 EntityManager,
		 * 覆盖了父类InjectedElement的缺省实现,这是在该类对象上调用#inject方法时,设置目标属性
		 * 时用于解析对应属性值的方法
		 */
		@Override
		protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
			// Resolves to EntityManagerFactory or EntityManager.
			if (this.type != null) {
				return (this.type == PersistenceContextType.EXTENDED ?
						resolveExtendedEntityManager(target, requestingBeanName) :
						resolveEntityManager(requestingBeanName));
			}
			else {
				// OK, so we need an EntityManagerFactory...
				return resolveEntityManagerFactory(requestingBeanName);
			}
		}

		// 获取 EntityManagerFactory 的方法
		private EntityManagerFactory resolveEntityManagerFactory(@Nullable String requestingBeanName) {
			// Obtain EntityManagerFactory from JNDI?
			// 从JNDI获取 EntityManagerFactory 
			EntityManagerFactory emf = getPersistenceUnit(this.unitName);
			if (emf == null) {
				// Need to search for EntityManagerFactory beans.
				// 从 bean 容器获取 EntityManagerFactory 
				emf = findEntityManagerFactory(this.unitName, requestingBeanName);
			}
			return emf;
		}

		// 获取 EntityManager  的方法
		private EntityManager resolveEntityManager(@Nullable String requestingBeanName) {
			// Obtain EntityManager reference from JNDI?
			// 从JNDI获取 EntityManager 
			EntityManager em = getPersistenceContext(this.unitName, false);
			if (em == null) {
				// No pre-built EntityManager found -> build one based on factory.
				// Obtain EntityManagerFactory from JNDI?
				// 从 bean 容器获取 EntityManagerFactory ,然后创建 EntityManager 
				EntityManagerFactory emf = getPersistenceUnit(this.unitName);
				if (emf == null) {
					// Need to search for EntityManagerFactory beans.
					emf = findEntityManagerFactory(this.unitName, requestingBeanName);
				}
				// Inject a shared transactional EntityManager proxy.
				if (emf instanceof EntityManagerFactoryInfo &&
						((EntityManagerFactoryInfo) emf).getEntityManagerInterface() != null) {
					// Create EntityManager based on the info's vendor-specific type
					// (which might be more specific than the field's type).
					em = SharedEntityManagerCreator.createSharedEntityManager(
							emf, this.properties, this.synchronizedWithTransaction);
				}
				else {
					// Create EntityManager based on the field's type.
					em = SharedEntityManagerCreator.createSharedEntityManager(
							emf, this.properties, this.synchronizedWithTransaction, getResourceType());
				}
			}
			return em;
		}

		// 获取 EntityManager扩展  的方法
		private EntityManager resolveExtendedEntityManager(Object target, 
			@Nullable String requestingBeanName) {
			// Obtain EntityManager reference from JNDI?
			EntityManager em = getPersistenceContext(this.unitName, true);
			if (em == null) {
				// No pre-built EntityManager found -> build one based on factory.
				// Obtain EntityManagerFactory from JNDI?
				EntityManagerFactory emf = getPersistenceUnit(this.unitName);
				if (emf == null) {
					// Need to search for EntityManagerFactory beans.
					emf = findEntityManagerFactory(this.unitName, requestingBeanName);
				}
				// Inject a container-managed extended EntityManager.
				em = ExtendedEntityManagerCreator.createContainerManagedEntityManager(
						emf, this.properties, this.synchronizedWithTransaction);
			}
			if (em instanceof EntityManagerProxy && beanFactory != null && requestingBeanName != null &&
					beanFactory.containsBean(requestingBeanName) && 
					!beanFactory.isPrototype(requestingBeanName)) {
				extendedEntityManagersToClose.put(target, ((EntityManagerProxy) 
					em).getTargetEntityManager());
			}
			return em;
		}
	}

从上面的代码可以看出,持久化属性的注入主要是通过InjectionMetadata#inject+PersistenceElement#getResourceToInject完成的。而persistence unitpersistence context首先尝试从JDNI获取,如果获取不到,则尝试从bean容器获取。下面我们继续分析这些组件的获取方法。

bean容器获取persistence unit/persistence context的方法

bean容器获取persistence unit

protected EntityManagerFactory findEntityManagerFactory(@Nullable String unitName, 
	@Nullable String requestingBeanName)
			throws NoSuchBeanDefinitionException {

		String unitNameForLookup = (unitName != null ? unitName : "");
		if (unitNameForLookup.isEmpty()) {
			unitNameForLookup = this.defaultPersistenceUnitName;
		}
		if (!unitNameForLookup.isEmpty()) {
			return findNamedEntityManagerFactory(unitNameForLookup, requestingBeanName);
		}
		else {
			return findDefaultEntityManagerFactory(requestingBeanName);
		}
	}
	/**
	 * Find an EntityManagerFactory with the given name in the current
	 * Spring application context.
	 * @param unitName the name of the persistence unit (never empty)
	 * @param requestingBeanName the name of the requesting bean
	 * @return the EntityManagerFactory
	 * @throws NoSuchBeanDefinitionException if there is no such EntityManagerFactory in the context
	 */
	protected EntityManagerFactory findNamedEntityManagerFactory(String unitName, 
		@Nullable String requestingBeanName)
			throws NoSuchBeanDefinitionException {

		Assert.state(this.beanFactory != null, 
			"ListableBeanFactory required for EntityManagerFactory bean lookup");

		EntityManagerFactory emf = EntityManagerFactoryUtils.findEntityManagerFactory(
			this.beanFactory, unitName);
		if (requestingBeanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {
			((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(unitName, 
				requestingBeanName);
		}
		return emf;
	}	

	/**
	 * Find a single default EntityManagerFactory in the Spring application context.
	 * @return the default EntityManagerFactory
	 * @throws NoSuchBeanDefinitionException if there is no single EntityManagerFactory in the context
	 */
	protected EntityManagerFactory findDefaultEntityManagerFactory(@Nullable String requestingBeanName)
			throws NoSuchBeanDefinitionException {

		Assert.state(this.beanFactory != null, 
			"ListableBeanFactory required for EntityManagerFactory bean lookup");

		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			// Fancy variant with dependency registration
			ConfigurableListableBeanFactory clbf = (ConfigurableListableBeanFactory) this.beanFactory;
			NamedBeanHolder<EntityManagerFactory> emfHolder = 
				clbf.resolveNamedBean(EntityManagerFactory.class);
			if (requestingBeanName != null) {
				clbf.registerDependentBean(emfHolder.getBeanName(), requestingBeanName);
			}
			return emfHolder.getBeanInstance();
		}
		else {
			// Plain variant: just find a default bean
			return this.beanFactory.getBean(EntityManagerFactory.class);
		}
	}

bean容器获取persistence context

从上面的的代码分析可以看出,如果persistence unit,也就是组件EntityManagerFactory bean,是从容器中获取得到的,则persistence context,也就是EntityManager实例,会基于该组件EntityManagerFactory bean,通过SharedEntityManagerCreator.createSharedEntityManager方法创建得到。

JNDI获取persistence unit/persistence context的方法

JNDI获取persistence unit

JNDI获取persistence unit的实现由PersistenceAnnotationBeanPostProcessor实现如下:

	@Nullable
	protected EntityManagerFactory getPersistenceUnit(@Nullable String unitName) {
		if (this.persistenceUnits != null) {
			String unitNameForLookup = (unitName != null ? unitName : "");
			if (unitNameForLookup.isEmpty()) {
				unitNameForLookup = this.defaultPersistenceUnitName;
			}
			String jndiName = this.persistenceUnits.get(unitNameForLookup);
			if (jndiName == null && "".equals(unitNameForLookup) && this.persistenceUnits.size() == 1) {
				jndiName = this.persistenceUnits.values().iterator().next();
			}
			if (jndiName != null) {
				try {
					return lookup(jndiName, EntityManagerFactory.class);
				}
				catch (Exception ex) {
					throw new IllegalStateException("Could not obtain EntityManagerFactory [" 
						+ jndiName + "] from JNDI", ex);
				}
			}
		}
		return null;
	}

JNDI获取persistence context

JNDI获取persistence context的实现由PersistenceAnnotationBeanPostProcessor实现如下:

	@Nullable
	protected EntityManager getPersistenceContext(@Nullable String unitName, boolean extended) {
		Map<String, String> contexts = (extended ? this.extendedPersistenceContexts : 
			this.persistenceContexts);
		if (contexts != null) {
			String unitNameForLookup = (unitName != null ? unitName : "");
			if (unitNameForLookup.isEmpty()) {
				unitNameForLookup = this.defaultPersistenceUnitName;
			}
			String jndiName = contexts.get(unitNameForLookup);
			if (jndiName == null && "".equals(unitNameForLookup) && contexts.size() == 1) {
				jndiName = contexts.values().iterator().next();
			}
			if (jndiName != null) {
				try {
					return lookup(jndiName, EntityManager.class);
				}
				catch (Exception ex) {
					throw new IllegalStateException("Could not obtain EntityManager [" + 
						jndiName + "] from JNDI", ex);
				}
			}
		}
		return null;
	}
	protected <T> T lookup(String jndiName, Class<T> requiredType) throws Exception {
		return new LocatorDelegate().lookup(jndiName, requiredType);
	}	

内部类LocatorDelegate – 从JNDI查找EntityManagerFactory/EntityManager

	// 从JNDI中查找EntityManagerFactory或者EntityManager的内部类,基于JndiLocatorDelegate 
	// 的一个封装
	private class LocatorDelegate {

		public <T> T lookup(String jndiName, Class<T> requiredType) throws Exception {
			JndiLocatorDelegate locator = new JndiLocatorDelegate();
			if (jndiEnvironment instanceof JndiTemplate) {
				locator.setJndiTemplate((JndiTemplate) jndiEnvironment);
			}
			else if (jndiEnvironment instanceof Properties) {
				locator.setJndiEnvironment((Properties) jndiEnvironment);
			}
			else if (jndiEnvironment != null) {
				throw new IllegalStateException("Illegal 'jndiEnvironment' type: " + 
					jndiEnvironment.getClass());
			}
			locator.setResourceRef(resourceRef);
			return locator.lookup(jndiName, requiredType);
		}
	}	

总结

通过上述分析可见,在一个Spring JPA应用中,PersistenceAnnotationBeanPostProcessor用于发现每个bean中的持久化注解并完成这些持久化属性的注入。

这些持久化属性指的是使用@PersistenceUnit/@PersistenceContext注解的bean实例成员属性EntityManagerFactory/EntityManager或者这些属性的设置方法。

这个过程发生在每个bean的创建过程中。具体来讲,持久化属性注入元数据在bean创建#postProcessMergedBeanDefinition阶段被获取并缓存在该BeanPostProcessor对象,在bean创建#postProcessProperties阶段被注入到bean中。

持久化属性的注入使用了框架内部工具InjectionMetadata+InjectedElement子类PersistenceElement。该子类PersistenceElement是一个PersistenceAnnotationBeanPostProcessor的嵌套子类。

persistence unitpersistence context组件的获取,由PersistenceElement使用PersistenceAnnotationBeanPostProcessor提供的方法getPersistenceUnit/getPersistenceContext/findEntityManagerFactoryJNDI或者bean容器获得。

你可能感兴趣的:(Spring,JPA,Spring,Data,分析)