• MyBatis 工作流程及插件开发


    1. MyBatis 框架分层架构

    2. MyBatis 工作流程

    1. 获取 SqlSessionFactory 对象:
      • 解析配置文件(全局映射,Sql映射文件)的每一个信息,并保存在Configuration中,返回包含Configuration
        的DefaultSqlSession;
      • MappedStatement: 代表一个增删改查标签的详细信息;
    2. 获取 SqlSession 对象:
      • 返回一个DefaultSqlSession对象,包含Executor和Configuration;
    3. 获取接口的代理对象(MapperProxy)
      • getMapper()使用MapperProxyFactory创建一个MapperProxy的代理对象;
      • 代理对象中包含了DefaultSqlSession(Executor);
    4. 执行增删改查方法
      • 代理对象查询依赖DefaultSqlSession对象中的Executor,Executor创建StatementHandler对象,
        同时,创建ParameterHandler和ResultSetHandler对象,而ParameterHandler和ResultSetHandler都依赖TypeHandler;
      • StatementHandler: 设置sql语句,预编译,设置参数等相关工作,以及执行增删改查方法;
      • ParameterHandler: 设置预编译参数;
      • ResultHandler: 处理查询结果集;
      • TypeHandler: 在设置参数和处理查询结果时,都是依赖TypeHandler,进行数据库类型和javaBean类型的映射;

    3. MyBatis 插件开发

    1. MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用.MyBatis支持对以下方法(四大对象)的拦截:
      • Executor
      • StatementHandler
      • ParameterHandler
      • ResultSetHandler
    2. 在创建四大对象的时候,并不是直接返回的,而是 interceptorChain.pluginAll(xxxHandler);
    3. pluginAll 方法就是获取到所有的Interceptor(插件需要实现的接口),调用interceptor.plugin(target),
      最终,返回包装后的target对象;
    // pluginAll() 源码
    public Object pluginAll(Object target){
        for(Interceptor interceptor : interceptors){
            target = interceptor.plugin(target);
        }
        return target;
    }
    

    3.1 插件的编写

    1. 步骤:
      • 编写Interceptor的实现类;
      • 使用@Intercepts注解完成插件签名;
      • 将写好的插件注册到全局配置文件中;
    2. 多个插件的执行顺序
      • 创建动态代理的时候,是按照插件的配置顺序,创建层层代理对象;
      • 执行目标方法的时候,按照逆序执行;

    // 编写Interceptor实现类: MyFirstPlugin.java
    // @Intercepts 注解: 为当前插件指定要拦截哪个对象的哪个方法,以及方法中的参数
    @Intercepts(
        {
            @Signature(type=StatementHandler.class,method="parameterize",
                       args=java.sql.Statement.class)
        }
    )
    public class MyFirstPlugin implements Interceptor{
        // 拦截目标对象中目标方法的执行
        public Object intercept(Invocation invocation) throws Throwable{
    
            // 执行目标方法
            Object proceed = invocation.proceed();
    
            // 返回拦截之后的目标方法
            return proceed;
        }
    
        // 包装目标对象,即为目标对象创建一个代理对象
        public Object plugin(Object target){
    
            // 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包装我们的目标对象
            // target: 目标对象, interceptor: 拦截器, this 表示使用当前拦截器
            Object proxy = Plugin.wrap(target,this);
            return proxy;
        }
    
        // 可以获取插件注册时,传入的property属性
        public void setProperties(Propreties properties){
            System.out.println("插件的配置信息:"+properties);
        }
    }
    
    // 在全局配置文件: mybatis-config.xml 中注册插件
    <plugins>   
        <!-- interceptor: 拦截器的类路径 -->
        <plugin interceptor="cn.itcast.mybatis.dao.MyFirstPlugin">
            <property name="username" value="zhangsan"/>
        </plugin>
    </plugins>
    

    4. 使用 PageHelper 插件进行分页

    // 测试类: 查询所有员工
    public class MyBatisTest{
    
        @Test
        public void test() throws IOException{
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    
            SqlSession openSession = sqlSessionFactory.openSession();
    
            try{
                EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
                // 参数说明: 1 表示第一页数据, 3 表示每页3条数据
                Page<Object> page = PageHelper.startPage(1,3);
                List<Employee> emps = mapper.getEmps();
                for(Employee emp : emps){
                    System.out.println(emp);
                }
    
                System.out.println("当前页码:"+page.getPageNum());
                System.out.println("总记录数:"+page.getTotal());
    			System.out.println("每页记录数:"+page.getPageSize());
    			System.out.println("总页码:"+page.getPages());
    
            } finally{
                openSession.close();
            }
        }
    }
    
    // 在mybatis-plugin中注册PageHelper插件
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
    

    5. 批量操作

    // 测试类: 批量保存员工
    public class MyBatisTest{
    
        @Test
        public void test() throws IOException{
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    
            // 可以执行批量操作的sqlSession
            SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    
            try{
                EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    
                // 向数据库中插入10000条数据
                for(int i=0; i<10000; i++){
                    mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0,5),
                                                    "zhangsan@163.com","1");
                }
    
                openSession.commit();
            }finally{
                openSession.close();
            }
        }
    

    6. 自定义类型处理器(TypeHandler)处理枚举

    1. 我们可以通过自定义TypeHandler的形式来在设置参数或者取出结果集的时候,自定义参数封装策略;
    2. 步骤:
      • 实现TypeHandler接口或者继承BaseTypeHandler;
      • 使用@MappedTypes定义处理的java类型;
        使用@MappedJdbcTypes定义jdbcType类型;
      • 在自定义结果集标签或者参数处理的时候,声明使用自定义 TypeHandler 进行处理
        或者在全局配置自定义TypeHandler;
    // Employee.java
    public class Employee{
        private Integer id;
        private String lastName;
        private String email;
        private String gender;
        // 枚举类型: 用户状态包括登录,登出,不存在
        // 用户默认状态:用户登出
        private EmpStatus empStatus=EmpStatus.LOGOUT;
    
        get 和 set 方法(略)
    }
    
    // EmpStatus.java
    public enum EmpStatus{
        LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在")
    }
    
    // EmployMapper.xml
    <!-- 保存客户 -->
    <insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
        insert into tbl_employee(last_name,email,gender,empStatus)
        values(#{lastName},#{email},#{gender},#{empStatus})
    </insert>
    
    // mybatis-config.xml
    <typeHandlers>
        <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
                     javaType="cn.itcast.mybatis.bean.EmpStatus"/>
    </typeHandlers>
    
    // 测试类
    public class MyBatisTest{
    
        @Test
        public void test() throws IOException{
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    
            SqlSession openSession = sqlSessionFactory.openSession();
    
            try{
                EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    
                Employee employee = new Employee("test_enum","zhangsan@163.com","1");
    
                // MyBatis 在处理枚举对象时,默认保存的是枚举的名字: EnumTypeHandler
                //                  也可以使用枚举的索引来保存:EnumOrdinalTypeHandler
                //
                mapper.addEmp(employee);
                System.out.println("保存成功"+employee.getId());
                openSession.commit();
            }finally{
                openSession.close();
            }
    
        @Test
        public void testEnumUse(){
            EmpStatus login = EmpStatus.LOGIN;
            System.out.println("枚举的索引:"+login.ordinal());
            System.out.println("枚举的名字:"+login.name());
        }
    }
    
    
    // 升级版: 数据库保存的是 100, 200等这些自定义的状态码,而不是枚举的索引或者枚举的名字
    // EmpStatus.java
    public enum EmpStatus{
        LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");
    
        private Integer code; // 状态码
        private String msg; // 枚举的提示信息
        // 有参构造函数
        private EmpStatus(Integer code, String msg){
            this.code = code;
            this.msg = msg;
        }
    
        get 和 set 方法(略)
    
        // 按照状态码,返回枚举对象
        public static EmpStatus getEmpStatusByCode(Integer code){
            switch(code){
                case 100:
                    return LOGIN;
                case 200:
                    return LOGOUT;
                case 300:
                    return REMOVE;
                default:
                    return LOGOUT;
            }
        }
    }
    
    // 自定义枚举处理器
    public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus>{
    
        //定义当前数据如何保存到数据库中
        public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
                                JdbcType jdbcType) throws SQLException{
            ps.setString(i,parameter.getCode().toString());                   
        }
    
        //从数据库中获取到枚举状态码,返回一个枚举对象
        public EmpStatus getResult(ResultSet rs, String columnName) throws SQLException{
    
            int code = rs.getInt(columnName);
            EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    
            return status;
        }
    
        public EmpStatus getResult(ResultSet rs, String columnIndex) throws SQLException{
    
            int code = rs.getInt(columnIndex);
            EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    
            return status;
        }
    
        public EmpStatus getResult(CallableStatement cs, String columnIndex) throws SQLException{
    
            int code = cs.getInt(columnIndex);
            EmpStatus status = EmpStatus.getEmpStatusByCode(code);
    
            return status;
        }
    }
    
    // mybatis-config.xml 中配置自定义枚举处理器
    <typeHandlers>
        <typeHandler handler="cn.itcast.mybatis.typehandler.MyEnumEmpStatusTypeHandler"
                     javaType="cn.itcast.mybatis.bean.EmpStatus"/>
    </typeHandlers>
    
    // 测试类
    public class MyBatisTest{
    
        @Test
        public void testEnumUse(){
            EmStatus login = EmpStatus.LOGIN;
            System.out.println("枚举的索引:"+login.ordinal());
            System.out.println("枚举的名字:"+login.name());
            System.out.println("枚举的状态码:"+login.getCode());
            System.out.println("枚举的提示信息:"+login.getMsg());
        }
    
        @Test
        public void testEnum() throws IOException{
            同上;
        }
    }
    

    参考资料

  • 相关阅读:
    8.7题解
    2019.9.16 csp-s模拟测试44 反思总结
    洛谷P3168 [CQOI2015]任务查询系统
    洛谷P2468 [SDOI2010]粟粟的书架
    2019.8.14 NOIP模拟测试21 反思总结
    2019.8.13 NOIP模拟测试19 反思总结
    2019.8.12 NOIP模拟测试18 反思总结
    大约是个告别【草率极了】
    2019.8.10 NOIP模拟测试16 反思总结【基本更新完毕忽视咕咕咕】
    2019.8.9 NOIP模拟测试15 反思总结
  • 原文地址:https://www.cnblogs.com/linkworld/p/7801007.html
Copyright © 2020-2023  润新知