【MyBatis】| Use javassist to generate classes and interface-oriented way for CRUD

Table of contents

One: Use javassist to generate classes

1. Use of Javassist

2. Dynamically generate classes and implement interfaces

 3. Interface proxy mechanism and usage in MyBatis

 Two: CRUD in an interface-oriented manner


One: Use javassist to generate classes

Javassist is an open source library for analyzing, editing, and creating Java bytecode. Created by Shigeru Chiba of the Department of Mathematics and Computer Science, Tokyo Institute of Technology. It has joined the open source JBoss application server project to implement a dynamic "AOP" framework (aspect-oriented programming) for JBoss by manipulating bytecodes using Javassist.

1. Use of Javassist

(1) Introduce javassist dependency

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode</groupId>
    <artifactId>javassist-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--javassist依赖-->
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.20.0-GA</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

</project>

(2) Write test classes

① First call the ClassPool.getDefault() method to obtain the class pool pool, and use this class pool to generate classes;

②Call the makeClass method of the class pool pool to create a class, and the parameter is used to specify the class name;

③With the class, you can call the CtMethod.make method to make the method, the first parameter is the defined method, and the second parameter is the manufactured class;

④Call the addMethod method of the manufacturing class to add the method to the class;

⑤Call the toClass() method of the manufacturing class to generate the class in memory;

⑥ Call the method through the reflection mechanism;

package com.bjpowernode.javassist;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.junit.Test;

import java.lang.reflect.Method;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.javassist
 * @Project:mybatis
 * @name:JavassistTest
 * @Date:2023/1/2 14:38
 */
public class JavassistTest {
    @Test
    public void testGenerateFirstClass() throws Exception {
        // 获取类池,这个类池就是用来生成class的
        ClassPool pool = ClassPool.getDefault();
        // 制造类(告诉javassist类名是啥)
        CtClass ctClass = pool.makeClass("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
        // 制造方法
        String methodCode = "public void insert(){System.out.println(123);}";
        CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
        // 将方法添加到类中
        ctClass.addMethod(ctMethod);
        // 在内存中生成class
        ctClass.toClass();

        // 调用方法,以下都是反射机制的知识点
        // 类加载到JVM当中,返回AccountDaoImpl的字节码
        Class<?> clazz = Class.forName("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
        // 创建对象
        Object obj = clazz.newInstance();
        // 获取AccountDaoImpl中的insert方法
        Method insertMethod = clazz.getDeclaredMethod("insert");
        // 调用obj的insert方法
        insertMethod.invoke(obj);
    }
}

Be careful when running after JDK8: add two parameters, otherwise there will be an exception.

①--add-opens java.base/java.lang=ALL-UNNAMED

②--add-opens java.base/sun.net.util=ALL-UNNAMED

2. Dynamically generate classes and implement interfaces

(1) Define an interface first

package com.bjpowernode.ban.dao;

public interface AccountDao {
    void delete();
}

(2) Use javassist to dynamically generate classes and implement interfaces, which is very similar to the above code, mainly to create the above interface into memory before adding methods, and then add this interface to the class.

Note 1: ctClass.addInterface(ctInterface); adding the interface to the class is actually equivalent to AccountDaoImpl implements AccountDao;

Note 2: ctClass.toClass() actually has two functions: one is to generate classes in memory, and the other is to load the generated classes into the JVM at the same time;

package com.bjpowernode.javassist;

import com.bjpowernode.ban.dao.AccountDao;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.junit.Test;

import java.lang.reflect.Method;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.javassist
 * @Project:mybatis
 * @name:JavassistTest
 * @Date:2023/1/2 14:38
 */
public class JavassistTest {
    @Test
    public void testGenerateImpl() throws Exception{
        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
        // 制造接口
        CtClass ctInterface = pool.makeInterface("com.bjpowernode.ban.dao.AccountDao");
        // 添加接口到类中
        ctClass.addInterface(ctInterface);
        // 实现接口中的方法
        // 制造方法
        CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"Hello World\");}", ctClass);
        // 将方法添加到类中
        ctClass.addMethod(ctMethod);
        // 在内存中生成类,同时将生成的类加载到JVM当中
        Class clazz = ctClass.toClass(); // 等价于上面的两行代码
        AccountDao accountDao = (AccountDao) clazz.newInstance(); // 强转,面向接口编程
        accountDao.delete();
    }

}

(3) Above we know that there is a class in the interface, so we wrote it dead when making the method; but what if we don't know how many methods there are in the interface? How to implement all the methods in the interface?

① There are multiple methods in the interface

package com.bjpowernode.ban.dao;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.ban.dao
 * @Project:mybatis
 * @name:AccountDao
 * @Date:2023/1/2 15:36
 */
public interface AccountDao {
    void delete();
    int insert(String actno);
    int update(String actno,Double balance);
    String selectByActno(String actno);
}

② Implement all the methods dynamically, mainly using the reflection mechanism to splice the code

package com.bjpowernode.javassist;

import com.bjpowernode.ban.dao.AccountDao;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.junit.Test;

import java.lang.reflect.Method;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.javassist
 * @Project:mybatis
 * @name:JavassistTest
 * @Date:2023/1/2 14:38
 */
public class JavassistTest {

