JdbcCursor流式查询导出数据库千万级数据

引言

翻看mybatis的源代码时发现有一个selectCursor的接口,网上查了下是一个流式接口用于按顺序查询大量数据,其方法签名具有三个参数,statement(mapper)、parameter(参数)、rowBounds(结果映射函数)。目前这边有一个需求,通过执行数据库中配置的查询语句来获取查询结果,显然这种方式mybatis无法实现,应为语句都是写在mapper的xml文件中的,,当然也可以使用${}这种占位符的方式进行替换,但是这种方式进行?参数赋值就很麻烦,于是编写JdbcCursor工具实现该功能。

mybaitis的cursor接口源码如下:

  /**
   * A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
   * @param <T> the returned cursor element type.
   * @param statement Unique identifier matching the statement to use.
   * @param parameter A parameter object to pass to the statement.
   * @param rowBounds  Bounds to limit object retrieval
   * @return Cursor of mapped objects
   */
  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

${}替换方式:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.monika.main.system.dao.CommonMapper">
    <select id="cursorQry" fetchSize="1000" parameterType="java.util.LinkedHashMap" resultType="java.util.LinkedHashMap">
        ${SQL}
    </select>
   
</mapper>
 @Test
    public void fn4() throws IOException {

        try(SqlSession sqlSession=sqlSessionFactory.openSession();){

            LinkedHashMap<String,String> sql=new LinkedHashMap<>();
            sql.put("SQL","select * from sys_user");
            try(Cursor<Map> cursor=sqlSession.selectCursor("com.monika.main.system.dao.CommonMapper.cursorQry",sql)){

                Iterator<Map> iterator = cursor.iterator();

                while (iterator.hasNext()){
                    System.out.println(iterator.next());
                }
            }
        }catch (Throwable t){
            throw t;
        }

    }

JdbcCursor流式查询实现

一、代码实现

package com.monika.main.system.util.cursor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.sql.*;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author:whh
 * @date: 2025-05-20 22:38
 * <p></p>
 */
public class JdbcCursor<T> implements Iterable<T>,AutoCloseable{


    public static Logger logger = LoggerFactory.getLogger(JdbcCursor.class);

    private Connection conn;

    private PreparedStatementSetter pst;
    private PreparedStatement ps;
    private ResultSet rs;

    private RowMapper rowMapper;




    private String sql;

    public JdbcCursor() {
    }
    public JdbcCursor<T> connection(Connection conn){
        this.conn=conn;
        return this;
    }

    public JdbcCursor<T> sql(String sql){
        this.sql=sql;
        return this;
    }



    public JdbcCursor<T> preparedStatementSetter(PreparedStatementSetter pst){
        this.pst=pst;
        return this;
    }


    public JdbcCursor<T> rowMapper(RowMapper rowMapper){
        this.rowMapper=rowMapper;
        return this;
    }

    public JdbcCursor<T> cursor() throws Exception {
        try{

            if (conn == null) {
                throw new IllegalArgumentException("connection cannot be null");
            }
            if (sql == null) {
                throw new IllegalArgumentException("sql cannot be null");
            }
            if(rowMapper == null){
                rowMapper= new DefaultMapRowMapper();
            }
            ps=conn.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            ps.setFetchSize(50);
            ps.setFetchDirection(ResultSet.FETCH_FORWARD);//按游标从头到尾取数
            if(pst!=null){
                pst.setValues(ps);
            }
            rs=ps.executeQuery();
        }catch (Exception ex){
            close();
            throw ex;
        }

        return this;
    }







    @Override
    public void close()  {

       closeResultSet(rs);
       rs=null;
       closeStatement(ps);
       releaseConnection(conn);
    }

    @Override
    public Iterator<T> iterator() {
        return new DefaultIterator<>();
    }



    private class DefaultIterator<T> implements Iterator<T>{
        private int rowNum=0;
        @Override
        public boolean hasNext() {
            try {
                return rs.next();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        @Override
        public T next() {
            try {
                return (T) rowMapper.mapRow(rs, rowNum++);
            } catch (SQLException e) {
                return null;
            }
        }
    }










     public void closeStatement( Statement stmt) {
        if (stmt != null) {
            try {
                stmt.close();
            }
            catch (SQLException ex) {
                logger.trace("Could not close JDBC Statement", ex);
            }
            catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                logger.trace("Unexpected exception on closing JDBC Statement", ex);
            }
        }
    }



