首页 > 其他分享 >史上最强!Spring Boot 3.3 高效批量插入万级数据的多种方案

史上最强!Spring Boot 3.3 高效批量插入万级数据的多种方案

时间:2024-10-27 10:47:14浏览次数:3  
标签:users 批处理 Spring Boot 万级 插入 user SQL public

SpringBoot 3.3 多种方式实现高效批量插入万级数据,史上最强!

在大数据处理场景下,如何高效地将大量数据插入数据库是一个重要课题。本文基于SpringBoot 3.3及MyBatis-Plus,介绍几种高效的批量插入数据的方法,包括:

使用JDBC批处理

使用自定义SQL批处理

单条插入(for循环)

拼接SQL语句插入

MyBatis-Plus的saveBatch方法

循环插入 + 开启批处理模式

每种方式都会结合代码示例进行深入讲解,前端将展示每种插入方式的执行时间,帮助你直观了解每种方法的性能表现。

运行效果:

若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。

项目依赖配置(pom.xml)

首先,在项目的pom.xml中添加必要的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>batch-insert</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>batch-insert</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>17</java.version>
		<mybatis-plus-boot-starter.version>3.5.7</mybatis-plus-boot-starter.version>
        <mybatis-spring.version>3.0.3</mybatis-spring.version>
	</properties>
	<dependencies>
		<!-- Spring Boot Starter Web -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	
	   <dependency>
	        <groupId>com.baomidou</groupId>
	        <artifactId>mybatis-plus-boot-starter</artifactId>
	        <version>${mybatis-plus-boot-starter.version}</version>
	    </dependency>
	   
	     <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis-spring.version}</version>
      	</dependency>
        <!-- 数据库驱动依赖 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        
	
	    <!-- Thymeleaf for template rendering -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-thymeleaf</artifactId>
	    </dependency>
	
	    <!-- Other dependencies -->
	    <dependency>
	        <groupId>org.projectlombok</groupId>
	        <artifactId>lombok</artifactId>
	        <optional>true</optional>
	    </dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
配置文件(application.yml)

application.yml中,配置数据库连接信息:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    cache: false
数据库表结构(user 表的DDL)

在开始实现之前,我们需要创建一个用户表user。下面是其DDL语句:

CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    age INT NOT NULL
);
后端实现

使用JDBC批处理

原理

JDBC批处理通过PreparedStatementaddBatchexecuteBatch方法一次性提交多条SQL插入操作。这种方法直接利用JDBC的批处理机制,可以显著提高插入效率。

优点
  • 高性能:JDBC的批处理机制可以提高性能,减少数据库交互的次数。

  • 标准化:使用标准的JDBC API,不依赖于特定的ORM框架。

缺点
  • 需要手动管理:需要手动管理批处理的大小、事务等,代码复杂度较高。

  • 资源管理:需要确保JDBC连接和PreparedStatement的正确关闭,避免资源泄漏。

适用场景

适用于需要高性能插入操作的场景,特别是当需要直接控制数据库交互的细节时。

@Service
public class UserService {

    @Autowired
    private DataSource dataSource;

    public void insertUsersWithJdbcBatch(List<User> users) {
        String sql = "INSERT INTO user (name, age) VALUES (?, ?)";
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (User user : users) {
                ps.setString(1, user.getName());
                ps.setInt(2, user.getAge());
                ps.addBatch();
            }
            ps.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
使用自定义SQL批处理
原理

自定义SQL批处理通过使用JdbcTemplatebatchUpdate方法,将多个插入操作打包成一个批量操作一次性提交。这种方法结合了Spring的JdbcTemplate和自定义的SQL批处理。

优点
  • 灵活性:可以自定义SQL语句并批量处理,提高灵活性。

  • 简化操作:JdbcTemplate简化了JDBC操作,避免了复杂的手动资源管理。

缺点
  • SQL语句复杂性:需要编写和管理复杂的SQL语句,增加了维护成本。

  • 性能调优:需要控制批处理的大小和其他性能相关参数。

适用场景

适用于需要复杂插入逻辑和自定义SQL的场景,特别是当数据处理逻辑需要高度自定义时。

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insertUsersWithCustomBatch(List<User> users) {
        List<Object[]> batchArgs = new ArrayList<>();
        for (User user : users) {
            batchArgs.add(new Object[]{user.getName(), user.getAge()});
        }
        jdbcTemplate.batchUpdate("INSERT INTO user (name, age) VALUES (?, ?)", batchArgs);
    }
}
单条插入(for循环)
原理

单条插入方法通过遍历每个用户对象,逐个调用insert方法将数据插入到数据库中。这种方法通常在MyBatis-Plus中使用baseMapper.insert(user)实现。

优点
  • 简单直接:实现起来非常简单,不需要额外的配置或复杂的逻辑。

