MyBatis插件开发 : 插件原理、插件开发流程

灰太狼 2023-01-15 10:26 365阅读 0赞

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
增强代码
在这里插入图片描述

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) {

    1. for (Interceptor interceptor : interceptors) {
    2. target = interceptor.plugin(target);
    3. }
    4. return target;

    }


1、编写插件

  1. /** * Description: * * @author guizy * @date 2021/4/22 12:58 */
  2. @SuppressWarnings("all")
  3. /* 完成插件签名: 告诉MyBatis当前插件用来拦截哪个对象哪个方法 */
  4. @Intercepts(
  5. {
  6. @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
  7. }
  8. )
  9. public class MyPlugin implements Interceptor {
  10. /** * 拦截对象目标方法的执行 * * @param invocation 拦截 * @return * @throws Throwable */
  11. @Override
  12. public Object intercept(Invocation invocation) throws Throwable {
  13. System.out.println("MyPlugin.intercept:" + invocation.getMethod());
  14. // 执行目标方法
  15. Object proceed = invocation.proceed();
  16. return proceed;
  17. }
  18. /** * 包装目标对象, 包装: 为目标对象创建一个代理对象 * * @param target * @return */
  19. @Override
  20. public Object plugin(Object target) {
  21. System.out.println("MyPlugin.plugin mybatis将要包装的对象" + target);
  22. // 该方法就是让当前的Interceptor拦截器来包装我们的对象
  23. Object wrap = Plugin.wrap(target, this);
  24. // wrap就是target的动态代理对象
  25. return wrap;
  26. }
  27. /** * 将插件注册时的property属性设置进来 * * @param properties */
  28. @Override
  29. public void setProperties(Properties properties) {
  30. System.out.println("插件配置的信息: " + properties);
  31. }
  32. }

2、配置插件

  1. <!-- mybatis-config.xml -->
  2. <plugins>
  3. <plugin interceptor="com.sunny.mapper.MyPlugin">
  4. <!-- 可选 -->
  5. <property name="username" value="root"/>
  6. <property name="password" value="8888"/>
  7. </plugin>
  8. </plugins>

3、测试插件

随便执行一个查询方法, 因为拦截的是StatementHandler的parameterize的方法, 因为在mapper.getUser的时候, 底层会调用这个预编译参数方法, 所以会拦截到该方法

  1. @Test
  2. public void testQueryOneUser(){
  3. SqlSession sqlSession = MybatisUtils.getSqlSession();
  4. // 使用方式一: 来找到SQL并执行
  5. //User user = sqlSession.selectOne("com.sunny.dao.UserMapper.getUser", 1L);
  6. // 使用方式二: 来找到SQL并执行
  7. UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  8. // 实际上底层调用的还是sqlSession的方法,注意:sqlSession调用CRUD方法只能传递一个参数
  9. User user = mapper.getUser(12);
  10. System.out.println(user);
  11. sqlSession.close();
  12. }

在这里插入图片描述

