Skip to content

Latest commit

 

History

History
1923 lines (1484 loc) · 51.5 KB

File metadata and controls

1923 lines (1484 loc) · 51.5 KB

Infinispan

Infinispan is an open source in-memory data grid focusing on high performance. As a data grid, you can deploy it on multiple servers - referred to as nodes - and connect to it as if it were a single storage engine: it will cleverly distribute both the computation effort and the data storage.

It is trivial to setup on a single node, in your local JVM, so you can easily try Hibernate OGM. But Infinispan really shines in multiple node deployments: you will need to configure some networking details but nothing changes in terms of application behaviour, while performance and data size can scale linearly.

From all its features we will only describe those relevant to Hibernate OGM; for a complete description of all its capabilities and configuration options, refer to the Infinispan project documentation at infinispan.org.

Configure Infinispan

You configure Hibernate OGM and Infinispan in two steps basically:

  • Add the dependencies to your classpath

  • And then choose one of:

    • Use the default Infinispan configuration (no action needed)

    • Point to your own configuration resource file

    • Point to a JNDI name of an existing Infinispan instance

Adding Infinispan dependencies

To add the dependencies via Maven, add the following module:

<dependency>
    <groupId>org.hibernate.ogm</groupId>
    <artifactId>hibernate-ogm-infinispan</artifactId>
    <version>{hibernate-ogm-version}</version>
</dependency>

If you’re not using a dependency management tool, copy all the dependencies from the distribution in the directories:

  • /lib/required

  • /lib/infinispan

  • Optionally - depending on your container - you might need some of the jars from /lib/provided

Infinispan specific configuration properties

The advanced configuration details of an Infinispan Cache are defined in an Infinispan specific XML configuration file; the Hibernate OGM properties are simple and usually just point to this external resource.

To use the default configuration provided by Hibernate OGM - which is a good starting point for new users - you don’t have to set any property.

Hibernate OGM properties for Infinispan
hibernate.ogm.datastore.provider

Set it to infinispan to use Infinispan as the datastore provider.

hibernate.ogm.infinispan.cachemanager_jndi_name

If you have an Infinispan EmbeddedCacheManager registered in JNDI, provide the JNDI name and Hibernate OGM will use this instance instead of starting a new CacheManager. This will ignore any further configuration properties as Infinispan is assumed being already configured. Infinispan can typically be pushed to JNDI via WildFly, Spring or Seam.

hibernate.ogm.infinispan.configuration_resource_name

Should point to the resource name of an Infinispan configuration file. This is ignored in case JNDI lookup is set. Defaults to org/hibernate/ogm/datastore/infinispan/default-config.xml.

hibernate.ogm.datastore.keyvalue.cache_storage

The strategy for persisting data in Infinispan. The following two strategies exist (values of the org.hibernate.ogm.datastore.keyvalue.options.CacheMappingType enum):

  • CACHE_PER_TABLE: A dedicated cache will be used for each entity type, association type and id source table.

  • CACHE_PER_KIND: Three caches will be used: one cache for all entities, one cache for all associations and one cache for all id sources.

Defaults to CACHE_PER_TABLE. It is the recommended strategy as it makes it easier to target a specific cache for a given entity.

Note

When bootstrapping a session factory or entity manager factory programmatically, you should use the constants accessible via InfinispanProperties when specifying the configuration properties listed above.

Common properties shared between stores are declared on OgmProperties (a super interface of InfinispanProperties).

For maximum portability between stores, use the most generic interface possible.

Cache names used by Hibernate OGM

Depending on the cache mapping approach, Hibernate OGM will either:

  • store each entity type, association type and id source table in a dedicated cache very much like what Hibernate ORM would do. This is the CACHE_PER_TABLE approach.

  • store data in three different caches when using the CACHE_PER_KIND approach:

    • ENTITIES: is going to be used to store the main attributes of all your entities.

    • ASSOCIATIONS: stores the association information representing the links between entities.

    • IDENTIFIER_STORE: contains internal metadata that Hibernate OGM needs to provide sequences and auto-incremental numbers for primary key generation.

The preferred strategy is CACHE_PER_TABLE as it offers both more fine grained configuration options and the ability to work on specific entities in a more simple fashion.

In the following paragraphs, we will explain which aspects of Infinispan you’re likely to want to reconfigure from their defaults. All attributes and elements from Infinispan which we don’t mention are safe to ignore. Refer to the Infinispan User Guide for the guru level performance tuning and customizations.