  • 调试方便:在调试阶段容易追踪每条记录的插入过程。

缺点
  • 性能较差:每次插入操作都涉及到一次数据库交互,这会导致大量的网络延迟和数据库操作开销。

  • 数据库压力大:对数据库的负载较重,可能会导致性能瓶颈,特别是当数据量很大时。

适用场景

适用于数据量较小或者对插入性能要求不高的场景。对于大批量数据插入,不建议使用这种方法。

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void insertUsersOneByOne(List<User> users) {
        for (User user : users) {
            userMapper.insert(user);
        }
    }
}

拼接SQL语句插入

原理

拼接SQL语句方法通过将所有插入记录拼接成一条长SQL语句,然后一次性提交到数据库。这种方法通过将多个插入操作合并到一个SQL语句中,可以大大减少数据库交互的次数。

优点
  • 性能提升:减少了与数据库的交互次数,从而提高了插入性能。

  • 数据库负载较轻:由于减少了网络和数据库操作的开销,数据库的负载相对较轻。

缺点
  • SQL长度限制:如果数据量过大,生成的SQL语句可能会超出数据库的长度限制。

  • SQL注入风险:需要确保拼接的SQL语句不会引入SQL注入风险,特别是在处理动态数据时。

适用场景

适用于数据量中等且对性能有一定要求的场景,但需要注意SQL长度和安全问题。

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void insertUsersBySql(List<User> users) {
        StringBuilder sql = new StringBuilder("INSERT INTO user (name, age) VALUES ");
        for (User user : users) {
            sql.append(String.format("('%s', %d),", user.getName(), user.getAge()));
        }
        sql.deleteCharAt(sql.length() - 1);
        userMapper.insertBySql(sql.toString());
    }
}

MyBatis-Plus的saveBatch方法

原理

saveBatch方法是MyBatis-Plus提供的批量插入方法。它使用批量插入的方式一次性插入多个记录。MyBatis-Plus内部会自动处理批量操作,通常使用JDBC的批处理功能。

优点
  • 性能较高:MyBatis-Plus内部优化了批量插入的实现,相比单条插入性能更好。

  • 简单易用:只需要调用一个方法,无需手动拼接SQL或处理数据库交互。

缺点
  • 批量大小限制:虽然性能较好,但仍然需要控制批量大小以避免过大的数据包造成问题。

  • 灵活性较低:如果需要复杂的批处理逻辑,saveBatch可能无法满足需求。

适用场景

适用于需要高效插入大量数据的场景,特别是当数据量较大且需要优化性能时。

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void saveUsersBatch(List<User> users) {
        userMapper.saveBatch(users);
    }
}

循环插入 + 开启批处理模式

原理

循环插入+批处理模式利用JDBC的批处理功能。通过在循环中将插入操作添加到批处理列表中,然后一次性提交所有操作。这种方法通常使用SqlSessionExecutorType.BATCH模式。

优点
  • 性能提升:使用批处理模式可以显著提高插入性能,因为它减少了数据库交互的次数。

  • 灵活性较高:可以在批处理过程中执行更复杂的操作。

缺点
  • 内存消耗:需要在内存中维护一个批处理列表,如果数据量过大,可能会导致内存消耗较高。

  • 配置复杂:需要配置JDBC批处理的相关参数,并管理事务。

适用场景

适用于需要处理大量数据且对性能要求较高的场景,尤其是当数据量特别大时。

