Neo4j is a robust (fully ACID) transactional property graph database. This kind of databases are suited for those type of problems that can be represented with a graph like social relationships or road maps for example.
At the moment only the support for the embedded Neo4j is included in OGM.
If your project uses Maven you can add this to the pom.xml:
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-neo4j</artifactId>
<version>{hibernate-ogm-version}</version>
</dependency>
Alternatively you can find the required libraries in the distribution package on SourceForge
hibernate.ogm.datastore.provider = neo4j_embedded
hibernate.ogm.neo4j.database_path = C:\example\mydb
The following properties are available to configure Neo4j support:
- hibernate.ogm.neo4j.database_path
-
The absolute path representing the location of the Neo4j database. Example:
C:\neo4jdb\mydb
- hibernate.ogm.neo4j.configuration_resource_name (optional)
-
Location of the Neo4j embedded properties file. It can be an URL, name of a classpath resource or file system path.
- hibernate.schema_update.unique_constraint_strategy (optional)
-
If set to
SKIP
, Hibernate OGM won’t create any unique constraints on the nodes representing the entities. This property won’t affect the unique constraints generated for sequences. Other possible values (defined on theorg.hibernate.tool.hbm2ddl.UniqueConstraintSchemaUpdateStrategy
enum) areDROP_RECREATE_QUIETLY
andRECREATE_QUIETLY
but the effect will be the same (since Neo4j constraints don’t have a name): keep the existing constraints and create the missing one. Default value isDROP_RECREATE_QUIETLY
.
Note
|
When bootstrapping a session factory or entity manager factory programmatically,
you should use the constants accessible via Common properties shared between stores are declared on For maximum portability between stores, use the most generic interface possible. |
Hibernate OGM tries to make the mapping to the underlying datastore as natural as possible so that third party applications not using Hibernate OGM can still read and update the same datastore.
To make things simple, each entity is represented by a node,
each embedded object is also represented by a node.
Links between entities (whether to-one to to-many associations)
are represented by relationships between nodes.
Entity and embedded nodes are labelled ENTITY
and EMBEDDED
respectively.
Each entity is represented by a node. Each property or more precisely column is represented by an attribute of this node.
The following types (and corresponding primitives) get passed to Neo4j without any conversion:
-
java.lang.Boolean
; Optionally the annotations@Type(type = "true_false")
,@Type(type = "yes_no")
and@Type(type = "numeric_boolean")
can be used to map boolean properties to the characters 'T'/'F', 'Y'/'N' or the int values 0/1, respectively. -
java.lang.Character
-
java.lang.Byte
-
java.lang.Short
-
java.lang.Integer
-
java.lang.Long
-
java.lang.Float
-
java.lang.Double
-
java.lang.String
The following types get converted into java.lang.String
:
-
java.math.BigDecimal
-
java.math.BigInteger
-
java.util.Calendar
stored as `String` with the format "yyyy/MM/dd HH:mm:ss:SSS Z"
-
java.util.Date
stored as `String` with the format "yyyy/MM/dd HH:mm:ss:SSS Z"
-
java.util.UUID
-
java.util.URL
Note
|
Hibernate OGM doesn’t store null values in Neo4J, setting a value to null is the same as removing the corresponding entry from Neo4J. This can have consequences when it comes to queries on null value. |
Entities are stored as Neo4j nodes, which means each entity property will be translated into a property of the node. The name of the table mapping the entity is used as label.
You can use the name property of the @Table
and @Column
annotations
to rename the label and the node’s properties.
An additional label ENTITY
is added to the node.
Warning
|
Neo4j does not support constraints on more than one property. For this reason, Hibernate OGM will create a unique constraint ONLY when it spans a single property and it will ignore the ones spanning multiple properties. The lack of unique constraints on node properties might result in the creation of multiple nodes with the same identifier. |
Hibernate OGM will create unique constraints for the identifier of entities and for the properties annotated with:
-
@Id
-
@EmbeddedId
-
@NaturalId
-
@Column( unique = true )
-
@Table( uniqueConstraints = @UniqueConstraint(columnNames = { "column_name" } ) )
Embedded identifiers are currently stored as dot separated properties.
Embedded elements are stored as separate nodes labeled with EMBEDDED
.
The type of the relationship that connects the entity node to the embedded node is the attribute name representing the embedded in the java class.
@Entity
public class News {
@EmbeddedId
private NewsID newsId;
@Embedded
private NewsPaper paper;
// getters, setters ...
}
@Embeddable
public class NewsID implements Serializable {
private String title;
private String author;
// getters, setters ...
}
@Embeddable
public class NewsPaper {
private String name;
private String owner;
// getters, setters ...
}
Note that in the previous examples no property is added to the relationships; in the following one, one property is added to keep track of the order of the elements in the list.
An association, bidirectional or unidirectional, is always mapped using one relationship, beginning at the owning side of the association. This is possible because in Neo4j relationships can be navigated in both directions.
The type of the relationships depends on the type of the association,
but in general it is the role of the association on the main side.
The only property stored on the relationship is going to be the index of the association when required,
for example when the association is annotated with @OrderColumn
or when a java.util.Map
is used.
In Neo4j nodes are connected via relationship, this means that we don’t need to create properties
which store foreign column keys. This means that annotation like @JoinColumn
won’t have any effect.
@Entity
public class SalesForce {
@Id
private String id;
private String corporation;
@OneToMany(mappedBy = "salesForce")
private Set<SalesGuy> salesGuys = new HashSet<SalesGuy>();
// getters, setters ...
}
@Entity
public class SalesGuy {
private String id;
private String name;
@ManyToOne
private SalesForce salesForce;
// getters, setters ...
}
@Entity
public class AccountOwner {
@Id
private String id;
private String SSN;
@ManyToMany
private Set<BankAccount> bankAccounts;
// getters, setters ...
}
@Entity
public class BankAccount {
@Id
private String id;
private String accountNumber;
@ManyToMany( mappedBy = "bankAccounts" )
private Set<AccountOwner> owners = new HashSet<AccountOwner>();
// getters, setters ...
}
Hibernate OGM supports the table generation strategy as well as the sequence generation strategy with Neo4j. It is generally recommended to work with the latter, as it allows a slightly more efficient querying for the next sequence value.
Sequence-based generators are represented by nodes in the following form:
@Entity
public class Song {
...
@Id
@GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "songSequenceGenerator" )
@SequenceGenerator(
name = "songSequenceGenerator",
sequenceName = "song_sequence",
initialValue = INITIAL_VALUE,
allocationSize = 10)
public Long getId() {
return id;
}
...
Each sequence generator node is labelled with SEQUENCE
.
The sequence name can be specified via @SequenceGenerator#sequenceName()
.
A unique constraint is applied to the property sequence_name
in order to ensure uniqueness of sequences.
If required, you can set the initial value of a sequence and the increment size via
@SequenceGenerator#initialValue()
and @SequenceGenerator#allocationSize()
, respectively.
The options @SequenceGenerator#catalog()
and @SequenceGenerator#schema()
are not supported.
Table-based generators are represented by nodes in the following form:
Each table generator node is labelled with TABLE_BASED_SEQUENCE
and the table name as specified via @TableGenerator#table()
.
The sequence name is to be given via @TableGenerator#pkColumnValue()
.
The node properties holding the sequence name and value can be configured via
@TableGenerator#pkColumnName()
and @TableGenerator#valueColumnName()
, respectively.
A unique constraint is applied to the property sequence_name
to avoid the same sequence name is used twice within the same "table".
If required, you can set the initial value of a sequence and the increment size via
@TableGenerator#initialValue()
and @TableGenerator#allocationSize()
, respectively.
The options @TableGenerator#catalog()
, @TableGenerator#schema()
, @TableGenerator#uniqueConstraints()
and @TableGenerator#indexes()
are not supported.
The maximum number of labels the database can contain is roughly 2 billion.
The following summary will help you to keep track of the labels assigned to a new node:
NODE TYPE | LABELS |
---|---|
Entity |
ENTITY, <Entity class name> |
Embeddable |
EMBEDDED, <Embeddable class name> |
GenerationType.SEQUENCE |
SEQUENCE |
GenerationType.TABLE |
TABLE_BASED_SEQUENCE, <Table name> |
In Neo4j, operations must be executed inside a transaction. Make sure your interactions with Hibernate OGM are within a transaction when you target Neo4J.
Session session = factory.openSession();
Transaction tx = session.beginTransaction();
Account account = new Account();
account.setLogin( "myAccount" );
session.persist( account );
tx.commit();
...
tx = session.beginTransaction();
Account savedAccount = (Account) session.get( Account.class, account.getId() );
tx.commit();
In the case of JTA, Hibernate OGM attaches the Neo4J internal transaction to the JTA transaction lifecycle. That way when the JTA transaction is committed or rollbacked (for example by an EJB CMT or explicitly), the Neo4J transaction is also committed or rollbacked. This makes for a nice integration in a Java EE container.
This is NOT a true JTA/XA integration but more a lifecycle alignment: changes on more than one datasource won't be executed as a single atomic transaction. In particular, if the JTA transaction involves multiple resources, Neo4j might commit before a failure of another resource. In this case, Neo4j won't be able to rollback even if the JTA transaction will.
You can express queries in a few different ways:
-
using JP-QL
-
using the Cypher query language
Note
|
Neo4J makes use of a Lucene version which is not compatible with the most recent Hibernate Search version. This unfortunately makes it impossible to use the latest Hibernate Search version and Neo4J embedded in the same application. |
While you can use JP-QL for simple queries, you might hit limitations. The current recommended approach is to use native Cypher queries if your query involves nested (list of) elements.
Hibernate OGM is a work in progress, so only a sub-set of JP-QL constructs is available when using the JP-QL query support. This includes:
-
simple comparisons using "<", "<=", "=", ">=" and ">"
-
IS NULL
andIS NOT NULL
-
the boolean operators
AND
,OR
,NOT
-
LIKE
,IN
andBETWEEN
-
ORDER BY
-
inner
JOIN
on embedded collections -
projections of regular and embedded properties
Queries using these constructs will be transformed into equivalent Cypher queries.
Note
|
Let us know by opening an issue or sending an email what query you wish to execute. Expanding our support in this area is high on our priority list. |
Hibernate OGM also supports Cypher queries for Neo4j. You can execute Cypher queries as shown in the following example:
@Entity
public class Poem {
@Id
private Long id;
private String name;
private String author;
// getters, setters ...
}
...
javax.persistence.EntityManager em = ...
// a single result query
String query1 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) RETURN n";
Poem poem = (Poem) em.createNativeQuery( query1, Poem.class ).getSingleResult();
// query with order by
String query2 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) " +
"RETURN n ORDER BY n.name";
List<Poem> poems = em.createNativeQuery( query2, Poem.class ).getResultList();
// query with projections
String query3 = MATCH ( n:Poem ) RETURN n.name, n.author ORDER BY n.name";
List<Object[]> poemNames = (List<Object[]>) em.createNativeQuery( query3 )
.getResultList();
The result of a query is a managed entity (or a list thereof) or a projection of attributes in form of an object array, just like you would get from a JP-QL query.
OgmSession session = ...
String query1 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) " +
"RETURN n";
Poem poem = session.createNativeQuery( query1 )
.addEntity( "Poem", Poem.class )
.uniqueResult();
String query2 = "MATCH ( n:Poem { name:'Portia', author:'Oscar Wilde' } ) " +
"RETURN n ORDER BY n.name";
List<Poem> poems = session.createNativeQuery( query2 )
.addEntity( "Poem", Poem.class )
.list();
Native queries can also be created using the @NamedNativeQuery
annotation:
@Entity
@NamedNativeQuery(
name = "AthanasiaPoem",
query = "MATCH ( n:Poem { name:'Athanasia', author:'Oscar Wilde' } ) RETURN n",
resultClass = Poem.class )
public class Poem { ... }
...
// Using the EntityManager
Poem poem1 = (Poem) em.createNamedQuery( "AthanasiaPoem" )
.getSingleResult();
// Using the Session
Poem poem2 = (Poem) session.getNamedQuery( "AthanasiaPoem" )
.uniqueResult();
Hibernate OGM stores data in a natural way so you can still execute queries using your favorite tool, the main drawback is that the results are going to be raw Neo4j elements and not managed entities.