模块化的开发在复杂的项目中是非常普通的事,在我们的项目中使用了Spring+JPA架构,最终产品可能部署于不同的容器中,但在部署到非J2EE环境下时,如部署到Tomcat中,运环境是J2SE时,分别按模块打包在不同JAR中的JPA的注解实体却出现了不能自动识别并添加到EntityManagerFactory所管辖的持久化单元中的问题,这些JAR中的注解实体类分属不同的通用功能模块并可以在不同的项目中重用,但由于各个JAR是在不同项目中累积的通用实体库,其中的persistence.xml配置也千差万别.而Spring 的JPA支持并不是那么完美,看过Spring源代码的人都知道,一个LocalContainerEntityManagerFactoryBean只能管理一个持久化单元,尽管其内部创建的DefaultPersistenceUnitManager类可以读取多个持久化单元信息(PersistenceUnitInfo),但最终只能有一个被返回给LocalContainerEntityManagerFactoryBean用于创建JPA实现提供者的EntityManagerFactory,并不像网上搜索到的资料或那些破烂书籍所说的那样能真正支持多个持久化单元(我以前没有看spirng源代码时就被误导了,害人不浅!!)。关于这个问题,官方文档说的也是很含糊、官方论坛里虽然也有一两个解决办法、但都不完美。
第一种是 :通过DefaultPersistenceUnitManager提供的设置PersistenceUnitPostProcessor接口实现类,传入DefaultPersistenceUnitManager扫描到的classpath中所有persistence.xml最终得到的对应PersistenceUnitInfo,并赶在将这些PersistenceUnitInfo其中一个(只能一个,而且是第一个扫描到的)返回给LocalContainerEntityManagerFactoryBean之前修改各个对应persistence.xml的PersistenceUnitInfo中的配置,从而将各个persistence.xml中配置的实体类合并成一个PersistenceUnitInfo并最终返回给LocalContainerEntityManagerFactoryBean用以创建EntityManagerFactory。
//PersistenceUnitPostProcessor实现类,用于修改PersistenceUnitManager读取到的各个对应persistence.xml产生的PersistenceUnitInfo package com.yotexs.config.persist.jpa; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor; /** * 合并多个JAR中的JPA实体到一个持久化单元 * <p> * This merges all JPA entities from multiple jars. To use it, all entities must be listed in their respective * persistence.xml files using the <class> tag. * * @see http://forum.springsource.org/showthread.php?t=61763 a * @see http://javathoughts.capesugarbird.com/2009/02/jpa-and-multiple-persistence -units.html b */ public class MergingPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor { Map<String, List<String>> puiClasses = new HashMap<String, List<String>>(); public MergingPersistenceUnitPostProcessor() { } public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) { List<String> classes = puiClasses.get(pui.getPersistenceUnitName()); if (classes == null) { classes = new ArrayList<String>(); puiClasses.put(pui.getPersistenceUnitName(), classes); } pui.getManagedClassNames().addAll(classes); final List<String> names = pui.getManagedClassNames(); classes.addAll(names); System.out.println("---------------------------------------------------------------"); System.out.println(pui.getPersistenceUnitName()); System.out.println(pui); for (String cn : names) { System.out.println(cn); } System.out.println("---------------------------------------------------------------"); } public void scanEntity() { } }
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitName" value="${jpa.persistenceUnitName}" /> <property name="jpaVendorAdapter" ref="jpaVendorAdapter" /> <property name="jpaProperties" ref="jpaProviderProperties" /> <property name="persistenceUnitPostProcessors" > <util:list> <bean class="com.yotexs.config.persist.jpa.MergingPersistenceUnitPostProcessor" /> </util:list> </property> </bean>
第二种做法是 :BeanPostProcessor监察LocalContainerEntityManagerFactoryBean,在其完成容器初始发前修改其内部的DefaultPersistenceUnitManager返回的哪一个PersistenceUnitInfo中注册的实体信息
package com.yotexs.config.persist.jpa; import java.util.List; import java.util.Set; import javax.persistence.Entity; import javax.persistence.spi.PersistenceUnitInfo; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import com.yotexs.util.ClassPathScaner; import com.yotexs.util.ReflectionUtils; /** * @author bencmai */ public class PersistenceClassesConfigProcessor implements BeanPostProcessor { private static final String ENTITY_MANAGER_FACTORY = "entityManagerFactory"; String entityManagerFactory = ENTITY_MANAGER_FACTORY; private String[] entityClassNames; public PersistenceClassesConfigProcessor() { } public String[] getEntityClassNames() { if (entityClassNames == null || entityClassNames.length < 1) {// 配置中没有指定添加到持久化单元的实体时进行自动扫描 ClassPathScaner p = new ClassPathScaner(); p.addIncludeFilter(new AnnotationTypeFilter(Entity.class)); Set<MetadataReader> entitys = p.findCandidateClasss("com.yotexs.model"); entityClassNames = new String[entitys.size()]; int i = 0; for (MetadataReader entity : entitys) { entityClassNames[i++] = entity.getClassMetadata().getClassName(); } } return entityClassNames; } public void setEntityClassNames(String[] entityClassNames) { this.entityClassNames = entityClassNames; } public void setBeanNameOfEntityManagerFactory(String entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals(entityManagerFactory) && bean instanceof LocalContainerEntityManagerFactoryBean) { LocalContainerEntityManagerFactoryBean unitManager = (LocalContainerEntityManagerFactoryBean) bean; PersistenceUnitInfo puf = (PersistenceUnitInfo) ReflectionUtils.getFieldValue(unitManager, "persistenceUnitInfo"); List<String> managedClass = puf.getManagedClassNames(); managedClass.clear(); for (String entityClassName : getEntityClassNames()) { if (!(managedClass.contains(entityClassName))) { managedClass.add(entityClassName);// 加入额外的持久化类 } } } return bean; } }
用法: 第一步 <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitManager" ref="persistenceUnitManager"/> <property name="dataSource" ref="dataSource"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.RedSoftJpaVendorAdapter"> </bean> </property> </bean> <bean id="persistenceUnitManager" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"> <property name="persistenceXmlLocations"> <list> <value>classpath*:/redsoft_jpa_persistence.xml</value> </list> </property> </bean> 第二步 <import resource="classpath*:/ormContext.xml"/> <bean id="entityClasses" class="com.abest.util.PersistenceClassesConfigProcessor"> <property name="entityClassNames"> <list> <value>com.abest.common.dao.TestBean</value> <value>com.abest.uwin.baseinfo.domain.Region</value> <value>com.abest.uwin.baseinfo.domain.Brand</value> <value>com.abest.uwin.baseinfo.domain.MeasureUnit</value> <value>com.abest.uwin.baseinfo.domain.ProductionFactory</value> <value>com.abest.uwin.baseinfo.domain.MaterialType</value> </list> </property> </bean>
上面两种做法(仅做演示,在你的环境中不一定通过)在一定程度上可以解决问题,但使用上还是相对麻烦而且有一定的限制,且使用起来不灵活,在我自己的实现中,
我使用了按指定的包名豆单列表扫描classpath的JAR的办法(实际上我做了另外的两种实现,一种是仿照Hibernate的Ejb3Configration的JAR包扫描,这种实现虽然方便和通用,但较为复杂,另一种是仿照spring的ClassPathScanningCandidateComponentProvider扫描@Component的办法,简单通用,这里以后者为例)
package com.yotexs.util; import java.io.IOException; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.persistence.Entity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.ClassUtils; import org.springframework.util.SystemPropertyUtils; /** * 类或资源扫描器,可以批量过滤CLASSPATH路径和JAR中的任意内容 * <p> * This implementation is based on Spring's {@link org.springframework.core.type.classreading.MetadataReader * MetadataReader} facility, backed by an ASM {@link org.springframework.asm.ClassReader ClassReader}. * * @author bencmai * @see org.springframework.core.type.classreading.MetadataReaderFactory * @see org.springframework.core.type.AnnotationMetadata */ public class ClassPathScaner { protected final Logger logger = LoggerFactory.getLogger(getClass()); private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); private String resourcePattern = DEFAULT_RESOURCE_PATTERN; private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>(); private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>(); /** Add an include type filter to the <i>end</i> of the inclusion list. */ public void addIncludeFilter(TypeFilter includeFilter) { this.includeFilters.add(includeFilter); } /** Add an exclude type filter to the <i>front</i> of the exclusion list. */ public void addExcludeFilter(TypeFilter excludeFilter) { this.excludeFilters.add(0, excludeFilter); } /** Reset the configured type filters. */ public void resetFilters() { this.includeFilters.clear(); this.excludeFilters.clear(); } /** * 将.包路径解释为/路径 <br> * Resolve the specified base package into a pattern specification for the package search path. * <p> * The default implementation resolves placeholders against system properties, and converts a "."-based package path * to a "/"-based resource path. * * @param basePackage the base package as specified by the user * @return the pattern specification to be used for package searching */ public String packageToRelativedPath(String basePackage) { return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)); } /** * 和配置的排除过滤器和包含过滤器进行匹配过滤<br> * Determine whether the given class does not match any exclude filter and does match at least one include filter. * * @param metadataReader the ASM ClassReader for the class * @return whether the class qualifies as a candidate component */ protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return true; } } return false; } /** * 扫描类路径并根据过滤器以找出合条件的类元数据<br> * <p> * 注:如果包是空白字符,只能扫描到非JAR中的内容 * * <pre> * ClassPathScaner p = new ClassPathScaner(); * // p.addIncludeFilter(new AssignableTypeFilter(TypeFilter.class)); * // Set<MetadataReader> bd = p.findCandidateClasss("org.springframework"); * p.addIncludeFilter(new AnnotationTypeFilter(Entity.class)); * Set<MetadataReader> bd = p.findCandidateClasss("com"); * for (MetadataReader b : bd) { * System.out.println(b.getClassMetadata().getClassName()); * } * p.resetFilters(); * </pre> * * @param basePackage the package to check for annotated classes * @return a corresponding Set of autodetected bean definitions */ public Set<MetadataReader> findCandidateClasss(String... basePackage) { Set<MetadataReader> candidates = new LinkedHashSet<MetadataReader>(); try { for (String s : basePackage) { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + packageToRelativedPath(s) + "/" + this.resourcePattern; if (logger.isDebugEnabled()) logger.debug("扫描包:" + packageSearchPath); Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath); for (Resource resource : resources) { if (resource.isReadable()) { try { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { candidates.add(metadataReader); if (logger.isDebugEnabled()) logger.debug("匹配:" + metadataReader.getClassMetadata().getClassName()); } } catch (Throwable ex) { // throw new Exception("类符合条件但读取失败: " + resource, ex); } } else { if (logger.isDebugEnabled()) logger.debug("因为扫描到的类无法读取元数据,忽略: " + resource); } } } } catch (IOException ex) { // throw new IOException("扫描时发生 I/O 异常", ex); } return candidates; } /** * 扫描CLASSPATH路径以找出合条件的文件 * <p> * 注:如果包是空白字符,只能扫描到非JAR中的内容 * * <pre> * ClassPathScaner p = new ClassPathScaner(); * List<Resource> rs = p.findCandidateFiles("**/*.properties";, "META-INF", "com.yotexs.model";, "org.yotexs.model";); * for (Resource r : rs) { * System.out.println(r.getURI().toURL().toString()); * } * </pre> * * @param patterns ant方式的匹配模式 如:**⁄*.xml * @param basePackage 要扫描的包组 如:com.yotexs,META-INF * @return */ public List<Resource> findCandidateFiles(String patterns, String... basePackage) { List<Resource> resources = new LinkedList<Resource>(); try { for (String p : basePackage) { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + packageToRelativedPath(p) + "/" + patterns; Resource[] current = this.resourcePatternResolver.getResources(packageSearchPath); if (current.length == 0) continue; resources.addAll(Arrays.asList(current)); } return resources; } catch (IOException ex) { // throw new IOException("扫描时发生 I/O 异常", ex); return null; } } // ---------------------------------------------------------------------------------------------- public static void main(String a[]) throws Exception { // -----------------------------------------直接扫描 // Enumeration<URL> xmls = Thread.currentThread().getContextClassLoader() // .getResources("META-INF/persistence.xml"); // while (xmls.hasMoreElements()) { // URL url = xmls.nextElement(); // System.out.println(url.toURI().toURL()); // } // -----------------------------------------直接扫描 // ResourcePatternResolver rp = new PathMatchingResourcePatternResolver(); // String packages = "META-INF"; // Resource[] rs = rp.getResources("classpath*:/" + packages + "/**/*.*"); // for (Resource r : rs) { // System.out.println(r); // } // -----------------------------------------类扫描 ClassPathScaner p = new ClassPathScaner(); // p.addIncludeFilter(new AssignableTypeFilter(TypeFilter.class)); // Set<MetadataReader> bd = p.findCandidateClasss("org.springframework"); p.addIncludeFilter(new AnnotationTypeFilter(Entity.class)); Set<MetadataReader> bd = p.findCandidateClasss("com"); for (MetadataReader b : bd) { System.out.println("<class>" + b.getClassMetadata().getClassName() + "</class>"); } // -----------------------------------------资源扫描 // ClassPathScaner p = new ClassPathScaner(); // List<Resource> rs = p.findCandidateFiles("**/*.xml", "META-INF", "com", "org"); // for (Resource r : rs) { // System.out.println(r.getURI().toURL().toString()); // } } }
package com.yotexs.config.persist.jpa; import java.util.List; import java.util.Set; import javax.persistence.Entity; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceException; import javax.persistence.spi.PersistenceProvider; import javax.persistence.spi.PersistenceUnitInfo; import javax.sql.DataSource; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; import com.yotexs.util.Assert; import com.yotexs.util.ClassPathScaner; import com.yotexs.util.StringUtils; /** * 重新实现JPA容器托管实体工厂代替LocalContainerEntityManagerFactoryBean以方便扫描打jar后的models * * @author bencmai */ public class ContainerEntityManagerFactoryBean extends AbstractEntityManagerFactoryBean { private DefaultPersistenceUnitManager persistenceUnitManager; private PersistenceUnitInfo persistenceUnitInfo; private DataSource dataSource; private String scanPackages = ""; // ---------------------------------------------------------------------------------------------- @Override public PersistenceUnitInfo getPersistenceUnitInfo() { return this.persistenceUnitInfo; } @Override public String getPersistenceUnitName() {// 指定实体工厂使用特定的持久化单元 if (this.persistenceUnitInfo != null) { return this.persistenceUnitInfo.getPersistenceUnitName(); } return super.getPersistenceUnitName(); } @Override public DataSource getDataSource() { if (dataSource != null) return dataSource; else if (this.persistenceUnitInfo != null) return this.persistenceUnitInfo.getNonJtaDataSource(); else return this.persistenceUnitManager.getDefaultDataSource(); } @Override protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException { Assert.isTrue((persistenceUnitManager != null || persistenceUnitInfo != null), "需要设置persistenceUnitManager或persistenceUnitInfo属性"); if (persistenceUnitInfo == null) this.persistenceUnitInfo = determinePersistenceUnitInfo(persistenceUnitManager); JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter(); if (jpaVendorAdapter != null && this.persistenceUnitInfo instanceof MutablePersistenceUnitInfo) { ((MutablePersistenceUnitInfo) this.persistenceUnitInfo).setPersistenceProviderPackageName(jpaVendorAdapter .getPersistenceProviderRootPackage()); } PersistenceProvider provider = getPersistenceProvider(); if (logger.isInfoEnabled()) logger.info("正在构建 JPA 容器 EntityManagerFactory, 持久化单元为:'" + this.persistenceUnitInfo.getPersistenceUnitName() + "'"); scanEntitys();// 扫描实体并在创建实体工厂前添加到所使用的持久化单元里 this.nativeEntityManagerFactory = provider.createContainerEntityManagerFactory(this.persistenceUnitInfo, getJpaPropertyMap()); postProcessEntityManagerFactory(this.nativeEntityManagerFactory, this.persistenceUnitInfo); return this.nativeEntityManagerFactory; } // ---------------------------------------------------------------------------------------------- protected PersistenceUnitInfo determinePersistenceUnitInfo(PersistenceUnitManager persistenceUnitManager) { if (getPersistenceUnitName() != null) return persistenceUnitManager.obtainPersistenceUnitInfo(getPersistenceUnitName()); else return persistenceUnitManager.obtainDefaultPersistenceUnitInfo(); } protected void postProcessEntityManagerFactory(EntityManagerFactory emf, PersistenceUnitInfo pui) { } public void setPersistenceUnitManager(DefaultPersistenceUnitManager persistenceUnitManager) { this.persistenceUnitManager = persistenceUnitManager; } // 直接指定PersistenceUnitInfo就不需要指定PersistenceUnitManager,但应指定DataSource public void setPersistenceUnitInfo(PersistenceUnitInfo persistenceUnitInfo) { this.persistenceUnitInfo = persistenceUnitInfo; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void setScanPackages(String scanPackages) {// null或不指定表示不扫描,空白表示扫描非jar目录 this.scanPackages = scanPackages; } /** 扫描classpath中的(包括分别打包在不同jar中的)实体类并添加到PersistenceUnitInfo中以创建EntityManagerFactory */ private void scanEntitys() { String[] pgs = StringUtils.commaDelimitedListToStringArray(scanPackages); if (pgs.length > -1) { ClassPathScaner p = new ClassPathScaner(); // p.addIncludeFilter(new AssignableTypeFilter(TypeFilter.class)); // Set<MetadataReader> bd = p.findCandidateClasss("org.springframework"); p.addIncludeFilter(new AnnotationTypeFilter(Entity.class)); Set<MetadataReader> bd = p.findCandidateClasss(pgs); List<String> managedClass = persistenceUnitInfo.getManagedClassNames(); for (MetadataReader b : bd) { if (!(managedClass.contains(b.getClassMetadata().getClassName()))) { managedClass.add(b.getClassMetadata().getClassName()); } } } } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd"> <context:property-placeholder location="classpath*:/META-INF/*.properties" /> <!-- jdbc额外属性 --> <util:properties id="jdbcExtraProperties" location="classpath:META-INF/jdbcextra.properties" /> <!-- jpa提供者属性 --> <util:properties id="jpaProviderProperties" location="classpath:META-INF/jpaprovider.properties" /> <context:annotation-config /> <tx:annotation-driven /> <!--数据源--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${connection.driver_class}" /> <property name="jdbcUrl" value="${connection.url}" /> <property name="user" value="${connection.username}" /> <property name="password" value="${connection.password}" /> <property name="properties" ref="jdbcExtraProperties" /> </bean> <!--持久化单元管理器 --> <bean id="persistenceUnitManager" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"> <property name="dataSources"> <map> <entry key="c3p0DataSourse" value-ref="dataSource" /> </map> </property> <property name="defaultDataSource" ref="dataSource" /> <!-- <property name="persistenceUnitPostProcessors"> <bean class="com.yotexs.config.jpa.MergingPersistenceUnitPostProcessor" /> </property> --> </bean> <!-- JPA 提供者实现 --> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="${jpa.showSql}" /> <property name="generateDdl" value="${jpa.generateDdl}" /> <property name="database" value="${jpa.database}" /> <property name="databasePlatform" value="${jpa.databasePlatform}" /> </bean> <!-- 实体管理器工厂 --> <bean id="entityManagerFactory" class="com.yotexs.config.persist.jpa.ContainerEntityManagerFactoryBean"> <property name="persistenceUnitManager" ref="persistenceUnitManager" /> <property name="persistenceUnitName" value="${jpa.persistenceUnitName}" /> <property name="jpaVendorAdapter" ref="jpaVendorAdapter" /> <!-- 指定需要扫描的存在@Entity实体的包,并将扫描到的实体 添加到持久化单元--> <property name="scanPackages" value="com.yotexs.model,org.yotexs.model" /> <property name="jpaProperties" ref="jpaProviderProperties" /> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <!-- 字节码织入方式 --> <context:load-time-weaver /> </beans>