JPA Config is an example how state machine concepts can be used with a machine configuration kept 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); } }
What comes for a machine config RepositoryStateMachineModelFactory
can be used as shown below.
@Configuration @EnableStateMachineFactory public static class Config extends StateMachineConfigurerAdapter<String, String> { @Autowired private StateRepository<? extends RepositoryState> stateRepository; @Autowired private TransitionRepository<? extends RepositoryTransition> transitionRepository; @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); } }
Let’s get into actual demo. Run the boot based sample application:
# java -jar spring-statemachine-samples-datajpa-1.2.8.RC4.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.
To access embedded console use JDBC URL jdbc:h2:mem:testdb
if it’s
not already set.
From console you can see how database tables look like and modify those as you wish.
Now that you got this far you probably wondered how those default
states and transitions got populated into a database. Spring Data
already have a nice trick to auto populate repositories and we simply
use this feature via Jackson2RepositoryPopulatorFactoryBean
.
@Bean public StateMachineJackson2RepositoryPopulatorFactoryBean jackson2RepositoryPopulatorFactoryBean() { StateMachineJackson2RepositoryPopulatorFactoryBean factoryBean = new StateMachineJackson2RepositoryPopulatorFactoryBean(); factoryBean.setResources(new Resource[]{new ClassPathResource("data.json")}); return factoryBean; }
Actual source for populator data is shown below.
[ { "@id": "10", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println('hello exit S1')" }, { "@id": "11", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println('hello entry S2')" }, { "@id": "12", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println('hello state S3')" }, { "@id": "13", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryAction", "spel": "T(System).out.println('hello')" }, { "@id": "1", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": true, "state": "S1", "exitActions": ["10"] }, { "@id": "2", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S2", "entryActions": ["11"] }, { "@id": "3", "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryState", "initial": false, "state": "S3", "stateActions": ["12"] }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", "source": "1", "target": "2", "event": "E1", "kind": "EXTERNAL" }, { "_class": "org.springframework.statemachine.data.jpa.JpaRepositoryTransition", "source": "2", "target": "3", "event": "E2", "actions": ["13"] } ]