Spring Boot从入门到进阶教程系列 -- MyBatis分页拦截器实现(包含数据库方言)

上一个教程我们讲解如何配置MyBatis实例化,数据库类型选择器,数据库方言实现,本次我们将接着上个教程的成果来整合MyBatis常用的分页拦截器实现,如需看上篇教程的同学可点击链接

【Spring Boot从入门到进阶教程系列 -- MyBatis配置,数据库选择器和分页方言实现】 

下面我们直接开启代码之旅

步骤1. 编写我们的分页对象接口

public interface Pagination<T> extends Serializable {

    public boolean isSpilled();

    public void setSpilled(boolean spilled);

    public Integer getPageNo();

    public void setPageNo(Integer pageNo);

    public Integer getPageSize();

    public void setPageSize(Integer pageSize);

    public Integer getPageTotal();

    public void setPageTotal(Integer pageTotal);

    public Integer getPageNumber();

    public void setPageNumber(Integer pageNumber);

    public List<T> getData();

    public void setData(List<T> data);

}


步骤2. 编写分页对象接口实现类

public class SimplePagination<T> implements Pagination<T> {

    private boolean spilled = true; // 是否重置溢出的PageNo
    private Integer pageNo;
    private Integer pageSize;
    private Integer pageTotal;
    private Integer pageNumber;

    private List<T> data;

    public SimplePagination() {

    }

    public SimplePagination(Integer pageNo, Integer pageSize, boolean spilled) {
        this(pageNo, pageSize, 0, null, null);
        this.spilled = spilled;
    }

    public SimplePagination(Integer pageNo, Integer pageSize) {
        this(pageNo, pageSize, 0, null, null);
    }

    public SimplePagination(Integer pageNo, Integer pageSize, Integer pageTotal) {
        this(pageNo, pageSize, pageTotal, (pageTotal % pageSize == 0) ? (pageTotal / pageSize) : (pageTotal / pageSize + 1), null);
    }

    public SimplePagination(Integer pageNo, Integer pageSize, Integer pageTotal, List<T> data) {
        this(pageNo, pageSize, pageTotal, (pageTotal % pageSize == 0) ? (pageTotal / pageSize) : (pageTotal / pageSize + 1), data);
    }

    public SimplePagination(Integer pageNo, Integer pageSize, Integer pageTotal, Integer pageNumber, List<T> data) {
        this.pageNo = pageNo;
        this.pageSize = pageSize;
        this.pageTotal = pageTotal;
        this.pageNumber = pageNumber;
        this.data = data;
    }

    public boolean isSpilled() {
        return spilled;
    }

    public void setSpilled(boolean spilled) {
        this.spilled = spilled;
    }

    public Integer getPageNo() {
        return pageNo;
    }

