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文件
<select id="selectGxids" resultType="java.lang.String" fetchSize="1000">
SELECT gxid from t_gxid
</select>
自定义ResultHandler来分批处理结果集
public class GxidResultHandler implements ResultHandler<String> {
// 这是每批处理的大小
private final static int BATCH_SIZE = 1000;
private int size;
// 存储每批数据的临时容器
private Set<String> gxids;
public void handleResult(ResultContext<? extends String> 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 id="insertBatch">
insert into user(name,age,is_delete)
<foreach collection="users" item="user" open="VALUES" close=";" separator=",">
(#{user.name},#{user.age},#{user.isDelete})
</foreach>
</insert>
List<User> 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<Country> 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 执行顺序。