searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

浅谈Mybatis的插件机制

2023-04-26 06:01:10
12
0

浅谈Mybatis的插件机制

关于MyBatis

在日常的开发中,操作数据库是非常频繁的操作,而一个优秀的ORM框架的作用是能帮助开发人员简化数据库操作,提高开发效率的。

MyBatis 就是一款优秀且高效的orm框架,他的前身是iBATIS。iBATIS最初是由Clinton Begin在2002年创建的,在2005年,iBATIS成为Apache基金会的顶级项目,并改名为MyBatis。MyBatis在iBATIS的基础上进行了许多改进和优化,例如支持更多的数据源、动态SQL、缓存等功能。MyBatis的目标是提供一种简单、灵活和高效的持久化解决方案,以帮助开发人员更加轻松地进行数据库编程。

关于MyBatis的常规使用和操作此处就不展开了,有兴趣的读者可以自行了解。下文将重点介绍Mybatis的插件机制

MyBatis插件的作用

MyBatis的插件给我们提供了一种扩展机制,使我们能在MyBatis运行过程中拦截某些操作,然后进行自定义的处理。例如拦截SQL语句的执行、参数的设置、结果集的处理等等,MyBatis的插件可使用于以下场景:

1 扩展功能

MyBatis插件可以对MyBatis进行自定义的扩展,例如添加一些自定义的功能或者对MyBatis的功能进行优化。

2 监控性能

MyBatis插件可以拦截SQL语句的执行,统计SQL语句的执行时间等信息,从而可以对系统的性能进行监控和优化。

3 安全检查

MyBatis插件可以拦截SQL语句的执行,对SQL语句进行安全性检查,从而可以防止SQL注入等安全问题。

4 数据库路由

MyBatis插件可以根据不同的条件,将SQL语句路由到不同的数据库中执行,从而可以实现多数据源的支持。

MyBatis插件的使用

我们在代码里要使用MyBatis的插件,只需要实现接口:org.apache.ibatis.plugin.Interceptor 即可,此接口有三个方法,相关说明如下:

public interface Interceptor {
    
        /**
         *
         * 实现插件功能的方法,
         * 在真正的方法sql逻辑执行前、执行后或执行异常时执行一些自定义的逻辑。
         * 入参invocation封装了当前方法的信息,包括方法名、参数、目标对象等。
         * 
         */    
         Object intercept(Invocation invocation) throws Throwable;

        /**
         *
         * 该方法用于在插件初始化时设置插件的属性。
         * MyBatis会在加载插件时调用该方法,并将插件的属性值通过Properties对象传递给插件。
         *
         */
         default void setProperties(Properties properties) {
            // NOP
         }     

        /**
         *
         * 返回代理原有对象的代理类,每个被拦截的对象都必须有代理类
         * 默认使用Plugin.wrap生成代理类,有特殊需要,也可以自行生成
         *
         */
         default Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
        
    }

在实现了自己的Interceptor类后,我们还需要声明这个Interceptor需要拦截哪些方法,示例如下

 @Intercepts({
        @Signature(type = Executor.class, method = "query",
             args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", 
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    })
    public class MyInterceptor  implements Interceptor{
        ....
    }
        

上述的MyInterceptor插件的声明,就是代表拦截了所有的查询语句,亦即所有查询语句都得进入MyInterceptor做处理。 @Signature注解的type代表的是类,method是此类的方法名,args即是方法参数,三个组合起来就能唯一标识一个具体的接口方法了。

@Intercepts 只能拦截mybatis自身的接口方法,一般用于拦截Executor接口,但是如有需要,也可用于拦截以下的接口:

  • Executor接口
  • StatementHandler接口
  • ParameterHandler接口
  • ResultSetHandler接口

最后,我们需要把自己的插件注册到mybatis里,并指定执行的顺序

mybatis的插件是严格按照声明的顺序执行的,先声明的插件先执行。

如果mybatis是用xml的形式配置的话,可以通过plugins标签声明自己的插件及其顺序

<plugins>
    <plugin interceptor="example.FirstPlugin"/>
    <plugin interceptor="example.SecondPlugin"/>
</plugins>

上述声明了两个插件,且执行顺序是firstPlugin先执行,secondPlugin后执行。

如果mybatis是和spring整合的话,则还可以通过以下方法声明插件

@Configuration
public class LoadPluginsConf{

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    @PostConstruct
    public void addPlugins() {
        Interceptor interceptor1 = new FirstPlugin();
        Interceptor interceptor2 = new SecondPlugin();
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor1);
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor2);
        }
    }
    
}

