Skip to content

Latest commit

 

History

History
910 lines (673 loc) · 22.1 KB

File metadata and controls

910 lines (673 loc) · 22.1 KB

Neo4j

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.

How to add Neo4j integration

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>{hibernate-ogm-version}</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

Configuring Neo4j

The following properties are available to configure Neo4j support:

Neo4j datastore configuration properties
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 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.

Note

When bootstrapping a session factory or entity manager factory programmatically, you should use the constants accessible via 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.

Storage principles

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.

Properties and built-in types

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

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 1. Default JPA mapping for an entity
@Entity
public class News {

    @Id
    private String id;
    private String title;

    // getters, setters ...
}
neo4j single node example
Example 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 @Column @Table example
Identifiers and unique constraints
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.

Example 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 ...
}
neo4j @EmbeddedId example
Embedded objects and collections

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 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 ...
}
neo4j @Embedded example
Example 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 ...
}
neo4j @ElementCollection example

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 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 ...
}
neo4j @ElementCollection @OrderColumn example

Associations

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 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 ...
}
neo4j uni one to one example
Example 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 ...
}
neo4j bi one to one example
Example 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 ...
}
neo4j uni one to many example
Example 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 ...
}
neo4j uni one to many with map example
Example 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 ...
}
neo4j uni one to many with @MapKeyColumn example
Example 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 ...
}
neo4j uni many to one example
Example 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 ...
}
neo4j bi many to one example
Example 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 ...
}
neo4j uni many to many example
Example 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 ...
}
neo4j bi many to many example

Auto-generated Values

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 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;
    }

    ...
neo4j sequence example

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 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;
    }

    ...
neo4j table based sequence example

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.

Labels summary

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 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>

Transactions

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 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.

Queries

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.

JP-QL queries

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 and IS NOT NULL

  • the boolean operators AND, OR, NOT

  • LIKE, IN and BETWEEN

  • 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.

Cypher queries

Hibernate OGM also supports Cypher queries for Neo4j. You can execute Cypher queries as shown in the following example:

Example 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 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 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.