An Infinispan configuration file is an XML file complying with the Infinispan schema; the basic structure is shown in the following example:

Example 1. Simple structure of an infinispan xml configuration file
<?xml version="1.0" encoding="UTF-8"?>
<infinispan
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="urn:infinispan:config:7.0 http://www.infinispan.org/schemas/infinispan-config-7.0.xsd"
    xmlns="urn:infinispan:config:7.0">

    <cache-container name="HibernateOGM" default-cache="DEFAULT">

        <!-- *************************** -->
        <!--   Default cache settings    -->
        <!-- *************************** -->
        <local-cache name="DEFAULT">
            <transaction mode="NON_DURABLE_XA"
                         transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup"/>
        </local-cache>

        <local-cache name="User"/>

        <local-cache name="Order"/>

        <local-cache name="associations_User_Order"/>

    </cache-container>
</infinispan>

There are global settings that can be set before the cache_container section. These settings will affect the whole instance; mainly of interest for Hibernate OGM users is the jgroups element in which we will set JGroups configuration overrides.

Inside the cache-container section are defined explicit named caches and their configurations as well as the default cache (named DEFAULT here) if we want to affect all named caches. This is where we will likely want to configure clustering modes, eviction policies and CacheStores.

Manage data size

In its default configuration Infinispan stores all data in the heap of the JVM; in this barebone mode it is conceptually not very different than using a HashMap: the size of the data should fit in the heap of your VM, and stopping/killing/crashing your application will get all data lost with no way to recover it.

To store data permanently (out of the JVM memory) a CacheStore should be enabled. The infinispan-core.jar includes a simple implementation able to store data in simple binary files, on any read/write mounted filesystem; this is an easy starting point, but the real stuff is to be found in the additional modules found in the Infinispan distribution. Here you can find many more implementations to store your data in anything from JDBC connected relational databases, other NoSQL engines, to cloud storage services or other Infinispan clusters. Finally, implementing a custom CacheStore is quite easy.

To limit the memory consumption of the precious heap space, you can activate a passivation or an eviction policy; again there are several strategies to play with, for now let’s just consider you’ll likely need one to avoid running out of memory when storing too many entries in the bounded JVM memory space; of course you don’t need to choose one while experimenting with limited data sizes: enabling such a strategy doesn’t have any other impact in the functionality of your Hibernate OGM application (other than performance: entries stored in the Infinispan in-memory space is accessed much quicker than from any CacheStore).

A CacheStore can be configured as write-through, committing all changes to the CacheStore before returning (and in the same transaction) or as write-behind. A write-behind configuration is normally not encouraged in storage engines, as a failure of the node implies some data might be lost without receiving any notification about it, but this problem is mitigated in Infinispan because of its capability to combine CacheStore write-behind with a synchronous replication to other Infinispan nodes.

Example 2. Enabling a FileCacheStore and eviction
<local-cache name="User">
    <transaction mode="NON_DURABLE_XA"
                 transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup"/>
    <eviction strategy="LIRS" max-entries="2000"/>
    <persistence passivation="true">
        <file-store
           shared="false"
           path="/var/infinispan/myapp/users"
            <write-behind flush-lock-timeout="15000" thread-pool-size="5" />
        </file-store>
    </persistence>
</local-cache>

In this example we enabled both eviction and a CacheStore (the persistence element). LIRS is one of the choices we have for eviction strategies. Here it is configured to keep (approximately) 2000 entries in live memory and evict the remaining as a memory usage control strategy.

The CacheStore is enabling passivation, which means that the entries which are evicted are stored on the filesystem.

Warning

You could configure an eviction strategy while not configuring a passivating CacheStore! That is a valid configuration for Infinispan but will have the evictor permanently remove entries. Hibernate OGM will break in such a configuration.

Clustering: store data on multiple Infinispan nodes

The best thing about Infinispan is that all nodes are treated equally and it requires almost no beforehand capacity planning: to add more nodes to the cluster you just have to start new JVMs, on the same or different physical servers, having your same Infinispan configuration and your same application.

Infinispan supports several clustering cache modes; each mode provides the same API and functionality but with different performance, scalability and availability options:

Infinispan cache modes
local

Useful for a single VM: networking stack is disabled

replication

All data is replicated to each node; each node contains a full copy of all entries. Consequentially reads are faster but writes don’t scale as well. Not suited for very large datasets.

distribution

Each entry is distributed on multiple nodes for redundancy and failure recovery, but not to all the nodes. Provides linear scalability for both write and read operations. distribution is the default mode.

