27 January 2019

blog java

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 :

Chargement Lazy de la propriété

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 ?

Association de la table avec elle même en oneToOne

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.

Classe fille avec propriété supplémentaire pour 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;
	}

}

Autres changements dans le code...

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.

Avantages inconvénients

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.

Conclusions

La seul façon de faire qui me satisfasse en terme de sémantique est celle qui ne fonctionne pas...

Autres références