Chapter 2. Configuration

2.1. Overview

When using Hibernate Shards you will find yourself making typical Hibernate Core API calls most of them time. However, in order to get your shard-aware datasource properly configured you'll need to understand a few concepts that are specific to Hibernate Shards. We'll introduce these new concepts as part of a concrete example. Let's take a look at the object model, database schema, and mapping we'll be using in our examples throughout the documentation.

Our example application will receive weather reports from cities all over the world and store this information in a relational database.

2.1.1. Weather Report Database Schema

CREATE TABLE WEATHER_REPORT (
    REPORT_ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    CONTINENT ENUM('AFRICA', 'ANTARCTICA', 'ASIA', 'AUSTRALIA', 'EUROPE', 'NORTH AMERICA', 'SOUTH AMERICA'),
    LATITUDE FLOAT,
    LONGITUDE FLOAT,
    TEMPERATURE INT,
    REPORT_TIME TIMESTAMP
);
                

2.1.2. Weather Report Object Model

public class WeatherReport {
    private Integer reportId;
    private String continent;
    private BigDecimal latitude;
    private BigDecimal longitude;
    private int temperature;
    private Date reportTime;

    ... // getters and setters
}
                

2.1.3. Contents of weather.hbm.xml

<hibernate-mapping package="org.hibernate.shards.example.model">
    <class name="WeatherReport" table="WEATHER_REPORT">
        <id name="reportId" column="REPORT_ID">
            <generator class="native"/>
        </id>
        <property name="continent" column="CONTINENT"/>
        <property name="latitude" column="LATITUDE"/>
        <property name="longitude" column="LONGITUDE"/>
        <property name="temperature" column="TEMPERATURE"/>
        <property name="reportTime" type="timestamp" column="REPORT_TIME"/>
    </class>
</hibernate-mapping>
                

2.2. Obtaining a ShardedSessionFactory

Before we show you how to obtain a ShardedSessionFactory let's look at some code that allows you to obtain a standard SessionFactory.

1    public SessionFactory createSessionFactory() {
2        Configuration config = new Configuration();
3        config.configure("weather.hibernate.cfg.xml");
4        config.addResource("weather.hbm.xml");
5        return config.buildSessionFactory();
6    }

This is pretty straightforward. We're instantiating a new Configuration object (line 2), telling that Configuration to read its properties from a resource named "weather.hibernate.cfg.xml" (line 3), and then providing "weather.hbm.xml" as a source of OR mapping data (line 4). We are then asking the Configuration to build a SessionFactory, which we return (line 5).

Let's also take a look at the configuration file we're loading in:

 1    <!-- Contents of weather.hibernate.cfg.xml -->
 2    <hibernate-configuration>
 3      <session-factory name="HibernateSessionFactory">
 4        <property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
 5        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
 6        <property name="connection.url">jdbc:mysql://localhost:3306/mydb</property>
 7        <property name="connection.username">my_user</property>
 8        <property name="connection.password">my_password</property>
 9      </session-factory>
 10   </hibernate-configuration>
                

As you can see, there's nothing particularly interesting going on in the configuration file or the mapping file.

You'll be pleased to know that the process of configuring your application to use Hibernate Shards is not radically different. The main difference is that we're providing connectivity information for multiple datasources, and we're also describing our desired sharding behavior via a ShardStrategyFactory. Let's look at some sample configuration code for our weather report application, which we're going to run with 3 shards.

1     public SessionFactory createSessionFactory() {
2         Configuration prototypeConfig = new Configuration().configure("shard0.hibernate.cfg.xml");
3         prototypeConfig.addResource("weather.hbm.xml");
4         List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
5         shardConfigs.add(buildShardConfig("shard0.hibernate.cfg.xml"));
6         shardConfigs.add(buildShardConfig("shard1.hibernate.cfg.xml"));
7         shardConfigs.add(buildShardConfig("shard2.hibernate.cfg.xml"));
8         ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
9         ShardedConfiguration shardedConfig = new ShardedConfiguration(
10            prototypeConfig,
11            shardConfigs,
12            shardStrategyFactory);
13        return shardedConfig.buildShardedSessionFactory();
14    }
15
16    ShardStrategyFactory buildShardStrategyFactory() {
17        ShardStrategyFactory shardStrategyFactory = new ShardStrategyFactory() {
18            public ShardStrategy newShardStrategy(List<ShardId> shardIds) {
19                RoundRobinShardLoadBalancer loadBalancer = new RoundRobinShardLoadBalancer(shardIds);
20                ShardSelectionStrategy pss = new RoundRobinShardSelectionStrategy(loadBalancer);
21                ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(shardIds);
22                ShardAccessStrategy pas = new SequentialShardAccessStrategy();
23                return new ShardStrategyImpl(pss, prs, pas);
24            }
25        };
26        return shardStrategyFactory;
27    }
28
29    ShardConfiguration buildShardConfig(String configFile) {
30        Configuration config = new Configuration().configure(configFile);
31        return new ConfigurationToShardConfigurationAdapter(config);
32    }
  