To use the replication or distribution cache modes Infinispan will use JGroups to discover and connect to the other nodes.

In the default configuration, JGroups will attempt to autodetect peer nodes using a multicast socket; this works out of the box in the most network environments but will require some extra configuration in cloud environments (which often block multicast packets) or in case of strict firewalls. See the JGroups reference documentation, specifically look for Discovery Protocols to customize the detection of peer nodes.

Nowadays, the JVM defaults to use IPv6 network stack; this will work fine with JGroups, but only if you configured IPv6 correctly. It is often useful to force the JVM to use IPv4.

It is also useful to let JGroups know which networking interface you want to use; especially if you have multiple interfaces it might not guess correctly.

Example 3. JVM properties to set for clustering
#192.168.122.1 is an example IPv4 address
-Djava.net.preferIPv4Stack=true -Djgroups.bind_addr=192.168.122.1
Note

You don’t need to use IPv4: JGroups is compatible with IPv6 provided you have routing properly configured and valid addresses assigned.

The jgroups.bind_addr needs to match a placeholder name in your JGroups configuration in case you don’t use the default one.

The default configuration uses distribution as cache mode and uses the jgroups-tcp.xml configuration for JGroups, which is contained in the Infinispan jar as the default configuration for Infinispan users. Let’s see how to reconfigure this:

Example 4. Reconfiguring cache mode and override JGroups configuration
<?xml version="1.0" encoding="UTF-8"?>
<infinispan
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="urn:infinispan:config:7.0 http://www.infinispan.org/schemas/infinispan-config-7.0.xsd"
    xmlns="urn:infinispan:config:7.0">

    <jgroups>
        <stack-file name="custom-stack" path="my-jgroups-conf.xml" />
    </jgroups>

    <cache-container name="HibernateOGM" default-cache="DEFAULT">
        <transport stack="custom-stack" />

        <!-- *************************************** -->
        <!--     Default cache used as template      -->
        <!-- *************************************** -->
        <distrubuted-cache name="DEFAULT" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA"
                transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </distributed-cache>

        <!-- Override the cache mode: -->
        <replicated-cache name="User" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA"
                transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </replicated-cache>

        <distributed-cache name="Order" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA"
                transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </distributed-cache>

        <distributed-cache name="associations_User_Order" mode="SYNC">
            <locking striping="false" acquire-timeout="10000"
                concurrency-level="500" write-skew="false" />
            <transaction mode="NON_DURABLE_XA"
                transaction-manager-lookup="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup" />
            <state-transfer enabled="true" timeout="480000"
                await-initial-transfer="true" />
        </distributed-cache>

    </cache-container>

</infinispan>

In the example above we specify a custom JGroups configuration file and set the cache mode for the default cache to distribution; this is going to be inherited by the Order and the associations_User_Order caches. But for User we have chosen (for the sake of this example) to use replication.

Now that you have clustering configured, start the service on multiple nodes. Each node will need the same configuration and jars.

Tip

We have just shown how to override the clustering mode and the networking stack for the sake of completeness, but you don’t have to!

Start with the default configuration and see if that fits you. You can fine tune these setting when you are closer to going in production.

Storage principles

To describe things simply, each entity is stored under a single key. The value itself is a map containing the columns / values pair.

Each association from one entity instance to (a set of) another is stored under a single key. The value contains the navigational information to the (set of) entity.

Properties and built-in types

Each entity is represented by a map. Each property or more precisely column is represented by an entry in this map, the key being the column name.

Hibernate OGM support by default the following property types:

  • java.lang.String

  • java.lang.Character (or char primitive)

  • java.lang.Boolean (or boolean primitive); 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.Byte (or byte primitive)

  • java.lang.Short (or short primitive)

  • java.lang.Integer (or integer primitive)

  • java.lang.Long (or long primitive)

  • java.lang.Integer (or integer primitive)

  • java.lang.Float (or float primitive)

  • java.lang.Double (or double primitive)

  • java.math.BigDecimal

  • java.math.BigInteger

  • java.util.Calendar

  • java.util.Date

  • java.util.UUID

  • java.util.URL

Note

Hibernate OGM doesn’t store null values in Infinispan, setting a value to null is the same as removing the corresponding entry from Infinispan.

This can have consequences when it comes to queries on null value.

Identifiers

Entity identifiers are used to build the key in which the entity is stored in the cache.

