这里面记录一下使用mybatis处理mysql的批量插入的问题,测试有可能不准。只愿世间风景千般万般熙攘过后,字里行间,人我两忘,相对无言。
mybatis的批量插入
我们的测试主体类是springboot环境中的一个控制器类,重要的代码如下,在我们的测试中Constants.MAX_BATCH_NUMBER = 10000。
@GetMapping("insert") public void insertBatchData() { // 构建一个list,大小为1百万条数据 long beginCreateList = System.currentTimeMillis(); List<Map<String, Object>> lists = new ArrayList<>(); for (int i = 0; i < 100000; i ++) { Map<String, Object> map = new HashMap<>(); map.put("userId", i + ""); map.put("username", "huhx" + i); map.put("password", "124" + i); map.put("sex", 1); map.put("address", System.currentTimeMillis()); lists.add(map); } long endCreateList = System.currentTimeMillis(); logger.debug("创建一个大小为10万的列表,耗时:" + (endCreateList - beginCreateList)); // 4103 // 插入数据 dbSessionTemplateSupport.simpleSqlInsertBatch("user.simpleInsertUserData", lists); long endInsertData = System.currentTimeMillis(); logger.debug("插入10万数据,耗时:" + (endInsertData - endCreateList)); // 49649 }
一、我们每10000条数据提交一次事务
public class DbSessionTemplateSupport extends SqlSessionTemplate { public DbSessionTemplateSupport(SqlSessionFactory sqlSessionFactory) { super(sqlSessionFactory); } // 支持批量的插入 public void baseInsertBatch(String sqlStatement, List<Map<String, Object>> list) { SqlSession session = getSqlSessionFactory().openSession(ExecutorType.BATCH, false); if (list == null || list.size() < 1) { return; } int listSize = list.size(); try { // 如果提交的列表条数小于提交阀值 if (listSize <= Constants.MAX_BATCH_NUMBER) { for (int i = 0; i < list.size(); i++) { session.insert(sqlStatement, list.get(i)); } session.commit(); } else { for (int i = 0; i < list.size(); ) { session.insert(sqlStatement, list.get(i)); i++; if (i % Constants.MAX_BATCH_NUMBER == 0 || i == listSize) { session.commit(); session.clearCache(); } } } } catch (Exception e) { session.rollback(); e.printStackTrace(); } finally { session.close(); } } }
这种方式处理插入,仍旧比较慢(其实是很慢很慢,可能是我的代码问题,没有统计时间,太慢了)。但是这种方式可以支持oracle,下面的这种方式非常快,但是oracle不支持。
二、采用mysql支持的拼接式插入数据
/** * mysql的批量插入方式,oracle不支持。 * * @param sqlStatement * @param list */ public void simpleSqlInsertBatch(String sqlStatement, List<Map<String, Object>> list) { if (list == null || list.size() < 1) { return; } // 如果提交的列表条数小于提交阀值 List<Map<String, Object>>[] splitLists = CommUtil.splitLists(list, Constants.MAX_BATCH_NUMBER); for (List<Map<String, Object>> tempList : splitLists) { insert(sqlStatement, tempList); } }
我们对原始的列表进行切割,然后依次的插入。每次的插入都是MAX_BATCH_NUMBER条数据。下面是切割的方法
/** * 对一个列表按照splitNum进行分割。 * * @param lists * @param splitNum * @param <T> * @return */ public static <T> List<T>[] splitLists(List<T> lists, int splitNum) { int listSize; if (lists == null || (listSize = lists.size()) < 1) { return new ArrayList[0]; } int length = listSize % splitNum == 0 ? listSize / splitNum : listSize / splitNum + 1; // 这里面如果用ArrayList,会在50行报错。ArrayList list = new List();这样会报错。 List<T>[] results = new List[length]; int fromIndex, toIndex; for (int i = 0; i < length; i++) { fromIndex = i * splitNum; toIndex = (fromIndex + splitNum) > listSize ? listSize : (fromIndex + splitNum); results[i] = lists.subList(fromIndex, toIndex); } return results; }
插入的sql语句在mybatis中是使用for...each的方式,如下:
<!-- mysql的批量插入方式 --> <insert id="simpleInsertUserData" parameterType="java.util.List"> INSERT INTO puser (userId, username, password, address, sex) VALUES <foreach collection ="list" item="item" index= "index" separator =","> ( #{item.userId}, #{item.username}, #{item.password}, #{item.address}, #{item.sex} ) </foreach > </insert>
10万条数据的分割时间加上插入到mysql数据库,这种方式耗时:15658毫秒。需要注意的是如果常数设置为10万条,也就是第10万插入一次。这种方式会报错的。