[JAVA] Gérer Enum avec Mybatis TypeHandler

Aperçu

Dans Mybatis de l'O / R mapping flake work, le mécanisme de conversion automatique de type fourni dans Mybatis: Handles Enum en utilisant TypeHandler. Nous présenterons également comment écrire du SQL dynamique et son application à l'aide de Spring MVC.

Raisons de résumer

Il existe une colonne [^ 1] qui gère les «indicateurs» et les «valeurs partitionnées» susceptibles d'exister dans la base de données utilisée par les systèmes et applications nouvellement créés qui ont déjà été créés, et la plage de ces valeurs est pratiquement inchangée [^ 2 ]est. Étant donné que ces valeurs sont souvent gérées en tant que type chaîne de caractères (CHAR ou VARCHAR) ou type numérique, au moins dans le monde du code Java, la chaîne et la valeur numérique ne sont pas traitées telles quelles et la plage de valeurs est S'il a été décidé, peut-il être géré par enum ...? C'est le début.

Prémisse de l'article

Dans cet article, nous avons créé et confirmé l'opération avec les versions suivantes.

Ensemble de fichiers source

Il est publié sur GitHub.

https://github.com/A-pZ/mybatis-spring-enum-sample

Implémentation de la fonction de recherche

Cette fois, une fonction qui renvoie le résultat de la recherche dans une table qui gère un certain indicateur ou une valeur de division est introduite.

Que mettre en œuvre

Pour expliquer brièvement ce qu'il faut mettre en œuvre

est.

Tableau de référence

C'est la définition de la table à référencer cette fois [^ 3].

Tableau des produits (nom du tableau: article)

Nom de colonne Moule Le rôle de la colonne Contraintes, etc.
id Valeur numérique Clé primaire qui définit de manière unique le produit Valeur à numéroter automatiquement
name Chaîne Nom du produit Nom du produitの表示に使う
status Chaîne Classification d'affichage des produits 0:Pas de statut 1:Ouvert au public 2:Membres seulement
display Chaîne Afficher / masquer les produits 0:Ne pas afficher 1:indiquer

Cette fois, nous nous concentrerons sur la valeur de division à trois valeurs définie par le statut et le drapeau géré par l'affichage.

Énumération qui définit les indicateurs et les valeurs de partition

Créez une énumération côté Java pour l'état et l'affichage des deux colonnes qui apparaissent cette fois. Étant donné que le statut est «celui qui détermine la portée de la divulgation du produit», renommez-le ItemPublish. Puisque l'affichage est une valeur binaire qui détermine s'il faut afficher ou non, il est généralement défini sur TrueOrFalse afin qu'il puisse être utilisé dans d'autres colonnes.

ItemPublish.java


import java.util.Arrays;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 *Catégorie d'affichage du produit.
 */
@Getter
@AllArgsConstructor
public enum ItemPublish {
	NONE("0"), PUBLISH("1"), MEMBER_ONLY("2"), NO_VALUE("");

	private String value;

	public static ItemPublish getDisplayStatus(String value) {
		return Arrays.stream(values())
				.filter(v -> v.getValue().equals(value))
				.findFirst()
				.orElse(NO_VALUE);
	}
}

NO_VALUE est défini [^ 4] dans le cas où une valeur en dehors de la plage que la valeur de division peut prendre est spécifiée.

TrueOrFalse.java


@Getter
@AllArgsConstructor
public enum TrueOrFalse {
  TRUE("1"), FALSE("0"), NONE("");

  private String value;

  TrueOrFalse(String value) {
    this.value = value;
  }

  public static TrueOrFalse getTrueOrFalse(String input) {
    return Arrays.stream(values())
        .filter(v -> v.getValue().equals(input))
          .findFirst().orElse(NONE);
  }
}

Une énumération qui définit une valeur booléenne (1 pour vrai, 0 pour faux). Les valeurs autres que 0 et 1 sont inattendues, mais les valeurs inattendues sont définies comme NONE.

Implémentation Spring et Mybatis