The key is comprised of the following information:

  • the identifier column names

  • the identifier column values

  • the entity table (for the CACHE_PER_KIND strategy)

In CACHE_PER_TABLE, the table name is inferred from the cache name. In CACHE_PER_KIND, the table name is necessary to identify the entity in the generic cache.

Example 5. Define an identifier as a primitive type
@Entity
public class Bookmark {

    @Id
    private Long id;

    private String title;

    // getters, setters ...
}
Table 1. Content of the Bookmark cache in CACHE_PER_TABLE
KEY MAP ENTRIES

["id"], [42]

id

42

title

"Hibernate OGM documentation"

Table 2. Content of the ENTITIES cache in CACHE_PER_KIND
KEY MAP ENTRIES

"Bookmark", ["id"], [42]

id

42

title

"Hibernate OGM documentation"

Example 6. Define an identifier using @EmbeddedId
@Embeddable
public class NewsID implements Serializable {

    private String title;
    private String author;

    // getters, setters ...
}

@Entity
public class News {

    @EmbeddedId
    private NewsID newsId;
    private String content;

    // getters, setters ...
}
Table 3. Content of the News cache in CACHE_PER_TABLE
KEY MAP ENTRIES

[newsId.author, newsId.title], ["Guillaume", "How to use Hibernate OGM ?"]

newsId.author

"Guillaume"

newsId.title

"How to use Hibernate OGM ?"

content

"Simple, just like ORM but with a NoSQL database"

Table 4. Content of the ENTITIES cache in CACHE_PER_KIND
KEY MAP ENTRIES

"News", [newsId.author, newsId.title], ["Guillaume", "How to use Hibernate OGM ?"]

newsId.author

"Guillaume"

newsId.title

"How to use Hibernate OGM ?"

content

"Simple, just like ORM but with a NoSQL database"

Identifier generation strategies

Since Infinispan has not native sequence nor identity column support, these are simulated using the table strategy, however their default values vary. We highly recommend you explicitly use a TABLE strategy if you want to generate a monotonic identifier.

But if you can, use a pure in-memory and scalable strategy like a UUID generator.

Example 7. Id generation strategy TABLE using default values
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE)
    private long id;

    private String name;

    // getters, setters ...
}
Table 5. Content of the hibernate_sequences cache in CACHE_PER_TABLE
KEY NEXT VALUE

["sequence_name"], ["default"]

2

Table 6. Content of the IDENTIFIERS cache in CACHE_PER_KIND
KEY NEXT VALUE

"hibernate_sequences", ["sequence_name"], ["default"]

2

As you can see, in CACHE_PER_TABLE, the key does not contain the id source table name. It is inferred by the cache name hosting that key.

Example 8. Id generation strategy TABLE using a custom table
@Entity
public class GuitarPlayer {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "guitarGen")
    @TableGenerator(
        name = "guitarGen",
        table = "GuitarPlayerSequence",
        pkColumnName = "seq"
        pkColumnValue = "guitarPlayer",
    )
    private long id;

    // getters, setters ...
}
Table 7. Content of the GuitarPlayerSequence cache in CACHE_PER_TABLE
KEY NEXT VALUE

["seq"], ["guitarPlayer"]

2

Table 8. Content of the IDENTIFIERS cache in CACHE_PER_KIND
KEY NEXT VALUE

"GuitarPlayerSequence", ["seq"], ["guitarPlayer"]

2

Example 9. SEQUENCE id generation strategy
@Entity
public class Song {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "songSequenceGenerator")
  @SequenceGenerator(
      name = "songSequenceGenerator",
      sequenceName = "song_sequence",
      initialValue = 2,
      allocationSize = 20
  )
  private Long id;

  private String title;

  // getters, setters ...
}
Table 9. Content of the hibernate_sequences cache in CACHE_PER_TABLE
KEY NEXT VALUE

["sequence_name"], ["song_sequence"]

11

Table 10. Content of the IDENTIFIERS cache in CACHE_PER_KIND
KEY NEXT VALUE

"hibernate_sequences", "["sequence_name"], ["song_sequence"]

11

Entities

Entities are stored in the cache named after the entity name when using the CACHE_PER_TABLE strategy. In the CACHE_PER_KIND strategy, entities are stored in a single cache named ENTITIES.

The key is comprised of the following information:

  • the identifier column names

  • the identifier column values

  • the entity table (for the CACHE_PER_KIND strategy)

In CACHE_PER_TABLE, the table name is inferred from the cache name. In CACHE_PER_KIND, the table name is necessary to identify the entity in the generic cache.