    public void setPageNo(Integer pageNo) {
        this.pageNo = pageNo;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public Integer getPageTotal() {
        return pageTotal;
    }

    public void setPageTotal(Integer pageTotal) {
        this.pageTotal = pageTotal;
    }

    public Integer getPageNumber() {
        return pageNumber;
    }

    public void setPageNumber(Integer pageNumber) {
        this.pageNumber = pageNumber;
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

}


步骤3. 通过MyBatis拦截器机制编写我们的自动分页程序,我们先写一个拦截器抽象类,方便我们提取共性代码

public abstract class MybatisInterceptor implements Interceptor {

	protected Logger logger = LoggerFactory.getLogger(getClass());

	protected Properties properties;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		return invocation.proceed();
	}

	@Override
	public void setProperties(Properties properties) {
		this.properties = properties;
		afterSetProperties();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}
	
	protected void afterSetProperties() {
	}

}


步骤4. 继承拦截器抽象类,编写具体的分页业务流程(核心代码,请重点阅读),其中DBSelector是我们在上篇教程中定义的参数对象,如有疑惑请翻看上篇教程

/**
 * 分页拦截器
 *
 * @author shadow
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PageableInterceptor extends MybatisInterceptor {

    private DBSelector dbSelector;

    public PageableInterceptor(DBSelector dbSelector) {
        this.dbSelector = dbSelector;
    }

    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        BoundSql boundSql = statementHandler.getBoundSql();
        Object obj = boundSql.getParameterObject();
        if (obj instanceof Pagination) {
            Pagination<?> pagination = (Pagination<?>) obj;
            if (pagination != null && pagination.getPageNo() != null && pagination.getPageNo().intValue() > 0) {
                String sql = boundSql.getSql();
                String countSql = getCountSql(sql);
                Connection connection = (Connection) invocation.getArgs()[0];
                PreparedStatement countStatement = connection.prepareStatement(countSql);
                ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");
                parameterHandler.setParameters(countStatement);
                ResultSet rs = countStatement.executeQuery();
                if (rs.next()) {
                    pagination.setPageTotal(rs.getInt(1));
                }
                fillPagination(pagination);
                String pageSql = getPageSql(pagination, sql);
                metaObject.setValue("delegate.boundSql.sql", pageSql);
            }
        }
        return invocation.proceed();
    }

    private void fillPagination(Pagination<?> pagination) {
        Integer pageNo = pagination.getPageNo();
        Integer pageSize = pagination.getPageSize();
        Integer pageTotal = pagination.getPageTotal();
        Integer pageNumber = 0;
        if (pageSize == null || pageSize <= 0 || pageSize > 1000) {
            pageSize = 50;
        }
        if (pageNo == null || pageNo <= 0) {
            pageNo = 1;
        }
        if (pageTotal == null || pageTotal <= 0) {
            pageTotal = 0;
        }
        pagination.setPageNo(pageNo);
        pagination.setPageSize(pageSize);
        pagination.setPageTotal(pageTotal);
        pageNumber = (pageTotal % pageSize == 0) ? (pageTotal / pageSize) : (pageTotal / pageSize + 1);
        pagination.setPageNumber(pageNumber);
        if (pagination.isSpilled() && pageNo > pageNumber) {
            if (pageNumber <= 0) {
                pagination.setPageNo(1);
            } else {
                pagination.setPageNo(pageNumber);
            }
        }
    }

    private String getCountSql(String sql) {
        return "select count(1) from (" + sql + ") as x";
    }

    private String getPageSql(Pagination<?> page, String sql) {
        return dbSelector.getDialect().getLimitString(sql, page.getPageNo(), page.getPageSize());
    }

}


步骤5. 最后我们在DruidConfiguration.java初始化类中的sqlSessionFactory方法,增加MyBatis的plugins参数

    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource, @Qualifier("dbSelector") DBSelector dbSelector) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        Resource[] mappers = new PathMatchingResourcePatternResolver().getResources(dbConfig.getMgbXmlLocation());
        bean.setMapperLocations(mappers);
        bean.setPlugins(new Interceptor[]{new PageableInterceptor(dbSelector)});
        return bean.getObject();
    }

完整的DruidConfiguration.java示例代码,可参考教程【Spring Boot从入门到进阶教程系列 -- MyBatis配置,数据库选择器和分页方言实现】

@Configuration
public class DruidConfiguration {

    @Autowired(required = false)
    private DbConfig dbConfig;

    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean reg = new ServletRegistrationBean();
        reg.setServlet(new StatViewServlet());
        reg.addUrlMappings("/druid/*");
        reg.addInitParameter("loginUsername", dbConfig.getUsername());
        reg.addInitParameter("loginPassword", dbConfig.getPassword());
        reg.addInitParameter("logSlowSql", dbConfig.getLogSlowSql());
        return reg;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        return filterRegistrationBean;
    }

    @Primary
    @Bean(name = "dataSource")
    public DataSource druidDataSource() {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(dbConfig.getUrl());
        datasource.setUsername(dbConfig.getUsername());
        datasource.setPassword(dbConfig.getPassword());
        datasource.setDriverClassName(dbConfig.getDriverClassName());
        datasource.setInitialSize(dbConfig.getInitialSize());
        datasource.setMinIdle(dbConfig.getMinIdle());
        datasource.setMaxActive(dbConfig.getMaxActive());
        datasource.setMaxWait(dbConfig.getMaxWait());
        datasource.setTimeBetweenEvictionRunsMillis(dbConfig.getTimeBetweenEvictionRunsMillis());
        datasource.setMinEvictableIdleTimeMillis(dbConfig.getMinEvictableIdleTimeMillis());
        datasource.setValidationQuery(dbConfig.getValidationQuery());
        datasource.setTestWhileIdle(dbConfig.isTestWhileIdle());
        datasource.setTestOnBorrow(dbConfig.isTestOnBorrow());
        datasource.setTestOnReturn(dbConfig.isTestOnReturn());
        try {
            datasource.setFilters(dbConfig.getFilters());
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return datasource;
    }

    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource, @Qualifier("dbSelector") DBSelector dbSelector) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        Resource[] mappers = new PathMatchingResourcePatternResolver().getResources(dbConfig.getMgbXmlLocation());
        bean.setMapperLocations(mappers);
        bean.setPlugins(new Interceptor[]{new PageableInterceptor(dbSelector)});
        return bean.getObject();
    }

    @Bean(name = "dbSelector")
    @Primary
    public DBSelector dbSelector() {
        return new SimpleDBSelector(dbConfig.getDatabase());
    }

    @Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}


总结,我们配置该MyBatis.plugins可扩展更多自定义拦截器,注意顺序性即可,例如可以考虑查询性能监控拦截,异常捕获拦截等等,在本教程里我只实现分页拦截器作为一种参考示例

猜你喜欢

转载自blog.csdn.net/shadowsick/article/details/80753774