打印信息

  1. // 打印了配置的信息
  2. 插件配置的信息: { password=8888, username=root}
  3. // 这里在创建4大对象的时候, 都会调用PluginAll方法, 然后里面会拿到所有实现Interceptor的
  4. // 拦截器进行拦截包装
  5. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@77ec78b9
  6. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@42607a4f
  7. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25bbf683
  8. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
  9. DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?;
  10. // 只会为StatementHandler对象创建代理对象, 拦截到parameterize方法
  11. // 因为Executor, parameterHandler, ResultSetHandler的签名不属于StatementHandler
  12. // 所以就没有为它们创建代理对象
  13. MyPlugin.intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
  14. DEBUG [main] - ==> Parameters: 12(Integer)
  15. TRACE [main] - <== Columns: id, name, pwd
  16. TRACE [main] - <== Row: 12, lisi, 44
  17. DEBUG [main] - <== Total: 1
  18. 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(

    1. {
    2. @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
    3. }

    )
    public class MyPlugin implements Interceptor {

  1. /** * 拦截对象目标方法的执行 * * @param invocation 拦截 * @return * @throws Throwable */
  2. @Override
  3. public Object intercept(Invocation invocation) throws Throwable {
  4. System.out.println("MyPlugin.intercept:" + invocation.getMethod());
  5. // 因为该插件拦截的是parameterize方法, 所以就动态改变一下sql运行的参数,来测试一下
  6. // 偷梁换柱: 我要原来要查1号员工信息, 实际从数据库查2号员工
  7. Object target = invocation.getTarget();
  8. System.out.println("当前拦截到的对象: " + target);
  9. // 通过原理, 我们只要修改setParameters中的parameterObject的值即可
  10. // 要拿到StatementHandler -> ParameterHandler -> parameterObject
  11. MetaObject metaObject = SystemMetaObject.forObject(target); // 通过该方法拿到target的元数据
  12. Object value = metaObject.getValue("parameterHandler.parameterObject");
  13. System.out.println("sql语句传入的参数为:" + value);
  14. // 修改sql语句传进来的参数为2
  15. metaObject.setValue("parameterHandler.parameterObject", 2);
  16. // 执行目标方法
  17. Object proceed = invocation.proceed();
  18. return proceed;
  19. }
  20. /** * 包装目标对象, 包装: 为目标对象创建一个代理对象 * * @param target * @return */
  21. @Override
  22. public Object plugin(Object target) {
  23. System.out.println("MyPlugin.plugin mybatis将要包装的对象" + target);
  24. // 该方法就是让当前的Interceptor拦截器来包装我们的对象
  25. Object wrap = Plugin.wrap(target, this);
  26. // wrap就是target的动态代理对象
  27. return wrap;
  28. }
  29. /** * 将插件注册时的property属性设置进来 * * @param properties */
  30. @Override
  31. public void setProperties(Properties properties) {
  32. System.out.println("插件配置的信息: " + properties);
  33. }
  34. }
  35. 插件配置的信息: {password=8888, username=root}
  36. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@77ec78b9
  37. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@42607a4f
  38. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25bbf683
  39. MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
  40. DEBUG [main] - ==> Preparing: SELECT * FROM user WHERE id = ?;
  41. MyPlugin.intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
  42. 当前拦截到的对象: org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
  43. sql语句传入的参数为:1
  44. DEBUG [main] - ==> Parameters: 2(Integer)
  45. TRACE [main] - <== Columns: id, name, pwd
  46. TRACE [main] - <== Row: 2, lisi, 44
  47. DEBUG [main] - <== Total: 1
  48. User{id=2, name='lisi', pwd='44'}
补充pageHelper分页插件的使用
  1. @Test
  2. public void test01() throws IOException {
  3. // 1、获取sqlSessionFactory对象
  4. SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
  5. // 2、获取sqlSession对象
  6. SqlSession openSession = sqlSessionFactory.openSession();
  7. try {
  8. EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
  9. Page<Object> page = PageHelper.startPage(5, 1);
  10. List<Employee> emps = mapper.getEmps();
  11. //传入要连续显示多少页
  12. PageInfo<Employee> info = new PageInfo<>(emps, 5);
  13. for (Employee employee : emps) {
  14. System.out.println(employee);
  15. }
  16. /*System.out.println("当前页码:"+page.getPageNum()); System.out.println("总记录数:"+page.getTotal()); System.out.println("每页的记录数:"+page.getPageSize()); System.out.println("总页码:"+page.getPages());*/
  17. ///xxx
  18. System.out.println("当前页码:"+info.getPageNum());
  19. System.out.println("总记录数:"+info.getTotal());
  20. System.out.println("每页的记录数:"+info.getPageSize());
  21. System.out.println("总页码:"+info.getPages());
  22. System.out.println("是否第一页:"+info.isIsFirstPage());
  23. // 就是页面右下角可以连续显示的页码数
  24. // 点击第1页, 1 2 3 4 5
  25. // 点击第2页, 1 2 3 4 5
  26. // 点击第3页, 1 2 3 4 5
  27. // 点击第4页, 2 3 4 5 6
  28. // 点击第5页, 3 4 5 6 7 这样的显示规则
  29. System.out.println("连续显示的页码:");
  30. int[] nums = info.getNavigatepageNums();
  31. for (int i = 0; i < nums.length; i++) {
  32. System.out.println(nums[i]);
  33. }
  34. //xxxx
  35. } finally {
  36. openSession.close();
  37. }
  38. }

发表评论

表情:
评论列表 (有 0 条评论,365人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Mybatis开发

    前面几篇文章介绍了Mybtis中四个重要的对象,其中提到它们都是在Configuration中被创建的,我们一起看一下创建四大对象的方法,代码如下所示: public