The entry value is an instance of org.infinispan.atomic.FineGrainedMap which contains all the entity properties - or to be specific columns. Each column name and value is stored as a key / value pair in the map. We use this specialized map as Infinispan is able to transport changes in a much more efficient way.

Example 10. Default JPA mapping for an entity
@Entity
public class News {

    @Id
    private String id;
    private String title;

    // getters, setters ...
}
Table 11. Content of the News cache in CACHE_PER_TYPE
KEY MAP ENTRIES

["id"], ["1234-5678"]

id

"1234-5678"

title

"On the merits of NoSQL"

Table 12. Content of the ENTITIES cache in CACHE_PER_KIND
KEY MAP ENTRIES

"News", ["id"], ["1234-5678"]

id

"1234-5678"

title

"On the merits of NoSQL"

As you can see, the table name is not part of the key for CACHE_PER_TYPE. In the rest of this section we will no longer show the CACHE_PER_KIND strategy.

Example 11. Rename field and collection using @Table and @Column
@Entity
@Table(name = "Article")
public class News {

    @Id
    private String id;

    @Column(name = "headline")
    private String title;

    // getters, setters ...
}
Table 13. Content of the Article cache
KEY MAP ENTRIES

["id"], ["1234-5678"]

id

"1234-5678"

headline

"On the merits of NoSQL"

Embedded objects and collections
Example 12. Embedded object
@Entity
public class News {

    @Id
    private String id;
    private String title;

    @Embedded
    private NewsPaper paper;

    // getters, setters ...
}

@Embeddable
public class NewsPaper {

    private String name;
    private String owner;

    // getters, setters ...
}
Table 14. Content of the News cache
KEY MAP ENTRIES

["id"], ["1234-5678"]

id

"1234-5678"

title

"On the merits of NoSQL"

paper.name

"NoSQL journal of prophecies"

paper.owner

"Delphy"

Example 13. @ElementCollection with one attribute
@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 ...
}
Table 15. Content of the GrandMother cache
KEY MAP ENTRIES

["id"], ["granny"]

id

"granny"

Table 16. Content of the associations_GrandMother_grandChildren cache in CACHE_PER_TYPE
KEY ROW KEY ROW MAP ENTRIES

["GrandMother_id"], ["granny"]

["GrandMother_id", "name"], ["granny", "Leia"]

GrandMother_id

"granny"

name

"Leia"

["GrandMother_id", "name"], ["granny", "Luke"]

GrandMother_id

"granny"

name

"Luke"

Table 17. Content of the ASSOCIATIONS cache in CACHE_PER_KIND
KEY ROW KEY ROW MAP ENTRIES

"GrandMother_grandChildren", ["GrandMother_id"], ["granny"]

["GrandMother_id", "name"], ["granny", "Leia"]

GrandMother_id

"granny"

name

"Leia"

["GrandMother_id", "name"], ["granny", "Luke"]

GrandMother_id

"granny"

name

"Luke"

Here, we see that the collection of elements is stored in a separate cache and entry. The association key is made of:

  • the foreign key column names pointing to the owner of this association

  • the foreign key column values pointing to the owner of this association

  • the association table name in the CACHE_PER_KIND approach where all associations share the same cache

The association entry is a map containing the representation of each entry in the collection. The keys of that map are made of:

  • the names of the columns uniquely identifying that specific collection entry (e.g. for a Set this is all of the columns)

  • the values of the columns uniquely identifying that specific collection entry

The value attack to that collection entry key is a Map containing the key value pairs column name / column value.

Example 14. @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 ...
}
Table 18. Content of the GrandMother cache
KEY MAP ENTRIES

["id"], ["granny"]

id

"granny"

Table 19. Content of the GrandMother_grandChildren cache
KEY ROW KEY ROW MAP ENTRIES

["GrandMother_id"], ["granny"]

["GrandMother_id", "birth_order"], ["granny", 0]

GrandMother_id

"granny"

birth_order

0

name

"Leia"

["GrandMother_id", "birth_order"], ["granny", 1]

GrandMother_id

"granny"

birth_order

1

name

"Luke"

Here we used an indexed collection and to identify the entry in the collection, only the owning entity id and the index value is enough.

Associations

Associations between entities are mapped like (collection of) embeddables except that the target entity is represented by its identifier(s).

Example 15. 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 double diameter;

    @OneToOne
    private Vehicule vehicule;

    // getters, setters ...
}
Table 20. Content of the Vehicule cache
KEY MAP ENTRIES