上述方法也实现了插件的注册,同时,执行顺序也是firstPlugin先执行,secondPlugin后执行。

MyBatis插件的实现原理

在我们自定义的插件成功工作后,我们还需要进一步去了解插件是怎样工作的,Mybatis在这个过程中帮我们做了什么事情。

拦截器链:InterceptorChain

在我们成功注册一个插件时,如上文的声明示例,其本质是调用了org.apache.ibatis.session.Configuration#addInterceptor方法,而Configuration#addInterceptor做的事情,就是把此插件加入到InterceptorChain里,此InterceptorChain是org.apache.ibatis.session.Configuration的内部对象。

拦截器链InterceptorChain的实现如下:

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

如上代码,addInterceptor 和 getInterceptors 很好理解,一个是把插件加入到拦截器链,另一个是获取当前拦截器链中的所有插件。

而pluginAll方法则是生成最终代理类的关键,如上文,每个插件都有自己的plugin方法用于生成目标对象的代理类,而拦截器链的pluginAll 则是为目标对象生成当前所有生效插件的统一代理类,他的实现方法是在for循环里,根据声明顺序,逐个调用插件的plugin生成的,亦即插件A为目标对象生成了代理对象A1,然后把A1作为新的目标对象,生成插件B的代理对象B1,以此类推,直到所有插件都生成完成为止。

有了上面的介绍,后面的处理就很清晰了,mybatis几个重要的内部对象:Executor、ResultSetHandler、StatementHandler、ParameterHandler 都是通过org.apache.ibatis.session.Configuration的以下方法生成的:

  • newExecutor
  • newResultSetHandler
  • newStatementHandler
  • newParameterHandler

而这些方法的实现都会调用InterceptorChain.pluginAll生成代理对象,从而实现插件功能,这也就呼应了上文的为啥@Intercepts只能拦截这几个类的实现方法的原因了。

代理类的生成原理

上文说到,插件的代理对象默认是通过Plugin.wrap方法生成的,现在我们来探讨下这个方法的原理: 我们先看下Plugin.wrap的源码:

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

上面的代码执行逻辑可以表示为:

1 获取此插件对象的signatureMap,亦即我们声明插件时的@Intercepts及@Signature注解

2 获取当前需要代理的类是否符合插件的注解

3 如果符合的话,interfaces.length > 0 ,使用jdk 动态代理生成代理类

4 如果不符合的话,interfaces.length = 0,不生成代理类,直接返回原对象

说明:Executor、ResultSetHandler、StatementHandler、ParameterHandler 这几个都是接口类,所以能使用动态代理生成代理对象,JDK的动态代理此处就不展开了,有兴趣的读者可自行了解

简单的示例

例1:统计查询SQL耗时

