學習Mybatis(5):編寫外掛
阿新 • • 發佈:2018-12-28
Mybatis的外掛用來對SQL語句進行修飾,或者在執行SQL語句前後進行一些工作
在Mybatis的配置檔案中,<plugins>標籤就是來配置自定義外掛的
例如:
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
自定義外掛需要有如下註解:
@Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
- type:攔截的元件型別,有Executor、StatementHandler、ResultSetHandler、ParameterHandler等,從名字可以看出來,外掛可以作用於SQL執行器、SQL語句、SQL引數、結果集等
- method:四大元件的方法,如下:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
- args:方法引數,如StatementHandler的prepare方法,引數為Connection和Integer型別,就寫{Connection.class,Integer.class}
編寫外掛需要實現Interceptor介面,該介面定義如下:
import java.util.Properties; public interface Interceptor { Object intercept(Invocation var1) throws Throwable; Object plugin(Object var1); void setProperties(Properties var1); }
setProperties用來為例項變數賦值
plugin一般只要呼叫Plugin.wrap(var1,this)即可
重點是intercept方法,用來實現攔截邏輯,不過一般最後都要呼叫var1.proceed();
還有一個重要類——MetaObject,用來獲取四大元件內部的例項變數(以及這些變數內部的變數),例如 StatementHandler 類,實際上框架呼叫的是 RoutingStatementHandler ,該類有一個StatementHandler型別的delegate變數,其內部有一個名為boundSql的物件,現在要獲取該物件,可以這麼寫:
MetaObject stmtHandlerMeta=SystemMetaObject.forObject(statementHandler);
BoundSql boundSql= (BoundSql) stmtHandlerMeta.getValue("delegate.boundSql");
實際編寫
以分頁查詢為例(結合Spring Boot):
1)編寫Page類:
public class Page {
private Integer page;
private Integer pageSize;
public Integer getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
很簡單的一個POJO類,包含兩個引數:頁碼和頁容量,及其getter、setter
2)編寫PagePlugin類:
import 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.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
//攔截StatementHandler的prepare方法
@Intercepts({
@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})
})
public class PagePlugin implements Interceptor {
private Integer defaultPage;
private Integer defaultPageSize;
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler= (StatementHandler) invocation.getTarget();
MetaObject statementHandlerMetaObject= SystemMetaObject.forObject(statementHandler);
Object o=null;
//外掛使用責任鏈模式重重代理,這裡是用來還原到原始類
while(statementHandlerMetaObject.hasGetter("h")){
o=statementHandlerMetaObject.getValue("h");
statementHandlerMetaObject=SystemMetaObject.forObject(o);
}
if(o!=null)
statementHandler= (StatementHandler) o;
MetaObject stmtHandlerMeta=SystemMetaObject.forObject(statementHandler);
String sql= (String) stmtHandlerMeta.getValue("delegate.boundSql.sql");
//只有select方法才會攔截
if(sql.trim().toLowerCase().startsWith("select")!=0)
return invocation.proceed();
BoundSql boundSql= (BoundSql) stmtHandlerMeta.getValue("delegate.boundSql");
Object paramObject=boundSql.getParameterObject();
Page page=getPage(paramObject);
//沒有帶頁面引數就直接反射
if (page==null)
return invocation.proceed();
int pageNum=page.getPage()==null?defaultPage:page.getPage();
int pageSize=page.getPageSize()==null?defaultPageSize:page.getPageSize();
int total=getTotal(invocation,stmtHandlerMeta,boundSql);
int totalPage=total/pageSize==0?total/pageSize:total/pageSize+1;
//頁碼過大(超過資料總數)就直接反射
if(!checkPage(pageNum,totalPage))
return invocation.proceed();
return changeSQL(invocation,stmtHandlerMeta,boundSql,pageNum,pageSize);
}
private Object changeSQL(Invocation invocation, MetaObject stmtHandlerMeta, BoundSql boundSql, int pageNum, int pageSize) throws InvocationTargetException, IllegalAccessException, SQLException {
String sql= (String) stmtHandlerMeta.getValue("delegate.boundSql.sql");
String newSql=sql+" limit ?,?";
stmtHandlerMeta.setValue("delegate.boundSql.sql",newSql);
PreparedStatement ps= (PreparedStatement) invocation.proceed();
int count=ps.getParameterMetaData().getParameterCount();
ps.setInt(count-1,(pageNum-1)*pageSize);
ps.setInt(count,pageSize);
return ps;
}
private boolean checkPage(int pageNum, Integer totalPage) throws Exception {
if (pageNum>totalPage)
return false;
return true;
}
private int getTotal(Invocation invocation, MetaObject stmtHandlerMeta, BoundSql boundSql) throws Throwable {
MappedStatement mappedStatement= (MappedStatement) stmtHandlerMeta.getValue("delegate.mappedStatement");
Configuration configuration=mappedStatement.getConfiguration();
String sql= (String) stmtHandlerMeta.getValue("delegate.boundSql.sql");
String countSql="select count(*) as total from ("+sql+") $_paging";
Connection conn= (Connection) invocation.getArgs()[0];
PreparedStatement ps=null;
int total=0;
try {
ps=conn.prepareStatement(countSql);
BoundSql bs=new BoundSql(configuration,countSql,boundSql.getParameterMappings(),boundSql.getParameterObject());
ParameterHandler ph=new DefaultParameterHandler(mappedStatement,boundSql.getParameterObject(),bs);
ph.setParameters(ps);
ResultSet rs=ps.executeQuery();
while (rs.next())
total=rs.getInt("total");
}finally {
if(ps!=null)
ps.close();
}
return total;
}
private Page getPage(Object paramObject) {
if(paramObject==null)
return null;
Page page=null;
if(paramObject instanceof Map){
Map<String,Object> paramMap= (Map<String, Object>) paramObject;
for(Map.Entry<String,Object> entry:paramMap.entrySet()){
if(entry.getValue() instanceof Page) {
return (Page) entry.getValue();
}
}
}else if(paramObject instanceof Page){
return (Page) paramObject;
}
return page;
}
@Override
public Object plugin(Object o) {
//Plugin工具類的wrap方法用來包裝外掛
return Plugin.wrap(o,this);
}
@Override
public void setProperties(Properties properties) {
defaultPage=1;
defaultPageSize=5;
}
}
3)配置:
首先將其注入為Bean:
@Bean
public PagePlugin pagePlugin(){
return new PagePlugin();
}
然後修改Mapper、Service、ServiceImpl,增加 getAllUsers(@Param Page page) 方法
然後寫SQL語句(UserMapper.xml):
<select id="getAllUsers" resultMap="BaseResultMap">
select * from user
</select>
然後在Controller新增相應方法:
@GetMapping("/getAllUsers")
public List<User> getAll(@RequestParam int page,@RequestParam int pageSize){
Page page=new Page();
page.setPage(page);
page.setPageSize(pageSize);
return mapper.getAllUser(page);
}
4)測試:(共6條測試資料)
GET /getAllUsers?page=1&pageSize=4
[
{
"id": 1,
"userName": "zhangsan",
"age": 1
},
{
"id": 2,
"userName": "lisi",
"age": 2
},
{
"id": 3,
"userName": "wangwu",
"age": 3
},
{
"id": 4,
"userName": "zhaoliu",
"age": 4
}
]
GET /getAllUsers?page=2&pageSize=4
[
{
"id": 5,
"userName": "zhouqi",
"age": 5
},
{
"id": 6,
"userName": "wangba",
"age": 6
}
]
GET /getAllUsers?page=3&pageSize=4
[
{
"id": 1,
"userName": "zhangsan",
"age": 1
},
{
"id": 2,
"userName": "lisi",
"age": 2
},
{
"id": 3,
"userName": "wangwu",
"age": 3
},
{
"id": 4,
"userName": "zhaoliu",
"age": 4
},
{
"id": 5,
"userName": "zhouqi",
"age": 5
},
{
"id": 6,
"userName": "wangba",
"age": 6
}
]