MyBatis | MyBatis中使用插件、使用PageInterceptor插件、自定义类型处理器

一、插件原理

在四大对象创建的时候,有以下几个特性:

  • 每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
  • 该方法获取到所有的Interceptor(插件需要实现的接口),调用interceptor.plugin(target);返回target包装后的对象。
    public Object pluginAll(Object target) {
        Interceptor interceptor;
        for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
            interceptor = (Interceptor)var2.next();
        }

        return target;
    }

因此,我们可以使用插件为目标对象创建一个代理对象,类似于AOP(面向切面编程)。我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象中每一个的执行。


二、插件编写(单个插件)

1、编写Interceptor的实现类

package com.cerr.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
public class MyFirstPlugin implements Interceptor {

    //拦截目标对象的目标方法的执行
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("intercept..."+invocation);
        //执行目标方法
        Object proceed = invocation.proceed();
        //返回执行后的返回值
        return proceed;
    }

    //包装目标对象,为目标对象创建一个代理对象
    public Object plugin(Object target) {
        System.out.println("plugin..."+target);
        //借助这个方法来使用当前Interceptor包装我们目标对象
        Object wrap = Plugin.wrap(target,this);
        //返回当前target创建的动态代理对象
        return wrap;
    }

    //将插件注册时的property属性设置进去
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息"+properties);

    }
}

2、使用@Intercepts注解完成插件的签名

在编写的插件类上标注@Intercepts,标注后的插件类如下:

package com.cerr.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;

//完成插件签名:告诉MyBatis当前插件来用拦截哪个对象的哪个方法
@Intercepts({
        @Signature(type = StatementHandler.class,method = "parameterize",
            args = java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {

    //拦截目标对象的目标方法的执行
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("intercept..."+invocation);
        //执行目标方法
        Object proceed = invocation.proceed();
        //返回执行后的返回值
        return proceed;
    }

    //包装目标对象,为目标对象创建一个代理对象
    public Object plugin(Object target) {
        System.out.println("plugin..."+target);
        //借助这个方法来使用当前Interceptor包装我们目标对象
        Object wrap = Plugin.wrap(target,this);
        //返回当前target创建的动态代理对象
        return wrap;
    }

    //将插件注册时的property属性设置进去
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息"+properties);

    }
}

3、将写好的插件注册到全局配置文件中

在全局配置文件中使用标签来注册:

    
        
    

此时我们就定义了一个拦截StatementHandler对象的parameterize方法的拦截器。


三、多个插件时的执行顺序

我们再编写一个插件类MySecondPlugin,也是和上面的插件类一样拦截的同一个对象的同一个方法。代码如下:

package com.cerr.dao;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.util.Properties;
@Intercepts({
        @Signature(type = StatementHandler.class,method = "parameterize",
                args = java.sql.Statement.class)
})
public class MySecondPlugin implements Interceptor {
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MySecondPlugin..intercept:"+invocation);
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        System.out.println("MySecondPlugin...plugin:"+target);
        return Plugin.wrap(target,this);
    }

    public void setProperties(Properties properties) {
    }
}

在全局配置文件中配置:

    
        
        
    

配置后我们随便找一个测试方法运行,例如:

    @Test
    public void testSimple() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            List< Employee > list = mapper.selectByExample(null);
        }finally {
            session.close();
        }
    }

结果如图1所示:


图1:控制台结果

从上图可以看出,在生成代理对象时,是先创建第一个插件类的代理对象,再创建第二个插件类的代理对象;但是在拦截目标方法的时候,则是先执行第二个插件类,再执行第一个插件类。

因此我们可以得出以下结论:创建动态代理的时候,是按照插件配置的顺序层层创建代理对象的。执行目标方法的时候,按照逆序顺序执行。

我们可以将该过程类比为如下的模型,首先创建的StatementHandler,然后再创建MyFirstPlugin代理对象,然后再创建了MySecondPlugin代理对象,其三者的关系是晚创建的包含早创建的,在执行目标方法的时候自然而然是从外向里执行。

图2:动态代理模型


四、使用PageInterceptor插件

1、导包

需要两个包,可以在GitHub上面下载:点此下载

图3:下载这两个jar包

2、在全局配置文件中注册PageInterceptor插件


    

3、编码

可以使用Page对象:

    @Test
    public void test() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            //第一个参数是页码,第二个是每页的记录数
            Page  page = PageHelper.startPage(1,5);
            List  list = mapper.getEmps();
            System.out.println("当前页码:"+page.getPageNum());
            System.out.println("总记录数:"+page.getTotal());
            System.out.println("每页的记录数:"+page.getPageSize());
            System.out.println("总页码:"+page.getPages());
        }finally {
            session.close();
        }
    }
 
 

