Spring Batch

jeu. 11 août 2011

Spring Batch est un framework Java qui a pour objectif de donner aux développements de batch Java un vrai cadre, avec son architecture et ses outils dédiées. Spring Batch s’appuie sur le framework Spring et son mécanisme d’injection de dépendance (et donc son conteneur léger).

Concrètement, ça fonctionne comment ?

Spring Batch permet de définir des Job (un batch dans son ensemble), euh même décomposer en Step. Les steps correspondent aux différentes étapes de traitements d’un batch.

Par défaut les tâches suivent l’organisation suivantes :

  • Un reader : indique comment les données doivent être lus et accéder
  • Un ou plusieurs procésseurs : chaque processeur effectue un traitement particulier sur les données. Pour chaque ligne lu par le reader, les processeurs vont être appellées un à un.
  • Un writer : indique comment les données vont être restitué (fichier, base de données, …).

Il est binesur toutefois possible de définir des tâches qui ne répondront pas à ce schéma.

Pour faciliter l’élaboration du batch, SpringBatch fournit des classes utilisables tels quels ou dérivable. Il fournit également un mécanisme de gestion de transaction (et de gestions des commits intermédiaires).

SpringBatch permet également de gérer le déroulement des Steps (quels steps appellés quand, dans quels conditions, …), de gérer une relance automatique des steps suites à échec, ou même la parallellisation de Step.

Un petit exemple ?

J’ai réalisé il y a peu un petit batch d’archivage Java. Ce batch est très simple et est constitué d’un seul step avec reader / writer.

Définition du batch

Le début du paramétrage se passe coté Spring. Je vous passe la définition du datasource, du transaction manager, et du jdbc template qui sont des objets Spring standard (de simple bean instancié et que l’on retrouve dans quasiment tout projet utilisant Spring).

Une fois ces trois objets disponible, je vais crée un repository (qui contiendra les jobs et leurs exécutions) et un launcher (pour permettre l’exécution du batch). Je rajoute donc dans mon fichiers Spring :

<!– 3/ Depot des jobs Spring Batch –>
<bean id=jobRepositoryclass=org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean>
    <property name=transactionManager ref=transactionManager/>
</bean>

<!– 4/ Lancement des jobs –>
<bean id=jobLauncher class=org.springframework.batch.core.launch.support.SimpleJobLauncher>
    <property name=jobRepository ref=jobRepository/>
</bean>

Vous pourrez constater que j’initialise les Beans sans vraiment faire de spécifique. Le job launcher est un launcher simple : il faut donc l’appeller par code Java pour lancer l’archivage.

Je définit ensuite mon batch et ses steps :

<!– 5/ Définition du batch d’archivage –>
<batch:job id=archivage job-repository=jobRepository>
    <batch:step id=archiveLog>
        <batch:tasklet transaction-manager=transactionManager >
            <batch:chunk reader=logReader writer=logWriter commit-interval=500/>
        </batch:tasklet>
    </batch:step>
</batch:job>

Voyons un peu ces quelques lignes.

  • Je crée un job nommé « archivage»
  • Je crée un step nommé «archiveLog»
  • Je définit le step comme étant transactionnel, et je référence le reader (logReader) et le writer (logWriter).
  • J’indique également un commit-interval : le batch va travailler par lot de 500 lignes.

Le Reader et le Writer vont également être des beans référencés dans Spring.

Le Reader

La définition que j’utilise pour le Reader est la suivante :

<!– Declaration des beans d’archivage pour la table de Log –>
<bean id=logReader class=org.springframework.batch.item.database.JdbcCursorItemReader>
    <property name=dataSource ref=dataSource />
    <property name=sql value=SELECT IOL_NOM_FICHIER, IOL_DAT_HEURE, IOL_COD_TRAITEMENT, IOL_COD_ETAPE, IOL_NIV_INFO, IOL_LIB_MESSAGE FROM T_IO_LOG_IOL WHERE IOL_DAT_HEURE < SYSDATE  7 />
    <property name=rowMapper ref=logMapper />
</bean>

Vous pourrez remarquer que j’utilise directement un objet fourni par Spring : JdbcCursorItemReader. Celui ci prend en paramètre ma base de donnée, une requête SQL a executer, et un rowMapper indiquant dans quels objets stockés les lignes. Rien de bien compliqué donc. Le row mapper est le suivant :

@Component(“logMapper”)
public  class  LogMapper  implements  RowMapper<DTOLog> {

    @Override
    public  DTOLog mapRow(ResultSet rs,  int  rowNum)  throws  SQLException {
        DTOLog dto =  new  DTOLog();
        dto.setNomFichier(rs.getString(LogEnum.
<em>FILENAME</em>
.getPosition()));
        dto.setDatHeure(rs.getDate(LogEnum.
<em>DAT_HEURE</em>
.getPosition()));
        dto.setEtape(rs.getString(LogEnum.
<em>COD_ETAPE</em>
.getPosition()));
        dto.setMessage(rs.getString(LogEnum.
<em>LIB_MESSAGE</em>
.getPosition()));
        dto.setNiveauInfo(rs.getString(LogEnum.
<em>NIV_INFO</em>
.getPosition()));
        dto.setTraitement(rs.getString(LogEnum.
<em>COD_TRAITEMENT</em>
.getPosition()));
         return  dto;
    }
}

Rien de bien compliqué ici, inutile que je vous l’expliquer : la seul particularité est l’implémentation de l’interface RowMapper fourni par SpringBatch. Il y a également l’annotation @Component qui m’évite de rajouter une ligne dans mon fichier xml pour déclarer le batch.

Le Writer

Implémenter le Writer signifie implémenter l’interface ItemWriter. J’obtient donc la classe suivante :

@Component(logWriter)
public class  LogWriter  implements  ItemWriter<DTOLog> {

    @Autowired
     private  DataSource dataSource;

    @Override
     public   void  write(List<?  extends  DTOLog> listItems)  throws  Exception {
        Connection dbConnection = dataSource.getConnection();
        try  {
            for  (DTOLog item : listItems) {
                insertDataArchive(dbConnection, item);
                deleteOriginData(dbConnection, item);
            }
        }  catch  (Exception e) {
            throw  e;
        }  finally  {
            dbConnection.close();
        }
    }
}

Je vous passe le détail des deux fonctions qui sont juste des requêtes JDBC. Encore une fois, rien de compliqué. Vous remarquerez que l’on travaille ici avec des listes d’objets. Le contenu de ces listes dépends du commit-interval : dans mon cas j’aurai donc des blocs de 500 lignes.

En Conclusion

Jusqu’à maintenant, le Batch était un peu la parent pauvre de Java : on réalisait tout from Scratch, avec des solutions très manuels et très « coding». Chaque batch était donc totalement différent : passer d’une application à une autre vous faisait perdre tous vos repères.

Spring Batch offre ici un cadre propre, bien pensée, et donne en plus nombre d’outils qui diminue la quantité de code nécessaire. Certes, la configuration Spring Batch devient vite verbeuse avec un fichier XML qui va grossir, toutefois ce fichier est simple et facile à générer. Vous pourrez également le spliter en différent fichier (1 par batch ? ) pour plus de clareté.

Les Batchs réalisées avec Spring Batch sont solide, facilement paramétrable, et offre de nombreuses fonctionnalités (paralléllisation, enchainement, …) qui sont normalement rébarbative à implémenter. La montée en compétence est en réalité un peu dure : peu d’exemple ou de tutorial. Par contre une fois les concept saisies, s’est un vrai plaisir :)