Tutoriel pour apprendre à gérer simplement les CLOB et BLOB avec Spring JDBC

| Netapsys | Abderrazek Chine |

Ce tutoriel vous apprend à gérer simplement les CLOB/BLOB (Character/Binary Large OBject) avec Spring.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tutoriel pour apprendre à gérer simplement les CLOB et BLOB avec Spring JDBC

- Auteurs : Netapsys, Abderrazek Chine

- Parution : 26 avril 2017

- Licence : Copyright ® 2017 Netapsys. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Résumé

- I-B. Démonstration

- II. Use-Case

- III. Partie SQL

- IV. Démonstration

- IV-A. POM DU PROJET DEMO

- IV-B. Classe Modèle

- IV-C. Classe Service

- IV-D. Imports et quelques attributs

- IV-E. Configurer le fichier application.properties

- IV-F. Implémentation de la méthode mailExistById

- IV-G. Implémentation de la méthode insertXlob

- IV-H. Implémentation de la méthode updateXlob

- IV-I. La première méthode de lecture xLob

- IV-J. La seconde méthode de lecture

- IV-K. Test JUnit

- V. Conclusion et remerciements

I - Introduction

Traiter les champs de taille importante, de plusieurs gigas, ou Large OBject (LOB) dans la base de données n'a jamais été une partie de plaisir. Et ce n'est pas par manque d'approches pour les stocker et/ou de les récupérer : différentes approches existent, voire beaucoup trop ! Aussi, leur gestion est un peu plus complexe, car dépend de la nature de la base de données et du type du connecteur/driver jdbc utilisé.

Dans cet article, nous explorons ce thème spécifiquement pour la base Postgres 9.x (driver v 9.4. 1207.jre7) avec Spring 4.

Nous allons néanmoins garder à l'esprit qu'il ne faut pas trop adhérer à la base Postgres et son driver.

Les champs (nommés Clob/Blob: Character/Binary Large OBject) peuvent comporter des données (binaires ou textes) de taille importante. Pour la suite, les champs Clob ou Blob sont notés par xLob.

Sur un de mes projets, Spring-JDBC est la solution la plus adaptée par rapport à la nature du projet qui implique une migration et un [peu de] dépoussiérage nécessaire. Et l'une des tâches à réaliser serait de migrer une base existante Informix vers Postgres avec gestion des xLob.

Il y a des solutions commerciales ou open source pour réaliser ce travail (plus ou moins automatisées), mais ce n'est pas le choix (techno-économique) retenu. Nous allons, comme d'habitude par la pratique, vous montrer comment gérer simplement ces champs si particuliers.

Insistons un peu : vu la taille importante, une vigilance supplémentaire doit être envisagée.

Pour simplifier cet article, nous nous contentons de considérer le cas de la table, sous Informix, nommée mail qui possède, entre autres, un champ xLob nommé corps. Postgres fournit deux approches pour traiter les données binaires : bytea ou LOB !

La première approche basique est de déclarer ce champ de type bytea. Le problème avec cette approche basique est que lorsque les données sont importantes, une OOM (Out of Memory) pointerait tôt ou tard, car les charger dans un tableau de bytes n'est pas sans inconvénient.

Nous pouvons contourner ce problème, en gardant le type toujours en bytea, mais en utilisant le streaming pour récupérer les données. C'est déjà mieux, mais il reste encore un souci : le type bytea est limité à 1Go.

L'approche que nous suivrons est tout simplement d'utiliser Large OBject (LOB) de postgres ! Surprenant, non ?

En faisant ainsi, aucune limitation de taille, car Postgres ne stocke qu'une référence (oid) sur le fichier de données binaires ou textes et nous pouvons récupérer les données en streaming pour obtenir de bonnes performances. Et c'est justement de cette force que vient un inconvénient : le delete d'une ligne de la base avec LOB ne signifie pas pour autant que le LOB est supprimé.

Une procédure spécifique doit être suivie.

