50. JPA Persist

JPA Persist is an example how state machine concepts can be used with persisting machine in a database. This sample is using embedded H2 database with a H2 Console to ease playing with a database.

This sample uses spring-statemachine-boot which on default auto-configures repositories and entity classes needed for JPA. Thus only @SpringBootApplication is needed.

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

StateMachineRuntimePersister is a new interface working on a runtime level of a 'StateMachine' and its implementation JpaPersistingStateMachineInterceptor is meant to be used with a 'JPA'.

@Bean
public StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister() {
    return new JpaPersistingStateMachineInterceptor<>(jpaStateMachineRepository);
}

'StateMachine' can be configured to use runtime persistence by using withPersistence config method.

@Autowired
private JpaStateMachineRepository jpaStateMachineRepository;

@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
        throws Exception {
    config
        .withPersistence()
            .runtimePersister(stateMachineRuntimePersister());
}

In this sample we also use DefaultStateMachineService which makes it easier to work with multiple machines

@Bean
public StateMachineService<States, Events> stateMachineService(StateMachineFactory<States, Events> stateMachineFactory,
        StateMachineRuntimePersister<States, Events, String> stateMachineRuntimePersister) {
    return new DefaultStateMachineService<States, Events>(stateMachineFactory, stateMachineRuntimePersister);
}

A logic using a StateMachineService in this sample is show below.

private synchronized StateMachine<States, Events> getStateMachine(String machineId) throws Exception {
    listener.resetMessages();
    if (currentStateMachine == null) {
        currentStateMachine = stateMachineService.acquireStateMachine(machineId);
        currentStateMachine.addStateListener(listener);
        currentStateMachine.start();
    } else if (!ObjectUtils.nullSafeEquals(currentStateMachine.getId(), machineId)) {
        stateMachineService.releaseStateMachine(currentStateMachine.getId());
        currentStateMachine.stop();
        currentStateMachine = stateMachineService.acquireStateMachine(machineId);
        currentStateMachine.addStateListener(listener);
        currentStateMachine.start();
    }
    return currentStateMachine;
}

Let’s get into actual demo. Run the boot based sample application:

# java -jar spring-statemachine-samples-datajpapersist-1.2.8.RELEASE.jar

Accessing application via http://localhost:8080 brings up a new constructed machine with every request and you can choose to send events to a machine. Possible events and machine configuration are updated from a database with every request.

Machines in this sample have a simple configuration with states 'S1' to 'S6' and events 'E1' to 'E6' transitioning machine between those states. Two machine identifiers 'datajpapersist1' and 'datajpapersist2' can be used to request particular machine.

sm datajpapersist 1

Sample defaults to using machine 'datajpapersist1' and goes to its initial state 'S1'.

sm datajpapersist 2

If events 'E1' and 'E2' are sent into machine 'datajpapersist1' its state is persisted as 'S3'.

sm datajpapersist 3

If requesting machine 'datajpapersist1' by not sending any events, machine is restored back to its persisted state 'S3'.