spring boot + sql server大数据量批量新增

1.SQLServerBulkCopy方式(推荐这种没什么诡异的bug)

代码
    /**
     * 获取类中所有属性注解@TableId和@TableField
     *
     * @param instance
     * @return
     * @throws NoSuchFieldException
     */
    public static Map<String, String[]> getDeclaredFieldsInfo(Object instance) throws NoSuchFieldException {
        Map<String, String[]> 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 <T>
     */
    public <T> void saveBulkCopy(List<T> 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<String, String[]> 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<String, Object> 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;


}
ConnectionFactory 建立连接方法
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左右

2.原始jdbc方式(有诡异bug,技术不够无法查明原因)

代码
public void insertBach(List<DashboardsDO> 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会出现时间精度差异问题;

诡异bug

在开始的时候新增也是很快,基本40w+数据在40秒左右,但是后来由于业务需求,我在调用新增方法前将数据处理赋值了一下,之后40w+数据要在240秒左右,至今无法查明到底是为什么(只是将某个字段进行了重新赋值,传入后数据新增变慢,如果不处理该数据依旧很快)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
非常感谢您的提问。以下是对于Java Spring Boot,PostgreSQL和Redis技术框架的描述。 1. Java Spring Boot Java Spring Boot是一个基于Spring框架的开源Java应用程序开发框架。它旨在简化Spring应用程序的配置和部署。Spring Boot可以大大简化Spring应用程序的开发过程,因为它可以自动配置很多东西。Spring Boot提供了很多功能,比如可嵌入的Tomcat服务器,自动配置的数据访问库等等。使用Spring Boot可以快速构建生产就绪的Java应用程序。 2. PostgreSQL PostgreSQL是一个高性能,可扩展的开源关系型数据库管理系统。它支持SQL标准并提供了很多高级功能,比如事务,崩溃恢复等等,可以处理大并发连接。除此之外,它还支持复杂数据类型,如JSON和XML。PostgreSQL非常灵活和可定制,可以集成到各种应用程序中,以提供持久化和数据存储。 3. Redis Redis是一个基于内存的键值对存储系统,可用于数据缓存,以提高应用程序的性能。Redis还提供了许多数据结构,如列表,集合,哈希表等等。Redis很容易扩展并具有高可用性,因为它支持主从复制和分片机制。Redis还支持事务和Lua脚本,可以用于复杂操作。 以上是对于Java Spring Boot,PostgreSQL和Redis技术框架的简要描述。希望能够帮助您更好地了解这些技术,并应用它们于实际项目中。如果您还有其他问题,请随时向我提问,我会尽力回答。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值