Siva
Siva

Reputation: 27

Unable to use two Neo4j Instances with Spring boot/Spring data neo4j

Expected Behavior

Trying to use two Neo4j instances with Spring boot and Spring data Neo4j

Current Behavior

Able to use only one Neo4j instances. Unable to use two repositories.

Steps to Reproduce (for bugs)

1. Run two Neo4j Instances
2. Create Data source configuration for both Neo4j Instances using spring boot.
3. Use Repository to access the Node entity
4. It will throw error

Context

Consider that I am running a library and renting books to other users. If the user is renting the book from me, the same node details will be present in their repository and I will allow them to edit the node entity through my application (like adding keywords, adding highlights about the books etc.)

So in both repository the node details will be same.

Below are the application. properties details for both Neo4j repositories.

My Neo4j Repository Details

spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=neo4j

Rental User's Neo4j Repository Details (Accessing through http which is running in some other machine)

rental.data.neo4j.uri=bolt://...:7687
rental.data.neo4j.username=neo4j
rental.data.neo4j.password=neo4j

Below is the Rental User's Neo4j configuration :

@configuration
@EnableNeo4jRepositories(basePackages = "com.metadata.dao.rentallibrary", sessionFactoryRef = "rentalSessionFactory", transactionManagerRef = "rentalUsertransactionManager")
@EnableTransactionManagement
@EntityScan("com.metadata.dao")
public class rentalUserNeo4jConfiguration {

@Value("${rental.data.neo4j.uri}")
private String url;

@Value("${rental.data.neo4j.username}")
private String userName;

@Value("${rental.data.neo4j.password}")
private String password;

@Bean(name = "rentalSessionFactory")
public SessionFactory rentalUserSessionFactory() {
    return new SessionFactory(rentalNeo4jconfiguration(), "com.metadata.dao.rentallibrary.entity");
}

@Bean
public org.neo4j.ogm.config.Configuration rentalNeo4jconfiguration() {
    org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder().uri(url)// "
            .credentials(userName, password)
            .build();
    return configuration;
}

@Bean
public Neo4jTransactionManager rentalUsertransactionManager() {
    return new Neo4jTransactionManager(rentalUserSessionFactory());
}
}

And below is my library's Neo4j configuration details :

@configuration
@EnableNeo4jRepositories(basePackages = "com.metadata.dao.mylibrary", sessionFactoryRef = "myUserSessionFactory", transactionManagerRef = "myUserTransactionManager")
@EnableTransactionManagement
public class MyUserNeo4jConfiguration {

@Value("${spring.data.neo4j.uri}")
private String url;

@Value("${spring.data.neo4j.username}")
private String userName;

@Value("${spring.data.neo4j.password}")
private String password;

@Bean(name = "myUserSessionFactory")
@Primary
public SessionFactory myUserSessionFactory() {
    return new SessionFactory(myUserconfiguration(), "com.metadata.dao.mylibrary.entity");
}

@Bean
public org.neo4j.ogm.config.Configuration myUserconfiguration() {
    org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration.Builder().uri(url)
            .credentials(userName, password)
            .build();
    return configuration;
}

@Bean
public Neo4jTransactionManager myUserTransactionManager() {
    return new Neo4jTransactionManager(myUserSessionFactory());
}
}

I am trying access both repositories using session Factory (via qualifier) it is working fine. But I am trying to access the data, through repositories I am facing the Issue.

**Accessing through SessionFactory :**

@Autowired
@Qualifier(value = "myUserSessionFactory")
SessionFactory myUserSessionFactory;

@Autowired
@Qualifier(value = "rentalUserSessionFactory")
SessionFactory rentalUserSessionFactory;

Below are the error details, I am getting when trying to access the data through :

