Fork me on GitHub

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{
        同上;
    }
}

参考资料

posted @ 2017-11-07 20:45  小a的软件思考  阅读(11644)  评论(1编辑  收藏  举报