@Service
public class UserService {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    public void insertUsersWithBatchProcessing(List<User> users) {
        SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            for (User user : users) {
                session.insert("com.example.UserMapper.insert", user);
            }
            session.commit();
        } finally {
            session.close();
        }
    }
}

完整项目实现:

MyBatis-Plus 实体类

首先,我们定义一个User实体类,用于映射数据库中的user表。

package com.icoderoad.batchinsert.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import lombok.Data;

@Data
@TableName("user")
public class User {
	
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

}

MyBatis-Plus Mapper 接口

定义一个UserMapper接口,继承自BaseMapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
    @Insert("${sql}")
    void insertBySql(@Param("sql") String sql);
}

Service 接口

定义一个UserService接口,包含多个插入方法。

package com.icoderoad.batchinsert.service;

import java.util.List;

import com.baomidou.mybatisplus.extension.service.IService;
import com.icoderoad.batchinsert.entity.User;

public interface UserService extends IService<User> {
    long insertUsersOneByOne(List<User> users);
    long insertUsersBySql(List<User> users);
    long saveUsersBatch(List<User> users);
    long insertUsersWithBatchProcessing(List<User> users);
    long insertUsersWithJdbcBatch(List<User> users);
    long insertUsersWithCustomBatch(List<User> users);
}

ServiceImpl 实现类

UserServiceImpl类实现UserService接口中的方法,每个方法返回执行时间。

package com.icoderoad.batchinsert.service.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.icoderoad.batchinsert.entity.User;
import com.icoderoad.batchinsert.mapper.UserMapper;
import com.icoderoad.batchinsert.service.UserService;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public long insertUsersOneByOne(List<User> users) {
        long startTime = System.currentTimeMillis();
        for (User user : users) {
            userMapper.insert(user);
        }
        return System.currentTimeMillis() - startTime;
    }

    @Override
    public long insertUsersBySql(List<User> users) {
        long startTime = System.currentTimeMillis();
        StringBuilder sql = new StringBuilder("INSERT INTO user (name, age) VALUES ");
        for (User user : users) {
            sql.append(String.format("('%s', %d),", user.getName(), user.getAge()));
        }
        sql.deleteCharAt(sql.length() - 1);
        userMapper.insertBySql(sql.toString());
        return System.currentTimeMillis() - startTime;
    }

    @Override
    public long saveUsersBatch(List<User> users) {
        long startTime = System.currentTimeMillis();
        saveBatch(users); // MyBatis-Plus 提供的批量插入方法
        return System.currentTimeMillis() - startTime;
    }

    @Override
    public long insertUsersWithBatchProcessing(List<User> users) {
        long startTime = System.currentTimeMillis();
        SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            for (User user : users) {
                session.insert("com.example.UserMapper.insert", user);
            }
            session.commit();
        } finally {
            session.close();
        }
        return System.currentTimeMillis() - startTime;
    }

    @Override
    public long insertUsersWithJdbcBatch(List<User> users) {
        long startTime = System.currentTimeMillis();
        String sql = "INSERT INTO user (name, age) VALUES (?, ?)";
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (User user : users) {
                ps.setString(1, user.getName());
                ps.setInt(2, user.getAge());
                ps.addBatch();
            }
            ps.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return System.currentTimeMillis() - startTime;
    }

    @Override
    public long insertUsersWithCustomBatch(List<User> users) {
        long startTime = System.currentTimeMillis();
        List<Object[]> batchArgs = new ArrayList<>();
        for (User user : users) {
            batchArgs.add(new Object[]{user.getName(), user.getAge()});
        }
        jdbcTemplate.batchUpdate("INSERT INTO user (name, age) VALUES (?, ?)", batchArgs);
        return System.currentTimeMillis() - startTime;
    }
}

InsertController 类

InsertController类中,每个方法调用UserService中的方法,并返回执行时间。

@RestController
@RequestMapping("/insert")
public class InsertController {

    @Autowired
    private UserService userService;

    @PostMapping("/single")
    public long insertSingle() {
        return userService.insertUsersOneByOne(getSampleUsers());
    }

    @PostMapping("/sql")
    public long insertSql() {
        return userService.insertUsersBySql(getSampleUsers());
    }

    @PostMapping("/saveBatch")
    public long saveBatch() {
        return userService.saveUsersBatch(getSampleUsers());
    }

