You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Spring Boot如何实现类似PHP Laravel的数据库seeder数据填充功能

Spring Boot H2测试数据填充方案

现有代码问题排查

最后一个版本数据行数不稳定的核心原因

你在循环外仅实例化了一次Employee和Project对象,循环过程中始终复用这两个实例修改属性,每次调用save()都是更新同一条记录而非新增,加上随机数重复的情况,最终保存的记录数自然会少于预期。

第二个循环版本启动失败的原因

你生成随机数的逻辑是random.nextInt((max - min) + 1),返回的最大值等于max,但数组下标最大为max-1,会触发数组越界异常导致运行失败。

合理的实现方案

1. 填充位置说明

CommandLineRunner是Spring Boot做开发环境数据填充的合理位置,更推荐将填充逻辑拆分到独立的Seeder类中,配合注解控制生效环境和执行顺序,避免所有逻辑都塞在启动类中。

2. 类Laravel Factory/Seeder实现

我们可以通过自定义工厂类封装实体生成逻辑,单独的Seeder类处理数据填充和关联绑定,完全可以实现和Laravel一致的灵活填充能力:

第一步:定义实体工厂类(对应Laravel Factory)

// EmployeeFactory.java 员工工厂,封装随机生成逻辑
@Component
public class EmployeeFactory {
    private final Faker faker = new Faker(Locale.US);

    // 生成单个员工实体
    public Employee create() {
        return new Employee(
            faker.name().firstName(),
            faker.name().lastName(),
            faker.internet().safeEmailAddress()
        );
    }

    // 批量生成员工实体
    public List<Employee> createBatch(int count) {
        return IntStream.range(0, count)
                .mapToObj(i -> create())
                .toList();
    }
}

同理创建ProjectFactory封装项目实体生成逻辑。

第二步:定义开发环境数据填充类(对应Laravel Seeder)

// DevDataSeeder.java 仅在开发环境生效的填充类
@Component
@Profile("dev") // 仅dev环境执行,生产环境自动跳过
@Order(2) // 数字越小优先级越高,如果有基础数据填充可以设置更小的Order
public class DevDataSeeder implements CommandLineRunner {
    private final EmployeeFactory employeeFactory;
    private final ProjectFactory projectFactory;
    private final EmployeeRepository employeeRepository;
    private final ProjectRepository projectRepository;
    private final Random random = new Random();

    // 构造注入依赖
    public DevDataSeeder(EmployeeFactory employeeFactory, ProjectFactory projectFactory, 
                        EmployeeRepository employeeRepository, ProjectRepository projectRepository) {
        this.employeeFactory = employeeFactory;
        this.projectFactory = projectFactory;
        this.employeeRepository = employeeRepository;
        this.projectRepository = projectRepository;
    }

    @Override
    public void run(String... args) throws Exception {
        // 生成10个员工、10个项目
        List<Employee> employees = employeeFactory.createBatch(10);
        List<Project> projects = projectFactory.createBatch(10);

        // 绑定多对多关联,对应Laravel的关联填充逻辑
        projects.forEach(project -> {
            // 每个项目随机绑定1-3个员工
            int bindEmpCount = random.nextInt(3) + 1;
            List<Employee> randomEmps = random.ints(bindEmpCount, 0, employees.size())
                    .mapToObj(employees::get)
                    .distinct() // 避免重复绑定同一个员工
                    .toList();
            // 双向绑定关联关系
            project.setEmployees(randomEmps);
            randomEmps.forEach(emp -> emp.getProjects().add(project));
        });

        // 批量保存,JPA会自动维护多对多中间表,不需要手动操作中间表
        employeeRepository.saveAll(employees);
        projectRepository.saveAll(projects);
    }
}

第三步:配置生效

在application-dev.yml中配置:

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: create-drop # 每次启动重建表,配合热重载每次生成新数据
    show-sql: true
  h2:
    console:
      enabled: true

3. 基础数据填充方案

如果需要填充用户角色这类固定基础数据,单独创建一个基础数据Seeder,设置更高的优先级即可:

@Component
@Profile("dev")
@Order(1) // 优先级高于业务数据填充,先执行
public class BaseDataSeeder implements CommandLineRunner {
    private final RoleRepository roleRepository;

    public BaseDataSeeder(RoleRepository roleRepository) {
        this.roleRepository = roleRepository;
    }

    @Override
    public void run(String... args) throws Exception {
        // 避免重复插入基础数据
        if (roleRepository.count() == 0) {
            roleRepository.saveAll(List.of(
                new Role("ADMIN", "管理员"),
                new Role("USER", "普通用户")
            ));
        }
    }
}

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 13:24:07