@Intercepts({
        @Signature(type = Executor.class, method = "query",
             args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", 
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    })
    public CostTimePlugin implements Interceptor {
    
        public Object intercept(Invocation invocation) throws Throwable{
            Object target = invocation.getTarget();
            if (target instanceof RoutingStatementHandler) {
                RoutingStatementHandler statementHandler = (RoutingStatementHandler) target;
                StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(statementHandler, "delegate");
                BoundSql boundSql = delegate.getBoundSql();
                String sql = getSql(boundSql);
                long beginTime = System.currentTimestamp();
                Object targetResult  = invocation.proceed();
                log.info("sql={}的执行时间:{}ms", sql, System.currentTimestamp() - beginTime );
                return targetResult;
            }else{
                return invocation.proceed();
            }
            
        }
        
        private String getSql(BoundSql boundSql) { 
            String sql = boundSql.getSql(); 
            //此处可以对sql进行一些美化工作
            return sql
        }
        
    }

例2: 简单的分页查询

@Intercepts({
        @Signature(type = Executor.class, method = "query",
             args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", 
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    })
    public class PageInterceptor implements Interceptor {


        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Object[] args = invocation.getArgs();
            MappedStatement mappedStatement = (MappedStatement) args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];

            if (mappedStatement.getSqlCommandType() != SqlCommandType.SELECT) {
                return invocation.proceed();
            }


            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String sql = boundSql.getSql().trim();
            //拼接查询数量的sql,实际可能需要考虑不同数据库的写法
            String countSql = "select count(*) from (" + sql + ") t";
            Connection connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
            PreparedStatement countStmt = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
            setParameters(countStmt, mappedStatement, countBS, parameter);
            ResultSet rs = countStmt.executeQuery();
            int totalCount = 0;
            if (rs.next()) {
                totalCount = rs.getInt(1);
            }
            rs.close();
            countStmt.close();
            connection.close();

            this.totalCount = totalCount;

            //page 和 pageSize在实际使用时动态获取,此处只是演示代码,所以固定为1和10
            int page = 1;
            int pageSize = 10;
            
            //拼接分页查询sql,实际可能需要考虑不同数据库的写法
            String pageSql = sql + " limit " + (page-1)*pageSize + " " + pageSize;
            args[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
            args[0] = copyFromMappedStatement(mappedStatement, new BoundSql(mappedStatement.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter));
            return invocation.proceed();
        }

        private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameter) throws SQLException {
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameter, boundSql);
            parameterHandler.setParameters(ps);
        }

        private MappedStatement copyFromMappedStatement(MappedStatement ms, BoundSql boundSql) {
            MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_cnt", ms.getSqlSource(), ms.getSqlCommandType());
            builder.resource(ms.getResource());
            builder.fetchSize(ms.getFetchSize());
            builder.statementType(ms.getStatementType());
            builder.keyGenerator(ms.getKeyGenerator());
            if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
                StringBuilder keyProperties = new StringBuilder();
                for (String keyProperty : ms.getKeyProperties()) {
                    keyProperties.append(keyProperty).append(",");
                }
                keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
                builder.keyProperty(keyProperties.toString());
            }
            builder.timeout(ms.getTimeout());
            builder.parameterMap(ms.getParameterMap());
            builder.resultMaps(ms.getResultMaps());
            builder.cache(ms.getCache());
            builder.flushCacheRequired(ms.isFlushCacheRequired());
            builder.useCache(ms.isUseCache());
            return builder.build();
        }

}
0条评论
作者已关闭评论
梁****健
11文章数
0粉丝数
梁****健
11 文章 | 0 粉丝
原创

浅谈Mybatis的插件机制

2023-04-26 06:01:10
12
0

浅谈Mybatis的插件机制

关于MyBatis

在日常的开发中,操作数据库是非常频繁的操作,而一个优秀的ORM框架的作用是能帮助开发人员简化数据库操作,提高开发效率的。

MyBatis 就是一款优秀且高效的orm框架,他的前身是iBATIS。iBATIS最初是由Clinton Begin在2002年创建的,在2005年,iBATIS成为Apache基金会的顶级项目,并改名为MyBatis。MyBatis在iBATIS的基础上进行了许多改进和优化,例如支持更多的数据源、动态SQL、缓存等功能。MyBatis的目标是提供一种简单、灵活和高效的持久化解决方案,以帮助开发人员更加轻松地进行数据库编程。

关于MyBatis的常规使用和操作此处就不展开了,有兴趣的读者可以自行了解。下文将重点介绍Mybatis的插件机制

MyBatis插件的作用

MyBatis的插件给我们提供了一种扩展机制,使我们能在MyBatis运行过程中拦截某些操作,然后进行自定义的处理。例如拦截SQL语句的执行、参数的设置、结果集的处理等等,MyBatis的插件可使用于以下场景:

1 扩展功能

MyBatis插件可以对MyBatis进行自定义的扩展,例如添加一些自定义的功能或者对MyBatis的功能进行优化。

2 监控性能

MyBatis插件可以拦截SQL语句的执行,统计SQL语句的执行时间等信息,从而可以对系统的性能进行监控和优化。

3 安全检查

MyBatis插件可以拦截SQL语句的执行,对SQL语句进行安全性检查,从而可以防止SQL注入等安全问题。

4 数据库路由

MyBatis插件可以根据不同的条件,将SQL语句路由到不同的数据库中执行,从而可以实现多数据源的支持。

MyBatis插件的使用

我们在代码里要使用MyBatis的插件,只需要实现接口:org.apache.ibatis.plugin.Interceptor 即可,此接口有三个方法,相关说明如下:

public interface Interceptor {
    
