美文网首页java高级开发
hibernate动态注册一个动态实体类参考

hibernate动态注册一个动态实体类参考

作者: 老鼠AI大米_Java全栈 | 来源:发表于2023-01-14 16:48 被阅读0次

hibernate如何动态注册一个动态生成的实体类

我们都知道,hibernate是在web容器启动的时候根据相关配置信息,扫描class文件,然后注册到SessionFactory中。通过getAllClassMetadata()可以得到已经注册过的实体类的元信息。那么如果是在容器启动已经完成后,程序正常运行期间产生的类,显然是不能直接使用hibernate的接口的,因为hibernate还不认识这个新创建的类呢。那么如何在这个阶段让它们认识并建立关系呢,思路很简单,就是注册呗,但是这里有个问题,我们不能使用原来的sessionFactory了,会导致事务安全的问题,而且hibernate也建议sessionFactory一旦创建好了,就不要对其做修改,所以即使configration的内容变化了,之前就创建好的sessionFactory也不会受到影响。

使用spring提供的LocalSessionFactoryBean来得到SessionFactory

一直不明白,spring为什么可以通过注入LocalSessionFactoryBean的bean名称来得到SessionFactory,起初以为LocalSessionFactoryBean必然是SessionFactory的派生类,但是分析源码发现这两个类没有任何关系,只是LocalSessionFactoryBean持有了一个SessionFactory的引用,后来得知,spring在注入LocalSessionFactoryBean的时候会自动调用LocalSessionFactoryBean中的getObject方法将LocalSessionFactoryBean中的SessionFactory属性返回,来替换掉LocalSessionFactoryBean的实例,那如果我们非要得到LocalSessionFactoryBean的实例怎么办呢,很简单,就在注入bean的名称的时候前面加上‘&’即可

public class myDao{   
    @Autowired
    @Qualifier("sessionFactory")
    private SessionFactory sessionFactory;

    private List<SessionFactory> sessionFactoryList = new ArrayList<SessionFactory>();

    /******************** 以下 方法只适用于对象是动态加载进JVM的情况******************************/
    /**
     * 获取Session工厂
     * 如果指定的model是动态加载到JVM中的,
     * 那么就更新当前的configuration,
     * 并重新创建一个Session工厂(因为Session工厂是全局单例的,所以创建好之后最好不要改动,容易出现事务安全问题),
     * 以便能为该对象执行数据库访问操作
     * 该方法如果频繁使用,会增加系统开销
     * @param entityClass
     * @return
     */
    private SessionFactory obtainSessionFactory(Class<?> entityClass){
        Map<String,ClassMetadata> map = sessionFactory.getAllClassMetadata();
        Set<String> set = map.keySet();
        if(!set.contains(entityClass.getName())){
            for(SessionFactory factory : sessionFactoryList){
                Set<String> existSet = factory.getAllClassMetadata().keySet();
                if(existSet.contains(entityClass.getName())){//该sessionFactory包含了此实体,就用该SessionFactory
                    return factory;
                }
            }
            LocalSessionFactoryBean localSessionFactoryBean = SpringContextUtil.getBean("&sessionFactory");
            Configuration configuration = localSessionFactoryBean.getConfiguration();
            synchronized(configuration){//避免并发操作导致configuration重复添加相同的entityClass
                if(configuration.getClassMapping(entityClass.getName())==null){
                    configuration.addAnnotatedClass(entityClass);
                }
            }
            ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
            SessionFactory newSessionFactory = configuration.buildSessionFactory(serviceRegistry);
            sessionFactoryList.add(newSessionFactory);
            return newSessionFactory;
        }else{
            return sessionFactory;
        }
    }
}

通过hibernate配置文件动态创建实体

核心代码如下:

/**
 * @author cjbi
 */
public class DynamicDdlTest {
    