Dans la partie démonstration en Java 8, pour gérer ce champ, nous nous appuyons sur deux API interfaces LobCreator et LobHandler fournies par Spring.

- LobCreator se charge de transmettre les ordres SQL Java (PreparedStatement) aux drivers de la base afin de créer les xLOB.

- LobHandler se charge de récupérer les données de ces xLob.

Spring offre des implémentations par défaut : DefaultLobCreator et DefaultLobHandler.

Signalons cet avertissement du site officiel de Postgres.

L'accès aux xLOB doit se faire dans un bloc de transaction SQL. Il faudrait démarrer le bloc de transactions avec setAutoCommit(false).

Aussi, comme l'explicite la documentation de Spring, en dehors de la base Oracle pour laquelle une implémentation spécifique existe, la majorité des drivers fonctionnent avec DefaultLobHandler.

Et particulièrement pour la base Postgres qui nous concerne, il faudrait configurer DefaultLobHandler avec la propriété wrapAsLob à true.

Notez aussi que pour les connecteurs compatibles JDBC 4, LobHandler/LobCreator supporte le streaming des xLob. Le streaming peut être activé via la propriété streamAsLob (false étant la valeur par défaut).

Enfin, lorsque wrapAsLob est à true, il est possible, si besoin, de configurer également la propriété createTemporaryLob en cas de limitation ou contrainte sur la taille des données.

Cette dernière propriété nous a causé quelques messages d'erreurs (cette fonction n'est pas encore implémentée) avec le driver choisi, nous la laissons de côté.

Pour toutes ces raisons, nous avons écrit notre classe MyDefaultLobHandler qui est une petite enveloppe de DefaultHandler.

I-A - Résumé

Pour les pressés, voici un résumé succinct de cet article.

- Pour la création ou la mise à jour des champs xLob, utilisez conjointement jdbcTempalte.execute, LobCreator.

- Pour lire les champs xLob, utilisez conjointement jdbcTemplate.query et LobHandler de Spring.

- Une des clés pour mieux comprendre l'article est de saisir l'impact des propriétés wrapAsLob et StreamAsLob.

Dans la démonstration ci-après, nous allons créer une table mail ayant une colonne xLOB nommée corps.

La démonstration est accompagnée d'un test JUnit permettant de réaliser les opérations standards de type CRUD :

- insérer une ligne avec un champ xLob ;

- mettre à jour le champ xLob ;

- lire le champ xLob,

Nous voulons que la lecture de xLob renvoie une chaîne facilement utilisable par d'autres couches applicatives. Enfin, merci de noter que Spring 4, Java 8 et le connecteur Postgres (v 9.4. 1207.jre7) compatible Jdbc4.0 sont utilisés.

I-B - Démonstration

La démonstration, en Java 8, se divise en deux actes.

- ACTE I. SQL : créer rapidement une table nommée EMAIL ayant les colonnes (id, corps, idRegion). Le champ corps est de type TEXT (clob de postgres).

- Écrire un service simple s'appuyant sur jdbcTemplate de Spring pour les opérations CRUD. Écrire un test d'intégration pour tester ces opérations.

Passons à l'action en mettant la main à la pâte à la sauce de spring jdbc et vous allez constater que c'est comme utiliser un ORM [presque !].

II - Use-Case

La démonstration consiste à stocker les mails entrants (y compris les corps des messages) dans la base de données Postgres ventilés par région.

III - Partie SQL

Le script de création est simple (brut!):

1. CREATE TABLE MAIL

2. (

3. id serial,

4. corps OID,

5. idregion character varying(2),

6. CONSTRAINT mail_pkey PRIMARY KEY (id)

7. )

8. WITH (OIDS=FALSE);

La seule chose à relever est que le champ corps est de type OID. OID est un type long qui référence le fichier ayant le contenu binaire ou textuel. Nous aurions pu aussi choisir corps de type TEXT qui est le clob de Postgres. Et le code reste le même.

IV - Démonstration

Notre démonstration est un projet Maven Java standard généré depuis la page Initializr de Spring ou dans l'IDE STS. Nous donnons presque toutes les informations utiles pour partir « from scratch ». Java 8 est utilisé.