        /**
         *
         * 实现插件功能的方法,
         * 在真正的方法sql逻辑执行前、执行后或执行异常时执行一些自定义的逻辑。
         * 入参invocation封装了当前方法的信息,包括方法名、参数、目标对象等。
         * 
         */    
         Object intercept(Invocation invocation) throws Throwable;

        /**
         *
         * 该方法用于在插件初始化时设置插件的属性。
         * MyBatis会在加载插件时调用该方法,并将插件的属性值通过Properties对象传递给插件。
         *
         */
         default void setProperties(Properties properties) {
            // NOP
         }     

        /**
         *
         * 返回代理原有对象的代理类,每个被拦截的对象都必须有代理类
         * 默认使用Plugin.wrap生成代理类,有特殊需要,也可以自行生成
         *
         */
         default Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
        
    }

在实现了自己的Interceptor类后,我们还需要声明这个Interceptor需要拦截哪些方法,示例如下

 @Intercepts({
        @Signature(type = Executor.class, method = "query",
             args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", 
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    })
    public class MyInterceptor  implements Interceptor{
        ....
    }
        

上述的MyInterceptor插件的声明,就是代表拦截了所有的查询语句,亦即所有查询语句都得进入MyInterceptor做处理。 @Signature注解的type代表的是类,method是此类的方法名,args即是方法参数,三个组合起来就能唯一标识一个具体的接口方法了。

@Intercepts 只能拦截mybatis自身的接口方法,一般用于拦截Executor接口,但是如有需要,也可用于拦截以下的接口:

  • Executor接口
  • StatementHandler接口
  • ParameterHandler接口
  • ResultSetHandler接口

最后,我们需要把自己的插件注册到mybatis里,并指定执行的顺序

mybatis的插件是严格按照声明的顺序执行的,先声明的插件先执行。

如果mybatis是用xml的形式配置的话,可以通过plugins标签声明自己的插件及其顺序

<plugins>
    <plugin interceptor="example.FirstPlugin"/>
    <plugin interceptor="example.SecondPlugin"/>
</plugins>

上述声明了两个插件,且执行顺序是firstPlugin先执行,secondPlugin后执行。

如果mybatis是和spring整合的话,则还可以通过以下方法声明插件

@Configuration
public class LoadPluginsConf{

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    @PostConstruct
    public void addPlugins() {
        Interceptor interceptor1 = new FirstPlugin();
        Interceptor interceptor2 = new SecondPlugin();
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor1);
            sqlSessionFactory.getConfiguration().addInterceptor(interceptor2);
        }
    }
    
}

上述方法也实现了插件的注册,同时,执行顺序也是firstPlugin先执行,secondPlugin后执行。

MyBatis插件的实现原理

在我们自定义的插件成功工作后,我们还需要进一步去了解插件是怎样工作的,Mybatis在这个过程中帮我们做了什么事情。

拦截器链:InterceptorChain

在我们成功注册一个插件时,如上文的声明示例,其本质是调用了org.apache.ibatis.session.Configuration#addInterceptor方法,而Configuration#addInterceptor做的事情,就是把此插件加入到InterceptorChain里,此InterceptorChain是org.apache.ibatis.session.Configuration的内部对象。

拦截器链InterceptorChain的实现如下:

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

如上代码,addInterceptor 和 getInterceptors 很好理解,一个是把插件加入到拦截器链,另一个是获取当前拦截器链中的所有插件。

而pluginAll方法则是生成最终代理类的关键,如上文,每个插件都有自己的plugin方法用于生成目标对象的代理类,而拦截器链的pluginAll 则是为目标对象生成当前所有生效插件的统一代理类,他的实现方法是在for循环里,根据声明顺序,逐个调用插件的plugin生成的,亦即插件A为目标对象生成了代理对象A1,然后把A1作为新的目标对象,生成插件B的代理对象B1,以此类推,直到所有插件都生成完成为止。

有了上面的介绍,后面的处理就很清晰了,mybatis几个重要的内部对象:Executor、ResultSetHandler、StatementHandler、ParameterHandler 都是通过org.apache.ibatis.session.Configuration的以下方法生成的:

  • newExecutor
  • newResultSetHandler
  • newStatementHandler
  • newParameterHandler

而这些方法的实现都会调用InterceptorChain.pluginAll生成代理对象,从而实现插件功能,这也就呼应了上文的为啥@Intercepts只能拦截这几个类的实现方法的原因了。

代理类的生成原理

