07、Javassist生成类与接口代理机制

一、概述

1、 什么是Javassist?;

是一个开源的分析、编辑和创建Java字节码的类库。已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

说的明白一点就是:为我们生成类的。

2、 我们为什么要使用它呢?我们自己手动创建一个类不是很方便吗?;

  • 适用于代码量不是很多的类,并且和业务没有什么关系
  • 例如对数据库操作的接口实现类:【此处不提交和关闭连接是因为在工具类做了特殊处理】
package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

/**
 * @author Bonbons
 * @version 1.0
 */
public class AccountDaoImpl implements AccountDao {

    @Override
    public Account selectByActno(String actno) {

        //开启会话,根据actno查询账户
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("com.powernode.bank.dao.AccountDao.selectByActno",actno);
//        关闭会话,返回账户信息
//        sqlSession.close();
        return account;

    }

    @Override
    public int updateActno(Account act) {

        //开启会话
        SqlSession sqlSession = SqlSessionUtil.openSession();
        //修改余额
        int count = sqlSession.update("com.powernode.bank.dao.AccountDao.updateActno", act);
//        //提交事务,关闭会话
//        sqlSession.commit();
//        sqlSession.close();
        //返回影响数据库表中记录的条数
        return count;
    }
}

这部分代码简单,而且没有多少内容,所以我们就像不去写这个类,而是通过Javassist去实现直接在内存中生成类。

二、使用Javassist

  • 先附上这部分模块的文件的层次结构,再依次展开论述
     

1、 我们需要在模块中导入javassist的依赖【mybatisjunit依赖也需要】;

<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.29.1-GA</version>
</dependency>

2、 我们这部分的目的就是>>为我们数据库操作接口生成类;

(1)写一下 dao 层的接口 AccoutDao 【增删改查四个操作】

package com.powernode.bank.dao;

/**
 * @author Bonbons
 * @version 1.0
 */
public interface AccountDao {

    void delete();
    int update(String actno, double balance);
    int insert(String actno);
    String selectByActno(String actno);
}

(2)接下来就可以写我们的 javassist 类了 JavassistTest【由浅入深该类中有三个 @Test

  • 先给出完整代码:
package com.powernode.javassist;

import com.powernode.bank.dao.AccountDao;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.junit.Test;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author Bonbons
 * @version 1.0
 */
