Skip to content

Instantly share code, notes, and snippets.

@michael-simons
Last active December 21, 2023 06:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michael-simons/d3137f64ac0b13713fae8e7e1a69367e to your computer and use it in GitHub Desktop.
Save michael-simons/d3137f64ac0b13713fae8e7e1a69367e to your computer and use it in GitHub Desktop.
package ac.simons.neo4j.examples;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import org.neo4j.configuration.connectors.BoltConnector;
import org.neo4j.configuration.helpers.SocketAddress;
import org.neo4j.dbms.api.DatabaseManagementServiceBuilder;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.TransactionContext;
public class GraphApplication {
public static void main(String... a) throws IOException {
// This is the db itself, should be long-lived
var graphDb = new DatabaseManagementServiceBuilder(Files.createTempDirectory("neo4j"))
.setConfig(BoltConnector.enabled, true)
.setConfig(BoltConnector.listen_address, new SocketAddress("localhost", 7687))
.build();
// You could also use the graph database api and skip using bolt.
// The advantage of using build also in an embedded scenario: You can switch to a server with ease.
// Same goes for the driver with the connection pool
// The session itself is short-lived
try (
var driver = GraphDatabase.driver("bolt://localhost:7687", AuthTokens.none());
var session = driver.session()
) {
session.executeWrite(t -> t.run("CREATE (p:Person {name: 'Arnold Schwarzenegger'}) - [:ACTED_IN] -> (:Movie {title: 'The Terminator'})").consume().counters().nodesCreated());
var movies = session.executeRead(GraphApplication::findMovieAndTheirActors);
movies.forEach(System.out::println);
}
graphDb.shutdown();
}
record Person(String name) {
}
record Movie(String title, List<Person>actedIn) {
}
static List<Movie> findMovieAndTheirActors(TransactionContext tx) {
var query = """
MATCH (m:Movie) <- [:ACTED_IN] - (p:Person)
WHERE m.title =~ $movieTitle
RETURN m.title AS title, collect(p.name) AS actors
""";
return tx.run(query, Map.of("movieTitle", ".*The.*")).list(r -> {
var actors = r.get("actors").asList(v -> new Person(v.asString()));
return new Movie(r.get("title").asString(), actors);
});
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ac.simons.neo4j</groupId>
<artifactId>neo4jtwitch</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<neo4j.version>5.15.0</neo4j.version>
<neo4j-java-driver.version>5.15.0</neo4j-java-driver.version>
</properties>
<dependencies>
<!-- Embbeded instance -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>${neo4j.version}</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-bolt</artifactId>
<version>${neo4j.version}</version>
</dependency>
<!-- Driver (for connection) -->
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>${neo4j-java-driver.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>appassembler-maven-plugin</artifactId>
<version>2.1.0</version>
<configuration>
<assembleDirectory>${project.build.directory}/assembly</assembleDirectory>
<repositoryLayout>flat</repositoryLayout>
<repositoryName>lib</repositoryName>
<programs>
<program>
<mainClass>ac.simons.neo4j.examples.GraphApplication</mainClass>
<id>app</id>
</program>
</programs>
</configuration>
<executions>
<execution>
<id>make-distribution</id>
<goals>
<goal>assemble</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@Podbrushkin
Copy link

It helped me, thank you.
Adding .setConfig(HttpConnector.enabled, true) and adding this method and calling it instead of graphDb.shutdown();

private static void registerShutdownHook( final DatabaseManagementService managementService ) {
    // Registers a shutdown hook for the Neo4j instance so that it
    // shuts down nicely when the VM exits (even if you "Ctrl-C" the
    // running application).
    Runtime.getRuntime().addShutdownHook( new Thread()
    {
	@Override
	public void run() {
	    System.out.println("Shutdown requested, please wait 3 seconds...");
	    managementService.shutdown();
	    System.out.println("Shutdown completed successfully.");
	}
    } );
}

allows you to access Http endpoint:

curl -X POST -H 'Content-type: application/json' http://localhost:7474/db/neo4j/tx/commit -d '{"statements": [{"statement": "MATCH (n) RETURN n LIMIT 5;"}]}'

or with powershell:

$uri = "http://localhost:7474/db/neo4j/tx/commit"
$body = '{"statements": [{"statement": "MATCH (n) RETURN n LIMIT 5;"}]}'
Invoke-WebRequest -Uri $uri -Method POST -Body $body -ContentType 'application/json' | % Content

which results in this output:

{"results":[{"columns":["n"],"data":[{"row":[{"name":"Arnold Schwarzenegger"}],"meta":[{"id":0,"elementId":"4:624d1352-b8bb-4a95-9788-9edc92056ef8:0","type":"node","deleted":false}]},{"row":[{"title":"The Terminator"}],"meta":[{"id":1,"elementId":"4:624d1352-b8bb-4a95-9788-9edc92056ef8:1","type":"node","deleted":false}]}]}],"errors":[],"lastBookmarks":["FB:kcwQYk0TUri7SpWXiJ7ckgVu+AmQ"]}

But why visiting locahost:7474 in browser redirects to /browser and shows a blank page? Can this running db be accessed with neo4j browser? And how to expose Http endpoint from Spring[Boot] webapp? It crashes with javax.servlet not found if HttpConnector is enabled.

@michael-simons
Copy link
Author

Neo4j Browser is not part of the embedded distribution.
Neo4j embedded inside Spring Boot is not likely to work with recent versions of Neo4j.
To use Neo4j from Spring Boot, use the bolt connector and an externally running Neo4j.

@Podbrushkin
Copy link

Podbrushkin commented Nov 10, 2023

Database of this app cannot be accessed with Neo4j Browser hosted at https://browser.neo4j.io/ either?
I know if db file structure will be created not in TempDirectory as it is but in /target/ directory, it will stay populated after app shutdown and you can run neo4j docker instance which will use this directory, making it possible to observe this data with neo4j browser.

Neo4j embedded inside Spring Boot is not likely to work with recent versions of Neo4j.

It works for me with latest neo4j and Spring Boot as long as HttpConnector is disabled. At least now I know it shouldn't have worked at all, thank you. I wonder how I should've known that Neo4j Embedded isn't compatible with Spring Boot. These compatibility issues between Spring Boot and Neo4j Embedded will be addressed or Neo4j team isn't interested on this matter?

@cybersam
Copy link

cybersam commented Nov 10, 2023

A couple of suggestions:

  1. I think there is a typo. "The advantage of using build" should probably be "The advantage of using bolt".
  2. You should probably schedule deletion of the temp directory via graphDb.deleteOnExit().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment