如题,扩展这个功能的需求是:遇到一些慢SQL有因为 select count(*) 导致CPU暴涨的,在索引已经加过的情况下,还不得劲。
话不多说,上码~
JF是支持接管分页查询的 config.getDialect().takeOverDbPaginate
我编写代码的时候,发现扩展DbPro更简单,就使用扩展 DbPro 了:
import com.jfinal.plugin.activerecord.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
/**
* 扩展 MyDbPro
* @author 杜福忠
*/
public class MyDbPro extends DbPro {
private static final ThreadLocal<Long> paginate_totalRow = new ThreadLocal();
public static Long setPaginateTotalRow(Long totalRow){
Long ret = getPaginateTotalRow();
paginate_totalRow.set(totalRow);
return ret;
}
public static void removePaginateTotalRow(){
paginate_totalRow.remove();
}
public static Long getPaginateTotalRow(){
return paginate_totalRow.get();
}
public MyDbPro() {
}
public MyDbPro(String configName) {
super(configName);
}
/**
* @param totalRow 总行数
*/
public static <T>T paginate(Long totalRow, Callable<T> callable){
Long former = setPaginateTotalRow(totalRow);
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
if (Objects.nonNull(former)){
setPaginateTotalRow(former);
} else {
removePaginateTotalRow();
}
}
}
protected Page<Record> doPaginateByFullSql(Config config, Connection conn, int pageNumber, int pageSize, Boolean isGroupBySql, String totalRowSql, StringBuilder findSql, Object... paras) throws SQLException {
if (pageNumber < 1 || pageSize < 1) {
throw new ActiveRecordException("pageNumber and pageSize must more than 0");
}
if (config.getDialect().isTakeOverDbPaginate()) {
return config.getDialect().takeOverDbPaginate(conn, pageNumber, pageSize, isGroupBySql, totalRowSql, findSql, paras);
}
long totalRow;
if (Objects.nonNull(paginate_totalRow.get())){
totalRow = paginate_totalRow.get();
} else {
List result = query(config, conn, totalRowSql, paras);
int size = result.size();
if (isGroupBySql == null) {
isGroupBySql = size > 1;
}
if (isGroupBySql) {
totalRow = size;
} else {
totalRow = (size > 0) ? ((Number)result.get(0)).longValue() : 0;
}
}
if (totalRow == 0) {
return new Page<Record>(new ArrayList<Record>(0), pageNumber, pageSize, 0, 0);
}
int totalPage = (int) (totalRow / pageSize);
if (totalRow % pageSize != 0) {
totalPage++;
}
if (pageNumber > totalPage) {
return new Page<Record>(new ArrayList<Record>(0), pageNumber, pageSize, totalPage, (int)totalRow);
}
// --------
String sql = config.getDialect().forPaginate(pageNumber, pageSize, findSql);
List<Record> list = find(config, conn, sql, paras);
return new Page<Record>(list, pageNumber, pageSize, totalPage, (int)totalRow);
}
}分页查询我一般都使用 Record 感觉更方便,
但是分享嘛 Model也一起写了吧,BaseModel代码和上面差不多:
import com.jfinal.plugin.activerecord.*;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public abstract class BaseModel<M extends Model<M>> extends Model<M> {
protected Page<M> doPaginateByFullSql(Config config, Connection conn, int pageNumber, int pageSize, Boolean isGroupBySql, String totalRowSql, StringBuilder findSql, Object... paras) throws Exception {
if (pageNumber < 1 || pageSize < 1) {
throw new ActiveRecordException("pageNumber and pageSize must more than 0");
}
if (config.getDialect().isTakeOverModelPaginate()) {
return config.getDialect().takeOverModelPaginate(conn, _getUsefulClass(), pageNumber, pageSize, isGroupBySql, totalRowSql, findSql, paras);
}
long totalRow;
Long paginateTotalRow = MyDbPro.getPaginateTotalRow();
if (Objects.nonNull(paginateTotalRow)){
totalRow = paginateTotalRow;
} else {
List result = CPI.query(config, conn, totalRowSql, paras);
int size = result.size();
if (isGroupBySql == null) {
isGroupBySql = size > 1;
}
if (isGroupBySql) {
totalRow = size;
} else {
totalRow = (size > 0) ? ((Number) result.get(0)).longValue() : 0;
}
}
if (totalRow == 0) {
return new Page<M>(new ArrayList<M>(0), pageNumber, pageSize, 0, 0); // totalRow = 0;
}
int totalPage = (int) (totalRow / pageSize);
if (totalRow % pageSize != 0) {
totalPage++;
}
if (pageNumber > totalPage) {
return new Page<M>(new ArrayList<M>(0), pageNumber, pageSize, totalPage, (int)totalRow);
}
// --------
String sql = config.getDialect().forPaginate(pageNumber, pageSize, findSql);
List<M> list = find(config, conn, sql, paras);
return new Page<M>(list, pageNumber, pageSize, totalPage, (int)totalRow);
}
}
再加个拦截器吧,很多时候一个Action就是一个分页查询,
@Before(PaginateInterceptor.class)
https://jfinal.com/doc/4-3
加个拦截器代码会简洁一些:
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
public class PaginateInterceptor implements Interceptor {
private String totalRowKey = "totalRow";
public void intercept(Invocation inv) {
Long totalRow = inv.getController().getLong(totalRowKey);
MyDbPro.paginate(totalRow, () -> {
inv.invoke();
return null;
});
}
}
OK,搞定,进入测试环节:
Record分页可以直接用,Model分页还得写个子类。。。
来吧:
public class TestModel extends BaseModel<TestModel> {
//测试使用, dao不建议写在这个位置
public static final TestModel dao = new TestModel().dao();
}执行类(直接用main方法进入了哈): PS:https://jfinal.com/doc/5-15
import com.jfinal.plugin.activerecord.*;
import com.jfinal.plugin.druid.DruidPlugin;
public class MyDbProTest {
public static void main(String[] args) {
DruidPlugin dp = new DruidPlugin(...);
ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
//注意设置 MyDbPro
arp.setDbProFactory(configName -> new MyDbPro(configName));
arp.addMapping("account", TestModel.class);
dp.start();
arp.start();
try {
//分页的总行, null 时会从数据库计算,可由前端自行缓存请求时传入参数,避免频繁查询
Long totalRow = 6L;
//页码
int pageNumber = 2;
//页面大小
int pageSize = 5;
String select = "SELECT * ";
String sqlExceptSelect = "FROM account";
//执行指定分页的总行的分页查询
//Record
Page<Record> page = MyDbPro.paginate(totalRow, () ->
//普通分页查询例子
Db.paginate(pageNumber, pageSize, select, sqlExceptSelect));
System.out.println(page);
System.out.println(page.getList());
System.out.println("----------");
//Model
Page<TestModel> paginate = MyDbPro.paginate(totalRow, () ->
//普通分页查询例子
TestModel.dao.paginate(pageNumber, pageSize, select, sqlExceptSelect));
System.out.println(paginate);
System.out.println(paginate.getList());
}finally {
dp.stop();
}
}
}
上面注意事项:
注意设置 MyDbPro:
arp.setDbProFactory(configName -> new MyDbPro(configName));
代码中使用:
MyDbPro.paginate(totalRow, XXXX
totalRow是分页的总行, null 时会从数据库计算,可由前端自行缓存,请求时传入参数,避免频繁select count(*)查询。
(后端缓存需要在业务代码中写缓存逻辑,取totalRow值,再放入即可)
按说应该新建一个PaginateKit类的,现在代码就合在MyDbPro一起吧,重在思路。
好了,有参考价值就点个赞呗~ 有特殊业务自行改造吧~