一岛蚤、插件原理
在四大對象創(chuàng)建的時候,有以下幾個特性:
- 每個創(chuàng)建出來的對象不是直接返回的婿屹,而是
interceptorChain.pluginAll(parameterHandler);
- 該方法獲取到所有的
Interceptor
(插件需要實現(xiàn)的接口)灭美,調(diào)用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;
}
因此昂利,我們可以使用插件為目標對象創(chuàng)建一個代理對象届腐,類似于AOP(面向切面編程)。我們的插件可以為四大對象創(chuàng)建出代理對象蜂奸,代理對象就可以攔截到四大對象中每一個的執(zhí)行犁苏。
二、插件編寫(單個插件)
1扩所、編寫Interceptor
的實現(xiàn)類
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 {
//攔截目標對象的目標方法的執(zhí)行
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("intercept..."+invocation);
//執(zhí)行目標方法
Object proceed = invocation.proceed();
//返回執(zhí)行后的返回值
return proceed;
}
//包裝目標對象围详,為目標對象創(chuàng)建一個代理對象
public Object plugin(Object target) {
System.out.println("plugin..."+target);
//借助這個方法來使用當前Interceptor包裝我們目標對象
Object wrap = Plugin.wrap(target,this);
//返回當前target創(chuàng)建的動態(tài)代理對象
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 {
//攔截目標對象的目標方法的執(zhí)行
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("intercept..."+invocation);
//執(zhí)行目標方法
Object proceed = invocation.proceed();
//返回執(zhí)行后的返回值
return proceed;
}
//包裝目標對象助赞,為目標對象創(chuàng)建一個代理對象
public Object plugin(Object target) {
System.out.println("plugin..."+target);
//借助這個方法來使用當前Interceptor包裝我們目標對象
Object wrap = Plugin.wrap(target,this);
//返回當前target創(chuàng)建的動態(tài)代理對象
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
方法的攔截器袁勺。
三雹食、多個插件時的執(zhí)行順序
我們再編寫一個插件類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();
}
}
結(jié)果如圖1所示:
從上圖可以看出,在生成代理對象時钝荡,是先創(chuàng)建第一個插件類的代理對象街立,再創(chuàng)建第二個插件類的代理對象;但是在攔截目標方法的時候埠通,則是先執(zhí)行第二個插件類赎离,再執(zhí)行第一個插件類。
因此我們可以得出以下結(jié)論:創(chuàng)建動態(tài)代理的時候端辱,是按照插件配置的順序?qū)訉觿?chuàng)建代理對象的蟹瘾。執(zhí)行目標方法的時候,按照逆序順序執(zhí)行掠手。
我們可以將該過程類比為如下的模型憾朴,首先創(chuàng)建的StatementHandler
,然后再創(chuàng)建MyFirstPlugin
代理對象喷鸽,然后再創(chuàng)建了MySecondPlugin
代理對象众雷,其三者的關系是晚創(chuàng)建的包含早創(chuàng)建的,在執(zhí)行目標方法的時候自然而然是從外向里執(zhí)行。
四砾省、使用PageInterceptor插件
1鸡岗、導包
需要兩個包,可以在GitHub上面下載:點此下載
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);
//第一個參數(shù)是頁碼,第二個是每頁的記錄數(shù)
Page <Object> page = PageHelper.startPage(1,5);
List <Employee> list = mapper.getEmps();
System.out.println("當前頁碼:"+page.getPageNum());
System.out.println("總記錄數(shù):"+page.getTotal());
System.out.println("每頁的記錄數(shù):"+page.getPageSize());
System.out.println("總頁碼:"+page.getPages());
}finally {
session.close();
}
}
可以使用Page
對象來獲取關于分頁的數(shù)據(jù)狠鸳,例如當前頁碼揣苏、總記錄數(shù)等等。PageHelper.startPage(1,5)
表示顯示第一頁件舵,然后每頁有5條記錄數(shù)卸察。
@Test
public void test() throws IOException {
SqlSessionFactory factory = getSqlSessionFactory();
SqlSession session = factory.openSession();
try{
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
//第一個參數(shù)是頁碼,第二個是每頁的記錄數(shù)
PageHelper.startPage(1,5);
List <Employee> list = mapper.getEmps();
PageInfo<Employee> info = new PageInfo <>(list);
System.out.println("當前頁碼:"+info.getPageNum());
System.out.println("總記錄數(shù):"+info.getTotal());
System.out.println("每頁的記錄數(shù):"+info.getPageSize());
System.out.println("總頁碼:"+info.getPages());
System.out.println("是否第一頁:"+info.isIsFirstPage());
System.out.println("是否最后一頁:"+info.isIsLastPage());
}finally {
session.close();
}
}
也可以使用PageInfo
對象來獲取分頁的數(shù)據(jù)铅祸,跟上面代碼差不多坑质。
五、使用BatchExecutor進行批量操作
在通過SqlSessionFactory
獲取SqlSession
的時候傳入一個參數(shù)即可临梗,即:
SqlSessionFactory factory = getSqlSessionFactory();
//可以執(zhí)行批量操作的SqlSession
SqlSession session = factory.openSession(ExecutorType.BATCH);
設置了該參數(shù)之后涡扼,現(xiàn)在該SqlSession
就是可以批量操作的了:
@Test
public void testBatch() throws IOException {
SqlSessionFactory factory = getSqlSessionFactory();
//可以執(zhí)行批量操作的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
字段盟庞,用來保存該類的狀態(tài)壳澳,該字段是一個枚舉類:
package com.cerr.mybatis;
public enum EmpStatus {
LOGIN,LOGOUT,REMOVE;
}
MyBatis在處理枚舉類型的時候有兩個TypeHandler
,一個是EnumTypeHandler
茫经,另一個是EnumOrdinalTypeHandler
。
-
EnumTypeHandler
:保存枚舉對象的時候默認保存的是枚舉對象的名字 -
EnumOrdinalTypeHandler
:保存枚舉對象的時候默認保存的是枚舉對象的索引萎津。
我們現(xiàn)在想自己來處理枚舉類型卸伞,就需要自定義TypeHandler來實現(xiàn),自定義的類需要實現(xiàn)TypeHandler
接口或者繼承BaseTypeHandler
锉屈。
我們首先將EmpStatus
來改造一下荤傲,因為我們想在保存進數(shù)據(jù)庫的時候存狀態(tài)碼,然后獲取的時候獲取的是該對象的信息颈渊,因此需要增加兩個字段msg
和code
遂黍,再增加對應的構(gòu)造方法、getter
和setter
方法后:
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;
/**
* 實現(xiàn)TypeHandler接口或者繼承BaseTypeHandler
*/
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
//定義數(shù)據(jù)如何保存到數(shù)據(jù)庫中
@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 {
//根據(jù)從數(shù)據(jù)庫中拿到的狀態(tài)碼返回枚舉對象
int code = resultSet.getInt(s);
return EmpStatus.getEmpStatusByCode(code);
}
@Override
public EmpStatus getResult(ResultSet resultSet, int i) throws SQLException {
//根據(jù)從數(shù)據(jù)庫中拿到的狀態(tài)碼返回枚舉對象
int code = resultSet.getInt(i);
return EmpStatus.getEmpStatusByCode(code);
}
@Override
public EmpStatus getResult(CallableStatement callableStatement, int i) throws SQLException {
//根據(jù)從數(shù)據(jù)庫中拿到的狀態(tài)碼返回枚舉對象
int code = callableStatement.getInt(i);
return EmpStatus.getEmpStatusByCode(code);
}
}
實現(xiàn)TypeHandler
后的方法中雾家,setParameter()
定義了數(shù)據(jù)如何保存在數(shù)據(jù)庫中,直接使用PreparedStatement
設置參數(shù)的方法將我們想保存的數(shù)據(jù)添加為參數(shù)绍豁,對于剩下的三個方法定義從數(shù)據(jù)庫拿到數(shù)據(jù)后如何處理芯咧,我們將數(shù)據(jù)庫中保存的狀態(tài)碼拿出來后,調(diào)用我們編寫的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>
標簽使用的是同一個類型處理器。