    @PostMapping("/batchProcessing")
    public long batchProcessing() {
        return userService.insertUsersWithBatchProcessing(getSampleUsers());
    }

    @PostMapping("/jdbcBatch")
    public long jdbcBatch() {
        return userService.insertUsersWithJdbcBatch(getSampleUsers());
    }

    @PostMapping("/customBatch")
    public long customBatch() {
        return userService.insertUsersWithCustomBatch(getSampleUsers());
    }

    private List<User> getSampleUsers() {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            users.add(new User("user" + i, i));
        }
        return users;
    }
}
前端实现

在前端,我们通过Thymeleaf模板引擎和Bootstrap来展示各种插入方法,并显示其执行时间。

在 src/main/resources/templates 目录下创建 index.html 文件:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>批量插入数据演示</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"/>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
    <h1>批量插入数据演示</h1>

    <div>
        <button id="btnSingleInsert" class="btn btn-primary">单条插入</button>
        <div id="timeSingleInsert" class="mt-2 alert alert-info" style="display:none;"></div>
    </div>

    <div>
        <button id="btnSqlInsert" class="btn btn-secondary">拼接SQL插入</button>
        <div id="timeSqlInsert" class="mt-2 alert alert-info" style="display:none;"></div>
    </div>

    <div>
        <button id="btnSaveBatch" class="btn btn-success">saveBatch插入</button>
        <div id="timeSaveBatch" class="mt-2 alert alert-info" style="display:none;"></div>
    </div>

    <div>
        <button id="btnBatchProcessing" class="btn btn-warning">批处理模式插入</button>
        <div id="timeBatchProcessing" class="mt-2 alert alert-info" style="display:none;"></div>
    </div>

    <div>
        <button id="btnJdbcBatch" class="btn btn-info">JDBC批处理插入</button>
        <div id="timeJdbcBatch" class="mt-2 alert alert-info" style="display:none;"></div>
    </div>

    <div>
        <button id="btnCustomBatch" class="btn btn-danger">自定义SQL批处理插入</button>
        <div id="timeCustomBatch" class="mt-2 alert alert-info" style="display:none;"></div>
    </div>
</div>

<script>
    $(document).ready(function () {
        function measureTime(url, timeId) {
            $.post(url, function (data) {
                // Convert milliseconds to seconds and format to 2 decimal places
                var seconds = (data / 1000).toFixed(2);
                $("#" + timeId).html("执行时间:" + seconds + " 秒").show();
            });
        }

        $("#btnSingleInsert").click(function () {
            measureTime("/insert/single", "timeSingleInsert");
        });

        $("#btnSqlInsert").click(function () {
            measureTime("/insert/sql", "timeSqlInsert");
        });

        $("#btnSaveBatch").click(function () {
            measureTime("/insert/saveBatch", "timeSaveBatch");
        });

        $("#btnBatchProcessing").click(function () {
            measureTime("/insert/batchProcessing", "timeBatchProcessing");
        });

        $("#btnJdbcBatch").click(function () {
            measureTime("/insert/jdbcBatch", "timeJdbcBatch");
        });

        $("#btnCustomBatch").click(function () {
            measureTime("/insert/customBatch", "timeCustomBatch");
        });
    });
</script>
</body>
</html>
启动项目并测试

启动Spring Boot应用,访问http://localhost:8080/,点击不同类型的插入按钮,大家将看到插入的执行时间展示在页面上。在实际应用中,我们对不同的批量插入方式进行了性能测试。以下是测试结果:

  • JDBC批处理插入:执行时间:1.59 秒

  • 自定义SQL批处理插入:执行时间:1.13 秒

  • 单条插入:执行时间:3.10 秒

  • 拼接SQL插入:执行时间:0.21 秒

  • saveBatch插入:执行时间:1.60 秒

  • 批处理模式插入:执行时间:1.16 秒

从测试结果可以看出,各种插入方式在性能上有明显差异。拼接SQL插入方式在这次测试中表现最好,执行时间最短,而单条插入方式执行时间最长。其他方式的执行时间也有所不同,但都比单条插入要优越。

总结