Une implémentation qui interroge la base de données. Référentiel et service Spring MVC. Les conditions de recherche sont définies dans la classe ItemCondition et un enregistrement des résultats de recherche est défini dans la classe Item.

ItemRepository.java


mport java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;
import serendip.spring.sample.mybatis.model.Item;
import serendip.spring.sample.mybatis.model.ItemCondition;

/**
 *Référentiel de recherche de produits.
 */
@Repository
@RequiredArgsConstructor
public class ItemRepository {
	private final SqlSession sqlSession;

	public List<Item> selectItems(ItemCondition condition) {
		return sqlSession.selectList("selectItems", condition);
	}
}

ItemService.java


import java.util.List;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import serendip.spring.sample.mybatis.model.Item;
import serendip.spring.sample.mybatis.model.ItemCondition;
import serendip.spring.sample.mybatis.repository.ItemRepository;

/**
 *Service de recherche de produits.
 */
@Service
@RequiredArgsConstructor
public class ItemService {
	private final ItemRepository repository;

	public List<Item> selectItems(ItemCondition condition) {
		return repository.selectItems(condition);
	}
}

Item Condition de la condition de recherche. L'énumération définie précédemment est spécifiée telle quelle en tant que condition.

ItemCondition.java


import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

/**
 *Conditions de recherche de produits.
 */
@Builder
@ToString
@Getter
public class ItemCondition {
	private int id;
	private ItemPublish status;
	private TrueOrFalse display;
}

Élément qui représente un enregistrement du résultat de la recherche. Les valeurs de statut et d'affichage des colonnes sont gérées par l'énumération définie.

Item.java


import lombok.Getter;
import lombok.Setter;

/**
 *Tableau des produits(Item)1 enregistrement de.
 */
@Getter @Setter
public class Item {
	private Integer id;
	private String name;
	private ItemPublish status;
	private TrueOrFalse display;
}

Définir le mappage SQL

Le SQL à exécuter cette fois est le suivant.

sql-mappings.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="serendip.spring.sample.mybatis.repository.MemberRepository">
	<select id="selectItems"
		parameterType="serendip.spring.sample.mybatis.model.ItemCondition"
		resultType="serendip.spring.sample.mybatis.model.Item">
		SELECT
			id,
			name,
			status,
			display
		FROM
			item
	</select>
</mapper>

La classe d'implémentation est maintenant prête. Cependant, lorsqu'il est exécuté tel quel, Mybatis se convertit automatiquement en enum, mais une exception d'exécution est levée car la méthode [^ 5] à exécuter par défaut ne peut pas être obtenue.

Par conséquent, Mybatis utilise TypeHandler qui effectue la conversion de type pour une énumération spécifique.

Créer TypeHandler pour enum

TypeHandler est ce que Mybatis effectue la conversion entre la classe Java et la base de données contenue dans la définition SQLMapping.

TypeHandler implémenté

Le TypeHandler implémenté est fourni dans le package org.apache.ibatis.type de Mybatis. Ici, nous définissons la conversion pour les types Java et le TypeHandler pour les types utilisés dans les colonnes de la base de données. Par exemple, la conversion de type Big Decimal de Java et la conversion de type BLOB de base de données sont également fournies, qui exécutent les fonctions trouvées dans l'instruction Pretared de JDBC.

Extension de TypeHandler

TypeHandler de Mybatis est fait par BaseTypeHandler <classe à convertir>. Implémentez quatre méthodes: setNonNullParameter, qui définit la valeur à transmettre à SQL, et getNullableResult (trois méthodes au total), qui reçoit la valeur.

ItemPublishTypeHandler.java


import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import serendip.spring.sample.mybatis.model.ItemPublish;

/**
 *Classe de conversion de type pour Enum du statut de publication du produit.
 */