Créons un projet Maven simple s'appuyant sur spring-boot (ce dernier est vraiment optionnel. Il est là pour nous faciliter les choses).

IV-A - POM DU PROJET DEMO

Le contenu du pom.xml se réduit à ces quelques lignes avec spring-boot :

1. .....

2. <properties>

3. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

4. <java.version>1.8</java.version>

5. </properties>

7. <dependencies>

8. <dependency>

9. <groupId>org.springframework.boot</groupId>

10. <artifactId>spring-boot-starter-jdbc</artifactId>

11. </dependency>

12. <dependency>

13. <groupId>org.postgresql</groupId>

14. <artifactId>postgresql</artifactId>

15. <scope>runtime</scope>

16. </dependency>

17. <dependency>

18. <groupId>org.springframework.boot</groupId>

19. <artifactId>spring-boot-starter-test</artifactId>

20. <scope>test</scope>

21. </dependency>

22. </dependencies>

23. <build>

24. <plugins>

25. <plugin>

26. <groupId>org.springframework.boot</groupId>

27. <artifactId>spring-boot-maven-plugin</artifactId>

28. </plugin>

29. </plugins>

30. </build>

31. </project>

Rien de particulier ici : la seule chose à noter est la présence de deux dépendances et du plugin spring-boot. Les dépendances sont :

- le starter spring-boot-starter-jdbc ;

- le connecteur jdbc postgres compatible jdbc 4.

Le starter regroupe toutes les dépendances nécessaires pour utiliser spring-jdbc.

IV-B - Classe Modèle

Une seule classe nommée Email.

1. public class Email implements Serializable {

2. private long id;

3. private String idRegion;

4. private String corps;

6. ... getters setters omis

7. }

IV-C - Classe Service

Commençons par l'interface, IService, de notre service.

1. import java.util.List;

3. public interface IService {

4. boolean mailExistById(long id);

6. void insertXlob(String fullPathFileClob, long idMail, String idRegion) throws Exception;

7. void updateXlob(String fullPathFileClob, long idMail) throws Exception;

9. List<Email> getCorpsEmailById(long idEmail);

10. List<Email> getCorpsEmailByRegion(String idRegion);

11. }

Nous observons :

- une méthode mailExistById permet de savoir si un mail existe par son id ;

- la présence de deux méthodes CRUD : insertXlob et updateXlob ;

- deux méthodes de lecture/récupération de xLob nommées getCorpsXXXX.

La classe ServiceImpl.java d'implémentation est réalisée avec la fameuse jdbcTemplate de Spring.

Nous détaillons l'implémentation de la classe ServiceImpl en procédant méthode par méthode. Vous trouverez le contenu ZIP du projet en fin de l'article.

IV-D - Imports et quelques attributs

Voici déjà l'entête de la classe ServiceImpl.java avec les imports nécessaires :

1. import static fr.abdou.Utils.*;

2. import java.io.*;

3. import java.sql.*;

4. import java.util.*;

5. import o.s.beans.factory.annotation.Autowired;

6. import o.s.dao.DataAccessException;

7. import o.s.jdbc.core.JdbcTemplate;

8. import o.s.jdbc.core.RowMapper;

9. import o.s.jdbc.support.lob.LobHandler;

10. import o.s.jdbc.support.lob.LobCreator;

11. import o.s.stereotype.Service;

12. import org.slf4j.*;

13. import o.s.transaction.annotation.Transactional;

14. @Service("service")

15. @Transactional

16. public class ServiceImpl implements IService {

17. final static Logger logger = LoggerFactory.getLogger(ServiceImpl.class);

19. private JdbcTemplate jdbcTemplate;

21. @Autowired

22. public void setDataSource(DataSource dataSource) {

23. jdbcTemplate = new JdbcTemplate(dataSource);

24. }

26. @Autowired @Qualifier("myDefaultLobHandler")

27. private LobHandler lobHandler;

28. ...methodes a venir...

La première ligne import static fr.abdou.Utils.* sert à importer diverses constantes (par exemple, SQL) utiles à notre démonstration. L'import o.s correspond à org.springframework, o.s.j.c désigne org.springframework.jdbc.core et o.s.j.s pour org.springframework.jdbc.support.

Ensuite, l'annotation @Service sert à injecter par spring le bean en tant que service.

L'annotation @Transactional permet, de manière déclarative, de déléguer les transactions à l'AOP de spring.

Nous arrivons alors à la déclaration de jdbcTemplate initialisée avec @Autowired sur le setter de la datasource.

Datasource ?? Eh oui ! C'est un peu la magie de spring-boot. Car excepté une dépendance sur le connecteur Postgres dans pom.xml, nous n'avons ni déclaré l'intention d'utiliser une datasource ni configuré celle-ci ! Et pourtant spring-boot anticipe notre besoin.

À partir justement de la présence de la dépendance dans le pom, spring-boot préconfigure un objet datasource. Il nous reste à reconfigurer correctement cette datasource en complétant le fichier application.properties. C'est ce que nous faisons ci-après.

IV-E - Configurer le fichier application.properties

Partant du fichier application.properties généré par spring-boot, complétons-le comme suit pour reconfigurer la datasource :

1. spring.datasource.url=jdbc:postgresql://localhost:5432/test

2. spring.datasource.username=postgres

3. spring.datasource.password=xxxxxxx

4. spring.datasource.driverClassName=org.postgresql.Driver

5. spring.datasource.initial-size=10

6. spring.datasource.max-active=20

7. spring.datasource.min-idle=1

8. spring.datasource.max-idle=2

9. spring.datasource.validation-query="SELECT 1 "

Pensez à adapter certaines valeurs.

Revenons à la suite du code de ServiceImpl, le second attribut de la classe ServiceImp nommé lobHandler, de type o.s.jdbc.support.lob.LobHandler, est intéressant, car il sera utile pour gérer les xLob.

Son implémentation MyDefaultLobHandler est juste une extension de DefaultLobHandler qui est l‘implémentation par défaut fournie par Spring.

Vous constatez que la propriété « wrapAsLob » est fixée à true et bien d'autres comme le montre le code de la classe :

1. import org.springframework.jdbc.support.lob.DefaultLobHandler;

2. import org.springframework.stereotype.Service;

3. @Service("myDefaultLobHandler")

4. public class MyDefaultLobHandlerImpl extends DefaultLobHandler {

6. public MyDefaultLobHandlerImpl() {

7. super();

8. this.setWrapAsLob(wrapLob);

9. this.setStreamAsLob(true);

10. }

11. }

Maintenant que nous connaissons tous les attributs de la classe ServiceImpl, nous allons passer à ses méthodes.

IV-F - Implémentation de la méthode mailExistById

Démarrons avec la première méthode simple (simpliste) mailExistById :

1. public boolean mailExistById(long id) {

2. try {

3. return jdbcTemplate.queryForObject(EXIST_EMAIL_BY_ID,Boolean.class,id);

4. } catch(DataAccessException e){

5. return false;

6. }

7. }

Pour ceux qui découvrent spring-jdbc, voilà une façon simple de récupérer des objets Java avec une requête SQL paramétrée.

Ici la méthode queryForObject est appelée avec trois arguments :

- le SQL paramétré EXIST_BY_ID= “ select true from MAIL where id=? “ ;

- le type d'objet à retourner ici Boolean ;

- la valeur du paramètre SQL.

Nous pouvons aussi retourner tout autre objet de type T, il suffit alors de passer un RowMapper<T> comme vous allez le découvrir plus loin.

IV-G - Implémentation de la méthode insertXlob

La méthode insertXlob permet de récupérer les données binaires ou textes depuis un fichier puis l'insérer dans le champ xLob de la base. Attention son code ci-dessous pique un peu, en plus, une lambda s'est glissée.

1. @Override

2. public void insertXlob(final String fullPathFileClob, final long idMail, String idRegion) throws Exception {

4. final File clobFileIn = new File(fullPathFileClob);

5. if(!clobFileIn.exists()){

6. logger.error("Fichier '"+clobFileIn.getPath()+ "' introuvable !");

7. return;

8. }

10. try (final InputStream clobFileIs = new FileInputStream(clobFileIn))

11. {

12. int retour= jdbcTemplate.execute(INSERT_LINE_EMAIL, (PreparedStatement ps)-> /* type necessaire car ambiguite*/

13. {

14. try(LobCreator lobCreator = lobHandler.getLobCreator()) {

15. ps.setLong(1,idMail);

16. lobCreator.setBlobAsBinaryStream(ps,

17. numParam4InsertXLob, clobFileIs, clobFileIs.available());

18. ps.setString(3,idRegion);

19. return ps.executeUpdate();

20. } catch (Exception e) { /*catch force par le compilo*/

21. logger.warn(e.getMessage());

22. return 0;

23. }

24. });

25. if(retour==1) logger.info("Ligne insérée dans '"+NAMETABLE_EMAIL+"'");

26. }

27. }

Le point important ici est : l'insertion d'une ligne dans la table MAIL passe par la méthode jdbcTemplate.execute prenant deux arguments :

- le premier argument est la chaîne SQL paramétrée, ici c'est la constante INSERT_LINE_EMAIL ayant trois paramètres : Note. INSERT_LINE_EMAIL="INSERT INTO MAIL (id, corps,idRegion) VALUES(? ,?, ?)" ;

- le second est une lambda qui implémente l'interface fonctionnelle PreparedStatementCallback<T> ayant une seule méthode : T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException.

Donc la lambda vise l'unique méthode doInPreparedStatement prenant un seul argument de type PreparedStatement. D'où le fait que la lambda commence par (PreparedStatement ps) ->

En effet, nous donnons le type de l'argument, car la méthode jdbcTemplate.execute possède deux signatures proches ayant chacune deux arguments dont le premier est un String :

- T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException

- T execute(String sql, CallableStatementCallback<T> action) throws DataAccessException

Alors il faut aider le compilo de Java pour connaître la cible de cette lambda. Ici la lambda définie dans le code vise la première signature. Jetons encore un œil sur le body du lambda (extrait de code).

1. (PreparedStatement ps)->

2. try(LobCreator lobCreator = lobHandler.getLobCreator()) {

3. ps.setLong(1,idMail);

4. lobCreator.setBlobAsBinaryStream(ps,numParam4InsertXLob, clobFileIs, clobFileIs.available());

5. ps.setString(3,idRegion);

6. return ps.executeUpdate();

7. }

8. }

C'est le lobCreator récupéré depuis lobHandler qui se charge de traiter le champ xLob en le settant correctement dans PreparedStatement puis executeUpdate est appelé. En un mot, jdbcTemplate.execute délègue la création des xLob au LobCreator.

Important. Free for Clob/Blob : les ressources mémoire liées aux xLOB sont aussi importantes que leur taille et leur durée de vie dépend de la durée de la transaction. Il est donc important d'appeler les méthodes (Jdbc4.0 lesser-known) Clob.free ou Blob.free. Pour notre cas, puisque nous utilisons LobHandler/LobCreator dans la lambda ci-dessus, nous avons appliqué l'ARM (Automatic Resources Management appelé aussi Try-With-Resources) afin de garantir que les xLob sont fermés. Nous appliquons l'ARM partout dans le code.

Spring offre la classe abstraite AbstractLobCreatingPreparedStatementCallback<T> pratique que nous aurions pu utiliser pour les avantages suivants :

- seule la méthode setValues est à écrire ;

- le code de plomberie est masqué.

Nous avons opté pour l'implémentation par des lambdas de l'interface fonctionnelle PreparedStatementCallback<T> ce qui nous amène à manipuler le LobCretaor via ARM.

IV-H - Implémentation de la méthode updateXlob

Même démarche que insertXlob comme l'illustre le code suivant :

