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
相关产品推荐
相关产品推荐

