Configure both MySQL and Neo4J in a Spring Boot / JHipster project
Several steps are needed to be able to address a MySQL relational database for some tasks and use a Neo4J graph-oriented DB to analyse measurements.
Generate a JHipster project. I started with a monolith app using Gradle and a MySQL DB as I wanted all relational entities and repositories to be created by the JHipster Generator. After importing the JDL model and configuring my local development MySQL DB url and credentials (in application-dev.yml), the project was ready for the relational part.
Now, let’s integrate Spring Data Neo4J (SDN). To do so, open the build.gradle file and add the following lines under the dependencies and repositories sections:
dependencies {
compile 'org.springframework.data:spring-data-neo4j:4.2.9.RELEASE'
}repositories {
maven {
url 'https://repo.spring.io/libs-release'
}
}
For SDN 4.2.x there is no need to import the specific HTTP Driver dependency. I first started to use the latest version of SDN (5.0.2.RELEASE) but there was clear problems with Spring Boot 1.5.7. As SDN 5 is compatible with Spring Boot 2 (coming out in early 2018), an updated article of doing things may be useful later.
Let’s now prepare the ground for declaring Neo4J entities and repositories: create two packages e.g. graph and relational under the domain and repository packages.
domain
enumeration
graph
relational
package-info.jsonrepository
graph
relational
package-info.json
The refactoring should update all project references, among which the “basePackages” definition in DatabaseConfiguration. One or two commits may be good practice here.
You may find quite a lot of tutorials about integrating Spring Data Neo4j into your Spring Boot project but the Spring Data Neo4j 4.2 release changed quite a lot of things. As stated in this article, you may want to exclude the old Neo4j auto config class (Neo4jDataAutoConfiguration) and DataSourceAutoConfiguration:
@ComponentScan
@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class, Neo4jDataAutoConfiguration.class,
DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
public class MachineTrackingAssystemApp
Next up is configuring the Primary datasource: MySQL. For this, we simply add some beans to the already existing DatabaseConfiguration class, holding the Liquibase config.
We will add a bean to define a default datasource for our relational DB, as well as an EntityManagerFactory and a relational JpaTransactionManager. All these will enable us to differentiate the transactions when starting queries on our repositories. The transactionManager uses a ChainedTransactionManager which will dispatch to the right manager depending on the request.
By the way, all mysql-related beans are “@Primary” which means that the relational datasource is the default one.
@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "mysqlTransactionManager",
basePackages = "com.company.project.repository.relational")
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
public class DatabaseConfiguration {
private final Logger log;// logger init
private final Environment env;
public DatabaseConfiguration(Environment env) {
this.env = env;
}
@Bean
public SpringLiquibase liquibase(@Qualifier("taskExecutor") TaskExecutor taskExecutor,
DataSource dataSource,
LiquibaseProperties liquibaseProperties) {
// The JHipster generated method, deleted for clarity
}
@Primary
@Bean(name = "dataSource")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder
.create()
.driverClassName("com.mysql.jdbc.Driver")
.build();
}
@Primary
@Bean(name = "entityManagerFactory")
public
LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("dataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.company.project.domain.relational")
.build();
}
@Primary
@Bean(name = "mysqlTransactionManager")
public JpaTransactionManager mysqlTransactionManager(
@Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return
new JpaTransactionManager(entityManagerFactory.getObject());
}
@Autowired
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager (Neo4jTransactionManager neo4jTransactionManager,
JpaTransactionManager mysqlTransactionManager) {
log.debug("Initializing platform transaction manager");
return new ChainedTransactionManager (mysqlTransactionManager, neo4jTransactionManager);
}
}
Let’s now do something very similar for Neo4j.
We will declare a Neo4jConfiguration class (CAVEAT: this class has nothing to do with the old class named alike and which had to be extended to configure Spring Data Neo4j!). Neo4j does not use entity factories but Session factories. Besides that, the configuration class looks similar.
@Configuration
@EnableTransactionManagement
@ComponentScan("com.company.project.domain.graph")
@EnableNeo4jRepositories(
sessionFactoryRef = "getSessionFactory",
transactionManagerRef = "graphTransactionManager",
basePackages = "com.company.project.repository.graph")
public class Neo4jConfiguration {
@Bean(name = "getSessionFactory")
public SessionFactory graphSessionFactory() {
return new SessionFactory(configuration(), "com.company.project.domain.graph");
}
@Bean(name = "graphTransactionManager")
public Neo4jTransactionManager graphTransactionManager(
@Qualifier("getSessionFactory")
SessionFactory sessionFactory) {
return new Neo4jTransactionManager(sessionFactory);
}
@Bean
public org.neo4j.ogm.config.Configuration configuration() {
org.neo4j.ogm.config.Configuration configuration =
new org.neo4j.ogm.config.Configuration();
configuration.driverConfiguration()
.setURI("http://neo4j:neo4j@localhost:7474");
return configuration;
}
}
Note that we did not configure another datasource for Neo4j in this example. No changes needed to the application-dev.yml.
You can now start creating your NodeEntities, Repositories, Services and Controllers. To do so, refer to the official guide.
A Neo4j Repository example:
@SuppressWarnings("unused")
@Repository
public interface MachineRepository extends GraphRepository<MachineNode> {
}
Also, in order for the system to dispatch to the correct transaction manager, you need to add some information to the “@Transactional” annotation. For MySQL services, this is not needed as we marked MySQL-related beans with “@Primary”.
@Service
@Transactional("graphTransactionManager")
public class MachineNodeService {
You can now make tests by calling a self-defined API and you will see that nodes are being saved on your local Neo4J database. Don’t forget to run the Neo4J database and to specify the right credentials in the config ;)
Many thanks to:
- Florian, a colleague @HardisGroup who did a small POC on having two datasources in a Spring Boot project some time ago and shared the code.
- https://engineering.logicgate.com/spring-boot-with-neo4j-mysql-4036a54efd3c
- https://graphaware.com/neo4j/2016/09/30/upgrading-to-sdn-42.html#SupportforSpringBoot
- http://www.baeldung.com/spring-data-jpa-multiple-databases
- https://medium.com/@joeclever/using-multiple-datasources-with-spring-boot-and-spring-data-6430b00c02e7
- https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-two-datasources