Spring Spring boot - LearningRbcRegistry/Wiki GitHub Wiki
Doc own training tuto and projects:
Training Spring (it was in french): http://rbc.labs.retengr.com/ Final training package: https://github.com/LearningRbcRegistry/WikiPictures/blob/master/Workshop%2BJava%2B-%2BRETENGR.zip
<title></title> <style type="text/css">code{white-space: pre;}</style> <style type="text/css"> div.sourceCode { overflow-x: auto; } table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode { margin: 0; padding: 0; vertical-align: baseline; border: none; } table.sourceCode { width: 100%; line-height: 100%; } td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; } td.sourceCode { padding-left: 5px; } code > span.kw { color: #007020; font-weight: bold; } /* Keyword */ code > span.dt { color: #902000; } /* DataType */ code > span.dv { color: #40a070; } /* DecVal */ code > span.bn { color: #40a070; } /* BaseN */ code > span.fl { color: #40a070; } /* Float */ code > span.ch { color: #4070a0; } /* Char */ code > span.st { color: #4070a0; } /* String */ code > span.co { color: #60a0b0; font-style: italic; } /* Comment */ code > span.ot { color: #007020; } /* Other */ code > span.al { color: #ff0000; font-weight: bold; } /* Alert */ code > span.fu { color: #06287e; } /* Function */ code > span.er { color: #ff0000; font-weight: bold; } /* Error */ code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */ code > span.cn { color: #880000; } /* Constant */ code > span.sc { color: #4070a0; } /* SpecialChar */ code > span.vs { color: #4070a0; } /* VerbatimString */ code > span.ss { color: #bb6688; } /* SpecialString */ code > span.im { } /* Import */ code > span.va { color: #19177c; } /* Variable */ code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */ code > span.op { color: #666666; } /* Operator */ code > span.bu { } /* BuiltIn */ code > span.ex { } /* Extension */ code > span.pp { color: #bc7a00; } /* Preprocessor */ code > span.at { color: #7d9029; } /* Attribute */ code > span.do { color: #ba2121; font-style: italic; } /* Documentation */ code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */ code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */ </style>- Installation de la base de données
- Gestion des transaction
- Utilisation des templates
- Utilisation de JPA & Hibernate
- REST
- Spring Boot
- Spring Batch
- JMS
- JMX
- API D'administration
Télécharger les ressources de la formation
La base de données que nous allons utiliser est HSQLDB. Téléchargez là ici
Une fois téléchargée et décompressée, la base de données peut être démarré depuis un shell en utilisant la ligne de commmande mentionnée dans le fichier README:
java -cp .\hsqldb\lib\hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 dbUn petit outil d'admistration vous permettant d'exécuter des requêtes peu-être lancé en utilisant la commande suivante:
java -cp .\hsqldb\lib\hsqldb.jar org.hsqldb.util.DatabaseManagerSwingLes informations de connexion à cette base de données sont:
- URL:
jdbc:hsqldb:hsql://localhost/db - Identifant:
sa - Mot de passe: vide
- Utilisez le manager que vous venez de démarrer afin de créer les tables
CLIENTetCOMPTE
DROP TABLE COMPTE if exists;
DROP TABLE CLIENT if exists;
/* Creating table CLIENT */
CREATE TABLE CLIENT (
ID integer identity PRIMARY KEY ,
NOM varchar(40) not null,
PRENOM varchar(40) not null,
UNIQUE (ID)
);
/* Creating table COMPTE */
CREATE TABLE COMPTE (
ID integer identity PRIMARY KEY ,
NUMERO int not null,
SOLDE integer,
FK_CLIENT integer,
DESCRIPTION varchar,
FOREIGN KEY (FK_CLIENT) REFERENCES CLIENT,
UNIQUE (ID)
);
- Puis insérez le jeu de test suivant:
INSERT INTO CLIENT (ID, NOM, PRENOM) VALUES ('1', 'Zidane', 'Zinedine' );
INSERT INTO CLIENT (ID, NOM, PRENOM) VALUES ('2', 'Platini', 'Michel' );
INSERT INTO CLIENT (ID, NOM, PRENOM) VALUES ('3', 'Papin', 'Jean-Pierre' );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION, FK_CLIENT) VALUES ('5', 1000,10000,'Compte Courant',1 );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION, FK_CLIENT) VALUES ('1', 1001,20000,'Compte Courant',2 );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION, FK_CLIENT) VALUES ('2', 1002,30000,'Compte Courant',3 );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION, FK_CLIENT) VALUES ('3', 1003,1000,'livret A',2 );
La solution de l'exercice est contenu dans le projet intitulé 09-SPRING-TX
Modifiez le paramétrage de Spring afin d'utiliser les véritable classes d'accès aux données qui vous sont fournies.
Notez sur un papier l'ensemble des critiques que vous souhaitez faire sur la qualité du code de ces deux DAOs.
Une bonne pratique nécessite l'usage systématique d'une datasource pour accéder à une base de données relationnelle.
Dans une configuration serveur, c'est l'environnement d'éxécution qui met à disposition cette datasource qui aura été paramétrée par un administrateur.
Dans le cas présent, nous sommes dans une application standalone le paramétrage d'une datasource doit donc se faire à la main.
Déclarez un bean Spring qui sera du type org.springframework.jdbc.datasource.DriverManagerDataSource. Ce bean sera initialisé avec les informations suivantes:
- url:
jdbc:hsqldb:hsql://localhost/DB - username=
SA - mot de passe: vide
- driverClassName:
org.hsqldb.jdbcDriver
Vous pourrez utilisez le fichier beans.xml ou alors une classe de Configuration (@Configuration et @Bean)
Une fois la datasource déclarée, vous l'injecterai dans les DAOs
Modifiez le code de vos DAO afin de récupérer la connexions à la base de données en utilisant le format suivant:
Connection connexion = DataSourceUtils.getConnection(getDatasource());en lieu et place de
Connection connexion = getDatasource().getConnection();Cette modification est nécessaire car nous souhaitons gérer des transactions en utilisant une datasource en dehors d'un véritable serveur d'application. Elle n'est pas nécessaire si l'application est déployée dans un véritable serveur d'application dans lequel la datasource est paramétrés.
Implémentez un cas de test permettant d'appeler la méthode transfert qui est implémentée dans la couche de service.
Après une rapide analyse de cette méthode, justifiez le fait que cette dernière doit s'exécuter au sein d'une transaction pour garantir l'intégrité des données manipulées.
Quel est résultat du code suivant ?
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Banque banqueService = ctx.getBean(Banque.class);
System.out.println(banqueService.getClass().getName());Conclusion ?
L'automatisation de gestion des transactions se fait par le biais d'un transaction manager. Il existe plusieurs types de Transaction Manager dans Spring, les suivants permettent de gérer des transactions locales:
- DataSourceTransactionManager
- HibernateTransactionManager
- JmsTransactionManager
Le JtaTransactionManager permet quand à lui de piloter des transactions distribuées en utilisant le protocole XA. Son usage implique d'être dans un serveur d'application qui implémente ce protocole.
Dans notre cas, nous allons utiliser le DataSourceTransactionManager:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<span class="kw"><bean</span><span class="ot"> id=</span><span class="st">"txManager"</span>
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource" />
</bean>
<span class="kw"><bean</span><span class="ot"> id=</span><span class="st">"datasource"</span>
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:url="jdbc:hsqldb:hsql://localhost/DB" p:username="SA" p:password=""
p:driverClassName="org.hsqldb.jdbcDriver">
</bean>
<span class="kw"><tx:annotation-driven</span><span class="ot"> transaction-manager=</span><span class="st">"txManager"</span> <span class="kw">/></span>
<span class="kw"><context:component-scan</span><span class="ot"> base-package=</span><span class="st">"com.retengr.dao.jdbc,com.retengr.service"</span> <span class="kw">/></span>
</beans>
Sur un papier, écrivez le code JDBC permettant d'implémenter une transaction (begin, commit et rollback). Déduisez-en la raison pour quelle raison il est nécessaire d'injecter la datasource dans le transaction manager.
Rajoutez l'annotation suivante sur la méthode transfert dans la classe BanqueImpl:
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class))
public void transfer(Long id1, Long id2, double m) {
// ...
} Quel est résultat du code suivant ?
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Banque banqueService = ctx.getBean(Banque.class);
System.out.println(banqueService.getClass().getName());Afin de vérifiez que la gestion de la transaction est correctement effectuée, modifiez le code de la méthode transfert
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = RuntimeException.class)//TechnicalError.class)
public void transfer(Long id1, Long id2, double m) {
Compte c1 = compteDAO.read(id1);
Compte c2 = compteDAO.read(id2);
c1.debit(m);
c2.credit(m);
<span class="co">// On force la violation d'une contrainte d'intégrité dans la base de données</span>
c2.<span class="fu">setNumero</span>(<span class="kw">null</span>);
compteDAO.<span class="fu">update</span>(c1);
compteDAO.<span class="fu">update</span>(c2);
}</code></pre></div>
Comment proposez vous de visualiser que le rollback est bien fait dans la base de données ?
Modifiez le paramétrage de vos logs dans le fichier src/main/resources/log4j.properties:
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
direct messages to file hibernate.log
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
set log levels - for more verbose logging change 'info' to 'debug'
log4j.rootLogger=debug, stdout
log4j.logger.org.springframework.beans.factory=info
log4j.logger.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
Rajoutez la dépendance suivante dans le pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
Utilisation des templates
Les templates Spring sont des classes utilitaires qui implémentent les bonnes pratiques à votre places. Leur usage est particulièrement pratique pour implémenter une couche d'accès aux données.
Pour faire suite aux remarques négatives qui ont été faites précédemment sur la couche d'acès aux données que nous vous avons livrés, analysez la raison pour laquelle le code utilisant un JDBCTemplate est plus propre et robuste que le code fourni.
- Code fourni
public void update(Compte c) {
try {
Connection connexion = <span class="fu">getDatasource</span>().<span class="fu">getConnection</span>();
PreparedStatement pSt;
pSt = connexion.<span class="fu">prepareStatement</span>(<span class="st">" UPDATE COMPTE SET NUMERO = ? , SOLDE = ? WHERE ID=?"</span>);
pSt.<span class="fu">setString</span>(<span class="dv">1</span>, c.<span class="fu">getNumero</span>());
pSt.<span class="fu">setDouble</span>(<span class="dv">2</span>, c.<span class="fu">getSolde</span>());
pSt.<span class="fu">setLong</span>(<span class="dv">3</span>, c.<span class="fu">getId</span>());
pSt.<span class="fu">executeUpdate</span>();
System.<span class="fu">out</span>.<span class="fu">println</span>(<span class="st">"Compte modifié"</span>);
} catch (SQLException e) {
throw new RuntimeException();
}
}
- Avec JDBC Template:
private static String SQL_UPDATE = "UPDATE COMPTE c SET c.SOLDE=:solde, c.NUMERO=:numero, c.DESCRIPTION=:description WHERE c.id=:id";
private Map<String, Object> convertCompteToMap(Compte c) {
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("id", c.getId());
parameters.put("numero", c.getNumero());
parameters.put("solde", c.getSolde());
parameters.put("description", c.getLabel());
<span class="kw">return</span> parameters;
}
public void update(Compte c) {
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(
datasource);
template.update(SQL_UPDATE, convertCompteToMap(c));
}
Modifiez les classes DAO afin d'utiliser la classe NamedParameterJdbcTemplate.
Les classes suivantes vous sont fournies afin de vous simplifier la tâche
CompteRowMapper
public class CompteRowMapper implements RowMapper<Compte> {
<span class="kw">public</span> Compte <span class="fu">mapRow</span>(ResultSet rs, <span class="dt">int</span> i) <span class="kw">throws</span> SQLException {
Compte result = <span class="kw">new</span> <span class="fu">Compte</span>();
result.<span class="fu">setId</span>(rs.<span class="fu">getLong</span>(<span class="st">"ID"</span>));
result.<span class="fu">setLabel</span>(rs.<span class="fu">getString</span>(<span class="st">"DESCRIPTION"</span>));
result.<span class="fu">setNumero</span>(rs.<span class="fu">getString</span>(<span class="st">"NUMERO"</span>));
result.<span class="fu">setSolde</span>(rs.<span class="fu">getDouble</span>(<span class="st">"SOLDE"</span>));
<span class="kw">return</span> result;
}
}
ClientRowMapper
public class ClientRowMapper implements RowMapper<Client> {
<span class="kw">public</span> Client <span class="fu">mapRow</span>(ResultSet rs, <span class="dt">int</span> i) <span class="kw">throws</span> SQLException {
Client result = <span class="kw">new</span> <span class="fu">Client</span>();
result.<span class="fu">setId</span>(rs.<span class="fu">getLong</span>(<span class="st">"ID"</span>));
result.<span class="fu">setNom</span>(rs.<span class="fu">getString</span>(<span class="st">"NOM"</span>));
result.<span class="fu">setPrenom</span>(rs.<span class="fu">getString</span>(<span class="st">"PRENOM"</span>));
<span class="kw">return</span> result;
}
}
Utilisation de JPA & Hibernate
La solution de l'exercice est contenu dans le projet intitulé 09-SPRING-TX-JPA
- Rajoutez les dépendances suivantes dans le pom.xml de maven:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.0.7.FINAL</version>
</dependency>
Modifiez les classes Compte et Client afin de les transformer en entités persistantes en utilisant les annotations @Entity et @Id.
Complétez le fichier beans.xml en déclarant un entityManagerFactory
<bean id="emf"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<span class="kw"><property</span><span class="ot"> name=</span><span class="st">"jpaProperties"</span><span class="kw">></span>
<span class="kw"><props></span>
<span class="kw"><prop</span><span class="ot"> key=</span><span class="st">"hibernate.show_sql"</span><span class="kw">></span>true<span class="kw"></prop></span>
<span class="kw"></props></span>
<span class="kw"></property></span>
<span class="kw"><property</span><span class="ot"> name=</span><span class="st">"persistenceUnitName"</span><span class="ot"> value=</span><span class="st">"RetengrPU"</span> <span class="kw">/></span>
<span class="kw"><property</span><span class="ot"> name=</span><span class="st">"dataSource"</span><span class="ot"> ref=</span><span class="st">"datasource"</span> <span class="kw">/></span>
<span class="kw"><property</span><span class="ot"> name=</span><span class="st">"packagesToScan"</span><span class="ot"> value=</span><span class="st">"com.retengr.model"</span> <span class="kw">/></span>
</bean>
REST
Présentation
Cet exercice va nous permettre de mettre en place un webservice REST de trois façon différentes:
- JAX-RS, le standard Java via son implémentation fournie par Jersey
- Via SpringMVC
- et enfin à partir de Springboot
Nous utiliserons Rest Shell afin de tester les webservices.
La solution de cet exercice est 10-annuaireREST
Jersey
Créez un projet maven en utilisant l'archetype maven-archetype-webapp. Nommez le HelloWorldREST
Rajoutez les dépendances suivantes:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.8</version>
</dependency>
<span class="kw"><dependency></span>
<span class="kw"><groupId></span>com.sun.jersey<span class="kw"></groupId></span>
<span class="kw"><artifactId></span>jersey-json<span class="kw"></artifactId></span>
<span class="kw"><version></span>1.8<span class="kw"></version></span>
<span class="kw"></dependency></span></code></pre></div>
- Créez le répertoire
src/main/java
- Rafraichissez le projet Eclipse afin que ce répertoire soit maintenant pris en compte
- Créez une classe
Personne qui a deux attributs: nom et prenom
-
Créez une classe Annuaire. La classe annuaire possèdera les méthodes suivantes:
-
list: retourne la liste des personnes qu'elle stocke dans une map
-
find: recherche l'objet associé à la clé correspondant au paramètre qui lui est passé
-
add: prend une Personnne en paramètre et l'ajoute à la map
En utilisant la documentation de JAX-RS, mappez la classe Annuaire afin de la rendre accessible au travers d'un webservice REST
-
Afin de pouvoir déployer facilement notre war, nous allons utiliser un plugin tomcat dans maven. Modifiez votre pom.xmlafin de pouvoir l'utiliser. Vous Créerez pour cela une option d'exécution dans eclipse:
- Dans Eclipse, "right-click project"
- Run As > Maven build... (PAS "Maven build")
- Dans le champs "Goals", saisissez "tomcat7:run"
- "Apply", puis "Run"
Pourquoi est-il nécessaire de déployer le code suivant dans le fichier WEB-INF/web.ml pour déployer notre webservice ? A quelle adresse (endpoint) le webservice est il accessible ?
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>Servlet JERSEY</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.rtgr.rest</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<span class="kw"><servlet-mapping></span>
<span class="kw"><servlet-name></span>jersey-serlvet<span class="kw"></servlet-name></span>
<span class="kw"><url-pattern></span>/rest/*<span class="kw"></url-pattern></span>
<span class="kw"></servlet-mapping></span>
</web-app>
Springboot - Premiers pas
La solution de cet exercice est 12-SPRINGBOOT-BANQUE
Objectif
Transformer notre application bancaire ainsi que le webservice pour fonctionner avec Spring Boot
Préparation du projet
En utilisant le starter de Spring Boot, créez un projet utilsiant les modules suivants:
- WEB
- JPA
Le starter va vous générer un projet maven avec les dépendances recquises, qu'il vous suffira d'importer dans Eclipse.
La base de données
Démarrez l'application Spring Boot. L'erreur suivante doit s'afficher:
***************************
APPLICATION FAILED TO START
***************************
Description:
Cannot determine embedded database driver class for database type NONE
Action:
If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
Justifiez cette erreur, puis rajoutez la dépendance suivante dans le pom.xml
Application bancaire et Spring Boot
Réutilisation de l'existant
Récupérez les packages dao et service que vous avez implémenté dans les exercices précédents. Nous allons maintenant déployer notre webservice dans Spring Boot. Pour cela nous pouvons l'implémenter en utilisant Spring MVC ou bien JAXRS (Jersey)
Si vous le souhaitez, vous pouvez n'implémenter que la couche service qui utilisera directement l'entityManager, vous affranchissant ainsi de la mise en oeuvre d'un DAO.
Création de BanqueWS
En vous inspirant du code qui suit, vous créerez un webservice REST com.retengr.rest.BanqueWS qui vous permettra de récupérer la liste des comptes, mais aussi de faire un transfer.
@RestController
public class GreetingController {
<span class="kw">private</span> <span class="dt">static</span> <span class="dt">final</span> String template = <span class="st">"Hello, %s!"</span>;
<span class="kw">private</span> <span class="dt">final</span> AtomicLong counter = <span class="kw">new</span> AtomicLong();
<span class="fu">@RequestMapping</span>(method = RequestMethod.<span class="fu">GET</span>,value=<span class="st">"/greeting"</span>)
<span class="kw">public</span> Greeting <span class="fu">greeting</span>(<span class="fu">@RequestParam</span>(value=<span class="st">"name"</span>, defaultValue=<span class="st">"World"</span>) String name) {
<span class="kw">return</span> <span class="kw">new</span> <span class="fu">Greeting</span>(counter.<span class="fu">incrementAndGet</span>(),
String<span class="fu">.format</span>(template, name));
}
<span class="fu">@RequestMapping</span>(method = RequestMethod.<span class="fu">GET</span>,value=<span class="st">"/greeting/{name}"</span>)
<span class="kw">public</span> Greeting <span class="fu">greeting</span>(<span class="fu">@PathVariable</span> String name) {
<span class="kw">return</span> <span class="kw">new</span> <span class="fu">Greeting</span>(<span class="st">"name"</span>)
}
}
Le webservice BanqueWS offrira les deux fonctionalités suivantes:
# Tous les comptes, en json
GET /comptes
Accept: application/json
Transfer de 100€ du compte 1 au compte 2
GET /transfer/1/2/100
Accept: application/json
Contrairement à Jersey, SpringMVC traite par défaut les données en entrée ou en sortie au format JSON. Spring MVC n'est pas compatible JAXRS
La datasource
Comme nous l'avons déja évoqué précédemment, Spring Boot automatise la création d'une datasource utilisant une base de données inmemory. Cette solution n'est pas satisfaisante, et nous souhaitons utiliser la base de données standalone que nous avons utilisé jusqu'à présent. Deux approches sont ici envisageables:
- Paramétrer la datasource existante en renseignant le fichier
resources/application.properties avec les informations de connexion suivantes:
# DataSource
spring.datasource.url=jdbc:hsqldb:hsql://localhost/db
spring.datasource.username=sa
spring.datasource.driverClassName=org.hsqldb.jdbcDriver
- Utiliser une classe de configuration qui déclare un bean de type datasource:
@Configuration
public class Config {
@Bean(name = "dataSource")
public DataSource dataSource() {
BasicDataSource datasource = new BasicDataSource();
datasource.setDriverClassName("org.hsqldb.jdbcDriver");
datasource.setUrl("jdbc:hsqldb:hsql://localhost/db");
datasource.setUsername("sa");
return datasource;
}
}
Hibernate & EntityManager
Avec le paramétrage par défaut effectué par Spring Boot, hibernate regénère l'intégralité du shéma relationnel, ce qui a pour conséquence de supprimer les données qui étaient contenues dans la base de données. Modifiez ce comportement en rajoutant le paramétrage suivant dans le fichier resources/application.properties:
# Hibernate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.HSQLDialect
spring.jpa.hibernate.ddl-auto=none
Nous en profitons pour demander l'affichage des requêtes sql exécutées par Hibernate (indispensable pour débugger)
Spring Batch
Objectif
Nous allons utiliser pring Batch afin de nettoyer un fichier de log d'un serveur web et d'en insérer les informations pertinentes dans une base de données (en mémoire).
Configuration
Nous allons utiliser Spring Boot afin de simplifier le paramétrage du l'environnement technique de l'environnement (notamment la datasource)
Afin de nous simplifier la tâche, nous allons nous inspirer de l'exemple mis à disposition de SPring. Téléchargez le et importez le dans Eclipse.
A faire
Ce fichier de log contenant les données qui vont être utilisées en entrée du batch. Ce fichier contient 200000 entrées. Sur le volume de données en entrée, nous souhaitons ne garder que les informations suivantes:
- L'adresse IP de l'utilisateur (ou le nom DNS)
- L'URL qu'il a sollicité
Parmis les lignes seules celles qui ont donné lieu à une réponse de la part du serveur (code retour commençant par 200) seront prises en compte.
Une fois dézippé, le fichier de log sera copié dans le répertoire resources de l'application.
- Ouvrez le fichier
resources/schema-all.sql. Ce fichier est utilisé par la datasource afin de recréer le schéma de la base de données qui va être créée par la base (inmemory)
DROP TABLE log IF EXISTS;
CREATE TABLE log (
id BIGINT IDENTITY NOT NULL PRIMARY KEY,
IP VARCHAR(60),
URL VARCHAR(200)
);
- Supprimez la classe
Personne``, et créez une classeLogItem``` qui permettra de mapper les informations contenus sur une ligne de notre fichier de log
public class LogItem {
private String ip;
private String url;
private String coderetour;
<span class="co">// Informations non pertinentes qui doivent être mappées, mais qui ne seront pas utilisées ici</span>
<span class="kw">private</span> String ELEM2;
<span class="kw">private</span> String ELEM3;
<span class="kw">private</span> String ELEM4;
<span class="kw">private</span> String ELEM1;
<span class="kw">private</span> String ELEM7;
<span class="co">// ...</span>
}
-
Modifiez la classe BatchConfigurationet adaptez les méthodes suivantes:
reader(): Analyse les données en entrée (attention, le séparateur est maintenatn un espace), et de le mapper sur une objet de type LogItem
writer(): à partir des propriétés d'un LogItem, enrichissez la table précédemmetn créée
processor: Que devez vous faire ?
Crééez une classe LogItemProcessor qui filtrera les LogItem (si la méthode process retourne null, le logitem ne sera pas envoyé au writer)
Analysez la classe JobCompletionNotificationListenerqui sera solicitée à la fin du traitement. Adaptez son code de la façon suivante:
@Override
public void afterJob(JobExecution jobExecution) {
if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
log.info("!!! JOB FINISHED! Time to verify the results");
String sql = "SELECT count(*) FROM log";
<span class="dt">int</span> count = jdbcTemplate.<span class="fu">queryForObject</span>(
sql, <span class="kw">new</span> Object[] { }, Integer.<span class="fu">class</span>);
log.<span class="fu">info</span>(<span class="st">"Found <"</span> + count + <span class="st">"> corresponding requests in the database."</span>);
}</code></pre></div>
JMS
Objectif
Nous souhaitons Rajouter une fonctionalité d'audit à notre application. Cette fonctionalité consiste à tracer l'ensemble des actions d'un utilisateur ainsi que les performance (temps de traitement).
Modification du schéma
- Utilisez le fichier
resources/data.sql suivant:
DROP TABLE COMPTE if exists;
DROP TABLE CLIENT if exists;
DROP TABLE AUDIT_LOG if exists;
DROP SEQUENCE hibernate_sequence if exists;
create sequence hibernate_sequence start with 1 increment by 1
create table audit_log (id bigint not null, user_id varchar(255), description varchar(255), date date, duration integer not null, primary key (id))
create table client (id bigint not null, nom varchar(255), prenom varchar(255), primary key (id))
create table compte (id bigint not null, description varchar(255), numero varchar(255), solde double not null, primary key (id))
Ce fichier est utilisé au moment de l'initialisation de la datasource par défaut de spring boot.
- Utilisez le fichier
resources/data.sql suivant.
INSERT INTO CLIENT (ID, NOM, PRENOM) VALUES ('1', 'Zidane', 'Zinedine' );
INSERT INTO CLIENT (ID, NOM, PRENOM) VALUES ('2', 'Platini', 'Michel' );
INSERT INTO CLIENT (ID, NOM, PRENOM) VALUES ('3', 'Papin', 'Jean-Pierre' );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION) VALUES ('5', 1000,10000,'Compte Courant' );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION) VALUES ('1', 1001,20000,'Compte Courant' );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION) VALUES ('2', 1002,30000,'Compte Courant' );
INSERT INTO COMPTE (ID, NUMERO, SOLDE, DESCRIPTION) VALUES ('3', 1003,1000,'livret A' );
JPA
- Créez un nouvel objet persistent
com.retengr.modele.AuditLog. Cet objet contiendra l'ensemble des informations d'audit (description, timestamp,userid,duree) , et sera mappé sur la table précédemment créée.
- Modifiez l'ensemble de vos classes persistentes pour préciser à Hibernate qu'il aura la responsabilité de gérer la clé primaire des objets qu'il sauvera dans la base. On utilisera un mécanisme de séquence pour cela (cf schéma de la base précédemment créé)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
JMS
La solution de cet exercice est 13-SPRINGBOOT-BANQUE-JMS
- Rajoutez une dépendance vers le starter
active-mq dans le pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
Afin d'activer JMS dans votre application, il est nécessaire de rajouter l'annotation @EnableJms sur la classe de votre main
@SpringBootApplication
@EnableJms
public class Application {
// ...
}
- Créez un bean spring
Audit. Ce bean pourra être injecté dans les classes qui souhaitent l'utiliser:
package com.retengr.service;
import javax.jms.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
@Component
public class AuditImpl implements Audit{
// ...
public void log(String msg) {
// Send message using JMS
}
}
Vous vous inspirerez de cet exemple afin de l'implémenter.
- Implémentez un bean Spring qui consomera le message. Quand vous aurez vérifiez que le message est bien reçu. Stockez-le dans la base de données en utilisant JPA et la classe
AuditLog précédemment créee.
JMX
La solution de cet exercice est 13-SPRINGBOOT-BANQUE-JMS
Objectif
Nous allons mettre en place un système de gestion de profil et de droit d'accès pour notre application bancaire.
Pour cela, nous allons implémenter une composant d'administration MBEAN. Nous utiliserons la JConsole afin d'utiliser ce composant.
Gestion du profil
Rajoutez une propriété onlyPositivede type boolean dans la classe BanqueImpl
Si onlyPositive == true, seuls les comptes ayant un solde positifs seront retournés. La requête JPQL a exécuter est from Compte c where c.solde > 0
ProfileManagerMBean
Implémentez un MBean permettant de modifier la propriété onlyPositive précédemment créée. La modification de la propriété du MBean donnera lieu à une entrée dans la table d'audit !
@Component
@ManagedResource(objectName = "com.retengr:type=rbcprofile", description = "RBC Profile Bean")
public class ProfileManager implements ProfileManagerMBean {
// ???
}
API d'administration
Mettez en place une API REST permettant d'administrer votre application bancaire:
- Modifier le paramétrage de
onlyPositive
- Visualiser la table d'audit