在这篇文章中,我们深入探讨了几种在SpringBoot 3.3中实现高效批量插入数据的方法,包括JDBC批处理、自定义SQL批处理、单条插入、拼接SQL、MyBatis-Plus的`saveBatch和循环插入+批处理。每种方法都具有独特的优点和适用场景,在实际开发中可以根据需求选择最合适的方法。

通过前端页面的演示,用户可以方便地比较每种方法的执行时间,直观地了解各自的性能表现。这种全栈式的实现方式,结合了后端高效数据处理和前端直观展示,能够帮助开发者快速构建高性能的数据处理应用。

标签:users,批处理,Spring,Boot,万级,插入,user,SQL,public
From: https://blog.csdn.net/m0_71190605/article/details/143218514

相关文章

  • SpringBoot母婴店购物系统9j5v8程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,商品种类,母婴商品,店员开题报告内容一、研究背景与目的随着母婴市场的蓬勃发展,消费者对购物便捷性、商品质量与个性化服务的需求日益增长。传统母婴店面......
  • SpringBoot秒杀系统实现asgyk--程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,商家,商铺信息,商品信息,限时秒杀,商品分类,顾客咨询,抢购提醒,营业统计开题报告内容一、研究背景秒杀活动作为电商平台的常见促销手段,可以极大提升用户......
  • SpringBoot面向网络直播平台的推荐系统y9tf8(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、课题背景随着互联网技术的飞速发展,网络直播已成为人们日常生活中不可或缺的一部分。然而,面对海量且日益增长的直播内容,用户往往难以快速找到符......
  • SpringBoot面向爱宠人群的宠物资讯系统36as8--(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表用户,宠物资讯,宠物知识开题报告内容一、选题背景与意义随着生活水平的提高和独居人口的增加,宠物已成为许多家庭的重要成员。宠物经济的蓬勃发展催生了对宠物......
  • 一段简单实用的PbootCMS页码显示样式代码
    {pboot:if({page:rows}>0)}<divclass="pagebar"><divclass="pagination"><aclass="page-itempage-linkhidden-sm"href="{page:index}"title="首页">首页</a><aclass=&quo......
  • PbootCMS模板当前栏目标签
    当前栏目标签适用范围:在列表页或详情页使用。标签作用:用于输出当前栏目的相关信息。示例代码:html {sort:tcode}当前栏目的顶级栏目编码{sort:topname}当前栏目的顶级栏目名称{sort:toplink}当前栏目的顶级栏目链接{sort:pcode}当前栏目的父栏目编码{sort:parentname......
  • PbootCMS后台帐号密码忘记了怎么办?
    方法一:通过后台找回密码功能访问后台登录页面打开浏览器,访问 http://www.domain.com/admin.php。点击“忘记密码”在登录页面上,点击“忘记密码”链接。输入邮箱或手机号输入注册时使用的邮箱或手机号。接收验证码按照提示操作,接收验证码。重置密码......
  • PbootCMS错误提示:执行SQL发生错误!错误:no such column: def1
    原因:升级过程中SQL语句未执行成功。解决方案:执行以下SQL语句:----------------------------------Sqlite数据库升级脚本--适用于PbootCMS3.0.0版本升级至3.0.6------------------------------------新增多图标题字段ALTERTABLEay_contentADDCOLUM......
  • Spring 版本更新
    ‌目前,SpringFramework的最新版本是6.0.0‌‌1。此外,SpringBoot的最新版本是3.4.0,该版本提供了许多新特性和缺陷修复,例如更新@ConditionalOnSingleCandidate以处理后备bean,以及在启用虚拟线程的情况下配置SimpleAsyncTaskScheduler类‌2。SpringFramework的历史版本Spr......
  • 【软件源码】eHR人力资源管理系统:功能强大的人力资源管理系统(Springboot+vue)
    eHR人力资源管理系统:功能强大的人力资源管理工具随着企业规模的不断扩大和业务需求的多样化,传统的人力资源管理模式已无法满足现代企业的需求。eHR人力资源管理系统作为一种先进的管理工具,能够为企业提供高效、准确、实时的人力资源管理。本文将介绍eHR人力资源管理系统的主要......