So what's going on here? First, you'll notice that we're actually allocating four Configurations. The first Configuration we allocate (line 2) is the prototype Configuration. The ShardedSessionFactory we eventually construct (line 13) will contain references to 3 standard SessionFactory objects. Each of these 3 standard SessionFactory objects will have been constructed from the prototype configuration. The only attributes that will differ across these standard SessionFactory objects are:

  • connection.url

  • connection.user

  • connection.password

  • connection.datasource

  • cache.region_prefix

The three ShardConfiguration objects we're loading (lines 5 - 7) will be consulted for the shard-specific database url, database user, database password, datasource identifier, cache region prefix, and that's all. (For a discussion of what these properties are and how they are used, please consult the Hibernate Core documentation.) This means that if you change the connection pool parameters in shard1.hibernate.cfg.xml, those parameters will be ignored. If you add another mapping file to the Configuration loaded with the properties defined in shard2.hibernate.cfg.xml, that mapping will be ignored. With the exception of the properties listed above, the configuration of our shard-aware SessionFactory comes entirely from the prototype Configuration. This may seem a bit strict, but the sharding code needs to assume that all shards are identically configured.

If you're looking at this code and thinking it seems a bit silly to provide fully-formed configuration documents that, save a couple special properties, are ignored, rest assured we've looked at this code and thought the same thing. That's why the ShardedConfiguration constructor takes a List<ShardConfiguration> as opposed to a List<Configuration>. ShardConfiguration is an interface so you can make the shard-specific configuration data available any way you'd like. In our example we're using an implementation of this interface that wraps a standard Configuration (line 31) just to avoid introducing any unfamiliar configuration mechanisms.

Once we've built our Configuration objects we need to put together a ShardStrategyFactory (line 8). A ShardStrategyFactory is an object that knows how to create the 3 types of strategies that programmers can use to control the sharding behavior of the system. For more information on these strategies please see the chapters titled Sharding Strategies.

Once we've instantiated our ShardStrategyFactory we can construct a ShardedConfiguration (line 9), and once we've constructed our ShardedConfiguration we can ask it to create a ShardedSessionFactory (line 13). It's important to note that ShardedSessionFactory extends SessionFactory. This means we can return a standard SessionFactory (line 1). Our application's Hibernate code doesn't need to know that it's interacting with sharded data.

Now let's take a look at the configuration and mapping files that we loaded in. You'll definitely recognize them, but there are a few key additions and modifications related to sharding.

 1    <!-- Contents of shard0.hibernate.cfg.xml -->
 2    <hibernate-configuration>
 3      <session-factory name="HibernateSessionFactory0"> <!-- note the different name -->
 4        <property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
 5        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
 6        <property name="connection.url">jdbc:mysql://dbhost0:3306/mydb</property>
 7        <property name="connection.username">my_user</property>
 8        <property name="connection.password">my_password</property>
 9        <property name="hibernate.connection.shard_id">0</property> <!-- new -->
 10      <property name="hibernate.shard.enable_cross_shard_relationship_checks">true</property> <!-- new -->
 11    </session-factory>
 12  </hibernate-configuration>
                

 1    <!-- Contents of shard1.hibernate.cfg.xml -->
 2    <hibernate-configuration>
 3      <session-factory name="HibernateSessionFactory1"> <!-- note the different name -->
 4        <property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
 5        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
 6        <property name="connection.url">jdbc:mysql://dbhost1:3306/mydb</property>
 7        <property name="connection.username">my_user</property>
 8        <property name="connection.password">my_password</property>
 9        <property name="hibernate.connection.shard_id">1</property> <!-- new -->
 10      <property name="hibernate.shard.enable_cross_shard_relationship_checks">true</property> <!-- new -->
 11    </session-factory>
 12  </hibernate-configuration>
                

