MyBatis如何实现分页

发布于:2024-04-10 ⋅ 阅读:(72) ⋅ 点赞:(0)

在 MyBatis 中实现分页通常有两种方式:使用数据库厂商提供的分页查询语句(如 MySQL 的 LIMIT)或者通过自定义 SQL 来实现分页逻辑等

MyBatis分页方式对比

MyBatis提供了多种分页方式,每种方式都有其特定的应用场景和优缺点。以下是对MyBatis中几种常见分页方式的对比:

  1. 基于RowBounds的分页(逻辑分页):
    ○ 原理:执行完整的SQL查询,将结果集全部加载到内存中,然后根据RowBounds指定的偏移量和限制数进行分页处理。
    ○ 优点:减少IO次数,对于频繁访问且数据量较小的情况较为适合。
    ○ 缺点:当数据量非常大时,容易造成内存溢出,性能下降。
  2. 基于数据库的分页(物理分页):
    ○ 原理:在SQL查询语句中使用LIMIT和OFFSET关键字来实现分页,直接在数据库层面进行分页处理。
    ○ 优点:适用于大数据量的情况,避免了内存溢出的风险,性能较好。
    ○ 缺点:需要数据库支持LIMIT和OFFSET语法,不同数据库厂商的语法可能有所不同。
  3. 基于插件的分页:
    ○ 原理:MyBatis提供了插件机制,通过自定义插件来拦截SQL语句的执行,并在查询结果返回之前添加分页逻辑。
    ○ 优点:插件封装了分页的具体实现细节,使用起来简单方便,适用于多种分页需求。
    ○ 缺点:可能需要一定的开发成本来编写和维护插件。
  4. 数组分页:
    ○ 原理:首先查询出全部数据,然后在Java代码的List中截取需要的部分。
    ○ 优点:实现简单。
    ○ 缺点:当数据量很大时,会消耗大量内存,并可能导致性能问题。
    综上所述,选择哪种分页方式取决于具体的应用场景和数据量大小。对于小数据量或频繁访问的场景,逻辑分页(如RowBounds)可能是一个不错的选择。而对于大数据量或需要高效分页的场景,物理分页(如基于数据库的分页)或基于插件的分页可能更为合适。在实际应用中,还需要考虑数据库类型、系统性能、开发成本等因素来做出决策。

使用数据库厂商提供的分页查询语句

许多数据库厂商都提供了用于分页的特定语法,如 MySQL 的 LIMIT、Oracle 的 ROWNUM、SQL Server 的 OFFSET FETCH 等。你可以直接在 SQL 查询语句中使用这些语法来实现分页,然后将分页参数传递给 MyBatis 的方法即可。
示例(MySQL):

<select id="getUserList" resultType="User">
    SELECT * FROM users
    LIMIT #{offset}, #{pageSize}
</select>
List<User> getUserList(@Param("offset") int offset, @Param("pageSize") int pageSize);

通过自定义 SQL 实现分页逻辑

如果你使用的数据库不支持特定的分页语法,或者想要更多灵活性,你可以通过自定义 SQL 实现分页逻辑。通常,你需要通过 RowBounds 或 PageHelper 来实现分页。

1. 使用 RowBounds 实现分页