["id"], ["V_01"]

id

"V_01"

brand

"Mercedes"

Table 21. Content of the Wheel cache
KEY MAP ENTRIES

["id"], ["W001"]

id

"W001"

diameter

0.0

vehicule_id

"V_01"

Example 16. Unidirectional one-to-one with @JoinColumn
@Entity
public class Vehicule {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}


@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    @JoinColumn( name = "part_of" )
    private Vehicule vehicule;

    // getters, setters ...
}
Table 22. Content of the Vehicle cache
KEY MAP ENTRIES

["id"], ["V_01"]

id

"V_01"

brand

"Mercedes"

Table 23. Content of the Wheel cache
KEY MAP ENTRIES

"Wheel", ["id"], ["W001"]

id

"W001"

diameter

0.0

part_of

"V_01"

Example 17. Unidirectional one-to-one with @MapsId and @PrimaryKeyJoinColumn
@Entity
public class Vehicule {

    @Id
    private String id;
    private String brand;

    // getters, setters ...
}

@Entity
public class Wheel {

    @Id
    private String id;
    private double diameter;

    @OneToOne
    @PrimaryKeyJoinColumn
    @MapsId
    private Vehicule vehicule;

    // getters, setters ...
}
Table 24. Content of the Vehicle cache
KEY MAP ENTRIES

["id"], ["V_01"]

id

"V_01"

brand

"Mercedes"

Table 25. Content of the Wheel cache
KEY MAP ENTRIES

["vehicule_id"], ["V_01"]

vehicule_id

"V_01"

diameter

0.0

Example 18. 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 ...
}
Table 26. Content of the Husband cache
KEY MAP ENTRIES

["id"], ["alex"]

id

"alex"

name

"Alex"

wife

"bea"

Table 27. Content of the Wife cache
KEY MAP ENTRIES

["id"], ["bea"]

id

"bea"

name

"Bea"

Table 28. Content of the associations_Husband cache
KEY ROW KEY MAP ENTRIES

["wife"], ["bea"]

["id", "wife"], ["alex", "bea"]

id

"alex"

wife

"bea"

Example 19. 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 ...
}
Table 29. Content of the Basket cache
KEY MAP ENTRIES

["id"], ["davide_basket"]

id

"davide_basket"

owner

"Davide"

Table 30. Content of the Product cache
KEY MAP ENTRIES

["name"], ["Beer"]

name

"Beer"

description

"Tactical Nuclear Penguin"

["name"], ["Pretzel"]

name

"Pretzel"

description

"Glutino Pretzel Sticks"

Table 31. Content of the associations_Basket_Product cache
KEY ROW KEY MAP ENTRIES

["Basket_id"], ["davide_basket"]

["Basket_id", "products_name"], ["davide_basket", "Beer"]

Basket_id

"davide_basket"

products_name

"Beer"

["Basket_id", "products_name"], ["davide_basket", "Pretzel"]

Basket_id

"davide_basket"

products_name

"Pretzel"

Example 20. Unidirectional one-to-many with @JoinTable
@Entity
public class Basket {

    @Id
    private String id;

    private String owner;

    @OneToMany
    @JoinTable( name = "BasketContent" )
    private List<Product> products = new ArrayList<Product>();

    // getters, setters ...
}

@Entity
public class Product {

    @Id
    private String name;

    private String description;

    // getters, setters ...
}
Table 32. Content of the Basket cache
KEY MAP ENTRIES

["id"], ["davide_basket"]

id

"davide_basket"

owner

"Davide"

Table 33. Content of the Basket cache
KEY MAP ENTRIES

["name"], ["Beer"]

name

"Beer"

description

"Tactical Nuclear Penguin"

["name"], ["Pretzel"]

name

"Pretzel"

description

"Glutino Pretzel Sticks"

Table 34. Content of the associations_BasketContent cache
KEY ROW KEY MAP ENTRIES

["Basket_id"], ["davide_basket"]

["Basket_id", "products_name"], ["davide_basket", "Beer"]

Basket_id

"davide_basket"

products_name

"Beer"

["Basket_id", "products_name"], ["davide_basket", "Pretzel"]

Basket_id

"davide_basket"

products_name

"Pretzel"

Example 21. 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 ...
}
Table 35. Content of the User cache
KEY MAP ENTRIES

["id"], ["user_001"]

id

"user_001"

