原文地址:https://xeblog.cn/articles/20
MyBatis插件開發(fā)流程
- 類實現
Interceptor
接口; - 類上添加注解
@Intercepts({@Signature(type, method, args)})
-
type:需要攔截的對象歧蕉,只可取四大對象之一
Executor.class灾部、StatementHandler.class、ParameterHandler.class惯退、ResultSetHandler.class
赌髓。 - method:攔截的對象方法。
- args:攔截的對象方法參數催跪。
-
type:需要攔截的對象歧蕉,只可取四大對象之一
- 實現攔截的方法
Object intercept(Invocation invocation)
锁蠕。
Interceptor接口
public interface Interceptor {
/**
* 此方法將直接覆蓋被攔截對象的原有方法
*
* @param invocation 通過該對象可以反射調度攔截對象的方法
* @return
* @throws Throwable
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* 為被攔截對象生成一個代理對象,并返回它
*
* @param target 被攔截的對象
* @return
*/
Object plugin(Object target);
/**
* 設置插件配置的參數
*
* @param properties 插件配置的參數
*/
void setProperties(Properties properties);
}
簡單分頁插件開發(fā)
確定攔截的方法簽名
需要在實現Interceptor
接口的類上加入@Intercepts({@Signature(type, method, args)})
注解才能夠運行插件懊蒸。
type-攔截的對象
-
Executor
執(zhí)行的SQL
全過程荣倾,包括組裝參數、組裝結果返回和執(zhí)行SQL
的過程等都可以攔截骑丸。 -
StatementHandler
執(zhí)行SQL
的過程舌仍,攔截該對象可以重寫執(zhí)行SQL
的過程。 -
ParameterHandler
執(zhí)行SQL
的參數組裝通危,攔截該對象可以重寫組裝參數的規(guī)則铸豁。 -
ResultSetHandler
執(zhí)行結果的組裝,攔截該對象可以重寫組裝結果的規(guī)則黄鳍。
對于分頁插件推姻,我們只需要攔截StatementHandler
對象平匈,重寫SELECT
類型的SQL
語句框沟,實現分頁功能藏古。
method-攔截的方法
我們已經能夠確定攔截的對象是StatementHandler
了,現在我們要確定攔截的是哪個方法忍燥,因為StatementHandler
是通過prepare
方法對SQL
進行預編譯的拧晕,所以我們需要對prepare
方法進行攔截,在這個方法執(zhí)行之前梅垄,完成SQL
的重新編寫(加入limit)厂捞。
StatementHandler
public interface StatementHandler {
/**
* 預編譯SQL
*
* @param connection
* @return
* @throws SQLException
*/
Statement prepare(Connection connection)
throws SQLException;
/**
* 設置參數
*
* @param statement
* @throws SQLException
*/
void parameterize(Statement statement)
throws SQLException;
/**
* 批處理
*
* @param statement
* @throws SQLException
*/
void batch(Statement statement)
throws SQLException;
/**
* 執(zhí)行更新操作
*
* @param statement
* @return 返回影響行數
* @throws SQLException
*/
int update(Statement statement)
throws SQLException;
/**
* 執(zhí)行查詢操作,將結果交給ResultHandler進行結果的組裝
*
* @param statement
* @param resultHandler
* @param <E>
* @return 返回查詢的數據列表
* @throws SQLException
*/
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
/**
* 得到綁定的sql
*
* @return
*/
BoundSql getBoundSql();
/**
* 得到參數處理器
*
* @return
*/
ParameterHandler getParameterHandler();
}
args-攔截的參數
args是一個Class
類型的數組队丝,表示的是被攔截方法的參數列表靡馁。由于我們已經確定了攔截的是StatementHandler
的prepare
方法,而該方法只有一個參數Connection
机久,所以我們只需要攔截這一個參數即可臭墨。
實現攔截方法
定義一個封裝分頁參數的類Page
package cn.xeblog.pojo;
public class Page {
/**
* 當前頁碼
*/
private Integer pageIndex;
/**
* 每頁數據條數
*/
private Integer pageSize;
/**
* 總數據數
*/
private Integer total;
/**
* 總頁數
*/
private Integer totalPage;
public Page() {
}
public Page(Integer pageIndex, Integer pageSize) {
this.pageIndex = pageIndex;
this.pageSize = pageSize;
}
// 省略get、set方法...
}
實現插件分頁的功能
package cn.xeblog.plugin;
import cn.xeblog.pojo.Page;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@Intercepts({@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class}
)})
public class PagingPlugin implements Interceptor {
/**
* 默認頁碼
*/
private Integer defaultPageIndex;
/**
* 默認每頁數據條數
*/
private Integer defaultPageSize;
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = getUnProxyObject(invocation);
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
String sql = getSql(metaObject);
if (!checkSelect(sql)) {
// 不是select語句膘盖,進入責任鏈下一層
return invocation.proceed();
}
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
Object parameterObject = boundSql.getParameterObject();
Page page = getPage(parameterObject);
if (page == null) {
// 沒有傳入page對象胧弛,不執(zhí)行分頁處理,進入責任鏈下一層
return invocation.proceed();
}
// 設置分頁默認值
if (page.getPageIndex() == null) {
page.setPageIndex(this.defaultPageIndex);
}
if (page.getPageSize() == null) {
page.setPageSize(this.defaultPageSize);
}
// 設置分頁總數侠畔,數據總數
setTotalToPage(page, invocation, metaObject, boundSql);
// 校驗分頁參數
checkPage(page);
return changeSql(invocation, metaObject, boundSql, page);
}
public Object plugin(Object target) {
// 生成代理對象
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
// 初始化配置的默認頁碼结缚,無配置則默認1
this.defaultPageIndex = Integer.parseInt(properties.getProperty("default.pageIndex", "1"));
// 初始化配置的默認數據條數,無配置則默認20
this.defaultPageSize = Integer.parseInt(properties.getProperty("default.pageSize", "20"));
}
/**
* 從代理對象中分離出真實對象
*
* @param invocation
* @return
*/
private StatementHandler getUnProxyObject(Invocation invocation) {
// 取出被攔截的對象
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStmtHandler = SystemMetaObject.forObject(statementHandler);
Object object = null;
// 分離代理對象
while (metaStmtHandler.hasGetter("h")) {
object = metaStmtHandler.getValue("h");
metaStmtHandler = SystemMetaObject.forObject(object);
}
return object == null ? statementHandler : (StatementHandler) object;
}
/**
* 判斷是否是select語句
*
* @param sql
* @return
*/
private boolean checkSelect(String sql) {
// 去除sql的前后空格软棺,并將sql轉換成小寫
sql = sql.trim().toLowerCase();
return sql.indexOf("select") == 0;
}
/**
* 獲取分頁參數
*
* @param parameterObject
* @return
*/
private Page getPage(Object parameterObject) {
if (parameterObject == null) {
return null;
}
if (parameterObject instanceof Map) {
// 如果傳入的參數是map類型的红竭,則遍歷map取出Page對象
Map<String, Object> parameMap = (Map<String, Object>) parameterObject;
Set<String> keySet = parameMap.keySet();
for (String key : keySet) {
Object value = parameMap.get(key);
if (value instanceof Page) {
// 返回Page對象
return (Page) value;
}
}
} else if (parameterObject instanceof Page) {
// 如果傳入的是Page類型,則直接返回該對象
return (Page) parameterObject;
}
// 初步判斷并沒有傳入Page類型的參數喘落,返回null
return null;
}
/**
* 獲取數據總數
*
* @param invocation
* @param metaObject
* @param boundSql
* @return
*/
private int getTotal(Invocation invocation, MetaObject metaObject, BoundSql boundSql) {
// 獲取當前的mappedStatement對象
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 獲取配置對象
Configuration configuration = mappedStatement.getConfiguration();
// 獲取當前需要執(zhí)行的sql
String sql = getSql(metaObject);
// 改寫sql語句德崭,實現返回數據總數 $_paging取名是為了防止數據庫表重名
String countSql = "select count(*) as total from (" + sql + ") $_paging";
// 獲取攔截方法參數,攔截的是connection對象
Connection connection = (Connection) invocation.getArgs()[0];
PreparedStatement pstmt = null;
int total = 0;
try {
// 預編譯查詢數據總數的sql語句
pstmt = connection.prepareStatement(countSql);
// 構建boundSql對象
BoundSql countBoundSql = new BoundSql(configuration, countSql, boundSql.getParameterMappings(),
boundSql.getParameterObject());
// 構建parameterHandler用于設置sql參數
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(),
countBoundSql);
// 設置sql參數
parameterHandler.setParameters(pstmt);
//執(zhí)行查詢
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
total = rs.getInt("total");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 返回總數據數
return total;
}
/**
* 設置總數據數揖盘、總頁數
*
* @param page
* @param invocation
* @param metaObject
* @param boundSql
*/
private void setTotalToPage(Page page, Invocation invocation, MetaObject metaObject, BoundSql boundSql) {
// 總數據數
int total = getTotal(invocation, metaObject, boundSql);
// 計算總頁數
int totalPage = total / page.getPageSize();
if (total % page.getPageSize() != 0) {
totalPage = totalPage + 1;
}
page.setTotal(total);
page.setTotalPage(totalPage);
}
/**
* 校驗分頁參數
*
* @param page
*/
private void checkPage(Page page) {
// 如果當前頁碼大于總頁數眉厨,拋出異常
if (page.getPageIndex() > page.getTotalPage()) {
throw new RuntimeException("當前頁碼[" + page.getPageIndex() + "]大于總頁數[" + page.getTotalPage() + "]");
}
// 如果當前頁碼小于總頁數,拋出異常
if (page.getPageIndex() < 1) {
throw new RuntimeException("當前頁碼[" + page.getPageIndex() + "]小于[1]");
}
}
/**
* 修改當前查詢的sql
*
* @param invocation
* @param metaObject
* @param boundSql
* @param page
* @return
*/
private Object changeSql(Invocation invocation, MetaObject metaObject, BoundSql boundSql, Page page) throws Exception {
// 獲取當前查詢的sql
String sql = getSql(metaObject);
// 修改sql兽狭,$_paging_table_limit取名是為了防止數據庫表重名
String newSql = "select * from (" + sql + ") $_paging_table_limit limit ?, ?";
// 設置當前sql為修改后的sql
setSql(metaObject, newSql);
// 獲取PreparedStatement對象
PreparedStatement pstmt = (PreparedStatement) invocation.proceed();
// 獲取sql的總參數個數
int parameCount = pstmt.getParameterMetaData().getParameterCount();
// 設置分頁參數
pstmt.setInt(parameCount - 1, (page.getPageIndex() - 1) * page.getPageSize());
pstmt.setInt(parameCount, page.getPageSize());
return pstmt;
}
/**
* 獲取當前查詢的sql
*
* @param metaObject
* @return
*/
private String getSql(MetaObject metaObject) {
return (String) metaObject.getValue("delegate.boundSql.sql");
}
/**
* 設置當前查詢的sql
*
* @param metaObject
*/
private void setSql(MetaObject metaObject, String sql) {
metaObject.setValue("delegate.boundSql.sql", sql);
}
}
配置分頁插件
在mybatis-config.xml
配置文件中配置自定義的分頁插件
<plugins>
<plugin interceptor="cn.xeblog.plugin.PagingPlugin">
<property name="default.pageIndex" value="1"/>
<property name="default.pageSize" value="20"/>
</plugin>
</plugins>
實現DAO
定義POJO對象Role
public class Role {
private Long id;
private String roleName;
private String note;
// 省略get憾股、set...
}
定義Mapper
接口,通過分頁對象查詢角色列表
public interface RoleMapper {
List<Role> listRoleByPage(Page page);
}
定義Mapper.xml
編寫查詢的SQL
語句
<mapper namespace="cn.xeblog.mapper.RoleMapper">
<select id="listRoleByPage" resultType="cn.xeblog.pojo.Role">
SELECT id, role_name, note FROM role
</select>
</mapper>
測試分頁插件
測試代碼
@Test
public void test() {
InputStream inputStream = null;
SqlSessionFactory sqlSessionFactory;
SqlSession sqlSession = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
// 分頁參數箕慧,從第一頁開始服球,每頁顯示5條數據
Page page = new Page(1, 5);
List<Role> roleList = roleMapper.listRoleByPage(page);
System.out.println("===分頁信息===");
System.out.println("當前頁碼:" + page.getPageIndex());
System.out.println("每頁顯示數據數:" + page.getPageSize());
System.out.println("總數據數:" + page.getTotal());
System.out.println("總頁數:" + page.getTotalPage());
System.out.println("=============");
System.out.println("===數據列表===");
for (Role role : roleList) {
System.out.println(role);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
數據庫role
表中的全部數據信息
id | role_name | note |
---|---|---|
1 | SUPER_ADMIN | 超級管理員 |
2 | admin | 管理員 |
3 | user | 用戶 |
4 | user2 | 用戶2 |
8 | user3 | 用戶3 |
9 | test | 測試 |
10 | test2 | 測試2 |
11 | test3 | 測試3 |
12 | test4 | 測試4 |
13 | test5 | 測試5 |
代碼執(zhí)行結果
===分頁信息===
當前頁碼:1
每頁顯示數據數:5
總數據數:10
總頁數:2
=============
===數據列表===
Role{id=1, roleName='SUPER_ADMIN', note=' 超級管理員'}
Role{id=2, roleName='admin', note='管理員'}
Role{id=3, roleName='user', note='用戶'}
Role{id=4, roleName='user2', note='用戶2'}
Role{id=8, roleName='user3', note='用戶3'}
打印的SQL
信息
==> Preparing: select count(*) as total from (SELECT id, role_name, note FROM role) $_paging
==> Parameters:
<== Columns: total
<== Row: 10
<== Total: 1
==> Preparing: select * from (SELECT id, role_name, note FROM role) $_paging_table_limit limit ?, ?
==> Parameters: 0(Integer), 5(Integer)
<== Columns: id, role_name, note
<== Row: 1, SUPER_ADMIN, 超級管理員
<== Row: 2, admin, 管理員
<== Row: 3, user, 用戶
<== Row: 4, user2, 用戶2
<== Row: 8, user3, 用戶3
<== Total: 5
參考
- 《深入淺出MyBatis技術原理與實戰(zhàn)》