arrière-plan
Springboot en a fait l'un des cadres de développement Web Java les plus traditionnels aujourd'hui car il fournit une variété de plugins prêts à l'emploi. Mybatis est un cadre ORM très léger et facile à utiliser. Redis est une base de données de valeurs clés distribuée très grand public aujourd'hui. Dans le développement Web, nous l'utilisons souvent pour mettre en cache les résultats de la requête de la base de données.
Ce blog présentera comment créer rapidement une application Web à l'aide de Springboot et utilisera MyBatis comme framework ORM. Pour améliorer les performances, nous utilisons Redis comme cache de deuxième niveau pour MyBatis. Pour tester notre code, nous avons écrit des tests unitaires et utilisé la base de données en mémoire H2 pour générer nos données de test. Grâce à ce projet, nous espérons que les lecteurs pourront rapidement maîtriser les compétences et les meilleures pratiques du développement Web de Java moderne.
L'exemple de code pour cet article peut être téléchargé dans GitHub: https://github.com/lovelcp/spring-boot-mybatis-with-redis/tree/master
environnement
Environnement de développement: Mac 10.11
IDE: Intellij 2017.1
JDK: 1.8
Spring-Boot: 1.5.3.release
Redis: 3.2.9
MySQL: 5.7
Flèche de printemps
Créer un nouveau projet
Tout d'abord, nous devons initialiser notre projet Spring-Boot. Grâce à l'initialisateur de printemps d'Intellij, il est très facile de créer un nouveau projet Spring-Boot. Tout d'abord, nous sélectionnons un nouveau projet dans Intellij:
Ensuite, dans l'interface pour sélectionner les dépendances, vérifiez le Web, MyBatis, Redis, MySQL, H2:
Une fois le nouveau projet réussi, nous pouvons voir la structure initiale du projet comme indiqué dans la figure ci-dessous:
Spring Initialiseur nous a aidés à générer automatiquement une classe de démarrage - SpringbootMyBatisWithRedisApplication. Le code de cette classe est très simple:
@Springbootapplicationpublic class SpringbootMyBatisWithRedisApplication {public static void main (String [] args) {SpringApplication.Run (SpringbootMyBatisWithRedisapplication.Class, Args); }}@Springbootapplication annotation signifie activer la fonction de configuration automatique de Spring Boot. D'accord, notre projet Skeleton a été construit avec succès afin que les lecteurs intéressés puissent commencer les résultats via Intellij.
Créer une nouvelle interface API
Ensuite, nous rédigerons une API Web. Supposons que notre ingénierie Web soit responsable de la gestion des produits du marchand (produit). Nous devons fournir une interface GET qui renvoie les informations sur les produits en fonction de l'ID du produit et d'une interface de put qui met à jour les informations sur les produits. Nous définissons d'abord la classe de produits, qui comprend l'ID du produit, le nom du produit et le prix:
Le produit de classe publique implémente sérialisable {private statique final long SerialVersionUID = 1435515995276255188l; ID long privé; nom de chaîne privé; prix long privé; // Getters setters}Ensuite, nous devons définir la classe de contrôleur. Étant donné que Spring Boot utilise Spring MVC comme composant Web en interne, nous pouvons rapidement développer notre classe d'interface via l'annotation:
@ RestController @ requestmapping ("/ product") public class ProductController {@getMapping ("/ {id}") public Product getProductInfo (@Pathvariable ("id") long produddid) {// todo return null; } @Putmapping ("/ {id}") Product UpdateProductInfo (@Pathvariable ("ID") Long ProductId, @Requestbody Product NewProduct) {// TODO return null; }}Présent brièvement les fonctions des annotations utilisées dans le code ci-dessus:
@RestController: signifie que la classe est un contrôleur et fournit une interface de repos, c'est-à-dire que les valeurs de toutes les interfaces sont renvoyées au format JSON. Cette annotation est en fait une annotation combinée de @Controller et @ResponseBody, ce qui nous facilite pour développer l'API de repos.
@RequestMapping, @getMapping, @putmapping: représente l'adresse URL de l'interface. L'annotation @RequestMapping annotée sur la classe signifie que les URL de toutes les interfaces sous la classe commencent avec / produit. @Getmapping signifie qu'il s'agit d'une interface GET HTTP, @putmapping signifie qu'il s'agit d'une interface HTTP put.
@Pathvariable, @Requestbody: représente la relation de mappage des paramètres. En supposant qu'une demande d'accès / produit / 123 GET, la demande sera traitée par la méthode GetProductInfo, où 123 dans l'URL sera mappé dans le productID. De même, s'il s'agit d'une demande de vente, le corps demandé sera mappé dans l'objet NewProduct.
Ici, nous ne définissons que l'interface, et la logique de traitement réelle n'a pas encore été terminée, car les informations du produit sont présentes dans la base de données. Ensuite, nous intégrerons MyBatis dans le projet et interagirons avec la base de données.
Intégration de mybatis
Configurer la source de données
Nous devons d'abord configurer notre source de données dans le fichier de configuration. Nous utilisons MySQL comme base de données. Ici, nous utilisons YAML comme format de notre fichier de configuration. Nous créons un nouveau fichier application.yml dans le répertoire des ressources:
Spring: # Configuration de la base de données DataSource: URL: jdbc: mysql: // {your_host} / {your_db} username: {your_username} mot de passe: {your_password} Driver-Class-name: org.gjt.mm.mysql.driverÉtant donné que Spring Boot dispose de la fonction de configuration automatique, nous n'avons pas besoin de créer une nouvelle classe de configuration de source de données. Spring Boot chargera automatiquement le fichier de configuration et établira un pool de connexion de base de données en fonction des informations de fichier de configuration, ce qui est très pratique.
L'auteur vous recommande d'utiliser YAML comme format de fichier de configuration. XML semble long et les propriétés n'ont pas de structure hiérarchique. Yaml compense juste les lacunes des deux. C'est également la raison pour laquelle Spring Boot prend en charge le format YAML par défaut.
Configurer Mybatis
Nous avons introduit la bibliothèque MyBatis-Spring-Boot-Starte dans POM.xml via Spring Initializer, qui nous aidera automatiquement à initialiser MyBatis. Tout d'abord, nous remplissons la configuration pertinente de MyBatis dans Application.yml:
# mybatis configure mybatis: # Configurez le nom du package où la classe de mappage est située de type-aliases-package: com.wooyoo.learning.dao.domain # Configurez le chemin où se trouve le fichier mappeur xml.xml, voici un tableau-locations de mappers: - mappers / productmapper.xml
Ensuite, définissez la classe ProductMapper dans le code:
@MapperPublic Interface ProductMapper {Product Select (@param ("id") Long ID); Mise à jour void (produit du produit);}Ici, tant que nous ajoutons l'annotation @mapper, Spring Boot chargera automatiquement la classe de mappeur lors de l'initialisation de MyBatis.
La principale raison pour laquelle Spring Boot est si populaire est sa fonction de configuration automatique. Les développeurs n'ont qu'à prêter attention à la configuration des composants (tels que les informations de connexion de la base de données) sans se soucier de la façon d'initialiser les composants individuels, ce qui nous permet de nous concentrer sur la mise en œuvre de l'entreprise et de simplifier le processus de développement.
Accéder à la base de données
Après avoir terminé la configuration MyBatis, nous pouvons accéder à la base de données dans notre interface. Nous présentons la classe Mapper via @Autowired sous ProductController et appelons la méthode correspondante pour implémenter les opérations de requête et de mise à jour sur le produit. Ici, nous prenons l'interface de requête comme exemple:
@ RestController @ requestmapping ("/ product") public class ProductController {@Autowired Private ProductMapper ProductMapper; @GetMapping ("/ {id}") Product public getProductInfo (@Pathvariable ("id") long productId) {return productMapper.Select (productId); } // Évitez trop longtemps et omettez le code de mise à jourProductInfo}Insérez ensuite quelques informations sur le produit dans votre MySQL et vous pouvez exécuter le projet pour voir si la requête réussit.
Jusqu'à présent, nous avons réussi à intégrer MyBatis dans notre projet, ajoutant la possibilité d'interagir avec la base de données. Mais ce n'est pas suffisant. Un projet Web moderne accélérera certainement notre requête de base de données sur Cache. Ensuite, nous présenterons comment intégrer scientifiquement Redis dans le cache secondaire de MyBatis pour réaliser le cache automatique des requêtes de base de données.
Redis intégré
Configurer Redis
Tout comme l'accès à une base de données, nous devons configurer les informations de connexion redis. Ajoutez la configuration suivante au fichier application.yml:
Spring: redis: # redis index de la base de données (par défaut est 0). Nous utilisons une base de données avec Index 3 pour éviter les conflits avec d'autres bases de données Base de données: 3 # Redis Server Address (Default Is LocalHost) Hôte: LocalHost # Redis Port (par défaut est 6379) infini) max-actif: 8 # Nombre maximum de connexions inactives (la valeur par défaut est 8, les nombres négatifs représentent infini) max-idle: 8 # nombre minimum de connexions inactives (la valeur par défaut est 0, cette valeur est seulement efficace) Min-idle: 0 # Obtenez le temps d'attente de connexion maximum à partir du pool de connexion (par défaut est -1, -1
Tous les indications ci-dessus sont des configurations couramment utilisées, et les lecteurs peuvent comprendre le rôle spécifique de chaque élément de configuration via des informations de commentaire. Puisque nous avons introduit la bibliothèque Spring-Boot-Starter-Data-Redis dans POM.xml, Spring Boot nous aidera à charger automatiquement les connexions Redis et les classes de configuration spécifiques
org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration. Grâce à cette classe de configuration, nous pouvons constater que la couche sous-jacente utilise la bibliothèque Jedis par défaut et fournit Redetemplate et StringTemplate hors de la boîte.
Utilisez Redis comme cache de niveau 2
Le principe de la mise en cache secondaire de Mybatis ne sera pas décrit dans cet article. Les lecteurs doivent seulement savoir que la mise en cache secondaire de MyBatis peut mettre automatiquement en cache les requêtes de base de données et peut mettre à jour automatiquement le cache lors de la mise à jour des données.
La mise en œuvre de la mise en cache secondaire de MyBatis est très simple. Il vous suffit de créer une nouvelle classe pour implémenter l'interface org.apache.ibatis.cache.cache.
Il existe cinq méthodes pour cette interface:
String getID (): L'identifiant de l'objet MyBatis Cache Operation. Un mappeur correspond à un objet d'opération de cache MyBatis.
void putObject (clé d'objet, valeur de l'objet): remplissez les résultats de la requête dans le cache.
Object GetObject (clé d'objet): obtenez le résultat de la requête en cache du cache.
Object SupporObject (touche objet): Supprimez la touche et la valeur correspondantes du cache. Tiré uniquement lors du retour en arrière. Généralement, nous n'avons pas besoin de le mettre en œuvre. Pour des méthodes d'utilisation spécifiques, veuillez vous référer à: org.apache.ibatis.cache.decorators.transactionalCache.
void clear (): effacer le cache lorsqu'une mise à jour se produit.
int getSize (): implémentation facultative. Renvoie le nombre de caches.
ReadWriteLock getReadWriteLock (): implémentation facultative. Utilisé pour implémenter les opérations de cache atomique.
Ensuite, nous créons une nouvelle classe Rediscache pour implémenter l'interface de cache:
classe publique Rediscache implémente cache {private static final logger logger = loggerfactory.getLogger (rediscache.class); Private Final ReadWriteLock ReadWriteLock = new ReentRanTreadWriteLock (); ID de chaîne finale privée; // ID d'instance de cache ID privé Redemplate Redemplate; Final statique privé Long expire_time_in_minutes = 30; // redis Expiration Time Public Rediscache (String id) {if (id == null) {Throw New illégalArgumentException ("Les instances de cache nécessitent un id"); } this.id = id; } @Override public String getID () {return id; } / ** * Mettez le résultat de la requête à redis * * @param key * @param value * / @Override @SuppressWarnings ("Unchecked") public void putObject (clé d'objet, valeur objet) {Redemplate reidemplate = getRedistEmplate (); ValueOperations opsForValue = redetemplate.opsForValue (); opsforvalue.set (key, valeur, expire_time_in_minutes, timeUnit.Minutes); Logger.debug ("Mettez le résultat de la requête à redis"); } / ** * Obtenez le résultat de la requête en cache à partir de redis * * @param key * @return * / @Override public objet getObject (objet clé) {redemplate redetemplate = getRedistEemplate (); ValueOperations opsForValue = redetemplate.opsForValue (); Logger.debug ("Get Cached Query Resultation de Redis"); return opsforvalue.get (key); } / ** * Supprimez le résultat de la requête en cache de redis * * @param key * @return * / @Override @SuppressWarnings ("Unchecked") Objet public SupporObject (Key object) {redesttemplate reidemplate = getRedistEmplate (); Redistetemplate.delete (clé); Logger.debug ("Supprimer le résultat de la requête en cache de redis"); retourner null; } / ** * Effiche cette instance de cache * / @Override public void clear () {redetemplate reidemplate = getRedistEemplate (); Redistemplate.ExECUTE ((redécallback) Connection -> {connection.flushdb (); return null;}); Logger.debug ("effacer tous les résultats de la requête en cache de redis"); } @Override public int getSize () {return 0; } @Override public readWriteLock getReadWriteLock () {return readWriteLock; } private reidemplate getRedistEemplate () {if (redetemplate == null) {redetemplate = applicationContexTholder.getBean ("redetemplate"); } return reidemplate; }}Permettez-moi d'expliquer quelques points clés dans le code ci-dessus:
Le cache de deuxième niveau que vous implémentez doit avoir un constructeur avec ID, sinon une erreur sera signalée.
Nous utilisons le redistetemplate encapsulé à ressort pour faire fonctionner Redis. Tous les articles en ligne qui introduisent Redis dans le cache secondaire du niveau 2 utilisent directement la bibliothèque Jedis, mais l'auteur estime que ce n'est pas assez de style printemps. De plus, Redetemplate résume l'implémentation sous-jacente. Si nous n'utilisons pas de Jedis à l'avenir, nous pouvons remplacer directement la bibliothèque sous-jacente sans modifier le code supérieur. Ce qui est plus pratique, c'est que l'utilisation de Redemplate, nous n'avons pas à nous soucier de la publication des connexions Redis, sinon il sera facile pour les novices d'oublier de libérer la connexion et de faire coincé l'application.
Il convient de noter que ReDistemplate ne peut pas être référencé via Autowire, car Rediscache n'est pas un haricot dans le conteneur à ressort. Nous devons donc appeler manuellement la méthode Getbean du conteneur pour obtenir ce haricot. Pour des méthodes d'implémentation spécifiques, veuillez vous référer au code de GitHub.
La méthode de sérialisation Redis que nous utilisons est la sérialisation JDK par défaut. Par conséquent, l'objet de requête de la base de données (comme la classe de produits) doit implémenter l'interface sérialisable.
De cette façon, nous mettons en œuvre une classe de cache élégante, scientifique et redis avec le style de printemps.
Allumez le cache de niveau 2
Ensuite, nous devons activer le cache de niveau 2 dans ProductMapper.xml:
<? xml version = "1.0" Encoding = "UTF-8"?> <! Doctype Mappep Public "- // Mybatis.org//dtd Mapper 3.0 // en" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><Mapper namespace = "com.wooyoo.learning.dao.mapper.productmapper"> <! - Activer le cache secondaire basé sur Redis -> <cache type = "com.wooyoo.learning.util.rediscache" /> <select id = "select" resultType = "Product"> Sélectionner * à partir des produits où id = # {} limite 1 </ select> ParamètreType = "Product" FlushCache = "true"> Mise à jour les produits set name = # {name}, prix = # {prix} où id = # {id} limite 1 </dated> </ mapper><cache type = "com.wooyoo.learning.util.rediscache" /> signifie activer le cache secondaire basé sur Redis, et dans l'instruction de mise à jour, nous définissons FlushCache sur true, de sorte que lors de la mise à jour des informations sur le produit, le cache peut être automatiquement invalidé (essentiellement, la méthode claire est appelée).
test
Configurer la base de données de mémoire H2
À ce stade, nous avons terminé tout le développement du code, et ensuite nous devons écrire le code de test unitaire pour tester la qualité de notre code. Dans le processus de développement, nous avons utilisé la base de données MySQL, et généralement nous avons souvent utilisé des bases de données en mémoire lors des tests. Ici, nous utilisons H2 comme base de données utilisée dans notre scénario de test.
Il est également très simple d'utiliser H2, il vous suffit de le configurer lors de l'utilisation de MySQL. Dans le fichier application.yml:
--- Spring: Profiles: Test # Configuration de la base de données DataSource: URL: JDBC: H2: MEM: Test Username: Root Motword: 123456 Driver-Class-Name: Org.H2.Driver Schema: ClassPath: Schema.Sql Data: ClassPath: Data.Sql
Afin d'éviter les conflits avec la configuration par défaut, nous utilisons --- pour démarrer un nouveau paragraphe et utiliser les profils: test pour indiquer qu'il s'agit de la configuration dans l'environnement de test. Ajoutez ensuite l'annotation @activeProfiles (Profils = "test") à notre classe de test pour activer la configuration dans l'environnement de test, afin que vous puissiez passer de la base de données MySQL à la base de données H2 en un seul clic.
Dans la configuration ci-dessus, Schema.sql est utilisé pour stocker notre instruction de création de table, et Data.SQL est utilisé pour stocker des données d'insertion. De cette façon, lorsque nous testons, H2 lira ces deux fichiers, initialise la structure de la table et les données dont nous avons besoin, puis la détruira à la fin du test, ce qui n'aura aucun impact sur notre base de données MySQL. C'est l'avantage des bases de données en mémoire. N'oubliez pas non plus de définir la portée de la dépendance de H2 à tester dans pom.xml.
L'utilisation de Spring Boot est aussi simple que vous pouvez facilement basculer de bases de données dans différents environnements sans modifier aucun code.
Code de test d'écriture
Parce que nous sommes initialisés via Spring Initializer, nous avons déjà une classe de test - SpringbootMyBatisWithRedisApplicationTests.
Spring Boot fournit certaines classes d'outils qui nous facilitent pour effectuer des tests d'interface Web, tels que TestRestTemplate. Ensuite, dans le fichier de configuration, nous ajustez le niveau de journal pour déboguer pour faciliter l'observation des journaux de débogage. Le code de test spécifique est le suivant:
@Runwith (springrunner.class) @springboottest (webenvironment = Springboottest.webenvironment.random_port) @activeprofiles (Profils = "test") public class SpringbootMyBatisWithRedisApplicationTests {@LocalServerport Private int port; @Autowired Private TestRestTemplate RestTemplate; @Test public void test () {long productId = 1; Product product = restTemplate.getForObject ("http: // localhost:" + port + "/ product /" + productId, product.class); affirmer (product.getPrice ()). IsEqualto (200); Product NewProduct = nouveau produit (); long newPrice = new Random (). NextLong (); newproduct.setName ("nouveau nom"); NewProduct.SetPrice (NewPrice); restTemplate.put ("http: // localhost:" + port + "/ product /" + productId, newProduct); Product TestProduct = RestTemplate.getForObject ("http: // localhost:" + port + "/ product /" + productId, product.class); assertThat (testProduct.getPrice ()). IsEqualto (newPrice); }}Dans le code de test ci-dessus:
Nous appelons d'abord l'interface Get et utilisons l'instruction ASSERT pour déterminer si l'objet attendu a été obtenu. À l'heure actuelle, l'objet produit sera stocké dans Redis.
Ensuite, nous appelons l'interface de put pour mettre à jour l'objet produit et le cache redis sera invalidé.
Enfin, nous appelons à nouveau l'interface Get pour déterminer si nous avons obtenu un nouvel objet de produit. Si un ancien objet est obtenu, cela signifie que le code non valide du cache n'a pas réussi à s'exécuter et qu'il y a une erreur dans le code, sinon cela signifie que notre code est OK.
Les tests d'unité d'écriture sont une bonne habitude de programmation. Bien que cela prenne un certain temps pour vous, lorsque vous devrez faire un travail de refactorisation à l'avenir, vous vous serez reconnaissant qui a écrit des tests unitaires dans le passé.
Afficher les résultats du test
Nous cliquons pour exécuter le cas de test dans Intellij, et les résultats des tests sont les suivants:
Le vert s'affiche, indiquant que le cas de test a été exécuté avec succès.
Résumer
Cet article présente comment construire rapidement un projet Web moderne avec Spring Boot, Mybatis et Redis, et présente également comment écrire des tests unitaires avec élégance sous Spring Boot pour assurer la qualité de notre code. Bien sûr, il y a un autre problème avec ce projet, c'est-à-dire que le cache de niveau 2 de Mybatis ne peut être mis en cache qu'envalidé en rinçant toute la base de données. Pour le moment, certains caches qui n'ont pas besoin d'être invalidés peuvent également être invalidés, il a donc certaines limites.