public class JavassistTest {

@Test
    public void testGenerateImpl() throws Exception{

        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
        // 制造接口
        CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
        // 将接口添加到类中[类似声明类实现哪个接口]
        ctClass.addInterface(ctInterface);
        // 实现接口中的方法
        // (1)制造方法
        CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"hello delete!\");}", ctClass);
        // (2)将方法添加到类中
        ctClass.addMethod(ctMethod);
        // 在内存中生成类,并将类加载到JVM中
        Class<?> clazz = ctClass.toClass();
        AccountDao accountDao = (AccountDao)clazz.newInstance();
        accountDao.delete();

    }

    @Test
    public void testGenerateFirstClass() throws Exception{

        // 获取类池,通过类池的实例创建Class
        ClassPool pool = ClassPool.getDefault();
        // 根据指定的类名创建类
        CtClass ctClass = pool.makeClass("com.powernode.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();
        // 至此,javassist的任务已经完成了 >> 在内存中生成类

        // 为了使用这个类和方法。我们添加一些JDK中的方法
        // 类加载到JVM中,返回类的字节码
        Class<?> clazz = Class.forName("com.powernode.bank.dao.impl.AccountDaoImpl");
        // 创建类的实例
        Object obj = clazz.newInstance();
        //获取它的insert方法
        Method insertMethod = clazz.getDeclaredMethod("insert");
        //调用这个方法
        insertMethod.invoke(obj);
    }

    // 动态的实现接口中的方法
    @Test
    public void testGenerateAccountDaoImpl() throws Exception{

        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
        // 制造接口
        CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
        // 实现接口[只有类实现了接口,最后才能强转Wie接口的类型]
        ctClass.addInterface(ctInterface);
        // 获取接口中所有的方法
        Method[] methods = AccountDao.class.getDeclaredMethods();
        // 流式遍历
        Arrays.stream(methods).forEach(method -> {

            // method 是接口中的抽象方法,我们现在要实现这些方法
            try {

                // 创建我们要拼接的方法
                StringBuilder methodCode = new StringBuilder();
                // 追加修饰符列表
                methodCode.append("public ");
                // 追加返回值类型
                methodCode.append(method.getReturnType().getName());
                // 追加空格
                methodCode.append(" ");
                // 追加方法名
                methodCode.append(method.getName());
                // 追加左括号
                methodCode.append("(");
                // 拼接参数列表:(1)先获得所有返回值类型
                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("){System.out.println(1111);");
                // 判断当前方法的返回值类型
                String returnTypeSimpleName = method.getReturnType().getSimpleName();
                // 分情讨论 [我们接口这块只有 void、String、double]
                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 = 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("bala");
        accountDao.delete();
        accountDao.update("bala", 1000.0);
        accountDao.selectByActno("bala");
    }
}

3、 第一个@Test的内容;

@Test
    public void testGenerateFirstClass() throws Exception{

        // 获取类池,通过类池的实例创建Class
        ClassPool pool = ClassPool.getDefault();
        // 根据指定的类名创建类
        CtClass ctClass = pool.makeClass("com.powernode.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();
        // 至此,javassist的任务已经完成了 >> 在内存中生成类

        // 为了使用这个类和方法。我们添加一些JDK中的方法
        // 类加载到JVM中,返回类的字节码
        Class<?> clazz = Class.forName("com.powernode.bank.dao.impl.AccountDaoImpl");
        // 创建类的实例
        Object obj = clazz.newInstance();
        //获取它的insert方法
        Method insertMethod = clazz.getDeclaredMethod("insert");
        //调用这个方法
        insertMethod.invoke(obj);
    }
  • 简单的引入 javassist,只是通过类池去创建一个类和方法,然后将方法添加到类中
  • 创建类的对象,最后调用这个方法

&nbsp;

4、 第二个@Test的内容;

    @Test
    public void testGenerateImpl() throws Exception{

        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
        // 制造接口
        CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
        // 将接口添加到类中[类似声明类实现哪个接口]
        ctClass.addInterface(ctInterface);
        // 实现接口中的方法
        // (1)制造方法
        CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"hello delete!\");}", ctClass);
        // (2)将方法添加到类中
        ctClass.addMethod(ctMethod);
        // 在内存中生成类,并将类加载到JVM中
        Class<?> clazz = ctClass.toClass();
        AccountDao accountDao = (AccountDao)clazz.newInstance();
        accountDao.delete();

    }

这是一个最简单的 javassist 应用案例,它的使用流程与注释步骤基本相同

获取类池 >> 制造类 >> 制造接口 >> 用类去实现接口 >> 创建方法 >> 将方法添加到类中

  • 以上这部分为 javassist 的工作内容,剩余在内存中生成类和将类加载到JVM中以及创建类的实例、调用类的方法都是JDK中提供的一些方法
  • 对于实现接口的那条语句类似于声明该类实现这个接口,但我们要具体实现这个接口中的方法
  • 所以我们创建一个接口方法的实现方法,最后再添加到类中【此处是随便写了一个方法】

&nbsp;

5、 第三个@Test的内容【动态的获取接口中的方法,但是随便实现了方法的内容】;

    // 动态的实现接口中的方法
    @Test
    public void testGenerateAccountDaoImpl() throws Exception{

        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
        // 制造接口
        CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
        // 实现接口[只有类实现了接口,最后才能强转Wie接口的类型]
        ctClass.addInterface(ctInterface);
        // 获取接口中所有的方法
        Method[] methods = AccountDao.class.getDeclaredMethods();
        // 流式遍历
        Arrays.stream(methods).forEach(method -> {

            // method 是接口中的抽象方法,我们现在要实现这些方法
            try {

                // 创建我们要拼接的方法
                StringBuilder methodCode = new StringBuilder();
                // 追加修饰符列表
                methodCode.append("public ");
                // 追加返回值类型
                methodCode.append(method.getReturnType().getName());
                // 追加空格
                methodCode.append(" ");
                // 追加方法名
                methodCode.append(method.getName());
                // 追加左括号
                methodCode.append("(");
                // 拼接参数列表:(1)先获得所有返回值类型
                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("){System.out.println(1111);");
                // 判断当前方法的返回值类型
                String returnTypeSimpleName = method.getReturnType().getSimpleName();
                // 分情讨论 [我们接口这块只有 void、String、double]
                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 = 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("bala");
        accountDao.delete();
        accountDao.update("bala", 1000.0);
        accountDao.selectByActno("bala");
    }
  • 在创建方法之前,先获取了接口中的所有方法,然后通过 Arrays 将数组转为流,再利用 forEach 遍历
  • 在内部创建一个 StringBuilder 的实例用于拼接方法串,拼接流程如下:

修饰符 >> 返回值类型 >> 方法名 >> 参数列表 【此处省略了空格、逗号、参数名】

  • 因为参数可能有多个,所以我们通过method.getParameterTypes()获取所有的参数类型,通过遍历将参数类型和参数拼接进去
  • 然后就是方法体,此处我们简化了,就拼接了一条输出语句
  • 最后就是返回值类型,通过method.getReturnType().getSimpleName()获取返回值类型,然后分情况讨论,我们返回的内容也简化了,因为此处只是想说明是一个什么样的原理
  • 然后根据我们拼接的字符串创建方法,再将方法添加到类中,最后就是创建类…【与上面剩余步骤大同小异】

&nbsp;

6、 如果运行的时候出现以下的报错:;

&nbsp;

这是因为高版本的 JDK 使用了base类,我们要通过以下操作来解决这个问题
&nbsp;
&nbsp;
7、 利用Javassist实现在dao层的AccountDao接口;

(1)接口如下:【包含参数和返回值类型】

package com.powernode.bank.dao;

import com.powernode.bank.pojo.Account;

/**
 * 账户的Dao对象,负责对 t_act 表进行增删改查
 * Dao 中的方法与业务逻辑不存在联系
 * @author Bonbons
 * @version 1.0
 */
public interface AccountDao {

    /**
     * 根据账号查询账户信息
     * @param actno 账户id
     * @return 返回账户的信息
     */
    Account selectByActno(String actno);

    /**
     * 更新账户的信息
     * @param act 被更新的账户
     * @return 返回更新结果(成功/失败)
     */
    int updateActno(Account act);
}

(2)我们通过 GenerateDaoProxygenerate 方法实现在内存中根据接口创建实现类

package com.powernode.bank.utils;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 可以动态的生成DAO的实现类
 * @author Bonbons
 * @version 1.0
 */
public class GenerateDaoProxy {

    /**
     * 生成dao接口的实现类,返回实现类的对象
     * @param daoInterface 接口
     * @return 返回的对象
     */
    public static Object generate(SqlSession sqlSession, Class daoInterface){

        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类[类名采用接口名 + Proxy(只要类名和接口名不冲突就行)]
        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 -> {

            try {

                // 拼接方法
                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);
                    methodCode.append(" ");
                    methodCode.append("arg");
                    methodCode.append(i);
                    if(i != parameterTypes.length - 1){

                        methodCode.append(",");
                    }
                }
                methodCode.append("){");
                // 内部代码
                // 拼接创建会话的语句,但注意要使用全限定类名
                methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession()");
                // mybatis 规定了namespace必须为Dao中接口的全限定类名,id必须为接口中的方法名 >> 此处我们就可以获得 sql的Id
                String sqlId = daoInterface.getName() + "." + method.getName();

                // 获取当前方法的SQL类型[CRUD]
//                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                String sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType().name();
                // 分情况讨论
                if("UPDATE".equals(sqlCommandType)){

                    methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
                }else if("SELECT".equals(sqlCommandType)){

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

三、接口代理机制

🌔1、对于我们用 Javassist 方式实现的 GenerateDaoProxy 类感觉比直接写dao中接口的实现类费事,那我们为什么还去写这个方法呢?

实际上确实没啥必要去自己写generate方法,因为 mybatis 为我们封装了这个接口,我们直接调用即可。
之所以我们自己去实现是为了容易理解javassist的底层是如何实现的。

🌔2、使用 mybatis 的接口有什么要求吗?

当然,在Mapper 映射文件中 namespace 只能为 接口的全限定类名,而且其中SQL语句的 id 要与接口中的方法名对应上。

🌔3、比较一下我们自己写的静态类方法和 mybatis 提供的接口代理是如何使用的?

    //数据库操作的对象[第一种采用接口的实现类]
    private AccountDao accountDao= new AccountDaoImpl();
    // 使用我们的工具类动态生成
    private AccountDao accountDao = (AccountDao) GenerateDaoProxy1.generate(SqlSessionUtil.openSession(), AccountDao.class);
    // 使用mybatis提供的接口代理机制
    private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);

🌔4、为了更好的说明如何使用这个接口代理,我们写一个增删改查的 java 案例

(1)准备数据库的一张表 t_car 【其实之前用过这个表】

&nbsp;
(2)在 idea 中创建一个新的普通Maven Java模块 mybatis-005-crud2

  • 老样子,第一步修改 pom.xml 文件 >> 确定打包方式和导入依赖
  • 创建我们需要的 xml 文件 >> mybatis的核心配置文件和SQL的映射文件【此处我就不用日志文件了】
  • 贴一张目录结构
    &nbsp;

(3)两个 xml 文件如下

  • mybatis-config.xml 核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
                <property name="username" value="root"/>
                <property name="password" value="111111"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>
  • CarMapper.xml 映射文件 【当表中字段名与我们封装的pojo类的属性名对应不上时记得起别名】
<?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.powernode.mybatis.mapper.CarMapper">
    <insert id="insert">
--         因为id在设计表的时候我们采用了自增的方式,所以插入数据的时候不用指定id
        insert into t_car values (null, #{

     carNum},#{

     brand},#{

     guidePrice},#{

     produceTime},#{

     carType})
    </insert>
    <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>
    <delete id="deleteById">
        delete from t_car where id = #{

     id}
    </delete>
    <select id="selectById" resultType="com.powernode.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.powernode.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)贴一下我们封装的汽车的普通 Java 类 【之所以说普通因为只有属性、构造方法、getsettoString方法】

package com.powernode.mybatis.pojo;

/**
 * @author Bonbons
 * @version 1.0
 */
public class Car {

    // id,car_num,brand,guide_price,produce_time,car_type
    // 使用包装类,出现null时不会报错
    // 对于这些私有属性要与表中数据对应上
    private Long id;
    private String carNum;
    private String brand;
    private Double guidePrice;
    private String produceTime;
    private String carType;
    //提供构造方法
    public Car(){

     }

    public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {

        this.id = id;
        this.carNum = carNum;
        this.brand = brand;
        this.guidePrice = guidePrice;
        this.produceTime = produceTime;
        this.carType = carType;
    }
    //提供get和set方法

    public Long getId() {

        return id;
    }

    public void setId(Long id) {

        this.id = id;
    }

    public String getCarNum() {

        return carNum;
    }

    public void setCarNum(String carNum) {

        this.carNum = carNum;
    }

    public String getBrand() {

        return brand;
    }

    public void setBrand(String brand) {

        this.brand = brand;
    }

    public Double getGuidePrice() {

        return guidePrice;
    }

    public void setGuidePrice(Double guidePrice) {

        this.guidePrice = guidePrice;
    }

    public String getProduceTime() {

        return produceTime;
    }

    public void setProduceTime(String produceTime) {

        this.produceTime = produceTime;
    }

    public String getCarType() {

        return carType;
    }

    public void setCarType(String carType) {

        this.carType = carType;
    }
    //重写toString方法

    @Override
    public String toString() {

        return "Car{" +
                "id=" + id +
                ", carNum='" + carNum + '\'' +
                ", brand='" + brand + '\'' +
                ", guidePrice=" + guidePrice +
                ", produceTime='" + produceTime + '\'' +
                ", carType='" + carType + '\'' +
                '}';
    }
}

(5)定义的工具类 SqlSessionUtil【用于创建连接对象,因为此处没啥业务,我们就不用ThreadLocal了】

package com.powernode.mybatis.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 Bonbons
 * @version 1.0
 */
public class SqlSessionUtil {

    private SqlSessionUtil(){

     }

    //定义一个SqlSession
    private static final SqlSessionFactory sqlSessionFactory;
    //在类加载的时候初始化SqlSessionFactory
    static {

        try {

            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {

            throw new RuntimeException(e);
        }
    }
    //定义一个全局的ThreadLocal,可以保证一个SqlSession对应一个线程
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    //通过一个公有的方法为外部提供会话的对象 >> 确保同一个线程操作的是同一个连接对象
    public static SqlSession openSession(){

        //我们用local去获取会话
        SqlSession sqlSession = local.get();
        //如果当前没有开启的会话就去创建一个,如果get到了就用这个[确保我们操作的是同一个连接对象]
        if(sqlSession == null){

            sqlSession = sqlSessionFactory.openSession();
            //将SqlSession对象绑定到当前线程上
            local.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭SqlSession对象并从当前线程中解绑
     * @param sqlSession 会话对象
     */
    public static void close(SqlSession sqlSession){

        if(sqlSession != null){

            sqlSession.close();
            local.remove();
        }
    }
}

(6)我们定义对数据库中表操作的接口 【习惯问题,正常都叫XxxDao,使用mybatisdao层都叫mapper层】

package com.powernode.mybatis.mapper;

import com.powernode.mybatis.pojo.Car;

import java.util.List;

/**
 * @author Bonbons
 * @version 1.0
 */
public interface CarMapper {

    /**
     * 新增车辆信息
     * @param car Car对象
     * @return 返回影响数据库表中记录的条数
     */
    int insert(Car car);

    /**
     * 删除指定id的车辆信息
     * @param id 车id
     * @return 返回影响的数据条数
     */
    int deleteById(Long id);

    /**
     * 修改已有的车辆信息
     * @param car 汽车对象
     * @return 返回影响的数据条数
     */
    int update(Car car);

    /**
     * 根据id查询车辆信息
     * @param id ID
     * @return 查询到的信息
     */
    Car selectById(Long id);

    /**
     * 获取所有的汽车信息
     * @return 返回所有查询结果的集合
     */
    List<Car> selectAll();
}

(7)最后就可以在test包下写我们的测试方法了

package com.powernode.mybatis.test;

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

import java.util.List;

/**
 * @author Bonbons
 * @version 1.0
 */
public class CarMapperTest {

    @Test
    public void testInsert(){

        // 创建连接对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 创建我们要添加的Car
        Car car = new Car(null, "1","卡罗拉", 100.0, "2022-11-04", "燃油车");
        // 调用我们的insert方法
        int count = mapper.insert(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testDeleteById(){

        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteById(1L);
        sqlSession.commit();
        sqlSession.close();
        System.out.println(count);
    }

    @Test
    public void testUpdate(){

        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(21L, "21","卡罗拉新款", 50.0, "2022-11-04", "燃油车");
        mapper.update(car);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testSelectById(){

        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(2L);
        System.out.println(car);
        sqlSession.close();
    }

    @Test
    public void testSelectAll(){

        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        cars.forEach(car -> System.out.println(car));
    }
}

(8)所有的方法测试都是通过的,最后再赘述一下流程

利用工具类创建会话 >> 调用getMapper方法(参数为 CarMapper.class) >> 利用mapper调用我们的方法