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、将写好的插件注册到全局配置文件中

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

    <plugins>
        <plugin interceptor="com.cerr.dao.MyFirstPlugin"></plugin>
    </plugins>

此时我们就定义了一个拦截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) {
    }
}

在全局配置文件中配置:

    <plugins>
        <plugin interceptor="com.cerr.dao.MyFirstPlugin"></plugin>
        <plugin interceptor="com.cerr.dao.MySecondPlugin"></plugin>
    </plugins>

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

    @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插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

3、编码

可以使用Page对象:

    @Test
    public void test() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            //第一个参数是页码,第二个是每页的记录数
            Page <Object> page = PageHelper.startPage(1,5);
            List <Employee> 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 <Employee> list = mapper.getEmps();
            PageInfo<Employee> 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<EmpStatus> {
    //定义数据如何保存到数据库中
    @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

语法格式如下:

<typeHandlers>
  <!-- 配置自定义的类型处理器 -->
  <typeHandler handler="自定义的TypeHandler全类名" javaType="指定要处理的类"/>
</typeHandlers>

在此处我们的配置如下:

    <typeHandlers>
        <!-- 配置自定义的类型处理器 -->
        <typeHandler handler="com.cerr.mybatis.dao.MyEnumEmpStatusTypeHandler" javaType="com.cerr.mybatis.EmpStatus"/>
    </typeHandlers>

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

  • 对于<insert>标签,我们直接在传参时加上typeHandler属性即可,例如#{empStatus,typeHandler=com.cerr.mybatis.dao.MyEnumEmpStatusTypeHandler}
  • 对于<select>标签,我们可以在定义<resultMap>的时候,对于枚举属性,加上一个typeHandler属性并指定为我们自定义的类型处理器即可。
  • 但是,如果使用这种方法指定使用类型处理器的话,比如保证<insert><select>标签使用的是同一个类型处理器。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容