We'll skip the contents of shard2.hibernate.cfg.xml because the pattern should by now be obvious. We're giving each session factory a unique name via the name attribute of the session-factory element, and we're associating each session factory with a different database server. We're also giving each session factory a shard id. This is required. If you try to configure a ShardedSessionFactory with a Configuration object that does not have a shard id you'll get an error. At the moment we require that the shard id of one of your session factories be 0. Beyond that, the internal representation of a shard id is a java.lang.Integer so all values within that range are legal. Finally, each shard that is mapped into a ShardedSessionFactory must have a unique shard id. If you have a duplicate shard id you'll get an error.

The other noteworthy addition is the rather verbose but hopefully descriptive "hibernate.shard.enable_cross_shard_relationship_checks." You can read more about this in the chapter on limitations.

Now let's still see how the mapping file has changed.

<hibernate-mapping package="org.hibernate.shards.example.model">
    <class name="WeatherReport" table="WEATHER_REPORT">
        <id name="reportId" column="REPORT_ID" type="long">
            <generator class="org.hibernate.shards.id.ShardedTableHiLoGenerator"/>
        </id>
        <property name="continent" column="CONTINENT"/>
        <property name="latitude" column="LATITUDE"/>
        <property name="longitude" column="LONGITUDE"/>
        <property name="temperature" column="TEMPERATURE"/>
        <property name="reportTime" type="timestamp" column="REPORT_TIME"/>
    </class>
</hibernate-mapping>
                

The only meaningful change in the mapping file from the non-sharded version is in our selection of a shard-aware id generator. We'll cover id generation in more detail in the chapter on Shard Strategies.

2.3. Using Hibernate Annotations With Shards

In the above example we're using Hibernate mapping files (hbm.xml) to specify our mappings, but it's just as easy to use Hibernate Annotations. We can annotate our WeatherReport class as follows:

@Entity
@Table(name="WEATHER_REPORT")
public class WeatherReport {

    @Id @GeneratedValue(generator="WeatherReportIdGenerator")
    @GenericGenerator(name="WeatherReportIdGenerator", strategy="org.hibernate.shards.id.ShardedUUIDGenerator")
    @Column(name="REPORT_ID")
    private Integer reportId;

    @Column(name="CONTINENT")
    private String continent;

    @Column(name="LATITUDE")
    private BigDecimal latitude;

    @Column(name="LONGITUDE")
    private BigDecimal longitude;

    @Column(name="TEMPERATURE")
    private int temperature;

    @Column(name="REPORT_TIME")
    private Date reportTime;

    ... // getters and setters
}

This is a pretty standard use of Hibernate Annotations. The only thing here that's particularly noteworthy is the use of the GenericGenerator annotation, which is part of Hibernate Annotations but not JPA. We need this to specify our shard-aware id generator.

The changes we now need to make to the createSessionFactory() method we implemented above are actually quite small:

 1     public SessionFactory createSessionFactory() {
 2         AnnotationConfiguration prototypeConfig = new AnnotationConfiguration().configure("shard0.hibernate.cfg.xml");
 3         prototypeConfig.addAnnotatedClass(WeatherReport.class);
 4         List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
 5         shardConfigs.add(buildShardConfig("shard0.hibernate.cfg.xml"));
 6         shardConfigs.add(buildShardConfig("shard1.hibernate.cfg.xml"));
 7         shardConfigs.add(buildShardConfig("shard2.hibernate.cfg.xml"));
 8         ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
 9         ShardedConfiguration shardedConfig = new ShardedConfiguration(
 10            prototypeConfig,
 11            shardConfigs,
 12            shardStrategyFactory);
 13        return shardedConfig.buildShardedSessionFactory();
 14    }
 

The only changes between this method and the non-annotated versions are on lines 2 and 3. On line 2 we're declaring and instantiating an AnnotationConfiguration instead of a Configuration, and on line 3 we're adding an annotated class to the configuration instead of an xml mapping file. That's it!

Please note that while Hibernate Shards works with Hibernate Annotations, Hibernate Shards does not ship with Hibernate Annotations. You'll need to download Hibernate Annotations and its dependencies separately.

2.4. Configuration Limitations

Many of you will quickly realize that the configuration mechanism we've provided won't work if you're configuring your SessionFactory via JPA. It's true. We expect this deficiency to be addressed shortly.