27 January 2019
Parmis les tables utilisées pour travailler sur les assets cinéma, il y a une table CplRaw dans laquelle un certain nombre d'information sont extrait, et un blob stocke le fichier XML à proprement parler.
Ce travail se fait dans le cadre d'une mise en oeuvre avec Spring Boot.
L'idée est de ne pas charger le contenu du blob en mémoire lorsque l'on fait de la récupértion de listes de CPL, que ce soit l'ensemble de la table ou un sous-ensemble filtré sur un critère.
Il faut donc charger de manière différée le blob, lorsque l'on en a besoin.
Une recherche montre au moins trois méthodes pour y parvenir :
Le mécanisme de chargement à la demande est prévu, avec l'annotation @Basic.
Extrait de la classe CplRaw avec les options Lazy activées.
...
@Column(name="cplfile")
@Lob
@Basic(fetch=FetchType.LAZY)
private byte[] cplFile;
...
Le problème rencontré pour cette solution, est que même si la propriété est étiquettée Lazy, elle est chargée systématiquement avec les autres propriétés, ce qui pose problème pour les listes et autres findAll.
Si l'on active l'affichage des requêtes SQL, on voit que les blobs sont récupérées à travers le même select.
Hibernate:
select
cplraw0_.id_cplraw as id_cplra1_0_,
cplraw0_.aspectratio as aspectra2_0_,
cplraw0_.barcodecst as barcodec3_0_,
cplraw0_.contentkind as contentk4_0_,
cplraw0_.contenttitletext as contentt5_0_,
cplraw0_.cplfile as cplfile6_0_,
cplraw0_.cpluuid as cpluuid7_0_,
cplraw0_.duration as duration8_0_,
cplraw0_.estvisible as estvisib9_0_,
cplraw0_.iddisk as iddisk10_0_,
cplraw0_.iscrypted as iscrypt11_0_
from
cplraw cplraw0_
where
cplraw0_.cpluuid=?
On peut constater que la colone cplfile est chargée.
Pour que le chargement soit effectif, il faut activer l'instrumentation du byte code.
Cela se fait en ajoutant quelques lignes dans le .pom.
La manipulation est documentée dans le post suivant :
On peut activer l'instrumentation avec les lignes suivantes dans le .pom :
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
<execution>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
Au chargement :
2019-01-27 12:13:12.164 INFO 5772 --- [ main] o.h.tuple.entity.EntityMetamodel : HHH000157: Lazy property fetching available for: fr.cst.rd.digitalCinemaAssetHandler.entities.Cplraw
Ce que l'on obtient dans la trace hibernate change :
Hibernate:
select
cplraw0_.id_cplraw as id_cplra1_0_,
cplraw0_.aspectratio as aspectra2_0_,
cplraw0_.barcodecst as barcodec3_0_,
cplraw0_.contentkind as contentk4_0_,
cplraw0_.contenttitletext as contentt5_0_,
cplraw0_.cpluuid as cpluuid7_0_,
cplraw0_.duration as duration8_0_,
cplraw0_.estvisible as estvisib9_0_,
cplraw0_.iddisk as iddisk10_0_,
cplraw0_.iscrypted as iscrypt11_0_
from
cplraw cplraw0_
where
cplraw0_.cpluuid=?
Par contre, si l'on appelle la propriété, on rencontre une erreur :
...
Caused by: org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [fr.cst.rd.digitalCinemaAssetHandler.entities.Cplraw.cplFile] - no session and settings disallow loading outside the Session
at org.hibernate.bytecode.enhance.spi.interceptor.Helper.throwLazyInitializationException(Helper.java:164)
...
Dans le contexte, les beans sont appelés depuis une application ligne de commande, sans qu'un service web soit appelé, peut-être est-ce pour cela qu'il n'y à pas de session ?
Cela fonctionne, mais oblige à des acrobatie lors de la création d'un enregistrement. Il enregistrer une première fois la ligne sans le blob, charger de nouveau l'entité, puis y ajouter le blob.
Des indications sont données dans le même post que pour le lazy loading de la propriété dans la partie "Fetching subentities".
Dans mon cas, cela oblige on obtient une hiérachie de classe, avec du coup des opérations plus propres.
Une super classe commune avec tous les attributs :
@MappedSuperclass
public class Cplraw
{
... // Tous les champs et getters/setters non montrés
}
Une classe entité sans attributs supplémentaires :
@Entity
@Table(name="cplraw")
public class CplInfo extends Cplraw
{
}
Et une classe fille avec le blob :
@Entity
@Table(name="cplraw")
public class CplFile extends Cplraw
{
@Lob
@Column(name="cplfile", columnDefinition = "BLOB")
private byte[] file;
public byte[] getFile() {
return file;
}
public void setFile(byte[] file) {
this.file = file;
}
}
Il faut modifier le CplRaw repository pour mettre des types CplInfo, puisque c'est elle l'entité... Pour accéder a blob, il faut récupérer un CplFile d'une nouvelle classe CplFileRepository.
L'avantage de l'approche est que l'on peut clairement controler la récupération d'objets permettant d'accéder à la propriété ou pas selon le contexte.
Inconvénient : perte de transparence dans le code, puiqu'il s'agit de la même abstraction dans cette représentation.
La seul façon de faire qui me satisfasse en terme de sémantique est celle qui ne fonctionne pas...