public class ItemPublishTypeHandler extends BaseTypeHandler<ItemPublish> {

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, ItemPublish parameter, JdbcType jdbcType)
			throws SQLException {
		ps.setString(i, parameter.getValue());
	}

	@Override
	public ItemPublish getNullableResult(ResultSet rs, String columnName) throws SQLException {
		return ItemPublish.getDisplayStatus(rs.getString(columnName));
	}

	@Override
	public ItemPublish getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		return ItemPublish.getDisplayStatus(rs.getString(columnIndex));
	}

	@Override
	public ItemPublish getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		return ItemPublish.getDisplayStatus(cs.getString(columnIndex));
	}
}

TrueOrFalseTypeHandler.java


import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import serendip.spring.sample.mybatis.model.TrueOrFalse;

/**
 *Une classe de conversion de type pour Enum qui gère les indicateurs.
 */
public class TrueOrFalseTypeHandler extends BaseTypeHandler<TrueOrFalse> {

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, TrueOrFalse parameter, JdbcType jdbcType)
			throws SQLException {
		ps.setString(i, parameter.getValue());
	}

	@Override
	public TrueOrFalse getNullableResult(ResultSet rs, String columnName) throws SQLException {
		return TrueOrFalse.getTrueOrFalse(rs.getString(columnName));
	}

	@Override
	public TrueOrFalse getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		return TrueOrFalse.getTrueOrFalse(rs.getString(columnIndex));
	}

	@Override
	public TrueOrFalse getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		return TrueOrFalse.getTrueOrFalse(cs.getString(columnIndex));
	}
}

Enregistrement de TypeHandler

Décrivez le TypeHandler créé dans le fichier de configuration Mybatis. Décrivez le TypeHandler créé par l'élément «» un par un dans l'élément «».

mybatis-config.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<typeHandlers>
		<typeHandler handler="serendip.spring.sample.mybatis.typehandler.ItemPublishTypeHandler"/>
		<typeHandler handler="serendip.spring.sample.mybatis.typehandler.TrueOrFalseTypeHandler"/>
	</typeHandlers>
...
</configuration>

S'applique aux conditions de recherche

En plus des résultats de recherche de la base de données, TypeHandler peut être appliqué aux arguments (paramètres).

Description de la gestion de l'énumération dans le mappage SQL

La condition de recherche utilise TrueOrFalse de ItemCondition mentionné ci-dessus. Pour l'utilisation la plus concise, il existe un moyen d'obtenir la valeur avec getValue () d'énumération.

sql-mappings.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="serendip.spring.sample.mybatis.repository.MemberRepository">
	<select id="selectItems"
		parameterType="serendip.spring.sample.mybatis.model.ItemCondition"
		resultType="serendip.spring.sample.mybatis.model.Item">
		SELECT
			id,
			name,
			status,
			display
		FROM
			item
		<where>
			<if test="display.getValue() != ''">
			     display = #{display.value}
			</if>
		</where>
	</select>
</mapper>

Cependant, cette méthode semble un peu déraisonnable car le contenu décrit uniquement avec enum est comparé aux chaînes de caractères à l'intérieur du SQL dynamique. Cependant, Mybatis peut écrire des instructions conditionnelles SQL dynamiques dans OGNL.

Utilisons-le.

Comparez directement enum avec SQL Mapping

Pour faire référence à enum dans OGNL, utilisez @ enum's full class name @ enumerator comme suit.

sql-mappings.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="serendip.spring.sample.mybatis.repository.MemberRepository">
	<select id="selectItems"
		parameterType="serendip.spring.sample.mybatis.model.ItemCondition"
		resultType="serendip.spring.sample.mybatis.model.Item">
		SELECT
			id,
			name,
			status,
			display
		FROM
			item
		<where>
			<if test="display != @serendip.spring.sample.mybatis.model.TrueOrFalse@NONE">
			    display = #{display.value}
			</if>
		</where>
	</select>
</mapper>

Vous pouvez maintenant appliquer enum au branchement conditionnel de SQL dynamique.

Via RestController de Spring MVC

Exécutez le contenu introduit cette fois depuis RestController de Spring et créez-le pour qu'il renvoie une réponse au format JSON.

Implémentation du contrôleur