    @Test
    public void testGenerateAccountDaoImpl() throws Exception {
        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
        // 制造接口
        CtClass ctInterface = pool.makeInterface("com.bjpowernode.ban.dao.AccountDao");
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口中所有的方法
        // 获取接口中所有的方法
        Method[] methods = AccountDao.class.getDeclaredMethods();
        for (Method method:methods){
            // 把抽象方法实现了
            CtMethod ctMethod = null;
            try {
                // 拼出methodCode的方法
                StringBuilder methodCode = new StringBuilder();
                methodCode.append("public "); // 追加修饰符列表
                methodCode.append(method.getReturnType().getSimpleName()); // 追加返回值类型
                methodCode.append(" ");
                methodCode.append(method.getName()); // 追加方法名
                methodCode.append("(");
                // 拼接参数类型
                Class<?>[] parameterTypes = method.getParameterTypes(); // 获取到所有的参数类型
                for (int i = 0; i <parameterTypes.length ; i++) { // 遍历这个数组
                    // 遍历,取出每个进行遍历拼接
                    Class<?> parameterType = parameterTypes[i];
                    methodCode.append(parameterType.getSimpleName()); // 拼接参数类型
                    methodCode.append(" ");
                    methodCode.append("arg"+i); // 拼接变量名,使用下标防止变量名冲突
                    if (i != parameterTypes.length-1){ // 如果不是最后一个参数,就加上逗号
                        methodCode.append(",");
                    }
                }
                methodCode.append("){System.out.println(1111);");
                // 添加返回值类型 return语句
                String returnTypeSimpleName = method.getReturnType().getSimpleName();
                if ("void".equals(returnTypeSimpleName)) {
                    // 什么都不做
                }else if ("int".equals(returnTypeSimpleName)){
                    methodCode.append("return 1;");
                }else if("String".equals(returnTypeSimpleName)){
                    methodCode.append("return \"Hello\";");
                }
                methodCode.append("}");
                // System.out.println(methodCode);
                ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        // 在内存中生成class,并且加载到JVM当中
        Class clazz = ctClass.toClass();
        // 创建对象
        AccountDao accountDao = (AccountDao) clazz.newInstance();
        // 调用方法
        accountDao.insert("111");
        accountDao.delete();
        accountDao.update("111",1000.0);
        accountDao.selectByActno("111");
    }

}

(4) Therefore, there is no need to manually write the AccountDaoImpl implementation class of AccountDao. Use javassist to write a tool class GenerateDaoProxy to dynamically generate AccountDaoImpl.

Note: In fact, Mybatis has built-in javassist, which can be used directly without the need to introduce javassist dependencies.

Important: The id of the sql statement is provided by the framework user and is variable. For the developers of the framework, no idea.
Since the framework developer does not know the sqlId, what should I do? The developers of the mybatis framework then issued a rule:

For those who use the GenerateDaoProxy mechanism: sqlId cannot be written casually, namespace must be the fully qualified name of the dao interface, and id must be the method name in the dao interface.

① Classes to be dynamically generated

package com.bjpowernode.bank.bao.impl;

import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;


public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
      /*  Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        return account;*/
        return (Account) sqlSession.selectOne("account.selectByActno", actno);
    }