    @Autowired
    private EntityManagerFactory entityManagerFactory;
    /**
     * 运行期的持久化实体没有必要一定表示为像POJO类或JavaBean对象那样的形式。
     * Hibernate也支持动态模型在运行期使用Map)和象DOM4J的树模型那样的实体表示。
     * 使用这种方法,你不用写持久化类,只写映射文件就行了。
    **/
    public static final String XML_MAPPING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "<!DOCTYPE hibernate-mapping PUBLIC\n" +
            "        \"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"\n" +
            "        \"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd\">\n" +
            "<hibernate-mapping>\n" +
            "    <class entity-name=\"Student\" table=\"t_student\">\n" +
            "        <id name=\"id\" type=\"java.lang.Long\" length=\"64\" unsaved-value=\"null\">\n" +
            "            <generator class=\"identity\" />\n" +
            "        </id>" +
            "        <property type=\"java.lang.String\" name=\"username\" column=\"username\"/>\n" +
            "        <property name=\"password\" type=\"java.lang.String\" column=\"password\"/>\n" +
            "        <property name=\"sex\" type=\"java.lang.String\" column=\"sex\"/>\n" +
            "        <property name=\"age\" type=\"java.lang.Integer\" column=\"age\"/>\n" +
            "        <property name=\"birthday\" type=\"java.util.Date\" column=\"birthday\"/>\n" +
            "    </class>" +
            "</hibernate-mapping>";

    @Test
    public void testDynamicDdl() {
        SessionFactory sessionFactory =  entityManagerFactory.unwrap(SessionFactory.class);
        StandardServiceRegistry serviceRegistry = sessionFactory.getSessionFactoryOptions().getServiceRegistry();
        MetadataSources metadataSources = new MetadataSources(serviceRegistry);
        sessionFactory.getSessionFactoryOptions();
        //读取映射文件
        metadataSources.addInputStream(new ByteArrayInputStream(XML_MAPPING.getBytes()));
        Metadata metadata = metadataSources.buildMetadata();
        //创建数据库Schema,如果不存在就创建表,存在就更新字段,不会影响已有数据
        SchemaExport schemaExport = new SchemaExport();
        schemaExport.createOnly(EnumSet.of(TargetType.DATABASE), metadata);
        
        Metadata metadata = metadataSources.buildMetadata();
        //创建会话工厂
        SessionFactory newSessionFactory = metadata.buildSessionFactory();
        //保存对象
        Session newSession = newSessionFactory.openSession();
        for (int i = 0; i < 100; i++) {
            Map<String, Object> student = new HashMap<>();
            student.put("username", "张三" + i);
            student.put("password", "adsfwr" + i);
            student.put("sex", i % 2 == 0 ? "male" : "female");
            student.put("age", i);
            student.put("birthday", new Date());
            newSession.save("Student", student);
        }
        //查询所有对象
        Query query = newSession.createQuery("from Student");
        List list = query.getResultList();
        System.out.println("resultList: " + list);
        //关闭会话
        newSession.close();
    }

}

java动态编译类文件并加载到内存

所谓动态编译,就是在程序运行时产生java类,并编译成class文件。

一、这里介绍两种动态编译java文件的方式。

第一种:使用Runtime执行javac命令

