[JAVA] Remarques sur l'utilisation de Spring Data JDBC

Remarques sur l'utilisation de Spring Data JDBC

introduction

Aperçu

J'utilise MyBatis3 jusqu'à présent, mais j'avais du mal à gérer la version du code source généré automatiquement, donc je cherchais un autre mappeur O / R autour de Spring. J'ai donc découvert Spring Data JDBC dans ce document et noté la partie d'extension que j'utilise souvent.

environnement

Java 11 Gradle 5.4.1 Spring Boot 2.3.1.RELEASE Spring Data JDBC 2.0.1.RELEASE

Caractéristiques de Spring Data JDBC

Jusqu'à présent, Spring Data a publié des modules prenant en charge JPA, qui est l'API de persistance RDB la plus couramment utilisée pour les applications Java. Le nouveau Spring Data JDBC est publié comme un module plus simple et plus facile à comprendre que JPA. Le document officiel mentionne spécifiquement les points suivants.

  1. N'utilisez pas de chargement différé ou de mise en cache pour charger des entités. Il émet du SQL à chaque fois et tous les champs de l'entité sont lus.
  2. Spring Data Le côté JDBC ne gère pas le cycle de vie de l'instance d'entité. Si vous enregistrez l'entité, elle sera enregistrée dans la base de données et si vous n'enregistrez pas explicitement l'entité, les modifications ne seront pas reflétées dans la base de données. Même si l'entité est réécrite par un autre thread, elle n'est pas détectée.
  3. Le mappage de table d'entité utilise une méthode de mappage simple. Si vous ne suivez pas la méthode de mappage préparée à l'avance, vous devez coder le mappage vous-même.

Contrairement à Spring Data JPA, il n'y a pas autant de fonctions, donc la conception selon la méthode fournie par Spring Data JDBC semble être la clé pour utiliser Spring Data JDBC.

Méthode d'introduction

Création de projet

Sélectionnez ce qui suit dans Spring Initializr et téléchargez le projet.

article Les choix
Project Gradle Project
Language Java
Spring Boot 2.2.1
Dependencies Spring Data JDBC、Lombok

Il est possible de sélectionner un projet et une langue différents, mais certaines des sources présentées ici seront remplacées. La version de Spring Boot qui peut être sélectionnée varie en fonction de l'heure, mais ce n'est pas grave si vous sélectionnez la version par défaut.

build.gradle devrait ressembler à ceci:

plugins {
	id 'org.springframework.boot' version '2.2.2.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	runtimeOnly 'com.h2database:h2'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

Définir la source de données au printemps

Créez ʻapplication.yml sous src / main / resources` et définissez la source de données comme suit. Cette fois, j'ai configuré H2Database pour qu'il démarre en mode PostgreSQL.

application.yml


spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:;DB_CLOSE_ON_EXIT=TRUE;MODE=PostgreSQL
    username: sa
    password:

Créer schema.sql

Créez un fichier SQL schema.sql qui décrit le test DDL sous src / main / resources.

schema.sql


create table member
(
	id varchar not null
		constraint member_pk
			primary key auto_increment,
	name varchar not null
);

Créer des entités et des référentiels

N'oubliez pas d'annoter la propriété qui correspond à la clé primaire de la table avec @ Id. Comme l'ID de la classe Member lorsqu'elle n'est pas persistante est Null, ajoutez également l'annotation @ Wither.

Member.java


@RequiredArgsConstructor
@Getter
@EqualsAndHashCode(of = {"id"})
@ToString
public class Member {
	@Id
	@Wither
	private final String id;
	private final String name;
}

Créez un référentiel qui hérite de CrudRepository. Spécifiez l'argument de type dans l'ordre du type d'entité et du type d'ID.

MemberRepository.java


public interface MemberRepository extends CrudRepository<Member, String> {
}

Avec cela seul, la méthode suivante est définie dans MemberCredentialRepository.

C'est similaire à JPA. La méthode save exécutera l'instruction INSERT et l'instruction UPDATE, mais la logique qui détermine laquelle exécuter est la suivante.

  1. La colonne avec l'annotation «@ Id» est Null.
  2. Entity est la classe d'implémentation de l'interface «Persistable» et la méthode isNew () est true.

Cette fois, j'ai adopté le modèle 1.

Vérifiez le fonctionnement

Préparez une classe de test pour vérifier l'opération. Je me demande si je peux enregistrer les données pour le moment et vérifier si les données sont incluses.

MemberRepositoryTest.java


@DataJdbcTest
class MemberRepositoryTest {

	@Autowired
	private MemberRepository memberRepository;

	@Test
	void test() {
		String name = "Kuchita";
		Member save = memberRepository.save(new Member(null, name));
		assertNotNull(save.getId());
		Optional<Member> maybeMember = memberRepository.findById(save.getId());
		assertTrue(maybeMember.isPresent());
		maybeMember
			.ifPresent(member -> assertEquals(save.getName(), member.getName()));
	}
}

Conseils: si Test n'utilise pas de base de données intégrée

Accorder @ DataJdbcTest démarrera la base de données intégrée par défaut. Cette fois, j'ai utilisé h2, donc ça va, mais si vous voulez vous connecter à un serveur de base de données externe, ajoutez ce qui suit à ʻapplication.properties`.

application.properties


spring.test.database.replace=none

En ajoutant comme ça, vous pouvez utiliser votre serveur de base de données préféré au moment du test.

Utilisation détaillée

Dépôt

Utilisation de base

Spring Data JDBC réalise l'accès à la base de données en créant une interface qui hérite de l'interface Repository préparée à l'avance. Une interface pratique est fournie en standard qui fournit des méthodes de base en fonction de l'ID spécifié dans l'argument type et du type de l'entité.

Variantes de l'interface du référentiel

Les cours préparés à l'avance sont les suivants.

Nom de l'interface spécification
Repository<Entity, Id> Fournit une interface de référentiel vide la plus élémentaire.
CrudRepository<Entity, Id> Outre CRUDcountOuexistsByIdFournissez des méthodes telles que.
PagingAndSortingRepository<Entity, Id> En plus de ce qui précède, il fournit une méthode pour renvoyer le résultat de la pagination et du tri.

Je pense que ce n'est pas grave si vous l'utilisez correctement comme suit.

** Spring Data JDBC 1.1.1.RELEASE actuellement ** PagingAndSortRepository ne fonctionnait pas correctement. stack overflow -PagingAndSortingRepository methods throw error when used with spring data jdbc-

Référentiel de base personnalisé

Vous souhaiterez peut-être définir une interface commune à tous les projets, en plus de l'interface standard. Dans ce cas, étendons l'interface. À ce stade, ajoutez l'annotation @ NoRepositoryBean à la classe.

Vous trouverez ci-dessous l'interface de base du référentiel Crud, qui définit uniquement les méthodes qui chargent l'entité.

ReadOnlyRepository.java


@NoRepositoryBean
public interface ReadOnlyRepository extends Repository<Member, String> {
    Iterable<Member> findAll();
    Optional<Member> findById(String id);
}

Requête personnalisée

Pour les méthodes qui ne sont pas fournies en standard, ajoutez l'annotation @ Query à la méthode et décrivez et définissez SQL dans l'argument de l'annotation. Les paramètres que vous voulez passer à SQL peuvent être spécifiés avec : nom de l'argument.

MemberRepository.java



public interface MemberRepository extends CrudRepository<Member, String> {
    @Query("SELECT * FROM member WHERE name = :name")
    List<Member> getMembersByNameEquals(String name);
}

entité

Définition de base

Le modèle d'implémentation de base consiste à définir des objets immuables ou des JavaBeans. Je vais le décrire en supposant que l'identifiant est généré automatiquement.

Objet immuable

Une entité qui définit un champ immuable et un constructeur qui prend tous les champs comme arguments.

Pour définir plusieurs constructeurs avec des arguments, vous devez ajouter l'annotation @ PersistenceConstructor au constructeur utilisé pour l'instanciation dans Spring Data JDBC. Si vous ne souhaitez pas annoter Spring Data JDBC, vous pouvez définir une méthode de fabrique séparément.

Ajoutez l'annotation «@ Id» à la colonne qui devient l'identifiant. Étant donné que l'identifiant se verra attribuer l'identifiant émis dans la base de données après l'enregistrement de l'entité, définissez le flétrissement pour que l'identifiant puisse être mis à jour.

** * La référence a montré deux méthodes, soit pour utiliser le constructeur d'argument complet, soit pour utiliser Wither, mais comme l'ancienne méthode ne fonctionnait pas dans l'environnement actuel, je vais introduire la méthode utilisant Wither. Je suis. ** **

Member.java


@RequiredArgsConstructor
@Getter
@EqualsAndHashCode(of = {"id"})
@ToString
public class Member {
    @Id
    @With
    private final String id;
    private final String name;

    public static Member createInstance(String name) {
        return new Member(null, name);
    }
}
Modèle JavaBeans

Une entité qui définit les accesseurs pour les constructeurs et les champs par défaut.

Member.java


@Getter
@Setter
@EqualsAndHashCode(of = {"id"})
@ToString
public class Member {
    @Id
    private String id;
    private String name;

    public static Member createInstance(String name) {
        return new Member(null, name);
    }
}

Spécifications pour la création d'objets

  1. Si un constructeur sans argument est défini, créez une instance avec le constructeur sans argument
  2. Si un constructeur avec des arguments est défini autre que 1, créez une instance avec le constructeur avec des arguments.
  3. S'il y a plusieurs constructeurs avec des arguments, créez une instance avec le constructeur avec @ PersistenceConstructor.

Spécifications d'attribution aux champs

  1. Si un garrot est défini pour un champ immuable, utilisez le garrot pour définir la valeur du champ.
  2. Si vous avez un setter, utilisez le setter pour définir la valeur dans le champ
  3. Si le champ est modifiable, définissez la valeur directement dans le champ

Spécifications pour la gestion du cycle de vie des entités

Dans Spring Data JDBC, la méthode save est utilisée pour enregistrer et mettre à jour l'entité. Le fait que vous souhaitiez émettre une instruction INSERT ou une instruction UPDATE dépend du fait que l'entité est déjà persistante ou pas encore persistante. Il existe deux logiques de jugement principales dans Spring Data JDBC.

  1. Déterminez si l'identificateur d'entité est nul.
  2. Si l'entité implémente «Persistable # isNew», déterminez si l'entité est persistante ou non en fonction de la valeur de retour de la méthode.

Si l'ID n'est pas généré automatiquement côté base de données, il est préférable de définir l'entité selon la deuxième méthode.

À propos de la conversion de type de données

Types pris en charge par défaut

Spring Data JDBC a un support limité pour les relations hasOne et hasMany. Conservez une entité ou son ensemble dans un champ uniquement si la relation entre l'entité racine et l'entité au sein de son agrégat dans la conception pilotée par domaine est maintenue.

Si vous définissez aléatoirement la relation entre hasOne et hasMany, NULL et Empty ne seront pas définis par "si les données existent réellement ou non" mais par "si les données sont jointes ou non par SQL". Même si vous n'utilisez pas Spring Data JDBC, cela peut entraîner de graves bogues et réduire la productivité.

Personnaliser la conversion de type

La conversion de type peut être personnalisée à l'aide de Converter ou Converter Factory. Appliquez le Converter et le ConverterFactory créés avec une classe de configuration qui hérite de AbstractJdbcConfiguration. Vous pouvez appliquer votre propre classe de conversion en remplaçant la méthode jdbcCustomConversions.

JdbcConfiguration.java


@Configuration
public class JdbcConfiguration extends AbstractJdbcConfiguration {
	@Override
	public JdbcCustomConversions jdbcCustomConversions() {
		return new JdbcCustomConversions(List.of(
			// Converter/Enregistrer le bean de ConverterFactory
		));
	}
}
Lorsque la destination de la conversion est une classe spécifique

Implémentez Conveter si la destination est une classe unique spécifique, telle que la conversion Enum en String.

Affectez @ ReadingConverter ou @ WritingConveter au Conveter défini. S'il s'agit d'un convertisseur utilisé lors de la lecture de la base de données, ajoutez @ ReadingConverter, et s'il s'agit d'un convertisseur utilisé lors de l'écriture dans la base de données, ajoutez @ WritingConverter.

EnumToStringConverter.java


@WritingConverter
public enum EnumToStringConverter implements Converter<Enum, String> {
	INSTANCE;
	@Override
	public String convert(Enum e) {
		return e.name();
	}
}
Lorsque la destination de la conversion est une implémentation d'interface ou une sous-classe d'une classe spécifique

Si la destination de conversion est une implémentation d'interface ou une sous-classe d'une classe spécifique, telle que la conversion de String en Enum, implémentez ConverterFactory.

Puisque l'argument formel de ConverterFactory # getConverter est les informations de la classe de destination de conversion, il est avantageux que les informations de la classe de destination de conversion puissent être gérées lors du processus de création d'une instance de Converter.

Les annotations sont les mêmes que pour Converter.

StringToEnumFactory.java


@ReadingConverter
public enum StringToEnumFactory implements ConverterFactory<String, Enum> {
	INSTANCE;
	@Override
	public <T extends Enum> Converter<String, T> getConverter(Class<T> aClass) {
		return new StringToEnum<T>(aClass);
	}

	@RequiredArgsConstructor
	private static class StringToEnum<T extends Enum> implements Converter<String, T> {
		private final Class<T> enumType;

		@Override
		public T convert(String s) {
			return s == null ? null : Enum.valueOf(enumType, s);
		}
	}
}

Mapper plusieurs colonnes sur des objets imbriqués

Vous pouvez utiliser l'annotation «@ Embedded» pour mapper des objets de valeur et des colonnes définis par l'utilisateur.

L'entité Member avec l'objet de valeur ʻAddress` dans le champ peut être mappée à une colonne de table en la définissant comme suit:

Address.java


@RequiredArgsConstructor
@Getter
@EqualsAndHashCode
public class Address {
	private final String postcode;
	private final String prefecture;
	private final String addressLine;
}

Member.java


@Getter
@EqualsAndHashCode(of = {"id"})
@ToString
public class Member {
    @Id
    @With
    private final String id;
    private final String name;
    @Embedded(prefix = "address_", onEmpty = Embedded.OnEmpty.USE_NULL)
    private final Address address;

    public static Member createInstance(String name, Address address) {
        return new Member(null, name, address);
    }
}

Comme dans l'exemple, l'annotation «@ Embedded» doit avoir deux arguments, «prefix» et «onEmpty».

prefix

Chaque champ de l'objet de valeur est mappé à une colonne en utilisant "préfixe" et "nom de champ dans l'objet de valeur". Ici, le mappage champ-colonne de ʻAddress` est résolu comme suit.

Nom de domaine Nom de colonne
postcode address_postcode
prefecture address_prefecture
addressLine address_address_line

onEmpty

Spécifie la valeur à définir dans le champ de l'entité si le champ correspondant à l'objet de valeur est NULL.

Définir la valeur Contenu
USE_NULL Définir NULL
USE_EMPTY Définir un objet de valeur vide

Fondamentalement, je pense qu'il est sûr d'utiliser USE_NULL.

à la fin

C'est la fin de la première édition, et j'aimerais organiser les connaissances tout en les exploitant dans le futur.

URL de référence

Référence officielle JDBC Spring Data stack overflow -PagingAndSortingRepository methods throw error when used with spring data jdbc- Convertir Enum en numéro de non-commande avec Spring Data JDBC

Recommended Posts

Remarques sur l'utilisation de Spring Data JDBC
Comment utiliser Lombok au printemps
[Comment installer Spring Data Jpa]
Comment utiliser ModelMapper (Spring boot)
Comment utiliser Map
Comment utiliser rbenv
Comment utiliser with_option
Comment utiliser fields_for
Comment utiliser java.util.logging
Comment utiliser la carte
Comment utiliser collection_select
Comment utiliser Twitter4J
Comment utiliser active_hash! !!
Comment utiliser MapStruct
Comment utiliser TreeSet
Aperçu de Spring Data JDBC
[Comment utiliser l'étiquette]
Comment utiliser l'identité
Comment utiliser le hachage
Comment utiliser Dozer.mapper
Comment utiliser Gradle
Comment utiliser org.immutables
Comment utiliser java.util.stream.Collector
Comment utiliser VisualVM
Comment utiliser Map
Comment utiliser MyBatis2 (iBatis) avec Spring Boot 1.4 (Spring 4)
Comment utiliser h2db intégré avec Spring Boot
Comment utiliser les attributs de session Spring Boot (@SessionAttributes)
Comment utiliser l'API Chain
[Java] Comment utiliser Map
Comment utiliser Queue avec priorité
[Rails] Comment utiliser enum
Comment utiliser java Facultatif
Comment utiliser JUnit (débutant)
Comment utiliser le retour Ruby
[Rails] Comment utiliser enum
[spring] Utilisons Spring Data JPA
Comment utiliser @Builder (Lombok)
Comment utiliser la classe Java
Comment utiliser Big Decimal
[Java] Comment utiliser removeAll ()
Comment utiliser String [] args
Comment utiliser la jonction de rails
Comment utiliser Java Map
Ruby: Comment utiliser les cookies
Comment utiliser Dependant :: Destroy
Comment utiliser Eclipse Debug_Shell
Comment utiliser Apache POI
[Rails] Comment utiliser la validation
Utiliser Spring JDBC avec Spring Boot
Comment utiliser les variables Java
[Rails] Comment utiliser authenticate_user!