上文说到,插件的代理对象默认是通过Plugin.wrap方法生成的,现在我们来探讨下这个方法的原理: 我们先看下Plugin.wrap的源码:

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

上面的代码执行逻辑可以表示为:

1 获取此插件对象的signatureMap,亦即我们声明插件时的@Intercepts及@Signature注解

2 获取当前需要代理的类是否符合插件的注解

3 如果符合的话,interfaces.length > 0 ,使用jdk 动态代理生成代理类

4 如果不符合的话,interfaces.length = 0,不生成代理类,直接返回原对象

说明:Executor、ResultSetHandler、StatementHandler、ParameterHandler 这几个都是接口类,所以能使用动态代理生成代理对象,JDK的动态代理此处就不展开了,有兴趣的读者可自行了解

简单的示例

例1:统计查询SQL耗时

@Intercepts({
        @Signature(type = Executor.class, method = "query",
             args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", 
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    })
    public CostTimePlugin implements Interceptor {
    
        public Object intercept(Invocation invocation) throws Throwable{
            Object target = invocation.getTarget();
            if (target instanceof RoutingStatementHandler) {
                RoutingStatementHandler statementHandler = (RoutingStatementHandler) target;
                StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(statementHandler, "delegate");
                BoundSql boundSql = delegate.getBoundSql();
                String sql = getSql(boundSql);
                long beginTime = System.currentTimestamp();
                Object targetResult  = invocation.proceed();
                log.info("sql={}的执行时间:{}ms", sql, System.currentTimestamp() - beginTime );
                return targetResult;
            }else{
                return invocation.proceed();
            }
            
        }
        
        private String getSql(BoundSql boundSql) { 
            String sql = boundSql.getSql(); 
            //此处可以对sql进行一些美化工作
            return sql
        }
        
    }

例2: 简单的分页查询

@Intercepts({
        @Signature(type = Executor.class, method = "query",
             args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", 
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    })
    public class PageInterceptor implements Interceptor {


        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Object[] args = invocation.getArgs();
            MappedStatement mappedStatement = (MappedStatement) args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];

            if (mappedStatement.getSqlCommandType() != SqlCommandType.SELECT) {
                return invocation.proceed();
            }


            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String sql = boundSql.getSql().trim();
            //拼接查询数量的sql,实际可能需要考虑不同数据库的写法
            String countSql = "select count(*) from (" + sql + ") t";
            Connection connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
            PreparedStatement countStmt = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
            setParameters(countStmt, mappedStatement, countBS, parameter);
            ResultSet rs = countStmt.executeQuery();
            int totalCount = 0;
            if (rs.next()) {
                totalCount = rs.getInt(1);
            }
            rs.close();
            countStmt.close();
            connection.close();

            this.totalCount = totalCount;

            //page 和 pageSize在实际使用时动态获取,此处只是演示代码,所以固定为1和10
            int page = 1;
            int pageSize = 10;
            
            //拼接分页查询sql,实际可能需要考虑不同数据库的写法
            String pageSql = sql + " limit " + (page-1)*pageSize + " " + pageSize;
            args[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
            args[0] = copyFromMappedStatement(mappedStatement, new BoundSql(mappedStatement.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter));
            return invocation.proceed();
        }

        private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameter) throws SQLException {
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameter, boundSql);
            parameterHandler.setParameters(ps);
        }

        private MappedStatement copyFromMappedStatement(MappedStatement ms, BoundSql boundSql) {
            MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_cnt", ms.getSqlSource(), ms.getSqlCommandType());
            builder.resource(ms.getResource());
            builder.fetchSize(ms.getFetchSize());
            builder.statementType(ms.getStatementType());
            builder.keyGenerator(ms.getKeyGenerator());
            if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
                StringBuilder keyProperties = new StringBuilder();
                for (String keyProperty : ms.getKeyProperties()) {
                    keyProperties.append(keyProperty).append(",");
                }
                keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
                builder.keyProperty(keyProperties.toString());
            }
            builder.timeout(ms.getTimeout());
            builder.parameterMap(ms.getParameterMap());
            builder.resultMaps(ms.getResultMaps());
            builder.cache(ms.getCache());
            builder.flushCacheRequired(ms.isFlushCacheRequired());
            builder.useCache(ms.isUseCache());
            return builder.build();
        }

}
文章来自个人专栏
微服务相关
9 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0