Java 之 sql 分页查询

一、前言

  • 三种分页的实现方式:
    1. 每次取查询结果的所有数据,然后根据页面显示指定的记录
    2. 根据页面只取一页的数据,然后显示这一页,这里要构造 sql 语句
    3. 取一定页数的数据,就是前两种的折中
  • 实现分页的步骤:
    1. 创建一个用于封装分页相关属性及操作的类
    2. 从页面增加分页导航条的功能
    3. 实现分页查询功能,从页面请求->Servlet->DAO的实现

二、自定义实现

1、借助数组分页(先查询所有数据,再分页)

  • 原理:先获取数据库中所有满足条件的记录,保存在应用的临时数组中,再通过 List 的 subList 方法,获取到满足条件的所有记录

(1) 创建StudentMapper.xml文件,编写查询的sql语句:

<select id="queryStudents" resultMap="studentMapper">
	select * from student
select>

(2) 定义 IStuService 接口,并且定义分页方法:

List<Student> queryStudents(int currentPage, int pageSize);

(3) 实现类 StuServiceIml 对获取到的数组通过 currPage 和 pageSize 进行分页:

public List<Student> queryStudents(int currentPage, int pageSize) {
     
	List<Student> studentList = studentMapper.queryStudents;
	int firstIndex = (currentPage - 1) * pageSize;
	int endIndex =  currentPage * pageSize;
	return studentList.subList(firstIndex, endIndex);
}

2、借助 Sql 语句分页(只查询一页的数据)

  • 原理:在 sql 语句后面添加 limit 分页语句

(1) 创建StudentMapper.xml文件,编写查询的sql语句:

<select id="queryStudentsBySql" parameterType="map" resultMap="studentMapper">
	select * from student limit #{currentIndex},#{pageSize}
select>

(2) 定义 IStuService 接口,并且定义分页方法:

List<Student> queryStudentsBySql(int currentPage, int pageSize);

(3) 实现类 StuServiceIml 对获取到的数组通过 currPage 和 pageSize 进行分页:

public List<Student> queryStudentsBySql(int currentPage, int pageSize) {
     
	Map<String, Object> data = new HashMap();
	data.put("currentIndex", (currentPage - 1) * pageSize);
	data.put("pageSize", pageSize);
	return studentMapper.queryStudentsBySql(data);
}

3、拦截器分页(取一定页数的数据)

推荐阅读:springboot+mybatis自定义拦截器实现分页查询

  • 自定义拦截器实现拦截所有以 ByPage 结尾的查询语句,并利用获取到的分页相关参数统一在 sql 语句后面加上 limit 分页的相关语句,不再需要在每个语句中单独去配置分页相关的参数

(1) 自定义分页参数 bean

@Data
public class PageBean<T> {
     
    private int currentPage = 1; //当前页
    private int pageSize = 10; //每页条数
    private int pageCount; //总页数
    private int totalCount; //总条数
    private List<T> data;
	
	public void setTotalCount(int totalCount) {
     
        this.totalCount = totalCount;
        this.calPageCount();
    }

    public void calPageCount() {
     
        this.pageCount = (int) Math.ceil((this.totalCount * 1.0) / this.pageSize);
    }
}

(2) mybatis 配置文件

<plugins>
   <plugin interceptor="com.xxx.PageInterceptor">plugin>
plugins>

(3) 实现 Interceptor 接口的 intercept 和 plugin方法

