【技术】JavaSE环境下JPA实体类自动注册

        在没有容器支持的环境下,JPA的实体类(Entity)一般要在persistence.xml中逐个注册,类似下面这样:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
 3     <persistence-unit name="security-common">
 4         <class>org.gems.han.security.common.MenuVO</class>
 5         <class>org.gems.han.security.common.MenuItemVO</class>
 6         <class>org.gems.han.security.common.ModuleVO</class>
 7         <class>org.gems.han.security.common.RoleVO</class>
 8         <class>org.gems.han.security.common.UserVO</class>
 9         <class>org.gems.han.security.common.UserRoleVO</class>
10     </persistence-unit>
11 </persistence>

不知道大家有没有跟我一样感觉很麻烦,也很疑惑,明明每个实体类都标记了@Entity,为什么还要再注册一遍? 有没有办法利用@Entity标记,免除注册实体类的麻烦?经过研究,找到如下方案,分享给大家。

      首先说明一下我使用的JPA实现是Eclipselink,我的方案也只在该实现下进行了验证。

      我们知道EntityManagerFactory是由PersistenceProvider创建的,就从它入手设法解决上述问题。这个类在不同JPA实现中有不同的实现类,Eclipselink下是org.eclipse.persistence.jpa.PersistenceProvider,我们首先继承这个类,覆盖其中的方法createEntityManagerFactoryImpl,如下:

1     @Override
2     protected EntityManagerFactoryImpl createEntityManagerFactoryImpl(PersistenceUnitInfo puInfo, Map properties,
3             boolean requiresConnection) {
4         List<String> classNameList = puInfo.getManagedClassNames();
5         List<String> entityClassNameList =getManagedClassNames();
6         classNameList.addAll(entityClassNameList);
7         return super.createEntityManagerFactoryImpl(puInfo, properties, requiresConnection);
8     }

原理说明:第4行,如果persistence.xml中没有注册实体类,那么classNameList将是一个空的List(注意是empty list,不是null);第5行,通过方法getManagedClassNames获取实体类;第6行,把实体类增加到列表中;第7行,调用父类方法,实现工厂类的创建。下面关键就是getManagedClassNames方法的实现。这里我使用了google的开源项目guava来扫描java类,然后从中筛选有@Entity标记的实体类,代码如下:

 1     public List<String> getManagedClassNames() {
 2         List<String> managedClassNameList = new ArrayList<>();
 3         ClassLoader loader = Thread.currentThread().getContextClassLoader();
 4         ImmutableSet<ClassInfo> cs = null;
 5         try {
 6             cs = ClassPath.from(loader).getTopLevelClasses();
 7         } catch (IOException e) {
 8             e.printStackTrace();
 9         }
10         managedClassNameList = new ArrayList<>();
11         if (cs != null && cs.isEmpty() == false) {
12             for (ClassInfo ci : cs) {
13                 Class<?> c = null;
14                 try {
15                     c = loader.loadClass(ci.getName());
16                 } catch (Throwable ex) {
17                 }
18                 if (c != null) {
19                     Entity entity = c.getAnnotation(Entity.class);
20                     if (entity != null) {
21                         managedClassNameList.add(c.getName());
22                     }
23                 }
24             }
25         }
26         return managedClassNameList;
27     }

其中类加载器(loader)根据具体情况使用,但如果使用了特殊的ClassLoader,需要再覆盖org.eclipse.persistence.jpa.PersistenceProvider的getClassLoader方法,以便保证运行时能够正常加载实体类。

      另外,在实验中发现PersisitenceProvider是通过SPI的方式加载的,而不是根据配置文件persistence.xml中的设置。因此,需要在META-INF下增加services/javax.persistence.spi.PersistenceProvider文件,在该文件中写入我们自己的PersistenceProvider。这一点我还有些疑惑,请大家在实践中进行验证。

你可能感兴趣的:(【技术】JavaSE环境下JPA实体类自动注册)