Table 36. Content of the Address cache
KEY MAP ENTRIES

["id"], ["address_001"]

id

"address_001"

city

"Rome"

["id"], ["address_002"]

id

"address_002"

city

"Paris"

Table 37. Content of the associations_User_address cache
KEY ROW KEY MAP ENTRIES

["User_id"], "user_001"]

["User_id", "addresses_KEY"], ["user_001", "home"]

User_id

"user_001"

addresses_KEY

"home"

addresses_id

"address_001"

["User_id", "addresses_KEY"], ["user_001", "work"]

User_id

"user_002"

addresses_KEY

"work"

addresses_id

"address_002"

Example 22. 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 ...
}
Table 38. Content of the User cache
KEY MAP ENTRIES

["id"], ["user_001"]

id

"user_001"

Table 39. Content of the Address cache
KEY MAP ENTRIES

["id"], ["address_001"]

id

"address_001"

city

"Rome"

["id"], ["address_002"]

id

"address_002"

city

"Paris"

Table 40. Content of the associations_User_address cache
KEY ROW KEY MAP ENTRIES

["User_id"], "user_001"]

["User_id", "addressType"], ["user_001", "home"]

User_id

"user_001"

addressesType

"home"

addresses_id

"address_001"

["User_id", "addressType"], ["user_001", "work"]

User_id

"user_002"

addressesType

"work"

addresses_id

"address_002"

Example 23. Unidirectional many-to-one
@Entity
public class JavaUserGroup {

    @Id
    private String jugId;
    private String name;

    // getters, setters ...
}

@Entity
public class Member {

    @Id
    private String id;
    private String name;

    @ManyToOne
    private JavaUserGroup memberOf;

    // getters, setters ...
}
Table 41. Content of the JavaUserGroup cache
KEY MAP ENTRIES

["jugId"], ["summer_camp"]

jugId

"summer_camp"

name

"JUG Summer Camp"

Table 42. Content of the Member cache
KEY MAP ENTRIES

["member_id"], ["emmanuel"]

member_id

"emmanuel"

name

"Emmanuel Bernard"

memberOf_jug_id

"summer_camp"

["member_id"], ["jerome"]

member_id

"jerome"

name

"Jerome"

memberOf_jug_id

"summer_camp"

Example 24. 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 ...
}
Table 43. Content of the SalesForce cache
KEY MAP ENTRIES

["id"], ["red_hat"]

id

"red_hat"

corporation

"Red Hat"

Table 44. Content of the SalesGuy cache
KEY MAP ENTRIES

["id"], ["eric"]

id

"eric"

name

"Eric"

salesForce_id

"red_hat"

["id"], ["simon"]

id

"simon"

name

"Simon"

salesForce_id

"red_hat"

Table 45. Content of the associations_SalesGuy cache
KEY ROW KEY MAP ENTRIES

["salesForce_id"], ["red_hat"]

["salesForce_id", "id"], ["red_hat", "eric"]

salesForce_id

"red_hat"

id

"eric"

["salesForce_id", "id"], ["red_hat", "simon"]

salesForce_id

"red_hat"

id

"simon"

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

The "Math" class has 2 students: John Doe and Mario Rossi

The "English" class has 2 students: Kate Doe and Mario Rossi

Table 46. Content of the ClassRoom cache
KEY MAP ENTRIES

["id"], [1]

id

1

name

"Math"

["id"], [2]

id

2

name

"English"

Table 47. Content of the Student cache
KEY MAP ENTRIES

["id"], ["john"]

id

"john"

name

"John Doe"

["id"], ["mario"]

id

"mario"

name

"Mario Rossi"

["id"], ["kate"]

id

"kate"

name

"Kate Doe"

Table 48. Content of the associations_ClassRoom_Student cache
KEY ROW KEY MAP ENTRIES

["ClassRoom_id"], [1]

["ClassRoom_id", "students_id"], [1, "mario"]

ClassRoom_id

1

students_id

"mario"

["ClassRoom_id", "students_id"], [1, "john"]

ClassRoom_id

1

students_id

"john"

["ClassRoom_id"], [2]

["ClassRoom_id", "students_id"], [2, "kate"]

ClassRoom_id

2

students_id

"kate"

["ClassRoom_id", "students_id"], [2, "mario"]

ClassRoom_id

2

students_id

"mario"

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

David owns 2 accounts: "012345" and "ZZZ-009"

Table 49. Content of the AccountOwner cache
KEY MAP ENTRIES

["id"], ["David"]