import java.util.List;
import java.util.Optional;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import serendip.spring.sample.mybatis.model.Item;
import serendip.spring.sample.mybatis.model.ItemCondition;
import serendip.spring.sample.mybatis.model.ItemPublish;
import serendip.spring.sample.mybatis.model.TrueOrFalse;
import serendip.spring.sample.mybatis.service.ItemService;

/**
 *Recherche de produits RestController.
 */
@RestController
@RequestMapping("/items")
@RequiredArgsConstructor
public class ItemController {

	private final ItemService service;

	@GetMapping("")
	public List<Item> searchItems(@RequestParam Optional<String> publish, @RequestParam Optional<String>  display) {
		ItemCondition condition = ItemCondition.builder()
				.status(ItemPublish.getDisplayStatus(publish.orElse("")))
				.display(TrueOrFalse.getTrueOrFalse(display.orElse("")))
				.build();

		return service.selectItems(condition);
	}
}

En supposant que la condition de recherche n'est pas définie, si le paramètre n'est pas défini, ce sera la valeur lorsqu'il n'est pas défini dans chaque valeur d'énumération.

Résultat d'exécution

http://127.0.0.1:8080/items

[
    {
        "id": 1,
        "name": "Chaise en bois",
        "status": "NONE",
        "display": "TRUE"
    },
    {
        "id": 2,
        "name": "Table en verre",
        "status": "PUBLISH",
        "display": "TRUE"
    },
    {
        "id": 3,
        "name": "Table en bois",
        "status": "MEMBER_ONLY",
        "display": "FALSE"
    }
]

http://127.0.0.1:8080/items?display=0

[
    {
        "id": 3,
        "name": "Table en bois",
        "status": "MEMBER_ONLY",
        "display": "FALSE"
    }
]

http://127.0.0.1:8080/items?display=1

[
    {
        "id": 1,
        "name": "Chaise en bois",
        "status": "NONE",
        "display": "TRUE"
    },
    {
        "id": 2,
        "name": "Table en verre",
        "status": "PUBLISH",
        "display": "TRUE"
    }
]

Pour cette valeur de paramètre, la valeur de la base de données est utilisée telle quelle comme valeur de condition, mais d'autres valeurs peuvent être utilisées dans le contrôleur. En outre, si vous souhaitez convertir davantage la valeur de réponse JSON, implémentez l'interface Json Serialize de jackson dans enum et elle sera convertie automatiquement.

en conclusion

Nous espérons que vous pourrez utiliser la conversion automatique de type et la conversion de valeur pour aider à créer une application plus robuste et maintenable (・ ω ・).


[^ 1]: Il existe également un "site de développement ou un dialecte de fournisseur" dans le contenu indiqué par le "drapeau" et la "valeur de classification". Dans cet article, «Indicateur» est une colonne qui n'a que deux valeurs, c'est-à-dire une colonne qui affiche uniquement vrai / faux de soi-disant booléen, et «Valeur de division» est une colonne qui a une plage fixe de valeurs possibles à des fins autres que booléenne. [^ 2]: Par exemple, il y a des cas où l'indicateur de suppression et l'indicateur de mise à jour, qui ne sont pas si souhaitables même à partir de 2018, existent, que le système soit ancien ou nouveau. 0: enregistrement valide, 1: supprimé, etc. [^ 3]: Le nom de la colonne est intentionnellement négligé, ce qui est difficile à deviner à partir du nom, qui a tendance à être un système hérité. Que sont le statut et l'affichage! ?? Qu'il sera? [^ 4]: Cette fois, je renvoie toujours une valeur, mais si une valeur impossible est spécifiée, cela peut lancer une exception d'application. [^ 5]: La méthode java.lang.Enum.valueOf est exécutée.

Recommended Posts

Gérer Enum avec Mybatis TypeHandler
Reportez-vous à enum dans Thymeleaf
Comment utiliser le type d'énumération Java (Enum) dans Mapper XML de MyBatis
Modèle de stratégie Enum en Java
Reproduire l'énumération Java en C #
Gérez vos propres annotations en Java
Manipulez bien le caractère C ** avec Swift