Spring State Machine JPA配置:无MachineId创建状态机实例报错求助
问题描述
我需要实现:当数据库中不存在指定ID的状态机实例时,自动创建新实例并将配置(状态、过渡)和上下文存储到数据库。但目前遇到两个问题:
- 无法在数据库中设置默认配置,插入过渡记录时不指定
machineId,创建新状态机时会抛出**"Must have at least one transition"**异常。 - 数据库中已存在未指定
machineId的过渡记录,希望在运行时创建带随机machineId的状态机,但代码里定义过渡无需指定machineId,数据库里这么做却无法正常工作。
现有配置代码
@Configuration @RequiredArgsConstructor @EnableStateMachine public class StateMachineConfig { @Bean public StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister( JpaStateMachineRepository jpaStateMachineRepository) { return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository); } @Bean public StateMachineService<String, String> stateMachineService( StateMachineFactory<String, String> stateMachineFactory, StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister) { return new DefaultStateMachineService<>(stateMachineFactory, stateMachineRuntimePersister); } @Bean public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[]{new ClassPathResource("data.json")}); return factoryBean; } @Configuration @EnableStateMachineFactory public static class Config extends StateMachineConfigurerAdapter<String, String> { @Autowired private StateRepository<? extends RepositoryState> stateRepository; @Autowired private TransitionRepository<? extends RepositoryTransition> transitionRepository; @Autowired private StateMachineRuntimePersister<String, String, String> stateMachineRuntimePersister; @Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withPersistence() .runtimePersister(stateMachineRuntimePersister); } @Override public void configure(StateMachineModelConfigurer<String, String> model) throws Exception { model .withModel() .factory(modelFactory()); } @Bean public StateMachineModelFactory<String, String> modelFactory() { return new RepositoryStateMachineModelFactory(stateRepository, transitionRepository); } } }
现有控制器代码
@RequestMapping("/state") public String feedAndGetStates( @RequestParam(value = "events", required = false) List<String> events, @RequestParam(value = "machine", required = false, defaultValue = MACHINE_ID_1) String machine, Model model) throws Exception { StateMachine<String, String> stateMachine = getStateMachine(machine); if (events != null) { for (String event : events) { stateMachine .sendEvent(Mono.just(MessageBuilder .withPayload(event).build())) .blockLast(); } } StringBuilder contextBuf = new StringBuilder(); StateMachineContext<String, String> stateMachineContext = stateMachinePersist.read(machine); if (stateMachineContext != null) { contextBuf.append(stateMachineContext.toString()); } if (ObjectUtils.nullSafeEquals(machine, MACHINE_ID_2)) { stateMachineContext = stateMachinePersist.read(MACHINE_ID_2R1); if (stateMachineContext != null) { contextBuf.append("\n---\n"); contextBuf.append(stateMachineContext.toString()); } stateMachineContext = stateMachinePersist.read(MACHINE_ID_2R2); if (stateMachineContext != null) { contextBuf.append("\n---\n"); contextBuf.append(stateMachineContext.toString()); } } model.addAttribute("allMachines", MACHINES); model.addAttribute("machine", machine); model.addAttribute("currentMachine", currentStateMachine); model.addAttribute("allEvents", getEvents()); model.addAttribute("messages", createMessages(listener.getMessages())); model.addAttribute("context", contextBuf.toString()); return "states"; } private synchronized StateMachine<String, String> getStateMachine(String machineId) throws Exception { listener.resetMessages(); if (currentStateMachine == null) { currentStateMachine = stateMachineService.acquireStateMachine(machineId, false); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) { stateMachineService.releaseStateMachine(currentStateMachine.getId()); currentStateMachine.stopReactively().block(); currentStateMachine = stateMachineService.acquireStateMachine(machineId, false); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } return currentStateMachine; }
解决方案
问题核心是默认的RepositoryStateMachineModelFactory会严格根据machineId过滤数据库中的状态和过渡记录,不带machineId的模板记录不会被加载,导致新状态机没有过渡配置而抛出异常。以下是具体解决步骤:
1. 自定义状态机模型工厂,支持模板复用
继承RepositoryStateMachineModelFactory,修改查询逻辑:当指定machineId没有对应记录时,加载machineId为null的模板记录,复制并保存为新machineId的实例。
public class CustomRepositoryStateMachineModelFactory extends RepositoryStateMachineModelFactory<String, String> { private final StateRepository<? extends RepositoryState> stateRepository; private final TransitionRepository<? extends RepositoryTransition> transitionRepository; public CustomRepositoryStateMachineModelFactory(StateRepository<? extends RepositoryState> stateRepository, TransitionRepository<? extends RepositoryTransition> transitionRepository) { super(stateRepository, transitionRepository); this.stateRepository = stateRepository; this.transitionRepository = transitionRepository; } @Override public StateMachineModel<String, String> build(String machineId) { // 先查询当前machineId的状态和过渡 List<? extends RepositoryState> states = stateRepository.findByMachineId(machineId); List<? extends RepositoryTransition> transitions = transitionRepository.findByMachineId(machineId); // 如果没有记录,加载模板(machineId为null) if (states.isEmpty() && transitions.isEmpty()) { states = stateRepository.findByMachineIdIsNull(); transitions = transitionRepository.findByMachineIdIsNull(); // 存在模板时,复制为当前machineId的实例并存库 if (!states.isEmpty() && !transitions.isEmpty()) { saveTemplateAsMachineInstance(machineId, states, transitions); } } return super.build(machineId); } private void saveTemplateAsMachineInstance(String machineId, List<? extends RepositoryState> templateStates, List<? extends RepositoryTransition> templateTransitions) { // 复制状态并关联新machineId Map<String, RepositoryState> newStateMap = new HashMap<>(); templateStates.forEach(template -> { RepositoryState newState = new RepositoryState(); // 复制模板属性,可使用BeanUtils.copyProperties(template, newState) newState.setState(template.getState()); newState.setInitial(template.isInitial()); newState.setMachineId(machineId); newState = stateRepository.save(newState); newStateMap.put(template.getState(), newState); }); // 复制过渡并关联新machineId和对应状态 templateTransitions.forEach(template -> { RepositoryTransition newTransition = new RepositoryTransition(); // 复制模板属性 newTransition.setEvent(template.getEvent()); newTransition.setMachineId(machineId); // 关联新创建的源状态和目标状态 newTransition.setSource(newStateMap.get(template.getSource().getState())); newTransition.setTarget(newStateMap.get(template.getTarget().getState())); transitionRepository.save(newTransition); }); } }
2. 替换配置中的模型工厂
修改StateMachineConfig.Config中的modelFactory Bean,使用自定义工厂:
@Bean public StateMachineModelFactory<String, String> modelFactory() { return new CustomRepositoryStateMachineModelFactory(stateRepository, transitionRepository); }
3. 调整状态机获取逻辑,支持随机ID生成
修改getStateMachine方法,支持生成随机machineId并触发模板复制:
private synchronized StateMachine<String, String> getStateMachine(String machineId) throws Exception { listener.resetMessages(); // 若传入标识为"new",生成随机UUID作为machineId if ("new".equals(machineId)) { machineId = UUID.randomUUID().toString(); // 提前触发模型构建,确保模板被复制到新ID下 stateMachineFactory.getStateMachine(machineId); } if (currentStateMachine == null) { currentStateMachine = stateMachineService.acquireStateMachine(machineId, false); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) { stateMachineService.releaseStateMachine(currentStateMachine.getId()); currentStateMachine.stopReactively().block(); currentStateMachine = stateMachineService.acquireStateMachine(machineId, false); currentStateMachine.addStateListener(listener); currentStateMachine.startReactively().block(); } return currentStateMachine; }
4. 确保数据库实体支持模板记录
修改RepositoryState和RepositoryTransition实体,允许machineId为null:
@Entity public class RepositoryState { // 其他字段(如id、state、initial等) @Column(name = "machine_id", nullable = true) private String machineId; // getter、setter方法 } @Entity public class RepositoryTransition { // 其他字段(如id、event等) @Column(name = "machine_id", nullable = true) private String machineId; // 关联状态字段 @ManyToOne private RepositoryState source; @ManyToOne private RepositoryState target; // getter、setter方法 }
内容的提问来源于stack exchange,提问作者Can Cinar
相关产品推荐
相关产品推荐