id

"David"

SSN

"0123456"

Table 50. Content of the BankAccount cache
KEY MAP ENTRIES

["id"], ["account_1"]

id

"account_1"

accountNumber

"X2345000"

["id"], ["account_2"]

id

"account_2"

accountNumber

"ZZZ-009"

Table 51. Content of the AccountOwner_BankAccount cache
KEY ROW KEY MAP ENTRIES

["bankAccounts_id"], ["account_1"]

["bankAccounts_id", "owners_id"], ["account_1", "David"]

bankAccounts_id

"account_1"

owners_id

"David"

["bankAccounts_id"], ["account_2"]

["bankAccounts_id", "owners_id"], ["account_2", "David"]

bankAccounts_id

"account_2"

owners_id

"David"

["owners_id"], ["David"]

["owners_id", "banksAccounts_id"], ["David", "account_1"]

bankAccounts_id

"account_1"

owners_id

"David"

["owners_id", "banksAccounts_id"], ["David", "account_2"]

bankAccounts_id

"account_2"

owners_id

"David"

Transactions

Infinispan supports transactions and integrates with any standard JTA TransactionManager; this is a great advantage for JPA users as it allows to experience a similar behaviour to the one we are used to when we work with RDBMS databases.

If you’re having Hibernate OGM start and manage Infinispan, you can skip this as it will inject the same TransactionManager instance which you already have set up in the Hibernate / JPA configuration.

If you are providing an already started Infinispan CacheManager instance by using the JNDI lookup approach, then you have to make sure the CacheManager is using the same TransactionManager as Hibernate:

Example 27. Configuring a JBoss Standalone TransactionManager lookup in Infinispan configuration
<default>
   <transaction
      transactionMode="TRANSACTIONAL"
      transactionManagerLookupClass=
    "org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup" />
</default>

Infinispan supports different transaction modes like PESSIMISTIC and OPTIMISTIC, supports XA recovery and provides many more configuration options; see the Infinispan User Guide for more advanced configuration options.

Storing a Lucene index in Infinispan

Hibernate Search, which can be used for advanced query capabilities (see [ogm-query]), needs some place to store the indexes for its embedded Apache Lucene engine.

A common place to store these indexes is the filesystem which is the default for Hibernate Search; however if your goal is to scale your NoSQL engine on multiple nodes you need to share this index. Network sharing file systems are a possibility but we don’t recommended that. Often the best option is to store the index in whatever NoSQL database you are using (or a different dedicated one).

Tip

You might find this section useful even if you don’t intend to store your data in Infinispan.

The Infinispan project provides an adaptor to plug into Apache Lucene, so that it writes the indexes in Infinispan and searches data in it. Since Infinispan can be used as an application cache to other NoSQL storage engines by using a CacheStore (see Manage data size) you can use this adaptor to store the Lucene indexes in any NoSQL store supported by Infinispan:

  • Cassandra

  • Filesystem (but locked correctly at the Infinispan level)

  • MongoDB

  • HBase

  • JDBC databases

  • JDBM

  • BDBJE

  • A secondary (independent) Infinispan grid

  • Any Cloud storage service supported by JClouds

How to configure it? Here is a simple cheat sheet to get you started with this type of setup:

  • Add org.hibernate:hibernate-search-infinispan:{hibernate-search-version} to your dependencies

  • set these configuration properties:

    • hibernate.search.default.directory_provider = infinispan

    • hibernate.search.default.exclusive_index_use = false

    • hibernate.search.infinispan.configuration_resourcename = [infinispan configuration filename]

The referenced Infinispan configuration should define a CacheStore to load/store the index in the NoSQL engine of choice. It should also define three cache names:

Table 52. Infinispan caches used to store indexes
Cache name Description Suggested cluster mode

LuceneIndexesLocking

Transfers locking information. Does not need a cache store.

replication

LuceneIndexesData

Contains the bulk of Lucene data. Needs a cache store.

distribution + L1

LuceneIndexesMetadata

Stores metadata on the index segments. Needs a cache store.

replication

This configuration is not going to scale well on write operations: to do that you should read about the master/slave and sharding options in Hibernate Search. The complete explanation and configuration options can be found in the Hibernate Search Reference Guide

Some NoSQL support storage of Lucene indexes directly, in which case you might skip the Infinispan Lucene integration by implementing a custom DirectoryProvider for Hibernate Search. You’re very welcome to share the code and have it merged in Hibernate Search for others to use, inspect, improve and maintain.