Mybatis中流式查询的实现示例

 更新时间:2024年09月27日 10:20:53   作者:Desiro_  
MyBatis的ResultHandler是用于处理数据库查询结果集的工具,可以通过回调函数对数据进行流式处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1.ResultHandler结果集处理

1.1 ResultHandler 简介

Mybatis 中的 ResultHandler 相当于数据结果集的处理器,它是一个回调函数(Callback),用来处理每一行数据的结果,这个回调函数可以在查询结果处理到一定量时触发,对结果集数据进行定制化处理。

ResultHandler 的使用可以大幅提升数据处理的效率,当我们需要处理大量的数据时,一般会使用 ResultHandler 来进行结果的处理,避免一次查询就全部返回结果,浪费内存资源或造成 OOM。

ResultHandler 接口只有一个方法 handleResult,我们可以实现这个方法去处理每一条 SQL 查询返回的数据。

1.2 ResultHandler 实现结果流式输出(两种写法)

Mybatis:

 <select id="selectByResultHandler" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List" />
    FROM tbl_test_user
  </select>

 Mapper:

package com.test.dao;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.ResultHandler;

public interface TblTestUserMapper {

	// SQL 返回值为 void,所以我们并没有接收这个返回值。所以不会产生大对象
	// 方法中的第一个参数是查询时的限制条件,第二个是结果处理器
    void selectByResultHandler(@Param(value = "actNo") String actNo,
                               ResultHandler resultHandler);
}

实现ResultHandler:

package com.test.handler;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import com.test.model.TblTestUser;

@Slf4j
public class TestResultHandler implements ResultHandler<TblTestUser> {