/**
     * 编译java类
     * 使用Runtime执行javac命令
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @throws java.io.IOException
     */
    public static void javac(String name) throws IOException {
        String javaPackageName = name.replace(".",File.separator)+".java";
        String javaAbsolutePath = classPath+javaPackageName;
        Process process = Runtime.getRuntime().exec("javac -classpath "+ jarAbsolutePath+ " " + javaAbsolutePath);
        try {
            InputStream errorStream = process.getErrorStream();
            InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            while ((line=bufferedReader.readLine()) != null){
                System.out.println(line);
            }
            int exitVal = process.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

第二种:使用jdk自带的rt.jar中的javax.tools包提供的编译器

/**
     * 编译java类
     * 使用rt.jar中的javax.tools包提供的编译器
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @throws java.io.IOException
     */
    public static void compiler(String name) throws IOException {
        String javaPackageName = name.replace(".",File.separator)+".java";
        String javaAbsolutePath = classPath+javaPackageName;
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        compiler.run(null,null,null,"-encoding","UTF-8","-classpath",jarAbsolutePath.toString(),javaAbsolutePath);
    }

二、使用Class.forName("");将class文件加载到内存中,并得到该类的class对象

/**
     * 动态编译一个java源文件并加载编译生成的class
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @throws java.io.IOException
     */
    public static Class<?> dynamicLoadClass(String name) throws IOException, ClassNotFoundException {
        if (!isClassExist(name)){
            compiler(name);
        }
        if(isJavaExist(name)){
            if(!FileUtil.destroyFile(classPath + name.replace(".",File.separator)+".java")){
                System.out.println("========================================>>>>删除源文件失败!");
            }
        }
        return Class.forName(name);
    }

完整代码如下:

package com.basic.core.classloader;

import com.basic.core.util.FileUtil;
import sun.tools.jar.Main;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * desc:自定义的类加载器,用于实现类的动态加载
 */
public class MyClassLoader extends ClassLoader {

    //类路径
    private static String classPath ;

    private static String jarPrefix;

    private static StringBuilder jarAbsolutePath;

    static{
        classPath = MyClassLoader.class.getClassLoader().getResource("").getPath();
        classPath = !classPath.startsWith("/")?classPath:classPath.substring(1);//去掉开始位置的/
        classPath = classPath.endsWith(File.separator)?classPath:classPath+File.separator;
        jarPrefix = classPath.substring(0,classPath.lastIndexOf("classes"))+File.separator+"lib"+File.separator;
        jarAbsolutePath = new StringBuilder().append(jarPrefix)
                .append("hibernate-core-4.2.0.Final.jar;")
                .append(jarPrefix).append("hibernate-jpa-2.0-api-1.0.1.Final.jar;")
                .append(jarPrefix).append("validation-api-1.0.0.GA.jar;");
    }

    /**
     * 如果父的类加载器中都找不到name指定的类,
     * 就会调用这个方法去从磁盘上加载一个类
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @return
     * @throws java.io.IOException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes = null;
        Class<?> clazz = null;
        try {
            //加载类的字节码
            classBytes = loadClassBytes(name);
            //将字节码交给JVM
            clazz = defineClass(name,classBytes,0,classBytes.length);
            if(clazz == null){
                throw new ClassNotFoundException(name);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return clazz;
    }

    /**
     * 加载类的字节码
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @return
     * @throws java.io.IOException
     */
    private byte[] loadClassBytes(String name) throws IOException {
        String classPackageName = name.replace(".",File.separator)+".class";
        String classAbsolutePath = classPath+classPackageName;
        //编译java文件
        javac(name);
        byte[] bytes = Files.readAllBytes(Paths.get(classAbsolutePath));
        return bytes;
    }

    /**
     * 指定的类的class是否存在
     * @param name
     * @return
     * @throws IOException
     */
    public static boolean isClassExist(String name) throws IOException {
        String classPackageName = name.replace(".",File.separator)+".class";
        return FileUtil.isExists(classPath+classPackageName)?true:false;
    }

    /**
     * 指定的类是否存在
     * @param name
     * @return
     * @throws IOException
     */
    public static boolean isJavaExist(String name) throws IOException {
        String classPackageName = name.replace(".",File.separator)+".java";
        return FileUtil.isExists(classPath+classPackageName)?true:false;
    }

    /**
     * 编译java类
     * 使用Runtime执行javac命令
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @throws java.io.IOException
     */
    public static void javac(String name) throws IOException {
        String javaPackageName = name.replace(".",File.separator)+".java";
        String javaAbsolutePath = classPath+javaPackageName;
        Process process = Runtime.getRuntime().exec("javac -classpath "+ jarAbsolutePath+ " " + javaAbsolutePath);
        try {
            InputStream errorStream = process.getErrorStream();
            InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            while ((line=bufferedReader.readLine()) != null){
                System.out.println(line);
            }
            int exitVal = process.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 编译java类
     * 使用rt.jar中的javax.tools包提供的编译器
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @throws java.io.IOException
     */
    public static void compiler(String name) throws IOException {
        String javaPackageName = name.replace(".",File.separator)+".java";
        String javaAbsolutePath = classPath+javaPackageName;
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        compiler.run(null,null,null,"-encoding","UTF-8","-classpath",jarAbsolutePath.toString(),javaAbsolutePath);
    }

    /**
     * 动态编译一个java源文件并加载编译生成的class
     * @param name 类的全限定包名 不带后缀  例如com.test.Notice  而不要写成com.test.Notice.java
     * @throws java.io.IOException
     */
    public static Class<?> dynamicLoadClass(String name) throws IOException, ClassNotFoundException {
        if (!isClassExist(name)){
            compiler(name);
        }
        if(isJavaExist(name)){
            if(!FileUtil.destroyFile(classPath + name.replace(".",File.separator)+".java")){
                System.out.println("========================================>>>>删除源文件失败!");
            }
        }
        return Class.forName(name);
    }

    public static void main (String[] args){
        
    }

}

相关文章

网友评论

    本文标题:hibernate动态注册一个动态实体类参考

    本文链接:https://www.haomeiwen.com/subject/mvbgcdtx.html