文件的上传和下载

Service层服务层

service层
//文件上传
void importData(MultipartFile multipartFile);
//文件上传2(同时添加上传文件时给数据库添加的通道id)
void importData1(MultipartFile multipartFile, Long id);
//文件下载
void exportData(HttpServletResponse response);

serviceImpl层

文件上传

 //文件上传
    @Override
    public void importData(MultipartFile multipartFile) {
​
        if (multipartFile.isEmpty()) {
            throw new GuiguException("0x500","文件为空");
        }
​
        ExcelListener<FunctionExcelVo> excelListener = new ExcelListener<>(functionMapper);
​
        try {
            EasyExcel.read(multipartFile.getInputStream(), FunctionExcelVo.class, excelListener)
                    .sheet().doRead();
        } catch (Exception e) {
            e.printStackTrace();
            throw new GuiguException("0x501","文件格式错误");
        }
    }
​
    //文件上传(同时添加上传文件时给数据库添加的通道id)
    @Override
    public void importData1(MultipartFile multipartFile, Long id) {
​
        if (multipartFile.isEmpty()) {
            throw new GuiguException("0x500","文件为空");
        }
​
        // 读取文件数据
        List<FunctionExcelVo1> dataList;
        try {
            dataList = EasyExcel.read(multipartFile.getInputStream()).head(FunctionExcelVo1.class).sheet().doReadSync();
        } catch (Exception e) {
            e.printStackTrace();
            throw new GuiguException("0x501","文件格式错误");
        }
​
        // 为每条数据添加通道 ID
        for (FunctionExcelVo1 functionExcelVo1 : dataList) {
            functionExcelVo1.setChannelId(id);
        }
​
        // 保存数据到数据库
        functionMapper.saveData1(dataList);
    }

逐句解释(文件上传)

