1. @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
    2. public class PageHelper implements Interceptor {
    3. //sql工具类
    4. private SqlUtil sqlUtil;
    5. //属性参数信息
    6. private Properties properties;
    7. //配置对象方式
    8. private SqlUtilConfig sqlUtilConfig;
    9. //自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行
    10. private boolean autoDialect = true;
    11. //运行时自动获取dialect
    12. private boolean autoRuntimeDialect;
    13. //多数据源时,获取jdbcurl后是否关闭数据源
    14. private boolean closeConn = true;
    15. //缓存
    16. private Map<String, SqlUtil> urlSqlUtilMap = new ConcurrentHashMap<String, SqlUtil>();
    17. private ReentrantLock lock = new ReentrantLock();
    18. // ...
    19. }
    • SqlUtil:数据库类型专用sql工具类,一个数据库url对应一个SqlUtil实例,SqlUtil内有一个Parser对象,如果是mysql,它是MysqlParser,如果是oracle,它是OracleParser,这个Parser对象是SqlUtil不同实例的主要存在价值。执行count查询、设置Parser对象、执行分页查询、保存Page分页对象等功能,均由SqlUtil来完成。
    • SqlUtilConfig:Spring Boot中使用。
    • autoRuntimeDialect:多个数据源切换时,比如mysql和oracle数据源同时存在,就不能简单指定dialect,这个时候就需要运行时自动检测当前的dialect。
    • Map<String, SqlUtil> urlSqlUtilMap:它就用来缓存autoRuntimeDialect自动检测结果的,key是数据库的url,value是SqlUtil。由于这种自动检测只需要执行1次,所以做了缓存。
    • ReentrantLock lock:这个lock对象是比较有意思的现象,urlSqlUtilMap明明是一个同步ConcurrentHashMap,又搞了一个lock出来同步ConcurrentHashMap做什么呢?简单的说,ConcurrentHashMap可以保证put或者remove方法一定是线程安全的,但它不能保证put、get、remove的组合操作是线程安全的,为了保证组合操作也是线程安全的,所以使用了lock。

      1. // Mybatis拦截器方法
      2. public Object intercept(Invocation invocation) throws Throwable {
      3. if (autoRuntimeDialect) {
      4. // 多数据源
      5. SqlUtil sqlUtil = getSqlUtil(invocation);
      6. return sqlUtil.processPage(invocation);
      7. } else {
      8. // 单数据源
      9. if (autoDialect) {
      10. initSqlUtil(invocation);
      11. }
      12. // 指定了dialect
      13. return sqlUtil.processPage(invocation);
      14. }
      15. }
      16. public synchronized void initSqlUtil(Invocation invocation) {
      17. if (this.sqlUtil == null) {
      18. this.sqlUtil = getSqlUtil(invocation);
      19. if (!autoRuntimeDialect) {
      20. properties = null;
      21. sqlUtilConfig = null;
      22. }
      23. autoDialect = false;
      24. }
      25. }
      26. public void setProperties(Properties p) {
      27. checkVersion();
      28. //多数据源时,获取jdbcurl后是否关闭数据源
      29. String closeConn = p.getProperty("closeConn");
      30. //解决#97
      31. if(StringUtil.isNotEmpty(closeConn)){
      32. this.closeConn = Boolean.parseBoolean(closeConn);
      33. }
      34. //初始化SqlUtil的PARAMS
      35. SqlUtil.setParams(p.getProperty("params"));
      36. //数据库方言
      37. String dialect = p.getProperty("dialect");
      38. String runtimeDialect = p.getProperty("autoRuntimeDialect");
      39. if (StringUtil.isNotEmpty(runtimeDialect) && runtimeDialect.equalsIgnoreCase("TRUE")) {
      40. this.autoRuntimeDialect = true;
      41. this.autoDialect = false;
      42. this.properties = p;
      43. } else if (StringUtil.isEmpty(dialect)) {
      44. autoDialect = true;
      45. this.properties = p;
      46. } else {
      47. autoDialect = false;
      48. sqlUtil = new SqlUtil(dialect);
      49. sqlUtil.setProperties(p);
      50. }
      51. }
      52. public SqlUtil getSqlUtil(Invocation invocation) {
      53. MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
      54. //改为对dataSource做缓存
      55. DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();
      56. String url = getUrl(dataSource);
      57. if (urlSqlUtilMap.containsKey(url)) {
      58. return urlSqlUtilMap.get(url);
      59. }
      60. try {
      61. lock.lock();
      62. if (urlSqlUtilMap.containsKey(url)) {
      63. return urlSqlUtilMap.get(url);
      64. }
      65. if (StringUtil.isEmpty(url)) {
      66. throw new RuntimeException("无法自动获取jdbcUrl,请在分页插件中配置dialect参数!");
      67. }
      68. String dialect = Dialect.fromJdbcUrl(url);
      69. if (dialect == null) {
      70. throw new RuntimeException("无法自动获取数据库类型,请通过dialect参数指定!");
      71. }
      72. SqlUtil sqlUtil = new SqlUtil(dialect);
      73. if (this.properties != null) {
      74. sqlUtil.setProperties(properties);
      75. } else if (this.sqlUtilConfig != null) {
      76. sqlUtil.setSqlUtilConfig(this.sqlUtilConfig);
      77. }
      78. urlSqlUtilMap.put(url, sqlUtil);
      79. return sqlUtil;
      80. } finally {
      81. lock.unlock();
      82. }
      83. }
      1. public class PageStaticSqlSource extends PageSqlSource {
      2. private String sql;
      3. private List<ParameterMapping> parameterMappings;
      4. private Configuration configuration;
      5. private SqlSource original;
      6. @Override
      7. protected BoundSql getDefaultBoundSql(Object parameterObject) {
      8. String tempSql = sql;
      9. String orderBy = PageHelper.getOrderBy();
      10. if (orderBy != null) {
      11. tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
      12. }
      13. return new BoundSql(configuration, tempSql, parameterMappings, parameterObject);
      14. }
      15. @Override
      16. protected BoundSql getCountBoundSql(Object parameterObject) {
      17. // localParser指的就是MysqlParser或者OracleParser
      18. // localParser.get().getCountSql(sql),可以根据原始的sql,生成一个count查询的sql
      19. return new BoundSql(configuration, localParser.get().getCountSql(sql), parameterMappings, parameterObject);
      20. }
      21. @Override
      22. protected BoundSql getPageBoundSql(Object parameterObject) {
      23. String tempSql = sql;
      24. String orderBy = PageHelper.getOrderBy();
      25. if (orderBy != null) {
      26. tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
      27. }
      28. // getPageSql可以根据原始的sql,生成一个带有分页参数信息的sql,比如 limit ?, ?
      29. tempSql = localParser.get().getPageSql(tempSql);
      30. // 由于sql增加了分页参数的?号占位符,getPageParameterMapping()就是在原有List<ParameterMapping>基础上,增加两个分页参数对应的ParameterMapping对象,为分页参数赋值使用
      31. return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
      32. }
      33. }

      ```java

    public abstract class AbstractParser implements Parser, Constant { public String getCountSql(final String sql) { return sqlParser.getSmartCountSql(sql); } }

    public class MysqlParser extends AbstractParser { @Override public String getPageSql(String sql) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); sqlBuilder.append(“ limit ?,?”); return sqlBuilder.toString(); }

    1. @Override
    2. public Map<String, Object> setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, Page<?> page) {
    3. Map<String, Object> paramMap = super.setPageParameter(ms, parameterObject, boundSql, page);
    4. paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
    5. paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
    6. return paramMap;
    7. }

    }

    1. ```java
    2. // PageSqlSource装饰原SqlSource
    3. public void processMappedStatement(MappedStatement ms) throws Throwable {
    4. SqlSource sqlSource = ms.getSqlSource();
    5. MetaObject msObject = SystemMetaObject.forObject(ms);
    6. SqlSource pageSqlSource;
    7. if (sqlSource instanceof StaticSqlSource) {
    8. pageSqlSource = new PageStaticSqlSource((StaticSqlSource) sqlSource);
    9. } else if (sqlSource instanceof RawSqlSource) {
    10. pageSqlSource = new PageRawSqlSource((RawSqlSource) sqlSource);
    11. } else if (sqlSource instanceof ProviderSqlSource) {
    12. pageSqlSource = new PageProviderSqlSource((ProviderSqlSource) sqlSource);
    13. } else if (sqlSource instanceof DynamicSqlSource) {
    14. pageSqlSource = new PageDynamicSqlSource((DynamicSqlSource) sqlSource);
    15. } else {
    16. throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");
    17. }
    18. msObject.setValue("sqlSource", pageSqlSource);
    19. //由于count查询需要修改返回值,因此这里要创建一个Count查询的MS
    20. msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms));
    21. }
    1. // 执行分页查询
    2. private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
    3. //保存RowBounds状态
    4. RowBounds rowBounds = (RowBounds) args[2];
    5. //获取原始的ms
    6. MappedStatement ms = (MappedStatement) args[0];
    7. //判断并处理为PageSqlSource
    8. if (!isPageSqlSource(ms)) {
    9. processMappedStatement(ms);
    10. }
    11. //设置当前的parser,后面每次使用前都会set,ThreadLocal的值不会产生不良影响
    12. ((PageSqlSource)ms.getSqlSource()).setParser(parser);
    13. try {
    14. //忽略RowBounds-否则会进行Mybatis自带的内存分页
    15. args[2] = RowBounds.DEFAULT;
    16. //如果只进行排序 或 pageSizeZero的判断
    17. if (isQueryOnly(page)) {
    18. return doQueryOnly(page, invocation);
    19. }
    20. //简单的通过total的值来判断是否进行count查询
    21. if (page.isCount()) {
    22. page.setCountSignal(Boolean.TRUE);
    23. //替换MS
    24. args[0] = msCountMap.get(ms.getId());
    25. //查询总数
    26. Object result = invocation.proceed();
    27. //还原ms
    28. args[0] = ms;
    29. //设置总数
    30. page.setTotal((Integer) ((List) result).get(0));
    31. if (page.getTotal() == 0) {
    32. return page;
    33. }
    34. } else {
    35. page.setTotal(-1l);
    36. }
    37. //pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count
    38. if (page.getPageSize() > 0 &&
    39. ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)
    40. || rowBounds != RowBounds.DEFAULT)) {
    41. //将参数中的MappedStatement替换为新的qs
    42. page.setCountSignal(null);
    43. BoundSql boundSql = ms.getBoundSql(args[1]);
    44. args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
    45. page.setCountSignal(Boolean.FALSE);
    46. //执行分页查询
    47. Object result = invocation.proceed();
    48. //得到处理结果
    49. page.addAll((List) result);
    50. }
    51. } finally {
    52. ((PageSqlSource)ms.getSqlSource()).removeParser();
    53. }
    54. //返回结果
    55. return page;