        public  void releaseConnection(Connection con) {
        try {
            if (con != null) {
                con.close();
            }
        }
        catch (SQLException ex) {
            logger.debug("Could not close JDBC Connection", ex);
        }
        catch (Throwable ex) {
            logger.debug("Unexpected exception on closing JDBC Connection", ex);
        }
    }



     public void closeResultSet(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            }
            catch (SQLException ex) {
                logger.trace("Could not close JDBC ResultSet", ex);
            }
            catch (Throwable ex) {
                // We don't trust the JDBC driver: It might throw RuntimeException or Error.
                logger.trace("Unexpected exception on closing JDBC ResultSet", ex);
            }
        }
    }


    /**
     * 默认结果映射器
     */
    public class DefaultMapRowMapper implements RowMapper<Map<String, Object>> {

        @Override
        public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            Map<String, Object> mapOfColumnValues = createColumnMap(columnCount);
            for (int i = 1; i <= columnCount; i++) {
                String column = rsmd.getColumnLabel(i);
                if (column == null || column.isEmpty()) {
                    column = rsmd.getColumnName(i);
                }

                mapOfColumnValues.putIfAbsent(getColumnKey(column), getColumnValue(rs, i));
            }
            return mapOfColumnValues;
        }

        protected Map<String, Object> createColumnMap(int columnCount) {
            return new LinkedHashMap<>(columnCount);
        }

        protected String getColumnKey(String columnName) {
            return columnName;
        }

        protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
            Object obj = rs.getObject(index);
            String className = null;
            if (obj != null) {
                className = obj.getClass().getName();
            }
            if (obj instanceof Blob) {
                Blob blob = (Blob) obj;
                obj = blob.getBytes(1, (int) blob.length());
            }
            else if (obj instanceof Clob) {
                Clob clob = (Clob) obj;
                obj = clob.getSubString(1, (int) clob.length());
            }
            else if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) {
                obj = rs.getTimestamp(index);
            }
            else if (className != null && className.startsWith("oracle.sql.DATE")) {
                String metaDataClassName = rs.getMetaData().getColumnClassName(index);
                if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) {
                    obj = rs.getTimestamp(index);
                }
                else {
                    obj = rs.getDate(index);
                }
            }
            else if (obj instanceof java.sql.Date) {
                if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {
                    obj = rs.getTimestamp(index);
                }
            }
            return obj;
        }

    }


    public interface PreparedStatementSetter {

        /**
         * Set parameter values on the given PreparedStatement.
         * @param ps the PreparedStatement to invoke setter methods on
         * @throws SQLException if an SQLException is encountered
         * (i.e. there is no need to catch SQLException)
         */
        void setValues(PreparedStatement ps) throws SQLException;

    }


    public interface RowMapper<T> {

        /**
         * Implementations must implement this method to map each row of data
         * in the ResultSet. This method should not call {@code next()} on
         * the ResultSet; it is only supposed to map values of the current row.
         * @param rs the ResultSet to map (pre-initialized for the current row)
         * @param rowNum the number of the current row
         * @return the result object for the current row (may be {@code null})
         * @throws SQLException if an SQLException is encountered getting
         * column values (that is, there's no need to catch SQLException)
         */
        T mapRow(ResultSet rs, int rowNum) throws SQLException;

    }
}

二、测试(SQL无参设置)

 @Test
    public void fn1() throws SQLException {



        try( JdbcCursor<Map> cursor=new JdbcCursor().connection(dataSource.getConnection()).sql("select * from sys_user").cursor();){


            for (Map map : cursor) {
                System.out.println(map);
            }

        }catch (Exception ex){
            ex.printStackTrace();
        }

    }

三、测试(SQL有参设置)

 @Test
    public void fn2() throws SQLException {

        try( JdbcCursor<Map> cursor=new JdbcCursor()
                .connection(dataSource.getConnection())
                .sql("select * from sys_user where user_id=?")
                .preparedStatementSetter(pst->{
                    pst.setFetchSize(50);//设置FetchSize,最好不要超过100
                    pst.setInt(1,1);
                })
                .cursor();){


            for (Map map : cursor) {
                System.out.println(map);
            }

        }catch (Exception ex){
            ex.printStackTrace();
        }

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值