@Component
@Intercepts({
     @Signature(type = StatementHandler.class, method = "prepare", args = {
      Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {
     
	@Override
    public Object intercept(Invocation invocation) throws Throwable {
     
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); // 获取拦截的对象
        BoundSql boundSql = statementHandler.getBoundSql(); // 待执行的sql对象
        // 判断是否查询语句
        if (!"".equals(boundSql.getSql()) && boundSql.getSql().toUpperCase().trim().startsWith("SELECT")) {
     
            Object params = boundSql.getParameterObject(); // 获取参数
            // 如果查询方法中传入的参数是 PageBean 的一个实例,则为分页查询
            if (params instanceof PageBean) {
     
                PageBean pageBean = (PageBean) params;
                Connection connection = (Connection) invocation.getArgs()[0];
                MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
				// 查询总条数
                int count = count(connection, (ParameterHandler) metaObject.getValue("delegate.parameterHandler"), boundSql);
                pageBean.setTotalCount(count);
                // 拼接分页语句
                String pageSql = boundSql.getSql() + " limit " + 
                					((pageBean.getCurrentPage() - 1) * pageBean.getPageSize()) + 
                					", " + pageBean.getPageSize();
                metaObject.setValue("delegate.boundSql.sql", pageSql);
                return invocation.proceed();
            }
        }
        return invocation.proceed();
    }
    
    @Override
    public Object plugin(Object o) {
     
    	// 注意:此处判断使用的是RoutingStatementHandler,而不是StatementHandler
        if (o instanceof RoutingStatementHandler) {
     
            return Plugin.wrap(o, this);
        } else {
     
            return o;
        }
    }

    public int count(Connection connection, ParameterHandler parameterHandler, BoundSql boundSql) {
     
        String countSql = "select count(0) from (" + boundSql.getSql() + ") as total";
        PreparedStatement countStmt = null;
        ResultSet rs = null;
        try {
     
            countStmt = connection.prepareStatement(countSql);
            parameterHandler.setParameters(countStmt);
            rs = countStmt.executeQuery();
            if (rs.next()) {
     
                return rs.getInt(1);
            }
        } catch (SQLException e) {
     
            e.printStackTrace();
        } finally {
     
            try {
     
                if (null != countStmt) {
     
                    countStmt.close();
                }
                if (null != rs) {
     
                    rs.close();
                }
            } catch (SQLException e) {
     
                e.printStackTrace();
            }
        }
        return 0;
    }
}

三、tk.mybatis.mapper 与 pagehelper

1、maven 依赖


<dependency>
	 <groupId>mysqlgroupId>
     <artifactId>mysql-connector-javaartifactId>
     <version>5.1.41version>
dependency>

<dependency>
	<groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>1.3.1version>
dependency>

<dependency>
	<groupId>tk.mybatisgroupId>
    <artifactId>mapper-spring-boot-starterartifactId>
    <version>1.2.4version>
dependency>

<dependency>
	<groupId>com.github.pagehelpergroupId>
    <artifactId>pagehelper-spring-boot-starterartifactId>
    <version>1.2.3version>
dependency>

<dependency>
	<groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-configuration-processorartifactId>
    <optional>trueoptional>
dependency>

<dependency>
	<groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>1.1.0version>
dependency>
<dependency>
	<groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>${druid.version}version>
dependency>

2、application.properties 配置

# 数据源基础配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#数据库
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# mybatis 配置
mybatis.type-aliases-package=com.mos.quote.model
mybatis.mapper-locations=classpath:mapper/*.xml 

# 通用 Mapper 配置(多个时逗号隔开)
mapper.mappers=com.mos.quote.common.MyMapper
#insert、update是否判断字符串类型!='' 即 test="str != null"表达式内是否追加 and str != ''
mapper.not-empty=false
#主键生成策略
mapper.identity=MYSQL

# 分页插件配置
#指定数据库分页类型
pagehelper.helperDialect=mysql
#页码<=0 查询第一页,页码>=总页数查询最后一页
pagehelper.reasonable=true
#支持通过 Mapper 接口参数来传递分页参数
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql

3、封装基类

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
     
}

tk.mapper 使用案例

public void selectByExampleTest() {
     
    Example example = new Example(Category.class);
    Example.Criteria criteria = example.createCriteria();
    criteria.andEqualTo("categoryID", 1);
    criteria.orEqualTo("categoryID", 2);
    categoryDao.selectByExample(example);
}

4、分页使用

@Service
public class AreaServiceImpl implements IAreaService {
     
    @Autowired
    private AreaMapper areaMapper;

    @Override
    public PageInfo<Area> queryPage(Integer pageNo, Integer pageSize, String parentId) {
     
        PageHelper.startPage(pageNo,pageSize); //分页参数配置
        List<Area> list = this.queryAllByPID(parentId); //查询
        return new PageInfo<>(list); //分页
    }
}

5、PageHelper 注意

执行 PageHelper.startPage(1, 10) 后紧跟着执行 Executor,即 countryMapper.selectIf(param1),避免这两行之间出现错误

  • 详情参考:Mybatis PageHelper分页遇到的坑,莫名其妙的增加了limit ?,?

6、PageHelper 应对大数据量的慢查询问题

大数据量建议不要使用 PageHelper 插件实现分页

  • 推荐阅读:浅谈PageHelper插件分页实现原理及大数据量下SQL查询效率问题解决
# 结果 4.73s
select * from user where age = 10 limit 100000,10;

替换为 

# 结果 0.53s
SELECT a.* FROM USER a
INNER JOIN 
    (SELECT id FROM USER WHERE age = 10 LIMIT 100000,10) b 
ON a.id = b.id;

注:PageHelper 使用拦截器重构 sql 的方式实现分页查询

解决MySql 的 order by 和 limit 不要一起使用

PageHelper 替代框架sqlhelper

你可能感兴趣的:(工作学习总结)