    @Override
    public void handleResult(ResultContext<? extends TblTestUser> resultContext) {
        TblTestUser tblTestUser = resultContext.getResultObject();

        // TODO 处理数据的逻辑
        try {
            // 打印每行日志
            log.info("userNo : [{}]", tblTestUser.getUserNo());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Service:

package com.test.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.test.dao.TblTestUserMapper;
import com.test.model.TblTestUser;

import javax.annotation.Resource;

@Service
@Slf4j
public class TestService {

    @Resource
    private TblTestUserMapper tblTestUserMapper;

    /**
     * 写法 1
     *
     * @param actNo 筛选条件:活动号
     */
    @Async
    public void test1(String actNo) {
        log.info("ResultHandler1 处理查询结果");
        TestResultHandler testResultHandler = new TestResultHandler();
        tblTestUserMapper.selectByResultHandler(actNo, testResultHandler);
    }

    /**
     * 写法 2
     *
     * @param actNo 筛选条件:活动号
     */
    @Async
    public void test2(String actNo) {
        log.info("ResultHandler2 处理查询结果");
        tblTestUserMapper.selectByResultHandler(actNo, new ResultHandler<TblTestUser>() {
            @Override
            public void handleResult(ResultContext<? extends TblTestUser> resultContext) {
                TblTestUser tblTestUser = resultContext.getResultObject();

                // 处理数据的逻辑
                try {
                    // 打印每行日志
                    log.info("userNo : [{}]", tblTestUser.getUserNo());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }
}

2.游标(Cursor)

MyBatis 3.2 以上版本支持游标,允许逐行处理结果集。它提供了一个叫 org.apache.ibatis.cursor.Cursor 的接口类用于流式查询,这个接口继承了 java.io.Closeable 和 java.lang.Iterable 接口,由此可知:

  • Cursor 是可关闭的;

  • Cursor 是可遍历的。

除此之外,Cursor 还提供了三个方法:

  • isOpen():用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据;

  • isConsumed():用于判断查询结果是否全部取完。

  • getCurrentIndex():返回已经获取了多少条数据

因为 Cursor 实现了迭代器接口,因此在实际使用当中,从 Cursor 取数据非常简单, 在 Mapper 接口中,通过指定 Mapper 方法的返回值为 Cursor 类型,MyBatis 就知道这个查询方法一个流式查询。

cursor.forEach(rowObject -> {...});  

2.1 构建Cursor

Mapper:

@Mapper  
public interface FooMapper {  
    @Select("select * from foo limit #{limit}")  
    Cursor scan(@Param("limit") int limit);  
}

2.1.1 错误示范

@GetMapping("foo/scan/0/{limit}")  
public void scanFoo0(@PathVariable("limit") int limit) throws Exception {  
    try (Cursor cursor = fooMapper.scan(limit)) {  // 1  
        cursor.forEach(foo -> {});                 // 2  
    }  
}  

说明: 

上面的代码中,fooMapper 是 @Autowired 进来的。

注释 1 处调用 scan 方法,得到 Cursor 对象并保证它能最后关闭;

注释 2 处则是从 cursor 中取数据。

上面的代码看上去没什么问题,但是执行 scanFoo0() 时会报错:

java.lang.IllegalStateException: A Cursor is already closed.

这是因为我们前面说了在取数据的过程中需要保持数据库连接,而 Mapper 方法通常在执行完后连接就关闭了,因此 Cusor 也一并关闭了。

所以,解决这个问题的思路不复杂,保持数据库连接打开即可。我们至少有三种方案可选。

 2.1.2 正确示范

SqlSessionFactory

我们可以用 SqlSessionFactory 来手工打开数据库连接:

@GetMapping("foo/scan/1/{limit}")  
public void scanFoo1(@PathVariable("limit") int limit) throws Exception {  
    try (  
        SqlSession sqlSession = sqlSessionFactory.openSession();  // 1  
        Cursor cursor =   
              sqlSession.getMapper(FooMapper.class).scan(limit)   // 2  
    ) {  
        cursor.forEach(foo -> { });  
    }  
}

上面的代码中,1 处我们开启了一个 SqlSession (实际上也代表了一个数据库连接),并保证它最后能关闭;2 处我们使用 SqlSession 来获得 Mapper 对象。这样才能保证得到的 Cursor 对象是打开状态的。

TransactionTemplate

在 Spring 中,我们可以用 TransactionTemplate 来执行一个数据库事务,这个过程中数据库连接同样是打开的:

@GetMapping("foo/scan/2/{limit}")  
public void scanFoo2(@PathVariable("limit") int limit) throws Exception {  
    TransactionTemplate transactionTemplate =   
            new TransactionTemplate(transactionManager);  // 1  
  
    transactionTemplate.execute(status -> {               // 2  
        try (Cursor cursor = fooMapper.scan(limit)) {  
            cursor.forEach(foo -> { });  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return null;  
    });  
}

上面的代码中,1 处我们创建了一个 TransactionTemplate 对象(此处 transactionManager 是怎么来的不用多解释,本文假设读者对 Spring 数据库事务的使用比较熟悉了),2 处执行数据库事务,而数据库事务的内容则是调用 Mapper 对象的流式查询。注意这里的 Mapper 对象无需通过 SqlSession 创建。

@Transactional 注解

这个本质上和方案二一样,代码如下:

@GetMapping("foo/scan/3/{limit}")  
@Transactional  
public void scanFoo3(@PathVariable("limit") int limit) throws Exception {  
    try (Cursor cursor = fooMapper.scan(limit)) {  
        cursor.forEach(foo -> { });  
    }  
}

它仅仅是在原来方法上面加了个 @Transactional 注解。这个方案看上去最简洁,但请注意 Spring 框架当中注解使用的坑:只在外部调用时生效。在当前类中调用这个方法,依旧会报错。

到此这篇关于Mybatis中流式查询的实现示例的文章就介绍到这了,更多相关Mybatis 流式查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java数据结构优先队列实练

    Java数据结构优先队列实练

    通常都把队列比喻成排队买东西,大家都很守秩序,先排队的人就先买东西。但是优先队列有所不同,它不遵循先进先出的规则,而是根据队列中元素的优先权,优先权最大的先被取出,这篇文章主要介绍了java优先队列的真题,感兴趣的朋友一起看看吧
    2022-07-07
  • SpringBoot整合Groovy脚本实现动态编程详解

    SpringBoot整合Groovy脚本实现动态编程详解

    这篇文章主要为大家介绍了SpringBoot整合Groovy脚本实现动态编程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • 记一次Feign中实现传实体Bean的问题

    记一次Feign中实现传实体Bean的问题

    这篇文章主要介绍了记一次Feign中如何传实体Bean的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java中判断集合是否相等的几种方法详解

    Java中判断集合是否相等的几种方法详解

    这篇文章主要介绍了Java中判断集合是否相等的几种方法详解,在平时的开发中,可能会遇到需要判断两个集合是否相等的需求,那么本文就来详细讲解一下几种实现方法,需要的朋友可以参考下
    2023-08-08
  • Java封装数组之动态数组实现方法详解

    Java封装数组之动态数组实现方法详解

    这篇文章主要介绍了Java封装数组之动态数组实现方法,结合实例形式详细分析了java动态数组的实现原理、操作步骤与相关注意事项,需要的朋友可以参考下
    2020-03-03
  • 详解Spring Boot 配置多个RabbitMQ

    详解Spring Boot 配置多个RabbitMQ

    本篇文章主要介绍了Spring Boot 配置多个RabbitMQ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Java实现非对称加密的三种方法

    Java实现非对称加密的三种方法

    本文主要介绍了Java实现非对称加密的三种方法,主要包括非对称加密算法--DH(密钥交换),非对称加密算法--RSA,非对称加密算法--EIGamal,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • MyBatis Log 插件无法显示SQL语句的原因解析

    MyBatis Log 插件无法显示SQL语句的原因解析

    MyBatis Log是IDEA一款下载量非常高的插件,该插件可以对控制台打印的日志进行解析,然后将对应的SQL语句整理并拼接好对应的参数,非常方便。这篇文章给大家介绍MyBatis Log 插件无法显示SQL语句的原因,感兴趣的朋友跟随小编一起看看吧
    2020-09-09
  • java实现文件下载的两种方式

    java实现文件下载的两种方式

    这篇文章主要为大家详细介绍了java实现文件下载的两种方式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • Spring MVC传递接收参数方式小结

    Spring MVC传递接收参数方式小结

    大家在开发中经常会用到Spring MVC Controller来接收请求参数,主要常用的接收方式就是通过实体对象以及形参等方式、有些用于GET请求,有些用于POST请求,有些用于两者,下面介绍几种常见的Spring MVC传递接收参数的方式
    2021-11-11

最新评论