jdbc,mybatis中fetchsize使用,批处理方案

jdbc,mybatis中fetchsize使用,批处理方案

    • 简介
    • jdbc获取大量数据
    • mybatis获取大量数据
      • mapper文件
      • 自定义ResultHandler来分批处理结果集
      • 使用
    • 批处理方案
      • for循环一条条插入,或者 去实现的批量操作
      • 使用ExecutorType.BATCH创建SqlSession
      • 总结

简介

在操作数据库或者使用框架的时候避免不了批处理的使用场景,本文就这些场景讲解对应的方案

jdbc获取大量数据

场景,多用于导出一次性拉取大量数据,出现卡死,内存溢出问题,可以进行分批拉取数据然后最后统一导出或者分批导出

public class testMain {
    public static long importData(String sql){
        String url = "jdbc:mysql://127.0.0.1:3306/gsong?characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8";
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e1) {
            e1.printStackTrace();
        }
        long allStart = System.currentTimeMillis();
        long count =0;

        Connection con = null;
        PreparedStatement ps = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            con = DriverManager.getConnection(url,"root","123456");
            ps = (PreparedStatement) con.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,
                    ResultSet.CONCUR_READ_ONLY);
            ps.setFetchSize(Integer.MIN_VALUE);
            ps.setFetchDirection(ResultSet.FETCH_REVERSE);
            rs = ps.executeQuery();
            while (rs.next()) {

//此处处理业务逻辑
                count++;
                if(count%600000==0){
                    long end = System.currentTimeMillis();
                    System.out.println(" 写入到第  "+(count/600000)+" 个文件中!+++"+(end-allStart));
                    allStart=end;

                }

            }
            System.out.println("取回数据量为  "+count+" 行!");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if(rs!=null){
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(ps!=null){
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(con!=null){
                    con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return count;

    }

    public static void main(String[] args) throws InterruptedException {

        String sql = "select * from user limit 0,4800000 ";
        importData(sql);

    }
}

mybatis获取大量数据

mapper文件


自定义ResultHandler来分批处理结果集

public class GxidResultHandler implements ResultHandler {
  // 这是每批处理的大小
  private final static int BATCH_SIZE = 1000;
  private int size;
  // 存储每批数据的临时容器
  private Set gxids;

  public void handleResult(ResultContext resultContext) {
    // 这里获取流式查询每次返回的单条结果
    String gxid = resultContext.getResultObject();
    // 你可以看自己的项目需要分批进行处理或者单个处理,这里以分批处理为例
    gxids.add(gxid);
    size++;
    if (size == BATCH_SIZE) {
      handle();
    }
  }

  private void handle() {
    try {
      // 在这里可以对你获取到的批量结果数据进行需要的业务处理
    } finally {
      // 处理完每批数据后后将临时清空
      size = 0;
      gxids.clear();
    }
  }

  // 这个方法给外面调用,用来完成最后一批数据处理
  public void end(){
    handle();// 处理最后一批不到BATCH_SIZE的数据
  }
}

使用

@Service
public class ServiceImpl implements Service {
  @Autowired
  SqlSessionTemplate sqlSessionTemplate;

  public void method(){
    GxidResultHandler gxidResultHandler = new GxidResultHandler();
    sqlSessionTemplate.select("flowselect.Mapper.selectGxids", gxidResultHandler);
    gxidResultHandler.end();
  }
}
总结

 流式查询:内存会保持稳定,不会随着记录的增长而增长。其内存大小取决于批处理
 大 小 BATCH_SIZE的设置,该尺寸越大,内存会越大。所以BATCH_SIZE应该根据业务
 情况设置合适的大小。另外要切记每次处理完一批结果要记得释放存储每批数据的
 临时容器,即上文中的gxids.clear();

批处理方案

for循环一条条插入,或者 去实现的批量操作


		insert into user(name,age,is_delete) 
		
			(#{user.name},#{user.age},#{user.isDelete})
		


List users=new ArrayList<>();
		for(int i=0;i<10000;i++){
			users.add(new User(null,"赵敏"+i,i,"镇雄"+i,false));
		}
		long begin=System.currentTimeMillis();
		int insertBatch = mapper.insertBatch(users);
		long end=System.currentTimeMillis();
		System.out.println(end-begin);
		System.out.println(insertBatch);
		sqlSession.close();
		总结:
		这种方式数据量大时将会卡死
		改进后!!!直接for循环,50次提交
		public void batchTest() {
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    CountryMapper mapper = sqlSession.getMapper(CountryMapper.class);
    List countries = mapper.selectAll();
    for (int i = 0; i < countries.size(); i++) {
        Country country = countries.get(i);
        country.setCountryname(country.getCountryname() + "Test");
        mapper.updateByPrimaryKey(country);
        //每 50 条提交一次
        if((i + 1) % 50 == 0){
            sqlSession.flushStatements();
        }
    }
    sqlSession.flushStatements();
}
总结:
每50次提交一次,是一种可行的方式
		

使用ExecutorType.BATCH创建SqlSession

public void test09(){
		SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
		IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
		
		long begin=System.currentTimeMillis();
		for(int i=0;i<10000;i++){
			mapper.insert(new User(null,"赵敏"+i,i,"镇雄"+i,false));
		}
		long end=System.currentTimeMillis();
		System.out.println(end-begin);
		sqlSession.close();
	}
	总结:不会被卡死,但实际运用中,选择改进后的自定义多少次提交更为友好

总结

批量提交只能应用于 insert, update, delete。

并且在批量提交使用时,如果在操作同一SQL时中间插入了其他数据库操作,就失去了其意义,所以在使用批量提交时,要控制好 SQL 执行顺序。

你可能感兴趣的:(jdbc,批处理方案)