Hibernate interceptor : nom de schéma dynamique
Par guillaume le dimanche 7 novembre 2010, 23:15 - java / j2ee - Lien permanent
Bien d'Hibernate soit capable d'aller chercher des données dans des tables situées dans différents schémas, il n'est pas possible directement de changer le schéma à l'exécution. En effet, les annotations sont lues à la compilation et le code d'une @Entity est alors figé sur un schéma et une table.
@Entity
@Table(name="MON_OBJET",schema="MON_SCHEMA")
public class MonObjet implements Serializable {
...
private Etat etat;
...
@OneToOne(...)
public Etat getEtat() { ... }
public void setEtat(Etat etat) { ... }
}
@Entity
@Table(name="ETAT",schema="MON_SCHEMA")
public class Etat {
...
}
Ceci devient gênant lorsque par exemple, une des entités peut se trouver dans différents schémas qui correspondent par exemple à des environnements différents. Par exemple, si MonObject est attaché en relation un à un à un objet Etat qui peut se trouver dans MON_SCHEMA, SCHEMA_PREPROD ou SCHEMA_PROD, on devra alors packager une application par schéma!
Une solution possible serait de créer un profile maven pour chaque environnement, puis d'utiliser la phase de génération des sources pour modifier les annotations dans le code java... Ce qui semble plutôt lourd à mettre en place! Et au final, il faut toujours une application par schéma utilisé! Il existe cependant une solution avec les interceptor d'Hibernate pour ne déployer qu'une seule application...

Dans le cas qui nous intéresse, nous allons surcharger la méthode onPrepareStatement(String) sur un héritage de la classe EmptyInterceptor d'Hibernate car c'est à cet endroit qu'il est possible de modifier à la volée les requêtes réalisées par Hibernate sur l'entité.
En supposant que le schéma de l'environnement est connu par l'intermédiaire d'une propriété système Java "env" (ex: -Denv=SCHEMA_PROD), on peut alors à l'exécution remplacer à la volée le nom du schéma par le schéma d'environnement uniquement pour l'objet Etat, c'est à dire pour l'entité pointant sur la table ETAT. Dans toute la requête SQL fabriquée par Hibernate, on aura alors remplacé tous les MON_SCHEMA.ETAT par SCHEMA_PROD.ETAT.
import org.hibernate.EmptyInterceptor;Ensuite, il faut intégrer l'intercepteur dans la sessionFactory Hibernate (contexte Spring) :
public class SchemaInterceptor extends EmptyInterceptor {
@Override
public String onPrepareStatement(String sql) {
String pstmt = super.onPrepareStatement(sql);
String schema = System.getProperty("env");
if( pstmt.matches("MON_SCHEMA.ETAT")) {
pstmt = pstmt.replaceAll("MON_SCHEMA.ETAT", schema + ".ETAT");
}
return pstmt;
}
}
<bean id="schemaInterceptor" class="fr.openfarm.hibernate.interceptors.SchemaInterceptor"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">...</property>
...
<property name="entityInterceptor">
<ref bean="schemaInterceptor"/>
</property>
</bean>
Attention : on ne peut déclarer qu'un seul intercepteur par session.
Les sources ci-dessous donnent d'autres exemples d'utilisation des intercepteurs (audit des entités ou la sécurité) :