> 文档中心 > 【MyBatis】| 使⽤javassist⽣成类、面向接口的方式进行CRUD

【MyBatis】| 使⽤javassist⽣成类、面向接口的方式进行CRUD

目录

一:使⽤javassist⽣成类

1. Javassist的使⽤

2.  动态生成类并实现接口

 3. MyBatis中接⼝代理机制及使⽤

 二:面向接口的方式进行CRUD


一:使⽤javassist⽣成类

Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤学的数学和计算机科学 系的 Shigeru Chiba (千叶 滋)所创建的。它已加⼊了开放源代码JBoss 应⽤服务器项⽬,通过使⽤ Javassist对字节码操作为JBoss实现动态"AOP"框架(面向切面编程)。

1. Javassist的使⽤

(1)引入javassist依赖

    4.0.0    com.bjpowernode    javassist-test    1.0-SNAPSHOT    jar     1.8 1.8               org.javassist     javassist     3.20.0-GA        junit     junit     4.12     

(2)编写测试类

①先调用ClassPool.getDefault()方法获取类池pool,通过这个类池用来生成class

②调用类池pool的

makeClass方法制造类,参数用来指定类名

③有了类就可以调用CtMethod.make方法制造方法,第一个参数是定义的方法,第二个参数是制造的类

④调用制造类的addMethod方法,将方法添加到类中

⑤调用制造类的toClass()方法,在内存中生成class

⑥通过反射机制,调用方法

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);    }}

JDK8之后运⾏要注意:加两个参数,要不然会有异常。

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

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

2.  动态生成类并实现接口

(1)先定义一个接口

package com.bjpowernode.ban.dao;public interface AccountDao {    void delete();}

(2)使用javassist动态生成类并实现接口,和上面的代码很类似,主要是在添加方法之前,制造上面的接口到内存当中,然后把这个接口添加到类当中即可。

注意1:ctClass.addInterface(ctInterface);把接口添加都类当中,实际上就等价于AccountDaoImpl implements AccountDao

注意2:ctClass.toClass()实际上有两个功能:一是在内存中生成类,二是同时将生成的类加载到JVM当中。

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)上面我们是已知接口中有一个类,所以在制造方法时写死了;但如果不知道接口中有多少个方法呢?怎么实现接口中所有的方法?

①接口中有多个方法

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);}

②动态的实现所有的方法,主要利用反射机制,进行代码的拼接

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)所以对于AccountDao的实现类AccountDaoImpl就不用手动写了,利用javassist编写一个工具类GenerateDaoProxy,用来动态生成AccountDaoImpl。

注意:实际上Mybatis已经内置了javassist,直接使用即可,不需要在引入javassist依赖。

重点:sql语句的id是框架使用者提供的,具有多变性。对于框架的开发人员来说,不知道。
既然框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的:sqlId都不能随便写,namespace必须是dao接口的全限定名称,id必须是dao接口中方法名。

①要动态生成的类

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);    }}

②修改专门编写sql语句的AccountMapper.xml文件:namespace修改为dao接口的全限定名称,id修改为dao接口中方法名

     select * from t_act where actno=#{actno}         update t_act set balance = #{balance} where actno = #{actno};    

③ GenerateDaoProxy工具类,专门用来动态实现Dao接口的实现类的

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调用实现类,原来是实现了 AccountDaoImpl类,Service通过new的方式创建;现在是通过上面的工具类 GenerateDaoProxy来创建对象

 3. MyBatis中接⼝代理机制及使⽤

(1)幸运的是对于GenerateDaoProxy功能的实现,Mybatis框架已经封装好了,在Mybatis当中实际上采用了代理模式。在内存中生成dao接口的代理类,然后创建代理类的实例。

(2)使用Mybatis的这种代理机制的前提:SqlMapper.xml文件中namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名。
(3)怎么用?代码怎么写?调用sqlSession对象的getMapper方法

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

 二:面向接口的方式进行CRUD

(1)既然已经学习了面向接口编程和使⽤javassist⽣成类,下面就尝试面向接口的方式进行CRUD操作。

(2)以后重点写映射文件CarMapper.xml和接口CarMapper就可以。

框架结构

(1)pom.xml中引入依赖

jdbc.properties连接数据库的配置文件、mybatis-config.xml核心配置文件、logback日志配置文件都和前面相同,这里就不在列出。

    4.0.0    com.bjpowernode    mybatis-005-crud2    1.0-SNAPSHOT    jar     1.8 1.8                   org.mybatis     mybatis     3.5.10        mysql     mysql-connector-java     5.1.23        junit     junit     4.12        ch.qos.logback     logback-classic     1.2.11     

(2)接口CarMapper,在这个接口中写抽象方法,下面用动态代理机制生成实现类

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 selectAll();}

(3)CarMapper.xml映射文件,编写sql语句

     insert into t_car values(null, #{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})          delete from t_car where id = #{id}         update t_car set car_num=#{carNum}, brand=#{brand}, guide_price=#{guidePrice}, produce_time=#{produceTime}, car_type=#{carType} where id = #{id}         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     id,     car_num as carNum,     brand,     guide_price as guidePrice,     produce_time as produceTime,     car_type as carType from t_car    

(4)测试类,使用动态代理机制实现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 cars = mapper.selectAll(); for (Car car : cars){     System.out.println(car); }    }}