Mybatis学习笔记3 在Web中应用Mybatis_biubiubiu0706的博客-CSDN博客
上篇最后在DAO实现类中,代码固定,没有业务逻辑,这篇笔记中对该实现类进行封装,就是说,以后不用写DAO实现类了

- <?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>org.example</groupId>
- <artifactId>javassist</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <properties>
- <maven.compiler.source>8</maven.compiler.source>
- <maven.compiler.target>8</maven.compiler.target>
- </properties>
-
- <dependencies>
- <!--javassist依赖-->
- <dependency>
- <groupId>org.javassist</groupId>
- <artifactId>javassist</artifactId>
- <version>3.29.1-GA</version>
- </dependency>
- <!--junit依赖-->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.13.2</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </project>
下面是几个javassist基本使用的demo
- import javassist.*;
-
- import java.io.File;
- import java.lang.reflect.Method;
- import java.net.URL;
- import java.net.URLClassLoader;
-
- /**
- * @author hrui
- * @date 2023/9/17 18:05
- */
- public class Test {
-
-
- @org.junit.Test
- public void test03() throws Exception {
- // 获取类池,这个类池就是用来生成class的
- ClassPool pool = ClassPool.getDefault();
- // 制造类(需要告诉javassist,类名是啥)
- CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
-
- //制造方法
- String methodCode="public void insert(){System.out.println(\"Hello World\");}";
- CtMethod ctMethod=CtMethod.make(methodCode,ctClass);
-
- // 给类添加⽅法
- ctClass.addMethod(ctMethod);
- //写入磁盘
- ctClass.writeFile("src/main/java/");
-
-
- //还可以这样 下面是可以加载到的,加载不到原因可能是我当时没有放到测试目录
- // Class<?> aClass = Class.forName("com.example.javassist.Test");//这样加载不到类
- //用自定义类加载器
- ClassLoader customClassLoader = new URLClassLoader(new URL[]{new File("src/main/java/").toURI().toURL()});
- Class<?> aClass = customClassLoader.loadClass("com.example.javassist.Test");
- Object instance = aClass.newInstance();
- // 调用生成的方法
- Method method = aClass.getDeclaredMethod("insert");
- method.invoke(instance);
-
- }
- @org.junit.Test
- public void test02() throws Exception {
- // 获取类池,这个类池就是用来生成class的
- ClassPool pool = ClassPool.getDefault();
- // 制造类(需要告诉javassist,类名是啥)
- CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
- //制造方法
- String methodCode="public void insert(){System.out.println(123);}";
- CtMethod ctMethod=CtMethod.make(methodCode,ctClass);
-
- // 给类添加⽅法
- ctClass.addMethod(ctMethod);
-
- // 用反射调⽤⽅法 这样是内存中直接调用
- Class<?> aClass = ctClass.toClass();
- Object o = aClass.newInstance();
- Method method = aClass.getDeclaredMethod("insert");
- method.invoke(o);
- }
-
-
- @org.junit.Test
- public void test01() throws Exception {
- // 获取类池,这个类池就是用来生成class的
- ClassPool pool = ClassPool.getDefault();
- // 制造类(需要告诉javassist,类名是啥)
- CtClass ctClass = pool.makeClass("com.example.javassist.Test");//在内存中
- // 制造⽅法
- // 1.返回值类型 2.⽅法名 3.形式参数列表 4.所属类
- CtMethod ctMethod = new CtMethod(CtClass.voidType, "insert", new
- CtClass[]{}, ctClass);
- // 设置⽅法的修饰符列表
- ctMethod.setModifiers(Modifier.PUBLIC);
- // 设置⽅法体
- ctMethod.setBody("{System.out.println(\"hello world\");}");
-
- // 给类添加⽅法
- ctClass.addMethod(ctMethod);
-
- // 用反射调⽤⽅法 这样是内存中直接调用
- Class<?> aClass = ctClass.toClass();
- Object o = aClass.newInstance();
- Method method = aClass.getDeclaredMethod("insert");
- method.invoke(o);
- }
- }
因本机安装的是JDK8,没有报错,将高版本报错情况记录下来,以便以后用高版本JDK遇到类似问题,方便解决


解决办法


设想一个问题 既然这样的话,能不能用javassist动态设计一个类,然后实现DAO接口呢