原理:通过RowBounds实现分页和通过数组方式分页原理差不多,都是一次获取所有符合条件的数据,然后在内存中对大数据进行操作,实现分页效果。只是数组分页需要我们自己去实现分页逻辑,这里更加简化而已。
存在问题:一次性从数据库获取的数据可能会很多,对内存的消耗很大,可能导师性能变差,甚至引发内存溢出。
适用场景:在数据量很大的情况下,建议还是适用拦截器实现分页效果。RowBounds建议在数据量相对较小的情况下使用。
RowBounds分页是Mybatis提供的一种分页方式,其原理主要是在执行SQL查询后,将返回的所有结果集加载到内存中,然后在内存中根据指定的偏移量(offset)和限制数(limit)进行分页处理。
具体来说,当我们在Mybatis的Mapper接口中调用查询方法时,可以传入一个RowBounds对象作为参数。这个RowBounds对象包含了分页所需的信息,比如当前页码、每页显示的记录数等。在执行查询时,Mybatis会首先执行完整的SQL查询语句,获取到所有满足条件的结果集。然后,Mybatis会根据RowBounds对象中指定的偏移量和限制数,在内存中对这些结果集进行截取,从而得到当前页需要展示的数据。
需要注意的是,RowBounds分页方式是一种逻辑分页,即在内存中进行分页处理。当数据量非常大时,这种方式可能会导致内存溢出的问题。因此,对于大数据量的分页需求,建议使用物理分页方式,即在SQL查询语句中添加LIMIT和OFFSET子句,直接在数据库层面进行分页处理。
此外,Mybatis还提供了另一种分页插件PageHelper,它使用拦截器的方式实现了物理分页。PageHelper插件会在Mybatis执行SQL查询之前,自动根据传入的分页参数改写SQL语句,添加LIMIT和OFFSET子句,从而实现物理分页。这种方式可以更有效地处理大数据量的分页需求,避免内存溢出的问题。
List getUserList(RowBounds rowBounds);
RowBounds rowBounds = new RowBounds(offset, pageSize);
List users = sqlSession.selectList(“getUserList”, rowBounds);

2. 使用 PageHelper 实现分页

PageHelper的分页原理主要基于MyBatis的插件机制。具体来说,PageHelper内部实现了一个PageInterceptor拦截器,这个拦截器会在MyBatis执行SQL查询之前进行拦截。
当我们在代码中调用PageHelper的startPage方法时,它会在当前线程上下文中设置一个ThreadLocal变量,用于保存分页的参数,如当前页码、每页显示的数量等。
随后,当MyBatis执行SQL查询时,PageInterceptor拦截器会拦截到这一操作。拦截器会从ThreadLocal中获取到分页参数,并根据这些参数来改写原始的SQL语句,添加LIMIT和OFFSET子句,以实现分页查询。
改写后的SQL语句会被发送到数据库执行,数据库返回的结果集就是根据分页参数查询得到的结果。
最后,PageInterceptor拦截器会将ThreadLocal中的分页参数清除,避免对后续操作产生影响。
通过这种方式,PageHelper实现了对MyBatis查询结果的分页处理,而无需修改原有的SQL语句、Mapper接口和XML文件,因此具有无侵入性和易用性。同时,由于分页操作是在数据库层面进行的,因此也具有较高的性能。
需要注意的是,PageHelper使用了ThreadLocal来保存分页参数,因此分页参数是与线程绑定的,这意味着不同的线程之间不会共享分页参数,从而保证了分页的准确性和独立性。
总的来说,PageHelper通过拦截MyBatis的SQL查询操作,并在查询语句中添加LIMIT和OFFSET子句,实现了对查询结果的分页处理,从而简化了分页操作的实现过程,提高了开发效率。
PageHelper 是一个 MyBatis 的分页插件,可以简化分页操作。
首先,在 MyBatis 的配置文件中配置 PageHelper 插件:

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="helperDialect" value="mysql"/>
    </plugin>
</plugins>
然后,在需要分页的查询方法中添加分页参数:
List<User> getUserList(@Param("pageNum") int pageNum, @Param("pageSize") int pageSize);
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.getUserList(pageNum, pageSize);
这两种方式都可以实现分页查询,你可以根据实际需求选择合适的方式。

数组分页

使用数组进行分页通常意味着在数据库中获取所有数据,然后在应用程序中对数据进行分割和展示。这种方法在数据量较小且不频繁变化时比较适用,但在数据量较大时可能会影响性能。下面是一个简单的 Java 代码示例,演示如何使用数组进行分页:

import java.util.ArrayList;
import java.util.List;

public class PaginationWithArrayExample {

    // 模拟从数据库中获取数据的方法,返回所有数据
    public List<String> fetchDataFromDatabase() {
        // 这里假设从数据库中获取了一些数据,实际情况根据需求修改
        List<String> dataList = new ArrayList<>();
        for (int i = 1; i <= 100; i++) {
            dataList.add("Data " + i);
        }
        return dataList;
    }