    @Override
    public int updateByActno(Account act) {
        SqlSession sqlSession = SqlSessionUtils.openSession();
        /*int count = sqlSession.update("account.updateByActno", act);
        return count;*/
        return sqlSession.update("account.updateByActno", act);
    }
}

② Modify the AccountMapper.xml file that specially writes sql statements: modify namespace to the fully qualified name of the dao interface, and modify id to the method name in the dao interface

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.bjpowernode.bank.bao.AccountDao">
    <select id="selectByActno" resultType="com.bjpowernode.bank.pojo.Account">
        select * from t_act where actno=#{actno}
    </select>

    <update id="updateByActno">
        update t_act set balance = #{balance} where actno = #{actno};
    </update>
</mapper>

③ GenerateDaoProxy tool class, which is specially used to dynamically realize the implementation class of Dao interface

package com.bjpowernode.bank.utils;

import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Method;

/**
 * 工具类:可以动态的生成DAO的实现类
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.utils
 * @Project:mybatis
 * @name:GenerateDaoProxy
 * @Date:2023/1/2 19:42
 */
public class GenerateDaoProxy {
    /**
     * 生成dao接口的实现类,并且将实现类的对象创建出来并返回
     * @param daoInterface
     * @return dao接口实现类的实例化对象
     */
    public static Object generate(SqlSession sqlSession,Class daoInterface){ // 参数传一个接口
        // 类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass(daoInterface.getName() + "Impl"); // 实际本质上就是在内存中动态生成一个代理类
        // 制造接口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口中的方法
        Method[] methods = daoInterface.getDeclaredMethods();
        for (Method method : methods){
            // 拼方法
            StringBuilder methodCode = new StringBuilder();
            methodCode.append("public");
            methodCode.append(method.getReturnType().getName());
            methodCode.append(" ");
            methodCode.append(method.getName());
            methodCode.append("(");
            // 方法的形式参数列表
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                methodCode.append(parameterType.getName());
                methodCode.append(" ");
                methodCode.append("arg"+i);
                if (i != parameterTypes.length-1){
                    methodCode.append(",");
                }
            }
            methodCode.append(")");
            methodCode.append("{");
            // 需要方法体当中的代码
            // 第一行代码是都相同的,注意要带上包名
            methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.bjpowernode.bank.utils.SqlSessionUtils.openSession();");
            // 第二行代码要先需要知道是什么类型的sql语句
            // SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sql语句的id).getSqlCommandType();
            // 现在关键问题就是如何获得sql语句的id?
            // 获取sqlId(这⾥⾮常重要:因为这⾏代码导致以后namespace必须是接⼝的全
            // 限定接⼝名,sqlId必须是接⼝中⽅法的⽅法名。)
            String sqlId = daoInterface.getName() + "." + method.getName();
            SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
            if (sqlCommandType == SqlCommandType.INSERT) {

            }
            if (sqlCommandType == SqlCommandType.DELETE) {

            }
            if (sqlCommandType == SqlCommandType.UPDATE) {
                // 变量名上面我们拼接用的是arg加下标,这里应该是arg0
                methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
            }
            if (sqlCommandType == SqlCommandType.SELECT) {
                // 动态获取强转的类型
                String returnType = method.getReturnType().getName();
                methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
            }


            methodCode.append("}");
            try {
                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        // 创建对象
        Object obj = null;
        try {
            Class<?> clazz = ctClass.toClass();
            obj = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }
}

④Service calls the implementation class, which originally implemented the AccountDaoImpl class, and the Service is created by new; now the object is created through the above tool class GenerateDaoProxy

 3. Interface proxy mechanism and usage in MyBatis

(1) Fortunately, for the realization of the GenerateDaoProxy function, the Mybatis framework has been encapsulated, and the proxy mode is actually adopted in Mybatis; the proxy class of the dao interface is generated in memory, and then an instance of the proxy class is created.

(2) The premise of using this proxy mechanism of Mybatis: The namespace in the SqlMapper.xml file must be the fully qualified name of the dao interface, and the id must be the method name in the dao interface.
(3) How to use it? How to write the code? Call the getMapper method of the sqlSession object

AccountDao accountDao = sqlSession.getMapper(AccountDao.class);

 Two: CRUD in an interface-oriented manner

(1) Now that you have learned interface-oriented programming and using javassist to generate classes, let's try to perform CRUD operations in an interface-oriented manner.

(2) In the future, focus on writing the mapping file CarMapper.xml and the interface CarMapper.

Framework

(1) Introduce dependencies in pom.xml

The jdbc.properties configuration file for connecting to the database, the mybatis-config.xml core configuration file, and the logback log configuration file are all the same as before, so they are not listed here.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode</groupId>
    <artifactId>mybatis-005-crud2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <!--引入依赖-->
    <dependencies>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.23</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--logback依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>
    </dependencies>


</project>

(2) Interface CarMapper, write the abstract method in this interface, and then use the dynamic proxy mechanism to generate the implementation class

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;

import java.util.*;

public interface CarMapper { // 就相当于CarMapper
    // 增
    int insert(Car car);
    // 删
    int deleteById(Long id);
    // 改
    int update(Car car);
    // 查一个
    Car selectById(Long id);
    // 查所有
    List<Car> selectAll();
}

(3) CarMapper.xml mapping file, write sql statement

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--接口名-->
<mapper namespace="com.bjpowernode.mybatis.mapper.CarMapper">
    <!--id都是接口中的方法-->
    <insert id="insert">
        insert into t_car values(null, #{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>
    <delete id="deleteById">
         delete from t_car where id = #{id}
    </delete>
    <update id="update">
        update t_car set
        car_num=#{carNum},
        brand=#{brand},
        guide_price=#{guidePrice},
        produce_time=#{produceTime},
        car_type=#{carType}
        where id = #{id}
    </update>
    <select id="selectById" resultType="com.bjpowernode.mybatis.pojo.Car">
        select
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from t_car where id = #{id}
    </select>
    <select id="selectAll" resultType="com.bjpowernode.mybatis.pojo.Car">
        select
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from t_car
    </select>
</mapper>

(4) Test class, using dynamic proxy mechanism to implement CRUD

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.*;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.mybatis.test
 * @Project:mybatis
 * @name:CarMapperTest
 * @Date:2023/1/3 12:10
 */
public class CarMapperTest {
    @Test
    public  void testInsert(){
        SqlSession sqlSession = SqlSessionUtils.openSession();
        // 面向接口获取接口的代理对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null, "4444", "奔驰C200", 32.0, "2000-10-10", "新能源");
        int count = mapper.insert(car);
        System.out.println(count);
        sqlSession.commit();
    }

    @Test
    public void testDeleteById(){
        SqlSession sqlSession = SqlSessionUtils.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteById(15L);
        System.out.println(count);
        sqlSession.commit();
    }

    @Test
    public void testUpdate(){
        SqlSession sqlSession = SqlSessionUtils.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(19L, "2222", "凯美瑞222", 3.0, "2000-10-10", "新能源");
        mapper.update(car);
        sqlSession.commit();
    }

    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtils.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(20L);
        System.out.println(car);
    }

    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtils.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        for (Car car : cars){
            System.out.println(car);
        }
    }
}

Guess you like

Origin blog.csdn.net/m0_61933976/article/details/128519763