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>標簽使用的是同一個類型處理器。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容