java.lang.IllegalArgumentException: Class class com.metadata.dao.Book is not a valid entity class. Please check the entity mapping.
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:88) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at org.neo4j.ogm.session.delegates.SaveDelegate.save(SaveDelegate.java:40) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at org.neo4j.ogm.session.Neo4jSession.save(Neo4jSession.java:469) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_141]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_141]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_141]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_141]
at org.springframework.data.neo4j.transaction.SharedSessionCreator$SharedSessionInvocationHandler.invoke(SharedSessionCreator.java:131) ~[spring-data-neo4j-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at com.sun.proxy.$Proxy81.save(Unknown Source) ~[na:na]

org.neo4j.ogm.exception.core.TransactionManagerException: Transaction is not current for this thread
at org.neo4j.ogm.session.transaction.DefaultTransactionManager.rollback(DefaultTransactionManager.java:86) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at org.neo4j.ogm.transaction.AbstractTransaction.rollback(AbstractTransaction.java:65) ~[neo4j-ogm-api-3.1.0.jar:3.1.0]
at org.neo4j.ogm.drivers.bolt.transaction.BoltTransaction.rollback(BoltTransaction.java:61) ~[neo4j-ogm-bolt-driver-3.1.0.jar:3.1.0]
at org.neo4j.ogm.transaction.AbstractTransaction.close(AbstractTransaction.java:144) ~[neo4j-ogm-api-3.1.0.jar:3.1.0]
at org.springframework.data.neo4j.transaction.Neo4jTransactionManager.doCleanupAfterCompletion(Neo4jTransactionManager.java:379) ~[spring-data-neo4j-5.0.8.RELEASE.jar:5.0.8.RELEASE]

Node Entity Name in dao.mylibrary.entity : Book

Node Entity Name in dao.rentallibrary.entity : RentedBook

Please let me know why this issue is occurring while using Neo4j repositories? Can't we use two Neo4j repositories with Spring data neo4j & Spring boot? Or Am I doing something wrong?

My Environment

OGM Version used: 3.1.0
Java Version used: 1.8
Neo4J Version used:3.2.3
Bolt Driver Version used (if applicable): 3.1.0
Operating System and Version: Windows
Please let me know if you need any additional information.

Upvotes: 2

Views: 1251

Answers (1)

Michael Simons
Michael Simons

Reputation: 4890

Update This has been addressed in Spring Data Neo4j Lovelace RC1 and we wrote a small howto: https://michael-simons.github.io/neo4j-sdn-ogm-tips/using_multiple_session_factories

Thanks for submitting this as GitHub issue #498.

It seems that the current Spring Data Neo4j version has a bug in propagating the different session factories to the repositories. In short: there is no way to make this work right now (for example like you can do with Spring Data JPA).

If you need (and want) repositories, I cannot help you right now. What work's however is injecting the different session factories:

    @Autowired
    @Qualifier("myUserSessionFactory")
    private SessionFactory myUserSessionFactory;

    @Autowired
    @Qualifier("rentalUserSessionFactory")
    private SessionFactory rentalUserSessionFactory;

and then do something like

Map<String, Object> params = new HashMap<>();
params.put("name", "test");
ThingEntity t = this.myUserSessionFactory.openSession().queryForObject(
                ThingEntity.class,
                "MATCH (n:`Thing`) WHERE n.name = $name WITH n RETURN n", params);

Regardless of the bug in our code, I recommend the following configuration. For the primary beans ("myUserconfiguration") use one config class

package gh.neo4jogm.gh498;

import gh.neo4jogm.gh498.domain1.ThingEntity;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;

@Configuration
@EnableNeo4jRepositories(
    basePackages = Domain1Config.BASE_PACKAGE,
    sessionFactoryRef = "myUserSessionFactory",
    transactionManagerRef = "myUserTransactionManager"
)
@EntityScan(basePackageClasses = ThingEntity.class)
class Domain1Config {

    static final String BASE_PACKAGE = "gh.neo4jogm.gh498.domain1";

    @Primary
    @Bean
    @ConfigurationProperties("spring.data.neo4j")
    public Neo4jProperties myNeo4jProperties() {
        return new Neo4jProperties();
    }

    @Primary
    @Bean
    public org.neo4j.ogm.config.Configuration myUserconfiguration() {
        return myNeo4jProperties().createConfiguration();
    }

    @Primary
    @Bean
    public SessionFactory myUserSessionFactory() {
        return new SessionFactory(myUserconfiguration(), BASE_PACKAGE);
    }

    @Bean
    public Neo4jTransactionManager myUserTransactionManager() {
        return new Neo4jTransactionManager(myUserSessionFactory());
    }
}

The basic idea is to use @ConfigurationProperties to map the default properties to an instance of Neo4jProperties (our properties class) and use it like we do create the action configuration.

The same then for the other session factory:

package gh.neo4jogm.gh498;

import gh.neo4jogm.gh498.domain2.OtherThingEntity;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;

import static gh.neo4jogm.gh498.Domain2Config.BASE_PACKAGE;

@Configuration
@EnableNeo4jRepositories(
    basePackages = BASE_PACKAGE,
    sessionFactoryRef = "rentalUserSessionFactory",
    transactionManagerRef = "rentalUsertransactionManager"
)
@EntityScan(basePackageClasses = OtherThingEntity.class)
class Domain2Config {

    static final String BASE_PACKAGE = "gh.neo4jogm.gh498.domain2";

    @Bean
    @ConfigurationProperties("rental.data.neo4j")
    public Neo4jProperties rentalNeo4jProperties() {
        return new Neo4jProperties();
    }

    @Bean
    public org.neo4j.ogm.config.Configuration rentalNeo4jconfiguration() {
        return rentalNeo4jProperties().createConfiguration();
    }

    @Bean
    public SessionFactory rentalUserSessionFactory() {
        return new SessionFactory(rentalNeo4jconfiguration(), BASE_PACKAGE);
    }

    @Bean
    public Neo4jTransactionManager rentalUsertransactionManager() {
        return new Neo4jTransactionManager(rentalUserSessionFactory());
    }
}

Here you map all properties prefixed with rental.data.neo4j to another properties instance.

Upvotes: 1

Related Questions