@Override
public void importData(MultipartFile multipartFile) {

这是一个java方法的声明,该方法名为'importData',它接收一个‘MultipartFile’类型的参数。‘@Override’注解表明该方法是覆盖(或实现)父类或接口中的同名方法。

    if (multipartFile.isEmpty()) {
        throw new GuiguException("0x500","文件为空");
    }

这段代码是用来检查文件是否为空。如果为空,就会抛出一个自定义异常‘GuiguException’,异常代码为“0x500”,异常信息为“文件为空”

ExcelListener<FunctionExcelVo> excelListener = new ExcelListener<>(functionMapper);

这里创建一个'ExcelListener'对象,用来监听Excel文件的读取。‘< FunctionExcelVo >’指定了读取的Excel行的类型,而“functionMapper”是在构造‘ExcelListenter’对象时传入的。

try {
        EasyExcel.read(multipartFile.getInputStream(), FunctionExcelVo.class, excelListener)
                .sheet().doRead();
    } catch (Exception e) {
        e.printStackTrace();
        throw new GuiguException("0x501","文件格式错误");
    }

这段带代码使用了EasyExcel库来读取Excel文件。它从‘multipartFile’中获取输入流,并将其传递给EasyExcel的‘read’方法。‘FunctionExcelVo.class’指定了Excel中每一行的数据类型。‘excelListener’则作为监听器传递给EasyExcel,以便处理读取的数据。

  • 在EasyExcel库中,.sheet().doRead()用于指定读取Excel文件的操作。让我们来详细解释它的用途:

    • .sheet():这个方法用于指定的读取的Excel工作表(sheet)。在大多数情况下,Excel文件包含多个工作表,通过‘.sheet’方法可以指定要读取的特定工作表。如果不指定,EasyExcel默认读取第一个工作表。

    • .doRead():这个方法执行实际的读取操作。它会启动Excel文件的读取,并将读取的数据通过之前设置的监听器进行处理。通过ExcelListenter监听器处理读取到的数据。

  • 综合起来,.sheet(),.doRead()的作用是指定要读取的Excel工作表,并执行读取操作。它会将读取到的数据传递给之前设置的监听器进行处理

如果读取过程中出现异常,捕获并打印异常信息,然后抛出一个自定义异常‘GuiguException’,异常代码‘0x500’,异常信息为“文件格式错误”。整体来说,这段代码的作用是从一个上传的Excel文件中读取数据,并将其保存到系统中。它确保文件不为空,并且通过EasyExcel库来读取Excel数据,同时处理可能出现的异常情况。

逐句解释(文件上传2+id)

@Override
public void importData1(MultipartFile multipartFile, Long id) {

这是一个Java方法的声明,名为importData1,它接受两个参数:一个是MultipartFile类型的上传文件,另一个是一个长整型的通道ID。

if (multipartFile.isEmpty()) {
    throw new GuiguException("0x500","文件为空");
}

这段代码检查上传的文件是否为空,如果为空,则抛出自定义异常GuiguException,异常代码为0x500,异常信息为"文件为空"。

List<FunctionExcelVo1> dataList;
try {
    dataList = EasyExcel.read(multipartFile.getInputStream()).head(FunctionExcelVo1.class).sheet().doReadSync();
} catch (Exception e) {
    e.printStackTrace();
    throw new GuiguException("0x501","文件格式错误");
}

这里使用EasyExcel都读取上传的Excel文件数据,并将其转换为‘FunctionExcelVo1’对象的列表,.head(FunctionExcelVo1.class)指定了Excel文件的表头。.sheet()用于指定要读取的工作表(默认为第一个工作表),.doReadSync()表示同步读取文件数据。如果读取过程中出现异常,将会捕获异常并抛出自定义异常GuiguException,异常代码为0x501,异常信息为"文件格式错误"。

for (FunctionExcelVo1 functionExcelVo1 : dataList) {
    functionExcelVo1.setChannelId(id);
}

这个循环遍历读取到的数据列表,并为每一条数据设置通道ID,以确保每条数据都与指定的通道相关联。

functionMapper.saveData1(dataList);

最后,将处理过的数据列表保存到数据库中,通过functionMapper对象调用saveData1方法来实现。

整体来说,这段代码的作用是将上传的Excel文件数据保存到数据库中,并在保存数据之前为每条数据设置指定的通道ID。

(文件上传)与(文件上传2+id)的不同

.doRead()与.doReadSync()区别
  1. 异步读取 (doRead()):

    • doRead()方法是异步执行的,意味着它会在后台线程中读取数据,并通过回调函数来处理读取到的数据。

    • 在使用doRead()方法时,你需要提供一个实现了ReadListener接口的监听器,用于处理读取到的数据。

    • 异步读取适用于大型数据集,可以提高读取性能,但相对复杂一些,因为需要处理多线程和异步回调。

  2. 同步读取 (doReadSync()):

    • doReadSync()方法是同步执行的,即它会阻塞当前线程,直到读取完整个Excel文件并返回所有数据。

    • 同步读取不需要使用监听器,读取完成后会直接返回数据列表。

    • 同步读取适用于数据量较小的情况,代码相对简单,但可能会影响性能,特别是在读取大型文件时,可能会导致程序响应变慢或阻塞。

因此,区别主要在于执行方式和是否需要使用监听器。如果需要在读取过程中执行一些自定义的逻辑或处理大量数据,推荐使用异步读取,并提供相应的监听器。如果只是简单地读取数据并将其保存到数据库,可以选择同步读取。

那是否可以调换

能,你可以将 .doReadSync() 替换为 .doRead()。但是,需要考虑以下几点:

  1. 性能影响

    • 使用 .doRead() 而不是 .doReadSync() 将读取行为改为异步模式。异步读取可以在读取文件时不阻塞当前线程,从而潜在地提高性能。但是,它也会增加复杂性,特别是在处理每一行数据的回调时。

  2. 错误处理

    • 异步读取可能需要不同的错误处理机制。在同步读取(.doReadSync())中,异常可以在 try-catch 块中立即捕获和处理。然而,在异步读取(.doRead())中,错误处理通常涉及到回调或监听完成事件,这可能根据具体情况更为复杂。

  3. 线程注意事项

    • 异步读取可能涉及多线程,如果处理不当可能会引入并发问题。确保在异步上下文中访问的任何共享资源都是线程安全的。

如果决定使用 .doRead() 而不是 .doReadSync(),请确保适当处理异步行为,包括错误处理和潜在的线程注意事项。

dataList = EasyExcel.read(multipartFile.getInputStream()).head(FunctionExcelVo1.class).sheet().doReadSync();与 EasyExcel.read(multipartFile.getInputStream(), FunctionExcelVo.class, excelListener).sheet().doRead();不同

这两种读取方式之间的主要区别在于同步与异步读取。(文件上传2+id)主要是有添加id的原因,需要用到数据列表。

文件下载

//文件导出(下载)
    @Override
    public void exportData(HttpServletResponse response) {
        //1.设置响应头信息和其他信息
        try {
            // 设置响应结果类型
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
​
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("分类数据", "UTF-8");
​
            //设置响应头
            response.setHeader("Content-disposition","attachment;filename=" + fileName + ".xlsx");
            response.setHeader("Access-Control-Expose-Headers","Content-disposition");
            //查询所有分类,返回list集合
            List<Stu> stuList = functionMapper.findAll();
​
            //最终数据list集合
            List<FunctionExcelBo> functionExcelBoList = new ArrayList<>();
​
            for (Stu stu : stuList) {
                FunctionExcelBo functionExcelBo = new FunctionExcelBo();
                BeanUtils.copyProperties(stu, functionExcelBo);
                functionExcelBoList.add(functionExcelBo);
            }
​
            //写入操作
            EasyExcel.write(response.getOutputStream(), FunctionExcelBo.class)
                    .sheet("数据").doWrite(functionExcelBoList);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

逐句讲解(文件下载)

这段代码是一个Java方法,用于将数据导出为Excel文件并通过HTTP响应下载。

  1. response.setContentType("application/vnd.ms-excel"); - 设置响应的内容类型为Excel文件。

  2. response.setCharacterEncoding("utf-8"); - 设置响应的字符编码为UTF-8,以支持中文字符。

  3. String fileName = URLEncoder.encode("分类数据", "UTF-8"); - 对文件名进行URL编码,以防止中文乱码。这里的文件名是“分类数据”。

  4. response.setHeader("Content-disposition","attachment;filename=" + fileName + ".xlsx"); - 设置响应头,指定文件名并声明为附件,告知浏览器以附件形式下载。

  5. response.setHeader("Access-Control-Expose-Headers","Content-disposition"); - 设置响应头,暴露Content-disposition字段,以便客户端可以读取并处理该响应头信息。

  6. List<Stu> stuList = functionMapper.findAll(); - 从数据库中查询所有分类数据,返回学生列表。

  7. List<FunctionExcelBo> functionExcelBoList = new ArrayList<>(); - 创建一个用于存储Excel数据对象的列表。

  8. BeanUtils.copyProperties(stu, functionExcelBo); - 将每个学生对象(Stu)转换为对应的Excel数据对象(FunctionExcelBo)。

  9. EasyExcel.write(response.getOutputStream(), FunctionExcelBo.class).sheet("数据").doWrite(functionExcelBoList); - 使用EasyExcel将Excel数据对象列表写入到输出流中,并生成Excel文件。.sheet("数据")表示在Excel中创建一个名为“数据”的工作表。

总的来说,这段代码实现了从数据库查询数据到生成Excel文件并提供下载的功能。

 
response.setHeader("Content-disposition","attachment;filename=" + fileName + ".xlsx");
response.setHeader("Access-Control-Expose-Headers","Content-disposition");

这两行代码分别设置了HTTP响应头中的两个字段:

  1. Content-disposition:这个响应头字段告诉浏览器如何处理服务器端返回的响应数据。在这段代码中,通过设置Content-dispositionattachment,告诉浏览器将响应内容作为附件下载,而不是在浏览器中直接显示。filename参数指定了下载的文件名,.xlsx表示下载的文件是一个Excel文件。这样设置之后,浏览器会提示用户下载文件,并将文件保存到本地。

  2. Access-Control-Expose-Headers:这个响应头字段用于CORS(跨域资源共享)设置。在这段代码中,通过设置Access-Control-Expose-HeadersContent-disposition,告诉浏览器在处理跨域请求时,允许前端JavaScript代码访问Content-disposition字段。这是因为某些情况下,跨域请求的JavaScript代码默认是无法访问所有的响应头字段的,但通过设置Access-Control-Expose-Headers,可以显式地将指定的响应头字段暴露给JavaScript代码。

Mapper层

    //插入数据到数据库(上传文件)
    void saveData(List<FunctionExcelVo> cachedDataList);
    //插入数据到数据库(文件上传2+id)
    void saveData1(List<FunctionExcelVo1> cachedDataList);
    //查询所有(文件下载)
    List<Stu> findAll();

mapper.xml层

 <!-- 插入数据到数据库(上传文件) -->
    <insert id="saveData">
        INSERT INTO
        student
        (stu_id,stu_name)
        VALUES
        <foreach collection="list" item="cachedDataList" separator=",">
            (#{cachedDataList.stuId}, #{cachedDataList.stuName})
        </foreach>
    </insert>
​
​
    <insert id="saveData1" parameterType="java.util.List">
        INSERT INTO student (stu_id, stu_name, channel_id)
        VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.stuId}, #{item.stuName}, #{item.channelId})
        </foreach>
    </insert>
    <!-- 查询所有学生(用于文件下载) -->
    <select id="findAll" resultType="com.tlm.people.entity.Stu">
        SELECT
            id,stu_id,stu_name,status
        FROM
            student
    </select>

Foreach知识点

<foreach> 标签是 MyBatis 中用于循环遍历集合或数组的标签,用于在 SQL 语句中动态生成多个相似的 SQL 片段。以下是 <foreach> 标签的一些关键知识点:

  1. 遍历集合或数组

    • <foreach> 标签通过 collection 属性指定要遍历的集合或数组的属性名或表达式。MyBatis 将会根据这个属性从对应的对象中获取数据进行遍历。

  2. 别名设置

    • 使用 item 属性可以指定在每次迭代中当前元素的别名。在 SQL 语句中可以使用这个别名引用当前遍历到的元素。

  3. 分隔符设置

    • 通过 separator 属性可以指定在迭代过程中每个元素之间的分隔符。这个分隔符会在生成的 SQL 片段中的每两个元素之间插入。

  4. 索引变量

    • 如果遍历的是数组,可以通过 index 属性设置索引变量的别名。索引变量是当前遍历到的元素在数组中的索引值。

  5. 集合参数

    • MyBatis 在执行 SQL 语句时会将整个集合作为参数传递给 SQL 语句,而不是每次迭代只传递一个元素。因此,在 SQL 语句中可以直接引用整个集合,而不需要在迭代过程中处理单个元素。

  6. 动态 SQL 生成

    • <foreach> 标签通常用于在 SQL 语句中动态生成多个 SQL 片段,例如批量插入或更新数据,动态 IN 查询等场景。

使用 <foreach> 标签可以灵活处理集合或数组数据,动态生成符合需求的 SQL 片段,从而实现更加灵活和高效的 SQL 操作。

案例

以下是一个示例展示如何在 MyBatis 中使用 <foreach> 标签进行动态 SQL 生成的案例:

假设有一个 User 实体类,包含 idnameage 属性。现在我们想要根据一组用户 ID 批量查询用户信息。

首先,我们需要定义一个 Mapper 接口,包含一个查询方法:

public interface UserMapper {
    List<User> findUsersByIds(List<Long> userIds);
}

然后,在对应的 Mapper XML 文件中编写 SQL 语句:

<select id="findUsersByIds" parameterType="java.util.List" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="list" item="userId" open="(" separator="," close=")">
        #{userId}
    </foreach>
</select>

在这个示例中,我们使用了 <foreach> 标签来动态生成 IN 子句中的用户 ID 列表。关键点如下:

  • collection="list":指定了要遍历的集合属性名为 list,这个集合包含了一组用户 ID。

  • item="userId":指定了在每次迭代中当前元素的别名为 userId,即当前遍历到的用户 ID。

  • open="("separator=","close=")":这三个属性分别定义了在迭代过程中 SQL 语句的开头、元素之间的分隔符、结尾,用于构建完整的 IN 子句。

这样,当调用 findUsersByIds 方法时,传入一组用户 ID,MyBatis 将会根据传入的 ID 动态生成 SQL 语句,并查询匹配的用户信息。

插入数据到数据库(上传文件)

  • <insert id="saveData">...</insert>: 这是一个MyBatis的插入语句,它的ID为"saveData",意味着可以通过这个ID在代码中调用这段插入语句。

  • INSERT INTO student (stu_id, stu_name) VALUES: 这是插入语句的标准格式,用于向名为"student"的表中插入数据,数据列为"stu_id"和"stu_name"。

其中

<foreach collection="list" item="cachedDataList" separator=",">
            (#{cachedDataList.stuId}, #{cachedDataList.stuName})
</foreach>
  • <foreach>: 这是MyBatis提供的一个迭代标签,用于遍历集合中的元素,并在SQL语句中动态生成相应的语句。

    • collection="list": 这里的"list"是指在调用插入语句时传入的参数列表,通常是一个包含多个对象的列表。

    • item="cachedDataList": 这里的"cachedDataList"是遍历过程中每个对象的别名,在每次迭代时代表当前遍历到的对象。

    • separator=",": 这个属性指定了在迭代过程中每个对象之间的分隔符,通常是逗号。

  • (#{cachedDataList.stuId}, #{cachedDataList.stuName}): 这是插入语句中的值部分,通过MyBatis的占位符#{}来引用对象的属性。cachedDataList.stuIdcachedDataList.stuName分别表示遍历到的对象中的"stuId"和"stuName"属性。

插入数据到数据库(文件上传2+id)

  • <insert id="saveData1" parameterType="java.util.List">:这里定义了一个名为 "saveData1" 的插入操作,它接受一个名为 "list" 的参数,参数类型为 java.util.List,这个列表中包含了要插入数据库的对象。

  • INSERT INTO student (stu_id, stu_name, channel_id) VALUES:这是 SQL 插入语句的开始部分,指定了要插入数据的表名以及列名。

其中

  
 <foreach collection="list" item="item" separator=",">
            (#{item.stuId}, #{item.stuName}, #{item.channelId})
   </foreach>
  • <foreach collection="list" item="item" separator=",">:这是 MyBatis 中用于遍历集合的标签。collection="list" 指定了要遍历的集合对象名为 "list",item="item" 表示在每次迭代中当前元素的别名为 "item",separator="," 表示在迭代过程中每个元素之间使用逗号分隔。

  • (#{item.stuId}, #{item.stuName}, #{item.channelId}):这是插入语句中的值部分,通过 MyBatis 的占位符 #{} 来引用对象的属性。#{item.stuId}#{item.stuName}#{item.channelId} 分别表示每个对象中的 "stuId"、"stuName" 和 "channelId" 属性。

监听器:

//监听器
public class ExcelListener<T> implements ReadListener<T> {
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    //构造传递mapper,操作数据库
    private FunctionMapper functionMapper;
    public ExcelListener(FunctionMapper functionMapper) {
        this.functionMapper = functionMapper;
    }
    public List<T> getCachedDataList() {
        return cachedDataList;
    }
    public ExcelListener() {
    }
    /**
     * 解析数据
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context analysis context
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        //把每行数据放到集合中
        cachedDataList.add(data);
        //达到临界值,就去存储依次,防止数据库内存溢出
        if(cachedDataList.size() >= BATCH_COUNT) {
            //批量把数据添加到数据库
            saveData();
            //清理集合
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //保存数据
        saveData();
    }
    //保存的方法
    void saveData() {
        functionMapper.saveData((List<FunctionExcelVo>)cachedDataList);
    }
​
}

逐句解释

//监听器
public class ExcelListener<T> implements ReadListener<T> {

这里是定义一个监听器的类“ExcelListener”,它实现了EasyExcel库中的“ReadListener”接口,泛型‘T’表示读取的数据类型。

private static final int BATCH_COUNT = 100;

这是一个常量,表示每读取100条数据就会执行一次批量保存操作。

private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

这里创建了一个缓存数据的列表,用于存储读取的数据。

ListUtils.newArrayListWithExpectedSize(BATCH_COUNT):用于创建一个初始容量为’BATCH_COUNT‘的空列表

private FunctionMapper functionMapper;

这是一个数据访问的mybits中的mapper,用于操作数据库。

public ExcelListener(FunctionMapper functionMapper) {this.functionMapper = functionMapper;}

这是一个监听器的构造方法,用于传入一个数据访问对象,以便于在监听器中进行数据库操作。

   @Override
   public void invoke(T data, AnalysisContext context) {
        //把每行数据放到集合中
       cachedDataList.add(data);
        //达到临界值,就去存储依次,防止数据库内存溢出
       if(cachedDataList.size() >= BATCH_COUNT) {
            //批量把数据添加到数据库
           saveData();
            //清理集合(只是创建一个新的列表)
           cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

在这段代码中,为什么使用cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);而不是cachedDataList.clear();的主要原因是为了提高性能。

解释:

  • 性能考虑:

    • cachedDataList.clear()方法会清空列表中的所有元素,但他并不会减少列表的容量。这意味着,虽然列表中的元素被清空了,但列表仍然占用相同的内存空间。

    • 如果不重新创建一个新的列表实例,而是使用cachedDataList.clear()来清空列表,那么在之后的数据添加操作中,当列表的大小再次达到临界值时,会触发的扩容操作。这会导致重新分配内存空间,可能会引起性能损失。

  • 减少内存分配开销:

    • 相比之下,ListUtils.newArrayListWithExpectedSize(BATCH_COUNT)会创建一个初始容量为BATCH_COUNT的新列表实例。由于预先知道了列表的初始容量,这可以避免在之后添加元素时的内部扩容操作,从而减少了内存分配的开销。

  • 更好的内存管理:

    • 重新创建一个新的列表实例,可以确保之前的列表实例及其所持有的对象被及时释放,从而更好地管理内存,防止内存泄漏。

因此,为了避免潜在的性能问题和更好地管理内存,代码选择重新创建一个新的列表实例来清理集合。

cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

这里是当缓存数据列表‘cachedDataList’达到了一定的数量(即‘BATCH_COUNT’),就会执行批量保存操作,并清除列表以释放内存。这里使用`ListUtils.newArrayListWithExpectedSize(BATCH_COUNT)‘重新创建一个新的列表实例,其初始容量为’(BATCH_COUNT)‘,以便重新开始缓存新的数据。

原因如下:

  • 存储管理:随者程序运行,缓存的数据列表会不断增长,可能占用大量内存。通过清空列表,可以释放已经使用的内存,防止内存占用过高导致内存溢出或性能下降。

  • 避免内存泄漏:如果不清空列表,而是一直是用列表实例,即使其中的数据被处理完毕,但由于列表对象仍然存在于内存中,并持有数据对象的引用,这可能导致内存泄漏问题。

  • 提高性能:重新创建一个新的列表实例,可以避免频繁的内存扩容操作,从而提高程序的性能。

清理集合的操作旨在有效地管理内存,避免内存泄漏,并提高程序的性能。

这个方法是在读取Excel文件时调用的,他将每一行的数据添加到缓存数据列表中,并在达到一定数量时执行批量保存操作,以防止数据内存溢出。

@Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //保存数据
        saveData();
    }

这个方法在所有数据解析完毕后调用,他确保最后一批次数据保存到数据库中。

    void saveData() {
        functionMapper.saveData((List<FunctionExcelVo>)cachedDataList);
    }

这是一个私有方法,用于将缓存的数据批量保存到数据库中。

整体上,这个监听器的作用是在读取Excel文件时对数据进行处理,并在达到一定数量时批量保存到数据库中,以防止内存溢出。

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值