天天看點

MyBatis插件開發 : 插件原理、插件開發流程

MyBatis插件開發 : 插件原理、插件開發流程
MyBatis插件開發 : 插件原理、插件開發流程
MyBatis插件開發 : 插件原理、插件開發流程
MyBatis插件開發 : 插件原理、插件開發流程

增強代碼

MyBatis插件開發 : 插件原理、插件開發流程

MyBatis在四大對象的建立過程中,都會有插件進行介入。插件可以利用動态代理機制一層層的包裝目标對象,而實作在目标對象執行目标方法之前進行攔截的效果。MyBatis 允許在已映射語句執行過程中的某一點進行攔截調用。

預設情況下,MyBatis 允許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
    MyBatis插件開發 : 插件原理、插件開發流程
/**
 * 插件原理
 * 在四大對象建立的時候
 * 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();
}
           
MyBatis插件開發 : 插件原理、插件開發流程

列印資訊

// 列印了配置的資訊
插件配置的資訊: {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'}
           
MyBatis插件開發 : 插件原理、插件開發流程
MyBatis插件開發 : 插件原理、插件開發流程
MyBatis插件開發 : 插件原理、插件開發流程

為StatementHandler對象建立代理對象

MyBatis插件開發 : 插件原理、插件開發流程
MyBatis插件開發 : 插件原理、插件開發流程
MyBatis插件開發 : 插件原理、插件開發流程

在執行到設定參數預編譯的方式時

MyBatis插件開發 : 插件原理、插件開發流程

4、多個插件同時攔截同一對象的方法的運作流程

MyBatis插件開發 : 插件原理、插件開發流程

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();
	}
}
           

繼續閱讀