    // 根据页码和每页显示数量,从数据集中获取指定页的数据
    public List<String> getDataForPage(List<String> dataList, int pageNumber, int pageSize) {
        int startIndex = (pageNumber - 1) * pageSize;
        int endIndex = Math.min(startIndex + pageSize, dataList.size());
        if (startIndex >= endIndex) {
            return new ArrayList<>(); // 如果起始索引大于等于结束索引,返回空列表
        }
        return dataList.subList(startIndex, endIndex);
    }

    // 示例用法
    public static void main(String[] args) {
        PaginationWithArrayExample example = new PaginationWithArrayExample();
        List<String> dataList = example.fetchDataFromDatabase(); // 模拟从数据库中获取数据
        int pageNumber = 2; // 第2页
        int pageSize = 10; // 每页显示10条数据
        List<String> pageData = example.getDataForPage(dataList, pageNumber, pageSize);
        // 输出当前页的数据
        System.out.println("Page " + pageNumber + " data:");
        for (String data : pageData) {
            System.out.println(data);
        }
    }
}

在这个示例中,fetchDataFromDatabase 方法模拟从数据库中获取数据,返回一个包含了所有数据的列表。然后,getDataForPage 方法根据传入的页码和每页显示数量,从数据集中获取指定页的数据,返回一个包含了当前页数据的子列表。在示例的 main 方法中,演示了如何使用这两个方法来获取指定页的数据,并将其打印输出。

使用 MyBatis-Plus 进行分页

MyBatis-Plus进行分页的原理主要依赖于其内置的分页插件和Page对象。
首先,MyBatis-Plus提供了分页插件,该插件会在MyBatis执行SQL查询之前进行拦截。当使用MyBatis-Plus进行分页查询时,分页插件会自动识别分页相关的参数,并对原始的SQL语句进行改写,添加LIMIT和OFFSET子句,以实现物理分页。
其次,MyBatis-Plus中的Page对象用于表示分页信息。这个对象包含了当前页码、每页记录数、总记录数等信息。在进行分页查询时,可以通过传递Page对象给MyBatis-Plus的查询方法,来告诉MyBatis-Plus需要进行分页查询以及分页的具体参数。
当MyBatis-Plus执行分页查询时,它会根据Page对象中的信息生成对应的分页SQL语句,并通过数据库执行这个语句。数据库会根据LIMIT和OFFSET子句返回指定范围的结果集。
最后,MyBatis-Plus将查询结果封装到Page对象中,并返回给调用者。这个Page对象不仅包含了实际的查询结果列表,还包含了分页相关的信息,如总记录数、总页数等。这使得分页操作更加方便,同时也提高了代码的可维护性。
需要注意的是,MyBatis-Plus的分页实现是基于物理分页的,即直接在数据库层面进行分页处理,而不是在内存中处理。这种方式在处理大数据量时性能较好,避免了内存溢出的风险。
总结来说,MyBatis-Plus进行分页的原理是通过分页插件和Page对象来实现物理分页,通过在SQL语句中添加LIMIT和OFFSET子句来获取指定范围的结果集,并将结果封装到Page对象中返回给调用者。
MyBatis-Plus 是 MyBatis 的增强工具包,提供了很多便捷的功能,其中包括了分页功能。MyBatis-Plus 的分页功能可以轻松地实现物理分页,让分页操作变得更加简单。
首先,你需要在项目中引入 MyBatis-Plus 的依赖。在 Maven 项目中,你可以在 pom.xml 文件中添加如下依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>latest_version</version>
</dependency>

接下来,假设你有一个 UserMapper 接口,用于操作用户信息。你可以在该接口中直接定义分页查询的方法,无需额外编写 XML 映射文件。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;

public interface UserMapper extends BaseMapper<User> {
    Page<User> selectUserPage(Page<User> page);
}

在该方法中,我们使用了 MyBatis-Plus 提供的 Page 类来实现分页。Page 类继承自 MyBatis 的 RowBounds 类,它除了包含分页信息外,还包含了分页查询返回的数据列表。
然后,你可以在 Service 层中调用这个方法来进行分页查询:

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public Page<User> getUserPage(int pageNum, int pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        return userMapper.selectUserPage(page);
    }
}

