Hibernate Shards
A Hibernate Shards egy olyan keretrendszer, amely olyan problémákra nyújt megoldást, ahol összes szükséges adatot nem tudjuk egy relációs adatbázisban eltárolni. Az adatok egységbe zárhatók, minimalizálva ezen műveletek komplexitását, támogatást nyújtva a horizontális particionálás megvalósítására a Hibernate Core-ban.
Használata abban az esetben javasolt például, amikor túl sok adattal, vagy elosztott architektúrán kell dolgozni. Ilyen esetekben több elosztott adatbázisra lehet szükség, amely kétségkívül bonyolítja egy alkalmazás fejlesztését.
A shard szó jelentése cserép, szilánk, a Hibernate Shards felfogható, mint egy keretrendszer, mely ezen külön álló szilánkokat egyesíti, hogy a fejlesztő egységként tudja kezelni őket.
Legfőbb tulajdonságai
- Sztenderd Hibernate programozási modell: lehetővé teszi, hogy az eddig használt Hibernate API (pl.
SessionFactory
,Session
,Criteria
,Query
) a szokásos módon használható legyen. Azaz aki ismerte a Hibernate használatát, már ismeri a Hibenate Shards használatát is. - Rugalmas sharding (szilánkolási) stratégiák: az adatok szabadon eloszthatók a szilánkok közt. Ehhez használhatók a kész stratégiák, vagy egy a felhasználó által definiált saját stratégia.
- Virtuális shard-ok (szilánkok) támogatása: Amennyiben a szilánkolási stratégia változik, az új szilánkok hozzáadása és rajtuk az adatok elosztása az nehéz feladat lehet, ám a Hibernate Shards támogatja a virtuális shard-ok (szilánkok) létrehozását, amivel az adatok újraelosztása egyszerűsíthető.
- Ingyenes / nyílt forráskódú: A Hibernate Shards a LGPL (Lesser GNU Public License) égisze alatt bejegyezett termék
Példa
Az alábbi példa a világ minden pontjáról érkező időjárási adatokat dolgoz fel és tárol el egy relációs adatbázisban:
Az adatbázissémát a következő sql definiálja:
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
);
Egy “jelentés” modellje:
public class WeatherReport {
private Integer reportId;
private String continent;
private BigDecimal latitude;
private BigDecimal longitude;
private int temperature;
private Date reportTime;
... // getters and setters
}
A weather.hbm.xml
tartalma a következő:
<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>
Hozzáférés a SharedSessionFactory
-hoz:
Az alábbi kódrészlet az időjárás jelentő alkalmazásból 3 szilánkot használ:
public SessionFactory createSessionFactory() {
Configuration prototypeConfig = new Configuration().configure("shard0.hibernate.cfg.xml");
prototypeConfig.addResource("weather.hbm.xml");
List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
shardConfigs.add(buildShardConfig("shard0.hibernate.cfg.xml"));
shardConfigs.add(buildShardConfig("shard1.hibernate.cfg.xml"));
shardConfigs.add(buildShardConfig("shard2.hibernate.cfg.xml"));
ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
ShardedConfiguration shardedConfig = new ShardedConfiguration(
prototypeConfig,
shardConfigs,
shardStrategyFactory);
return shardedConfig.buildShardedSessionFactory();
}
ShardStrategyFactory buildShardStrategyFactory() {
ShardStrategyFactory shardStrategyFactory = new ShardStrategyFactory() {
public ShardStrategy newShardStrategy(List<ShardId> shardIds) {
RoundRobinShardLoadBalancer loadBalancer = new RoundRobinShardLoadBalancer(shardIds);
ShardSelectionStrategy pss = new RoundRobinShardSelectionStrategy(loadBalancer);
ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(shardIds);
ShardAccessStrategy pas = new SequentialShardAccessStrategy();
return new ShardStrategyImpl(pss, prs, pas);
}
};
return shardStrategyFactory;
}
ShardConfiguration buildShardConfig(String configFile) {
Configuration config = new Configuration().configure(configFile);
return new ConfigurationToShardConfigurationAdapter(config);
}
A fenti kódrészletből kiderül, hogy valójában 4 Configuration
számára foglalunk helyet. Az első a prototípus, a létrehozott ShardSessionFactory
pedig a 3 sztenderd SessionFactory
objektum referenciáit tárolja. Ezek közül mind három a prototípus konfigurációját hordozza. Mindössze pár attribútumukban térnek el:
- Connection.url
- Connection.user
- Connection.password
- Connection.datasource
- Cache.region_prefix
A 3 ShardConfiguration
objektum shard-specifikus adatbázis url, adatbázis felhasználó, adatbázis kód, adatforrás azonosító és cache-régió prefix-et után fog nézni. Ez azt jelenti, hogy még ha meg is változtatjuk a kapcsolódási paramétereket a shard1.hibernate.xml
-ben, a változtatások figyelmen kívül lesznek hagyva. Minden beállítás a fent felsoroltakat leszámítva a prototípus Configuration-ből származik.
A Configuration
objektum létrehozása után össze kell raknunk egy ShardStrategyFactory
-t. Ezután következhet a ShardedConfiguration
elkészítése, melynek befejeztével kérhetjük a ShardedSessionFactory
létrehozását. Fontos megjegyezni, hogy a ShardedSessionFactory
a SessionFactory
-ből származik.
Ezután vessünk egy pillantást a configuration és mapping fájlokra:
<!-- Contents of shard0.hibernate.cfg.xml -->
<hibernate-configuration>
<session-factory name="HibernateSessionFactory0"> <!-- note the different name -->
<property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://dbhost0:3306/mydb</property>
<property name="connection.username">my_user</property>
<property name="connection.password">my_password</property>
<property name="hibernate.connection.shard_id">0</property> <!-- new -->
<property name="hibernate.shard.enable_cross_shard_relationship_checks">true</property> <!-- new -->
</session-factory>
</hibernate-configuration>
<!-- Contents of shard1.hibernate.cfg.xml -->
<hibernate-configuration>
<session-factory name="HibernateSessionFactory1"> <!-- note the different name -->
<property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://dbhost1:3306/mydb</property>
<property name="connection.username">my_user</property>
<property name="connection.password">my_password</property>
<property name="hibernate.connection.shard_id">1</property> <!-- new -->
<property name="hibernate.shard.enable_cross_shard_relationship_checks">true</property> <!-- new -->
</session-factory>
</hibernate-configuration>
A shard2.hibernate.cfg.xml
tartalmát nem részletezzük, az a fentiekből egyértelműen következik. Mindegyik session factory-nek egyedi nevet adunk a session-factory elem név attribútumával. Mindegyik session factory-hoz másik adatbázis szervert adunk meg. Ezen kívúl a session factory-knak adunk egy egyedi shard id-t.
Ezután a mapping file megváltozik:
<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>
A Hibernate annotációk használata shard-okkal
A fenti példában Hibernate mapping fájlokat használtunk, hogy a mapping-eket specifikáljuk, de ugyanezt megtehetjük Hibernate annotációkkal is:
@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
}
A fenti példa az annotációk használatára egy egyszerű példa. Ezután csak pár egyszerű változtatásra van szükség a createSessionFactory
metódusban:
public SessionFactory createSessionFactory() {
AnnotationConfiguration prototypeConfig = new AnnotationConfiguration().configure("shard0.hibernate.cfg.xml");
prototypeConfig.addAnnotatedClass(WeatherReport.class);
List<ShardConfiguration> shardConfigs = new ArrayList<ShardConfiguration>();
shardConfigs.add(buildShardConfig("shard0.hibernate.cfg.xml"));
shardConfigs.add(buildShardConfig("shard1.hibernate.cfg.xml"));
shardConfigs.add(buildShardConfig("shard2.hibernate.cfg.xml"));
ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
ShardedConfiguration shardedConfig = new ShardedConfiguration(
prototypeConfig,
shardConfigs,
shardStrategyFactory);
return shardedConfig.buildShardedSessionFactory();
}