Search code examples
neo4jgraph-databasesspring-data-neo4j-4neo4j-ogm

Neo4j 3.0.6 and neo4j-ogm 2.0.5 - Create duplicate data on database


I have a problem with neo4j database. When I try to init data, it should be just create one sample data but sometimes when I try to init data, it create double sample data. There is no trace about calling second times. This is the Config of my Neo4j

@Configuration
@EnableNeo4jRepositories(basePackages = "com.example.neo.repository")
@EnableTransactionManagement
public class Neo4jConfig extends Neo4jConfiguration {
    @Override
    @Bean
    public SessionFactory getSessionFactory() {
        // with domain entity base package(s)
        return new SessionFactory("com.example.neo.model", "BOOT-INF.classes.com.example.neo.model");
    }

    // needed for session in view in web-applications
    @Override
    @Bean
    @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public Session getSession() throws Exception {
        return super.getSession();
    }
}

This is how I called my function

@RequestMapping(value = "/initCurrency")
public ModelAndView initCurrency() {
    initializationService.initCurrency();

    ModelAndView model = new ModelAndView("redirect:/");
    return model;
}

This is the initializationService function

private String[][] currencyList = {
        { "USD", "7.5" },
        { "DKK", "1" },
        { "AFN", "1"},{ "EUR", "1"},{ "ALL", "1"},{ "DZD", "1"},{ "USD", "1"},{ "AOA", "1"},{ "XCD", "1"},
        { "ARS", "1"},{ "AMD", "1"},{ "AWG", "1"},{ "SHP", "1"},{ "AUD", "1"},{ "AZN", "1"},{ "BSD", "1"},
        { "BHD", "1"},{ "BDT", "1"},{ "BBD", "1"}
}
@Override
public void initCurrency() {
    for (String[] currency : currencyList) {
        Currency existCurrency = currencyService.findByName(currency[0]);

        if (existCurrency == null) {
            existCurrency = new Currency(currency[0], Double.valueOf(currency[1]));
            currencyService.save(existCurrency);
        }
    }
}

Solution

  • The only reliable way to avoid duplicates is to have an actual unicity constraint on the property:

    CREATE CONSTRAINT ON (n:Currency) ASSERT n.name IS UNIQUE;
    

    There's no way in SDN 4.[0-2] (yet) to create such a constraint from the model (there was in SDN 3.x with the @Indexed or @Indexed(unique = true) annotation), so you'll have to run the query independently (for example using Liquigraph!).

    Doing a lookup to guard the creation is not enough in a concurrent environment (sequential calls are OK), because there's no lock between the read and the write, which can result in the following scenario with an interleaved execution:

    • Thread A checks the existence, doesn't find a node
    • Thread B checks the existence, doesn't find a node either
    • Thread A creates a currency node named "USB"
    • Thread B creates another currency node with the same name

    Since you end up with duplicates, 2 concurrent calls are happening. A load balancer with a really short timeout and configured to retry? Activate HTTP logs, add some logging in the Spring controller or service, capture the traffic with tcpdump, etc. It will be easier to isolate the second call once the unicity constraint is active, since you'll get an exception.