Hibernate.orgCommunity Documentation
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.
1. Add the dependencies to your project. 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>5.0.4.Final</version>
</dependency>
Alternatively you can find the required libraries in the distribution package on SourceForge
2. Add the following properties:
hibernate.ogm.datastore.provider = neo4j_embedded
hibernate.ogm.neo4j.database_path = C:\example\mydb
The following properties are available to configure Neo4j support:
Neo4j datastore configuration properties
C:\neo4jdb\mydb
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 the org.hibernate.tool.hbm2ddl.UniqueConstraintSchemaUpdateStrategy
enum) are DROP_RECREATE_QUIETLY
and RECREATE_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 is DROP_RECREATE_QUIETLY
.When bootstrapping a session factory or entity manager factory programmatically,
you should use the constants defined on org.hibernate.ogm.datastore.neo4j.Neo4jProperties
when specifying the configuration properties listed above.
Common properties shared between stores are declared on OgmProperties
(a super interface of Neo4jProperties
).
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
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.
Example 12.1. Default JPA mapping for an entity
@Entity
public class News {
@Id
private String id;
private String title;
// getters, setters ...
}
Example 12.2. Rename node label and properties using @Table and @Column
@Entity
@Table(name="ARTICLE")
public class News {
@Id
private String id;
@Column(name = "headline")
private String title;
// getters, setters ...
}
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.
Example 12.3. Entity with @EmbeddedId
@Entity
public class News {
@EmbeddedId
private NewsID newsId;
private String content
// getters, setters ...
}
@Embeddable
public class NewsID implements Serializable {
private String title;
private String author;
// getters, setters ...
}
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.
Example 12.4. Embedded object
@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 ...
}
Example 12.5. @ElementCollection
@Entity
public class GrandMother {
@Id
private String id;
@ElementCollection
private List<GrandChild> grandChildren = new ArrayList<GrandChild>();
// getters, setters ...
}
@Embeddable
public class GrandChild {
private String name;
// 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.
Example 12.6. @ElementCollection with @OrderColumn
@Entity
public class GrandMother {
@Id
private String id;
@ElementCollection
@OrderColumn( name = "birth_order" )
private List<GrandChild> grandChildren = new ArrayList<GrandChild>();
// getters, setters ...
}
@Embeddable
public class GrandChild {
private String name;
// getters, setters ...
}
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.
Example 12.7. Unidirectional one-to-one
@Entity
public class Vehicule {
@Id
private String id;
private String brand;
// getters, setters ...
}
@Entity
public class Wheel {
@Id
private String id;
private String company;
private double diameter;
@OneToOne
private Vehicule vehicule;
// getters, setters ...
}
Example 12.8. Bidirectional one-to-one
@Entity
public class Husband {
@Id
private String id;
private String name;
@OneToOne
private Wife wife;
// getters, setters ...
}
@Entity
public class Wife {
@Id
private String id;
private String name;
@OneToOne(mappedBy = "wife")
private Husband husband;
// getters, setters ...
}
Example 12.9. Unidirectional one-to-many
@Entity
public class Basket {
@Id
private String id;
private String owner;
@OneToMany
private List<Product> products = new ArrayList<Product>();
// getters, setters ...
}
@Entity
public class Product {
@Id
private String name;
private String description;
// getters, setters ...
}
Example 12.10. Unidirectional one-to-many using maps with defaults
@Entity
public class User {
@Id
private String id;
@OneToMany
private Map<String, Address> addresses = new HashMap<String, Address>();
// getters, setters ...
}
@Entity
public class Address {
@Id
private String id;
private String city;
// getters, setters ...
}
Example 12.11. Unidirectional one-to-many using maps with @MapKeyColumn
@Entity
public class User {
@Id
private String id;
@OneToMany
@MapKeyColumn(name = "addressType")
private Map<String, Address> addresses = new HashMap<String, Address>();
// getters, setters ...
}
@Entity
public class Address {
@Id
private String id;
private String city;
// getters, setters ...
}
Example 12.12. Unidirectional many-to-one
@Entity
public class JavaUserGroup {
@Id
private String jug_id;
private String name;
// getters, setters ...
}
@Entity
public class Member {
@Id
private String id;
private String name;
@ManyToOne
private JavaUserGroup memberOf;
// getters, setters ...
}
Example 12.13. Bidirectional many-to-one
@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 ...
}
Example 12.14. Unidirectional many-to-many
@Entity
public class Student {
@Id
private String id;
private String name;
// getters, setters ...
}
@Entity
public class ClassRoom {
@Id
private long id;
private String lesson;
@ManyToMany
private List<Student> students = new ArrayList<Student>();
// getters, setters ...
}
Example 12.15. Bidirectional many-to-many
@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:
Example 12.16. GenerationType.SEQUENCE
@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:
Example 12.17. GenerationType.TABLE
@Entity
public class Video {
...
@Id
@GeneratedValue( strategy = GenerationType.TABLE, generator = "video" )
@TableGenerator(
name = "video",
table = "Sequences",
pkColumnName = "key",
pkColumnValue = "video",
valueColumnName = "seed"
)
public Integer getId() {
return id;
}
...
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:
Table 12.1. Summary 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.
Example 12.18. Example of starting and committing transactions
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:
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:
IS NULL
and IS NOT NULL
AND
, OR
, NOT
LIKE
, IN
and BETWEEN
ORDER BY
JOIN
on embedded collectionsQueries using these constructs will be transformed into equivalent Cypher queries.
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:
Example 12.19. Using the JPA API
@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.
Example 12.20. Using the Hibernate native API
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:
Example 12.21. Using @NamedNativeQuery
@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.