- @org.junit.Test
- public void testGenerateImpl() throws Exception {
- //获取类池
- ClassPool pool = ClassPool.getDefault();
- //制造类
- CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl");//在内存中
- //制造接口
- CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
- //类去实现接口 又可以说是类去添加接口
- ctClassImpl.addInterface(ctClassInterface);//TestImpl implements Test1
- //去实现接口中的方法 这个相对比较复杂,这里我们假设接口里就一个方法
-
- //先制造方法
- CtMethod ctMethod=CtMethod.make("public void delete(){System.out.println(\"删除成功!\");}",ctClassImpl);
- //将方法添加到类中
- ctClassImpl.addMethod(ctMethod);
-
- //在内存中生成类(这一步JVM已经将类加载到内存中)
- Class<?> aClass = ctClassImpl.toClass();
- Test1 test=(Test1)aClass.newInstance();
- test.delete();
-
- }

上面方法示例可见,我们是有办法动态生成接口实现类的
上面方法演示过于简单
比如说

下面使用javassist动态生成实现类并实现接口中所有方法

- @org.junit.Test
- public void testGenerateImpl2(){
- //获取类池
- ClassPool pool = ClassPool.getDefault();
- //制造类
- CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl2");//在内存中
- //制造接口
- CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
- //类去实现接口 又可以说是类去添加接口
- ctClassImpl.addInterface(ctClassInterface);//TestImpl2 implements Test1
- //实现接口中所有方法
- //先获取接口中的所有方法
- Method[] declaredMethods = Test1.class.getDeclaredMethods();
- //System.out.println(declaredMethods.length);
- //System.out.println(Arrays.toString(declaredMethods));
- Arrays.stream(declaredMethods).forEach(method -> {
- //method是接口中的抽象方法,我们需要把抽象方法实现了
- try {
- //methodCode public void delete(){}
- //methodCode public int update(String actno,double balance)
- StringBuilder methodCode=new StringBuilder();
- methodCode.append("public ");
- methodCode.append(method.getReturnType().getName()+" ");//返回值类型
- methodCode.append(method.getName());//追加方法名
- methodCode.append("(");
-
- Class<?>[] parameterTypes = method.getParameterTypes();//参数有可能1个也可能有多个
- 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("){System.out.println(\"Hello World\");}");
- System.out.println(methodCode);
-
- //CtMethod ctMethod=CtMethod.make(methodCode.toString(),ctClassImpl);
- //将方法添加到类中
- //ctClassImpl.addMethod(ctMethod);
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- }
程序写到这部 先执行一下
public int update(java.lang.String arg0,double arg1){System.out.println("Hello World");}
public void delete(){System.out.println("Hello World");}
public int insert(java.lang.String arg0){System.out.println("Hello World");}
public java.lang.String selectByActno(java.lang.String arg0){System.out.println("Hello World");}
接下来就是关于返回值的问题
- @org.junit.Test
- public void testGenerateImpl2() throws IllegalAccessException, InstantiationException, CannotCompileException {
- //获取类池
- ClassPool pool = ClassPool.getDefault();
- //制造类
- CtClass ctClassImpl = pool.makeClass("com.example.javassist.TestImpl2");//在内存中
- //制造接口
- CtClass ctClassInterface = pool.makeInterface("com.example.javassist.Test1");//在内存中
- //类去实现接口 又可以说是类去添加接口
- ctClassImpl.addInterface(ctClassInterface);//TestImpl2 implements Test1
- //实现接口中所有方法
- //先获取接口中的所有方法
- Method[] declaredMethods = Test1.class.getDeclaredMethods();
- //System.out.println(declaredMethods.length);
- //System.out.println(Arrays.toString(declaredMethods));
- Arrays.stream(declaredMethods).forEach(method -> {
- //method是接口中的抽象方法,我们需要把抽象方法实现了
- try {
- //methodCode public void delete(){}
- //methodCode public int update(String actno,double balance)
- StringBuilder methodCode=new StringBuilder();
- methodCode.append("public ");
- methodCode.append(method.getReturnType().getName()+" ");//返回值类型
- methodCode.append(method.getName());//追加方法名
- methodCode.append("(");
-
- Class<?>[] parameterTypes = method.getParameterTypes();//参数有可能1个也可能有多个
- 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("){System.out.println(\"Hello World\");");
- //动态添加renturn语句
- String simpleName = method.getReturnType().getSimpleName();//比如 int void String
- if("void".equals(simpleName)){
- //如果是void啥都不写
- }else if("int".equals(simpleName)){
- methodCode.append("return 1;");
- }else if("String".equals(simpleName)){
- methodCode.append("return \"1\";");
- }
- //System.out.println(simpleName);
- methodCode.append("}");
- System.out.println(methodCode);
-
- CtMethod ctMethod=CtMethod.make(methodCode.toString(),ctClassImpl);
- //将方法添加到类中
- ctClassImpl.addMethod(ctMethod);
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- //在内存中生成类(这一步JVM已经将类加载到内存中)
- Class<?> aClass = ctClassImpl.toClass();
- Test1 test=(Test1)aClass.newInstance();
- test.delete();
- int count = test.insert("sad");
- test.update("sad",1.1);
-
-
- }
上面方式虽然实现的比较low,主要为说明Mybatis底层javassist的使用
下面对
Mybatis学习笔记3 在Web中应用Mybatis_biubiubiu0706的博客-CSDN博客
中的项目进行修改,就是说AccountDaoImpl不写了

mybatis-03中只引入了mybatis依赖,但是可以直接使用ClassPool,而这个类是javassist的,原因是mybatis对javassist进行了封装
下面这段代码就是大概性的介绍Mybatis内部的一种封装
- package com.example.utils;
-
- 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;
- import java.util.Arrays;
-
- /**
- * 工具类:可以动态的生成DAO的实现类。(或者说可以动态生成DAO的代理类)
- * 注意注意注意注意注意!!!!!!:
- * 凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。
- */
- public class GenerateDaoProxy { // GenerateDaoProxy是mybatis框架的开发者写的。
-
- /**
- * 生成dao接口实现类,并且将实现类的对象创建出来并返回。
- * @param daoInterface dao接口
- * @return dao接口实现类的实例化对象。
- */
- public static Object generate(SqlSession sqlSession, Class daoInterface){
- // 类池
- ClassPool pool = ClassPool.getDefault();
- // 制造类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
- CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); // 实际本质上就是在内存中动态生成一个代理类。
- // 制造接口
- CtClass ctInterface = pool.makeInterface(daoInterface.getName());
- // 实现接口
- ctClass.addInterface(ctInterface);
- // 实现接口中所有的方法
- Method[] methods = daoInterface.getDeclaredMethods();
- Arrays.stream(methods).forEach(method -> {
- // method是接口中的抽象方法
- // 将method这个抽象方法进行实现
- try {
- // Account selectByActno(String actno);
- // public Account selectByActno(String actno){ 代码; }
- 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.example.utils.SqlSessionUtil.openSession();");
- // 需要知道是什么类型的sql语句
- // sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。
- // 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的。
- // sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。
- 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) {
- 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("}");
- 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;
- }
-
- }
那么业务层就可以这么写


上面这步,出了点错,写完时候还没查出来
其实这个封装Mybatis已经做好了

完整的SqlSessionUtil类
- package com.example.utils;
-
- import org.apache.ibatis.io.Resources;
- import org.apache.ibatis.session.SqlSession;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.apache.ibatis.session.SqlSessionFactoryBuilder;
-
- import java.io.IOException;
-
- /**
- * @author hrui
- * @date 2023/9/8 14:55
- */
- public class SqlSessionUtil {
-
- //工具类的构造方法一般都是私有化
- //方法都是静态的
- //为了防止new对象,构造方法私有化
- private SqlSessionUtil(){
-
- }
- private static SqlSessionFactory sqlSessionFactory;
-
- //类加载时候执行
- //SqlSessionUtil工具类在被加载的时候,解析mybatis-config1.xml.创建sqlSessionFactory对象
- static{
- try {
- //SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
- //下面这么写的原因是SqlSessionFactoryBuilder就是为了创建sqlSessionFactory而来的,使用完后,就不需要,都不需要创建个局部变量
- //一个sqlSessionFactory对应一个数据库
- sqlSessionFactory= new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config1.xml"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- //全局的 服务器级别的,一个服务器当中定义一个即可
- private static ThreadLocal<SqlSession> local=new ThreadLocal<>();
-
- //获取会话对象 返回会话对象
- public static SqlSession openSession(){
- SqlSession sqlSession=local.get();
- if(sqlSession==null){
- sqlSession = sqlSessionFactory.openSession();
- local.set(sqlSession);
- }
-
- return sqlSession;
- }
-
- //提供一个关闭的方法
- public static void close(SqlSession sqlSession){
- if(sqlSession!=null){
- //因为核心配置文件中配置POOLED 这里关闭是交还给连接池
- sqlSession.close();
- //注意移除SqlSession对象和当前线程的绑定关系
- //因为Tomcat服务器支持线程池 比如说t1线程用完了,close交还给连接池了,这个sqlSession属于不可用的状态,你没有remove出去 如果t2线程拿到了,那么这个sqlSession不可用
- local.remove();
- }
- }
- }