上一个教程我们讲解如何配置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可扩展更多自定义拦截器,注意顺序性即可,例如可以考虑查询性能监控拦截,异常捕获拦截等等,在本教程里我只实现分页拦截器作为一种参考示例