增強代碼
MyBatis在四大對象的建立過程中,都會有插件進行介入。插件可以利用動态代理機制一層層的包裝目标對象,而實作在目标對象執行目标方法之前進行攔截的效果。MyBatis 允許在已映射語句執行過程中的某一點進行攔截調用。
預設情況下,MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
/**
* 插件原理
* 在四大對象建立的時候
* 1、每個建立出來的對象不是直接傳回的,而是
* interceptorChain.pluginAll(parameterHandler);
* 2、擷取到所有的Interceptor(攔截器)(插件需要實作的接口);
* 調用interceptor.plugin(target);傳回target包裝後的對象
* 3、插件機制,我們可以使用插件為目标對象建立一個代理對象;AOP(面向切面)
* 我們的插件可以為四大對象建立出代理對象;
* 代理對象就可以攔截到四大對象的每一個執行;
*/
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
1、編寫插件
/**
* Description:
*
* @author guizy
* @date 2021/4/22 12:58
*/
@SuppressWarnings("all")
/*
完成插件簽名: 告訴MyBatis目前插件用來攔截哪個對象哪個方法
*/
@Intercepts(
{
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
}
)
public class MyPlugin implements Interceptor {
/**
* 攔截對象目标方法的執行
*
* @param invocation 攔截
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyPlugin.intercept:" + invocation.getMethod());
// 執行目标方法
Object proceed = invocation.proceed();
return proceed;
}
/**
* 包裝目标對象, 包裝: 為目标對象建立一個代理對象
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("MyPlugin.plugin mybatis将要包裝的對象" + target);
// 該方法就是讓目前的Interceptor攔截器來包裝我們的對象
Object wrap = Plugin.wrap(target, this);
// wrap就是target的動态代理對象
return wrap;
}
/**
* 将插件注冊時的property屬性設定進來
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的資訊: " + properties);
}
}
2、配置插件
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="com.sunny.mapper.MyPlugin">
<!-- 可選 -->
<property name="username" value="root"/>
<property name="password" value="8888"/>
</plugin>
</plugins>
3、測試插件
随便執行一個查詢方法, 因為攔截的是StatementHandler的parameterize的方法, 因為在mapper.getUser的時候, 底層會調用這個預編譯參數方法, 是以會攔截到該方法
@Test
public void testQueryOneUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 使用方式一: 來找到SQL并執行
//User user = sqlSession.selectOne("com.sunny.dao.UserMapper.getUser", 1L);
// 使用方式二: 來找到SQL并執行
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 實際上底層調用的還是sqlSession的方法,注意:sqlSession調用CRUD方法隻能傳遞一個參數
User user = mapper.getUser(12);
System.out.println(user);
sqlSession.close();
}
列印資訊
// 列印了配置的資訊
插件配置的資訊: {password=8888, username=root}
// 這裡在建立4大對象的時候, 都會調用PluginAll方法, 然後裡面會拿到所有實作Interceptor的
// 攔截器進行攔截包裝
MyPlugin.plugin mybatis将要包裝的對象org.apache.ibatis.executor.CachingExecutor@77ec78b9
MyPlugin.plugin mybatis将要包裝的對象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@42607a4f
MyPlugin.plugin mybatis将要包裝的對象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25bbf683
MyPlugin.plugin mybatis将要包裝的對象org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?;
// 隻會為StatementHandler對象建立代理對象, 攔截到parameterize方法
// 因為Executor, parameterHandler, ResultSetHandler的簽名不屬于StatementHandler
// 是以就沒有為它們建立代理對象
MyPlugin.intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
DEBUG [main] - ==> Parameters: 12(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 12, lisi, 44
DEBUG [main] - <== Total: 1
User{id=12, name='lisi', pwd='44'}
為StatementHandler對象建立代理對象
在執行到設定參數預編譯的方式時
4、多個插件同時攔截同一對象的方法的運作流程
5、開發插件
- 我們自己編寫的插件會為目标對象建立一個代理對象(通過動态代理), 當目标對象的目标方法要執行的時候, 都會來到代理對象的intercept方法, 可以在該方法中在執行目标方法的前後, 做增強/包裝
- 完成功能: 偷梁換柱, 傳入使用者id為1, 實際查出來的是id為2的員工
/**
* Description:
*
* @author guizy
* @date 2021/4/22 12:58
*/
@SuppressWarnings("all")
/*
完成插件簽名: 告訴MyBatis目前插件用來攔截哪個對象哪個方法
*/
@Intercepts(
{
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
}
)
public class MyPlugin implements Interceptor {
/**
* 攔截對象目标方法的執行
*
* @param invocation 攔截
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyPlugin.intercept:" + invocation.getMethod());
// 因為該插件攔截的是parameterize方法, 是以就動态改變一下sql運作的參數,來測試一下
// 偷梁換柱: 我要原來要查1号員工資訊, 實際從資料庫查2号員工
Object target = invocation.getTarget();
System.out.println("目前攔截到的對象: " + target);
// 通過原理, 我們隻要修改setParameters中的parameterObject的值即可
// 要拿到StatementHandler -> ParameterHandler -> parameterObject
MetaObject metaObject = SystemMetaObject.forObject(target); // 通過該方法拿到target的中繼資料
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql語句傳入的參數為:" + value);
// 修改sql語句傳進來的參數為2
metaObject.setValue("parameterHandler.parameterObject", 2);
// 執行目标方法
Object proceed = invocation.proceed();
return proceed;
}
/**
* 包裝目标對象, 包裝: 為目标對象建立一個代理對象
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("MyPlugin.plugin mybatis将要包裝的對象" + target);
// 該方法就是讓目前的Interceptor攔截器來包裝我們的對象
Object wrap = Plugin.wrap(target, this);
// wrap就是target的動态代理對象
return wrap;
}
/**
* 将插件注冊時的property屬性設定進來
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的資訊: " + properties);
}
}
插件配置的資訊: {password=8888, username=root}
MyPlugin.plugin mybatis将要包裝的對象[email protected]
MyPlugin.plugin mybatis将要包裝的對象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@42607a4f
MyPlugin.plugin mybatis将要包裝的對象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25bbf683
MyPlugin.plugin mybatis将要包裝的對象org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?;
MyPlugin.intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
目前攔截到的對象: org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
sql語句傳入的參數為:1
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, lisi, 44
DEBUG [main] - <== Total: 1
User{id=2, name='lisi', pwd='44'}
補充pageHelper分頁插件的使用
@Test
public void test01() throws IOException {
// 1、擷取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、擷取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Page<Object> page = PageHelper.startPage(5, 1);
List<Employee> emps = mapper.getEmps();
//傳入要連續顯示多少頁
PageInfo<Employee> info = new PageInfo<>(emps, 5);
for (Employee employee : emps) {
System.out.println(employee);
}
/*System.out.println("目前頁碼:"+page.getPageNum());
System.out.println("總記錄數:"+page.getTotal());
System.out.println("每頁的記錄數:"+page.getPageSize());
System.out.println("總頁碼:"+page.getPages());*/
///xxx
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());
// 就是頁面右下角可以連續顯示的頁碼數
// 點選第1頁, 1 2 3 4 5
// 點選第2頁, 1 2 3 4 5
// 點選第3頁, 1 2 3 4 5
// 點選第4頁, 2 3 4 5 6
// 點選第5頁, 3 4 5 6 7 這樣的顯示規則
System.out.println("連續顯示的頁碼:");
int[] nums = info.getNavigatepageNums();
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
//xxxx
} finally {
openSession.close();
}
}