可以使用Page对象来获取关于分页的数据,例如当前页码、总记录数等等。PageHelper.startPage(1,5)表示显示第一页,然后每页有5条记录数。

    @Test
    public void test() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            //第一个参数是页码,第二个是每页的记录数
            PageHelper.startPage(1,5);
            List  list = mapper.getEmps();
            PageInfo info = new PageInfo <>(list);
            System.out.println("当前页码:"+info.getPageNum());
            System.out.println("总记录数:"+info.getTotal());
            System.out.println("每页的记录数:"+info.getPageSize());
            System.out.println("总页码:"+info.getPages());
            System.out.println("是否第一页:"+info.isIsFirstPage());
            System.out.println("是否最后一页:"+info.isIsLastPage());
        }finally {
            session.close();
        }
    }

也可以使用PageInfo对象来获取分页的数据,跟上面代码差不多。


五、使用BatchExecutor进行批量操作

在通过SqlSessionFactory获取SqlSession的时候传入一个参数即可,即:

SqlSessionFactory factory = getSqlSessionFactory();
//可以执行批量操作的SqlSession
SqlSession session = factory.openSession(ExecutorType.BATCH);

设置了该参数之后,现在该SqlSession就是可以批量操作的了:

    @Test
    public void testBatch() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        //可以执行批量操作的SqlSession
        SqlSession session = factory.openSession(ExecutorType.BATCH);
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            for (int i = 0;i < 10000;++i){
                mapper.addEmps(new Employee(null,"a","a","a"));
            }
            session.commit();
        }finally {
            session.close();
        }
    }

六、自定义TypeHandler来处理枚举类型

我们在Employee类中添加一个EmpStatus字段,用来保存该类的状态,该字段是一个枚举类:

package com.cerr.mybatis;

public enum EmpStatus {
    LOGIN,LOGOUT,REMOVE;
}

MyBatis在处理枚举类型的时候有两个TypeHandler,一个是EnumTypeHandler,另一个是EnumOrdinalTypeHandler

  • EnumTypeHandler:保存枚举对象的时候默认保存的是枚举对象的名字
  • EnumOrdinalTypeHandler:保存枚举对象的时候默认保存的是枚举对象的索引。

我们现在想自己来处理枚举类型,就需要自定义TypeHandler来实现,自定义的类需要实现TypeHandler接口或者继承BaseTypeHandler

我们首先将EmpStatus来改造一下,因为我们想在保存进数据库的时候存状态码,然后获取的时候获取的是该对象的信息,因此需要增加两个字段msgcode,再增加对应的构造方法、gettersetter方法后:

package com.cerr.mybatis;
public enum EmpStatus {
    LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");
    private Integer code;
    private String msg;
    public static EmpStatus getEmpStatusByCode(Integer code){
        switch (code){
            case 100:
                return LOGIN;
            case 200:
                return LOGOUT;
            case 300:
                return REMOVE;
             default:
                 return LOGOUT;
        }
    }
    EmpStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}

1、自定义TypeHandler类

package com.cerr.mybatis.dao;
import com.cerr.mybatis.EmpStatus;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
 * 实现TypeHandler接口或者继承BaseTypeHandler
 */
public class MyEnumEmpStatusTypeHandler implements TypeHandler {
    //定义数据如何保存到数据库中
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, EmpStatus empStatus, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i,empStatus.getCode().toString());
    }

    @Override
    public EmpStatus getResult(ResultSet resultSet, String s) throws SQLException {
        //根据从数据库中拿到的状态码返回枚举对象
        int code = resultSet.getInt(s);
        return EmpStatus.getEmpStatusByCode(code);
    }

    @Override
    public EmpStatus getResult(ResultSet resultSet, int i) throws SQLException {
        //根据从数据库中拿到的状态码返回枚举对象
        int code = resultSet.getInt(i);
        return EmpStatus.getEmpStatusByCode(code);
    }

    @Override
    public EmpStatus getResult(CallableStatement callableStatement, int i) throws SQLException {
        //根据从数据库中拿到的状态码返回枚举对象
        int code = callableStatement.getInt(i);
        return EmpStatus.getEmpStatusByCode(code);
    }
}

实现TypeHandler后的方法中,setParameter()定义了数据如何保存在数据库中,直接使用PreparedStatement设置参数的方法将我们想保存的数据添加为参数,对于剩下的三个方法定义从数据库拿到数据后如何处理,我们将数据库中保存的状态码拿出来后,调用我们编写的EmpStatus.getEmpStatusByCode(code)方法返回该枚举对象的信息。

2、在全局配置文件中注册该TypeHandler

语法格式如下:


  
  

在此处我们的配置如下:

    
        
        
    

当然了我们除了在全局配置文件中使用javaType来指定哪个类被我们自定义的类型处理器处理之外,我们还可以在sql映射文件中配置:

  • 对于标签,我们直接在传参时加上typeHandler属性即可,例如#{empStatus,typeHandler=com.cerr.mybatis.dao.MyEnumEmpStatusTypeHandler}
  • 对于标签使用的是同一个类型处理器。

你可能感兴趣的:(MyBatis | MyBatis中使用插件、使用PageInterceptor插件、自定义类型处理器)