在这个示例中,我们创建了一个新的 Page 对象,并传入了当前页码和每页显示数量。然后调用 selectUserPage 方法进行分页查询,并将结果返回。
最后,在 Controller 层中调用 Service 方法并将结果返回给前端即可完成分页查询的操作。

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public Page<User> getUserPage(@RequestParam(defaultValue = "1") int pageNum,
                                  @RequestParam(defaultValue = "10") int pageSize) {
        return userService.getUserPage(pageNum, pageSize);
    }
}

通过 MyBatis-Plus,你可以很方便地实现分页查询,而无需编写繁琐的 SQL 语句或者额外的 XML 映射文件。

MyBatis物理分页和逻辑分页

MyBatis中的物理分页和逻辑分页是两种不同的分页方式,它们在实现方式和性能上有显著的区别。
物理分页:
物理分页是在数据库层面上实现的分页,它依赖于数据库自身提供的分页功能。在MyBatis中,物理分页通常是通过在SQL语句中添加LIMIT和OFFSET子句来实现的。这种方式在查询数据库时,数据库会根据指定的偏移量和限制数直接返回分页后的结果,而不需要加载全部数据到内存中。因此,物理分页在处理大数据量时性能较好,避免了内存溢出的风险。然而,需要注意的是,不同的数据库可能具有不同的分页语法,因此在使用物理分页时需要考虑到数据库方言的问题。
逻辑分页:
逻辑分页则是在应用层面上实现的分页,它依赖于查询结果集。逻辑分页首先会查询出全部的数据,然后将这些数据加载到内存中,再根据分页要求筛选出合适的数据进行分页。这种方式在数据量较小时可能比较方便,但在处理大数据量时,会消耗大量的内存,并可能导致性能问题。因此,逻辑分页通常适用于数据量较小或对数据实时性要求不高的场景。
在实际应用中,选择物理分页还是逻辑分页需要根据具体的需求和场景来决定。对于大数据量或对数据性能要求较高的场景,建议使用物理分页以提高性能和避免内存溢出。而对于数据量较小或对数据实时性要求不高的场景,可以选择逻辑分页以简化实现过程。
此外,MyBatis还提供了分页插件如PageHelper来简化分页操作的实现。这些插件内部实现了物理分页的逻辑,使得开发者可以更方便地进行分页操作,而无需手动编写复杂的分页SQL语句。然而,在使用分页插件时,仍然需要注意插件的性能和兼容性等问题。

MyBatis 手写一个 拦截器分页

MyBatis 拦截器可以用于在 SQL 执行前后进行一些额外的处理,例如实现分页功能。下面我会给出一个简单的示例,包含了 DAO 层和业务层的代码。

假设你有一个 UserDao 接口,其中定义了获取用户列表的方法:
public interface UserDao {
    List<User> getUserList();
}
接下来,我们创建一个拦截器来实现分页功能:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;

@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PaginationInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];

        // 判断是否需要进行分页
        if (rowBounds != RowBounds.DEFAULT) {
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String sql = boundSql.getSql();
            // 自己实现分页逻辑,这里简单起见,直接拼接 LIMIT
            sql += " LIMIT " + rowBounds.getOffset() + ", " + rowBounds.getLimit();
            BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            MappedStatement newMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
            args[0] = newMappedStatement;
            args[2] = RowBounds.DEFAULT;
        }

        return invocation.proceed();
    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms, BoundSqlSqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        builder.keyProperty(ms.getKeyProperty());
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

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

    @Override
    public void setProperties(Properties properties) {
        // 这里可以接收配置参数,但我们这里不需要配置参数
    }

    private static class BoundSqlSqlSource implements org.apache.ibatis.mapping.SqlSource {
        private final BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}

然后,在你的 MyBatis 配置文件中配置该拦截器:

<plugins>
    <plugin interceptor="your.package.name.PaginationInterceptor"/>
</plugins>
最后,在业务代码中使用分页功能:
import org.apache.ibatis.session.RowBounds;

public class UserService {

    private final UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public List<User> getUsersByPage(int pageNum, int pageSize) {
        // 计算 offset
        int offset = (pageNum - 1) * pageSize;
        // 使用 RowBounds 进行分页
        RowBounds rowBounds = new RowBounds(offset, pageSize);
        return userDao.getUserList(rowBounds);
    }
}

这样,当调用 getUsersByPage 方法时,会自动进行分页查询。


网站公告

今日签到

点亮在社区的每一天
去签到