1. @Override

2. public void updateBlob(final String fullPathFileClob, final long idMail) throws Exception {

3. final File clobFileIn = new File(fullPathFileClob);

4. if(!clobFileIn.exists()) {

5. logger.error("Fichier '"+clobFileIn.getPath()+ "' introuvable !");

6. return;

7. }

8. try (final InputStream clobFileIs = new FileInputStream(clobFileIn))

9. {

10. int retour= jdbcTemplate.execute(UPDATE_CORPS_BY_ID,

11. (PreparedStatement ps)->

12. {

13. try(LobCreator lobCreator = lobHandler.getLobCreator()) {

14. lobCreator.setBlobAsBinaryStream(ps,numParam4UpdateXLob, clobFileIs, clobFileIs.available());

15. ps.setLong(2,idMail);

16. return ps.executeUpdate();

17. } catch (Exception e) { /*catch force par le compilo*/

18. logger.warn(e.getMessage());

19. return 0;

20. }

21. });

22. if(retour==1) logger.info("Ligne updatee dans '"+NAMETABLE_EMAIL+"'");

23. }

24. }

Presque seule la requête SQL paramétrée a changé !

IV-I - La première méthode de lecture xLob

Enchaînons avec cet extrait de ServiceImpl.java donnant l'implémentation de la méthode getCorpsEmailById. Nous détaillons getCorpsEmailById car, rappelez vous, le champ corps est un xLob. Son implémentation contient une lamda aussi, mais ça reste lisible.

1. ....

