- /**
- * 获取类中所有属性注解@TableId和@TableField
- *
- * @param instance
- * @return
- * @throws NoSuchFieldException
- */
- public static Map
getDeclaredFieldsInfo(Object instance) throws NoSuchFieldException { - Map
map = new HashMap(); - Class> clazz = instance.getClass();
- Field[] fields = clazz.getDeclaredFields();
- StringBuilder stringBuilder = new StringBuilder();
-
- for (int i = 0; i < fields.length; i++) {
- String annotationName = "";
- boolean annotationId = fields[i].isAnnotationPresent(TableId.class);
- if (annotationId) {
- // 获取注解值
- annotationName = fields[i].getAnnotation(TableId.class).value();
- }
- boolean annotationPresent = fields[i].isAnnotationPresent(TableField.class);
- if (annotationPresent) {
- // 获取注解值
- annotationName = fields[i].getAnnotation(TableField.class).value();
- }
- stringBuilder.append(annotationName);
- stringBuilder.append(",");
- // 字段名称
- String attributesName = fields[i].getName();
- map.put(attributesName, new String[]{annotationName, fields[i].getType().getName()});
- }
- return map;
- }
-
-
- /**
- * 采用saveBulkCopy的方式批量保存数据
- *
- * @param objects
- * @param
- */
- public
void saveBulkCopy(List objects, String tableName) throws SQLException { - if (ObjectUtil.isEmpty(objects)) {
- return;
- }
- T objClass = objects.get(0);
- Connection connection = ConnectionFactory.getConnection();
- try {
- // 数据库表名
- // String tableName = objClass.getClass().getAnnotation(TableName.class).value();
- // 表字段属性名和类型@TableId和@TableField
- Map
declaredFieldsInfo = getDeclaredFieldsInfo(objClass); -
- ResultSet rs = executeSQL(connection, "select * from " + tableName + " where 1=0");
- CachedRowSetImpl crs = new CachedRowSetImpl();
- crs.populate(rs);
- //循环插入数据
- long startTime = System.currentTimeMillis();
- //既然是批量插入肯定是需要循环
- for (int i = 0, leg = objects.size(); i < leg; i++) {
- //移动指针到“插入行”,插入行是一个虚拟行
- crs.moveToInsertRow();
- //更新虚拟行的数据(实体类要新增的字段,大家根据自己的实体类的字段来修改)
- //数据库字段 ,填写的值
- JSONObject jsonObject = JSONUtil.parseObj(objects.get(i), false, true);
- for (Map.Entry
map : jsonObject.entrySet()) { - // 属性和值
- String key = map.getKey();
- Object value = map.getValue();
- // 过滤不是数据库的字段
- if (!declaredFieldsInfo.containsKey(key)) {
- continue;
- }
- // 字段名称,字段类型
- String[] strings = declaredFieldsInfo.get(key);
- String type = strings[1];
-
- if (StrUtil.equals(type, String.class.getName())) {
- if (ObjectUtil.isEmpty(value) || StrUtil.equals("null", value.toString())) {
- crs.updateString(key, null);
- } else {
- crs.updateString(key, value.toString());
- }
- } else if (StrUtil.equals(type, Integer.class.getName())) {
- if (ObjectUtil.isEmpty(value) || StrUtil.equals("null", value.toString())) {
- crs.updateInt(key, 0);
- } else {
- crs.updateInt(key, Integer.parseInt(value.toString()));
- }
- } else if (StrUtil.equals(type, LocalDateTime.class.getName())) {
- LocalDateTime localDateTime = (LocalDateTime) value;
- crs.updateDate(key, localTimeToDate(localDateTime));
- } else if (StrUtil.equals(type, LocalDate.class.getName())) {
- LocalDate localDate = (LocalDate) value;
- crs.updateDate(key, java.sql.Date.valueOf(localDate));
- } else if (StrUtil.equals(type, Long.class.getName())) {
- crs.updateLong(key, 0);
- } else if (StrUtil.equals(type, Double.class.getName())) {
- crs.updateDouble(key, (Double) value);
- } else if (StrUtil.equals(type, int.class.getName())) {
- crs.updateInt(key, (Integer) value);
- } else if (StrUtil.equals(type, Float.class.getName())) {
- crs.updateFloat(key, (Float) value);
- } else if (StrUtil.equals(type, java.util.Date.class.getName())) {
- java.util.Date date = (java.util.Date) value;
- crs.updateDate(key, new java.sql.Date(date.getTime()));
- } else if (StrUtil.equals(type, java.math.BigDecimal.class.getName())) {
- if (ObjectUtil.isEmpty(value) || StrUtil.equals("null", value.toString())) {
- crs.updateBigDecimal(key, BigDecimal.ZERO);
- } else {
- BigDecimal bigDecimal = null;
- if (value instanceof BigDecimal) {
- bigDecimal = (BigDecimal) value;
- } else if (value instanceof String) {
- bigDecimal = new BigDecimal((String) value);
- } else if (value instanceof BigInteger) {
- bigDecimal = new BigDecimal((BigInteger) value);
- } else if (value instanceof Number) {
- bigDecimal = new BigDecimal(((Number) value).doubleValue());
- }
- crs.updateBigDecimal(key, bigDecimal);
- }
- } else {
- log.info("未知的数据类型:{}", type);
- }
- }
- //插入虚拟行
- crs.insertRow();
- //移动指针到当前行
- crs.moveToCurrentRow();
- }
- //进行批量插入
- SQLServerBulkCopyOptions copyOptions = new SQLServerBulkCopyOptions();
- copyOptions.setKeepIdentity(false);
- copyOptions.setBatchSize(1000);
- copyOptions.setUseInternalTransaction(false);
-
- SQLServerBulkCopy bulkCopy = new SQLServerBulkCopy(connection);
- bulkCopy.setBulkCopyOptions(copyOptions);
- bulkCopy.setDestinationTableName(tableName);
- bulkCopy.writeToServer(crs);
- crs.close();
- bulkCopy.close();
- log.info("耗时:{},数量:{}", System.currentTimeMillis() - startTime, objects.size());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-
- // 数据查找,返回查找的内容,向上抛异常
- public ResultSet executeSQL(Connection con, String sql, Object... object) throws SQLException {
- PreparedStatement ps = con.prepareStatement(sql);
- for (int i = 0; i < object.length; i++) {
- //ps传入参数的下标是从1开始
- ps.setObject(i + 1, object[i]);
- }
- //返回结果集
- return ps.executeQuery();
- }
-
- public static java.sql.Date localTimeToDate(LocalDateTime lt) {
- return new java.sql.Date(lt.atZone(ZoneId.systemDefault()).toInstant()
- .toEpochMilli());
- }
因为我有很多张表(表名称不同,字段相同)都要走这个方法,所以我的表名是直接写死的,如果你不是这样的话可以在实体类上直接使用@Table()指定数据库表即可,无需传入表名。
直接复制粘贴代码就可以使用,调用的是saveBulkCopy方法,这个方法是sqlserver独有的新增方法,mybaits plus的批量新增对于sqlserver来说其实还是慢新增,而且也无法手写foreach新增,因为sqlserver对与参数是有限制的限制在2100个参数。
用以上方法记得要在实体类标记对应注解,我把我的sqlserver数据库变成了自增主键,也是需要打上注解的,所以无需担心,直接使用即可。
- package cn.iocoder.yudao.module.biz.dal.dataobject;
-
- import com.baomidou.mybatisplus.annotation.TableField;
- import lombok.Data;
-
- import java.math.BigDecimal;
- import java.util.Date;
-
- @Data
- public class SdyDataVisualDailySortResult {
- @TableField("id")
- private Integer id;
- @TableField("targetTime")
- private Date targetTime;
- @TableField("manager")
- private String manager;
-
-
- }
- public class ConnectionFactory {
- private static String DRIVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
- private static String URL = "你的数据库连接";
- private static String USER = "账号";
- private static String PASSWORD = "密码";
-
- /**
- * 提供getConnection()方法
- * @return Connection
- */
- public static Connection getConnection(){
- Connection conn = null;
- try {
- Class.forName(DRIVER);
- conn = DriverManager.getConnection(URL, USER, PASSWORD);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- return conn;
- }
- }
实测第一种方式新增40w+数据仅需30左右
- public void insertBach(List
dashboardsDOS) throws SQLException { - Connection conn = null;
- try {
- // SQLSERVER驱动
- Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
- // 情况2:使用批处理 ==> rewriteBatchedStatements=true ==> 每次50000条 耗时: 6秒
- conn = DriverManager.getConnection("数据库连接", "账号", "密码");
- //批量插入50000
- int batchSize = 50000;
- // 总条数1000000
- int count = dashboardsDOS.size();
- //设置自动提交为false
- conn.setAutoCommit(false);
- PreparedStatement ps = conn.prepareStatement("insert into " + "数据库表名" + " ("字段1","字段2","字段3") values ("值1","值2","值3")");
- Long t1 = System.currentTimeMillis();
- System.out.println("========开始运行=========");
- for (int i = 0; i < dashboardsDOS.size(); ++i) {
- //设置第一个参数的值为i
- ps.setString(1, dashboardsDOS.get(i).getStoreCode());
- ps.setString(2, dashboardsDOS.get(i).getStoreName());
- ps.setTimestamp(3, new Timestamp(format.parse(format.format(calendar.getTime())).getTime()));
- //将该条记录添加到批处理中
- ps.addBatch();
- if (i % batchSize == 0) {
- //执行批处理
- ps.executeBatch();
- //提交
- conn.commit();
- System.out.println(i + ":添加" + batchSize + "条");
- }
- }
- if (count % batchSize != 0) {
- ps.executeBatch();
- conn.commit();
- }
- ps.close();
- Long t2 = System.currentTimeMillis();
- System.out.println("总条数:" + count + "条 每次插入" + batchSize + "条 " + " 每次耗时:" + (t2 - t1) / 1000 + "秒");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- conn.close();
- }
- }
如果你也有时间类型的数据建议使用Timestamp来接收处理值,使用sql.Date会出现时间精度差异问题;
在开始的时候新增也是很快,基本40w+数据在40秒左右,但是后来由于业务需求,我在调用新增方法前将数据处理赋值了一下,之后40w+数据要在240秒左右,至今无法查明到底是为什么(只是将某个字段进行了重新赋值,传入后数据新增变慢,如果不处理该数据依旧很快)