2. public class ServiceImpl implements IService {

3. ....

4. @Override

5. public List<Email> getCorpsEmailById(final long idEmail) {

6. return jdbcTemplate.query(

7. EMAIL_CORPS_SQL_BY_ID, new Object[] { idEmail },

8. (rs,num) -> {

9. final Email email= new Email();

10. email.setId(idEmail);

11. email.setIdRegion(rs.getString("idRegion"));

12. email.setCorps(lobHandler.getClobAsString(rs,"corps"));

13. return email;

14. });

15. }

16. ...

Pour lire le champ xLob, nous appelons l'API jdbcTemplate.query qui prend trois arguments :

- le premier argument est la chaîne SQL paramétrée, ici c'est la constante EMAIL_CORPS_SQL_BY_ID ayant un paramètre. Note. la constante EMAIL_CORPS_SQL_BY_ID="select corps, idRegion from MAIL where id=?"

- le second la valeur du paramètre id du SQL ;

- le troisième est une implémentation de l'interface fonctionnelle RowMapper<T> ayant la seule méthode mapRow. Cette implémentation permet de transformer le resultSet traditionnel en une clase Java Email. Notre implémentation est réalisée avec une lambda. Pour rappel, la signature de la méthode mapRow est : T mapRow(ResultSet rs, int rowNum) throws SQLException.

Reprenons donc le code implémentant RowMapper avec une lambda :

1. (rs,num) ->{

2. final Email email= new Email();

3. email.setId(idEmail);

4. email.setIdRegion(rs.getString("idRegion"));

5. email.setCorps(lobHandler.getClobAsString(rs,"corps"));

6. return email;

7. }

Comme vous le voyez, la lambda prend deux arguments (rs, num) et retourne dans notre cas une instance de Email.

IV-J - La seconde méthode de lecture

Poursuivons notre découverte du code de ServiceImpl cette fois avec la méthode [cousine] nommée getCorpsEmailByRegion(final String idRegion). Cousine, car le code est identique à un détail près :

1. @Override

2. public List<Email> getCorpsEmailByRegion(final String idRegion) {

3. return  jdbcTemplate.query(

4. EMAIL_CORPS_SQL_BY_REGION, new Object[] { idRegion },

5. (rs,num) ->{

6. final Email email= new Email();

7. email.setIdRegion(idRegion);

8. email.setId(rs.getLong("id"));

9. email.setCorps(lobHandler.getClobAsString(rs,"corps"));

10. return email;

11. });

12. }

Seul le premier argument de jdbcTemplate.query change. Désormais c'est EMAIL_CORPS_SQL_BY_REGION="select corps, id from MAIL where idRegion=?"

Le reste est un ajustement simple au contexte. C'est tout pour la lecture de xLob !

En un mot, un appel à jdbcTemplate.query avec un LobHandler adapté suivi lobHandler.getClobAsString. Et le tour est joué.

IV-K - Test JUnit

Notre test JUnit (d'intégration) permet de valider l'insertion, la mise à jour et la lecture d'un xLob.

1. import static org.junit.Assert.assertNotNull;

2. import java.util.List;

3. import org.junit.*;

4. import org.junit.runner.RunWith;

5. import org.slf4j.*;

6. import o.s.beans.factory.annotation.Autowired;

7. import o.s.beans.factory.annotation.Qualifier;

8. import o.s.boot.test.SpringApplicationConfiguration;

9. import o.s.test.annotation.Rollback;

10. import o.s.test.context.junit4.SpringJUnit4ClassRunner;

11. import o.s.transaction.annotation.Transactional;

13. @RunWith(SpringJUnit4ClassRunner.class)

14. @SpringApplicationConfiguration(classes = JdbcSpringClobBlobApplication.class)

15. @Transactional

16. @Rollback(true) //false si on doit persister reellement

17. public class JdbcSpringClobBlobApplicationTests {

19. final static Logger logger = LoggerFactory.getLogger(JdbcSpringClobBlobApplicationTests.class);

20. final static long ID_EMAIL_TEST= 222L;

21. final static String ID_REGION_TEST="54";

22. final static String FULL_PATH_FILE_TEST="/tmp/emailCLOB.txt";

24. @Autowired

25. @Qualifier("service")

26. IService service;

28. @Before

29. public void testInsertClob() throws Exception {

30. final String fullPathFile = FULL_PATH_FILE_TEST;

31. boolean exist = service.mailExistById(ID_EMAIL_TEST);

32. if(!exist){

33. service.insertXlob(fullPathFile,  ID_EMAIL_TEST, ID_REGION_TEST);

34. }

35. }

37. @Test

38. public void testUpdateClob() throws Exception {

39. final String fullPathFileClob = FULL_PATH_FILE_TEST;

40. service.updateBlob(fullPathFileClob, ID_EMAIL_TEST);

41. }

43. @Test

44. public void testReadClobById() throws Exception {

45. List<Email> liste = service.getCorpsEmailById(ID_EMAIL_TEST);

46. assertNotNull(liste);

47. liste.stream().map(Object::toString).forEach(logger::info);

48. }

50. @Test

51. public void testReadClob() throws Exception {

52. final String idRegion = ID_REGION_TEST;

53. List<Email> liste = service.getCorpsEmailByRegion(idRegion);

54. assertNotNull(liste);

55. }

57. }

Explications :

- l'annotation @Transactional délègue la gestion des transactions à Spring (aop) ;

- vous avez constaté qu'aucune configuration ni dans XML ni dans Java n'est faite pour transcationManager ;

- Spring use du principe de convention over configuration ou configuration par exception ;

- l'annotation @Rollback avec paramètre à false permet de réellement persister les ordres SQL ;

- de préférence switcher ce paramètre à false pour les tests ;

- le premier test testInsertClob s'appuie sur la méthode mailExistById pour voir s'il faudrait insérer une ligne qui sera récupérée dans les tests des méthodes de lecture.

V - Conclusion et remerciements

Nous avons laissé de côté :

- la gestion de l'encodage du contenu des fichiers. Un prochain article abordera le thème ;

- l'action complète de delete xLOb n'a pas été spécifiée ;

- la propriété createTemporaryLob n'a pas été gérée pour la raison déjà évoquée.

Cet article a été publié avec l'aimable autorisation de Netapsys.

Nous tenons à remercier Claude Leloup pour la relecture orthographique de cet article et Mickael Baron pour la mise au gabarit.