[JAVA] J'ai essayé Spring Data JDBC 1.0.0.BUILD-SNAPSHOT (-> 1.0.0.RELEASE)

La première entrée en 2018 est ... J'ai vu @ sndr "Spring Data JDBC Preview" et j'ai pensé "Hey" Spring Data JDBC C'est un mémo quand je l'ai essayé. Il semble qu'il ne dispose toujours que d'un support de niveau CRUD simple, mais Spring Data JDBC a été officiellement publié et Spring Data REST Nous sommes (très) impatients d'être soutenus sur: //projects.spring.io/spring-data-rest/)! ↓ ** Depuis sa sortie officielle le 21/09/2018, le contenu a été modifié en fonction de la version 1.0.0.RELEASE! ** **

Version de vérification

REMARQUE: Historique des mises à jour

2018-02-06 :

  • DATAJDBC-161 Prend en charge les modifications d'interface en raison de la prise en charge
  • Revalidé avec Spring Boot 2.0.0.RC1

2018-02-08 :

  • Ajout de l'utilisation de la méthode @ Query (correspond à DATAJDBC-172)

2018-03-08 :

  • Ajouté que le type simple est pris en charge comme valeur de retour de la méthode @ Query en raison du support de DATAJDBC-175.
  • Revalidé avec Spring Boot 2.0.0.RELEASE

2018-03-09 :

  • Ajout d'une description sur la gestion des objets liés
  • En ce qui concerne ce qui précède, a ajouté qu'il est nécessaire d'importer Lovelace-BUILD-SNAPSHOT du train spring-data-release lors de l'utilisation de Spring Data JDBC sur Spring Boot 2.0.0.RELEASE.

2018-03-10 :

  • Ajout de l'utilisation de la requête de mise à jour (@ Modifying) (correspondant à DATAJDBC-182)

2018-03-23 :

  • Correction de ne pas utiliser DefaultNamingStrategy en raison de la prise en charge de DATAJDBC-189 (l'implémentation par défaut est définie comme NamingStrategy.INSTANCE)
  • Mise à jour vers MyBatis Spring Boot Starter 1.3.2

2018-03-31 :

  • Correction de ne pas spécifier NamedParameterJdbcOperations lors de la génération de DefaultDataAccessStrategy en raison de la prise en charge de DATAJDBC-155
  • Ajout de NamingStrategy pour prendre en charge les cas de serpent (délimiteurs de soulignement) avec le support de DATAJDBC-184.

2018-04-03 :

  • Avec le support de DATAJDBC-178, en définissant une instance arbitraire NamespaceStrategy dans MyBatisDataAccessStrategy, la convention de dénomination de l'espace de noms peut être modifiée. Ajouté qu'il peut être changé

2018-05-18 :

  • Ajout de la description de la prise en charge de la fonction d'audit basée sur les annotations (correspondant à DATAJDBC-204)
  • Mettre à jour vers Spring Boot 2.0.2.RELEASE et revalider

2018-05-19 :

  • Modifications de la configuration du package reflétées (correspondant à DATAJDBC-138)

2018-06-28 :

  • Reflète la configuration du package et les changements de nom de classe (correspond à DATAJDBC-226)
  • Ajouté que l'implémentation par défaut de NamingStrategy a été changée en Snake Case en réponse à DATAJDBC-207.
  • Mis à jour vers Spring Boot 2.0.3.RELEASE et revalidé

2018-07-03 :

  • Suppression de l'importation explicite de spring-data-releasetrain Lovelace-BUILD-SNAPSHOT et ajout de <spring-data-releasetrain.version> Lovelace-BUILD-SNAPSHOT </ spring-data-releasetrain.version> aux propriétés. Fixé à ajouter

2018-07-20 :

  • Reflète le changement de méthode d'application du convertisseur personnalisé (correspondant à DATAJDBC-235)
  • Correction d'un problème où MyBatis donne une erreur lors de deleteAll. Il semble que l'ID SQL qui supprime l'entité a changé! ??

2018-07-28 :

  • Reflète le changement dans la méthode d'application de JdbcConfiguration (correspondant à DATAJDBC-243)

2018-09-22 :

  • Correction de la version de vérification JDBC Spring Data à 1.0.0.RELEASE
  • Correction de la version de vérification de Spring Boot à 2.0.5.
  • Décrit spring-boot-starter-data-jdbc

2018-09-23 :

  • Correction de l'explication sur la façon de spécifier / étendre JdbcConfiguration (correction due à l'influence de DATA JDBC-267)

Projet de démonstration

Les sources décrites dans cette entrée sont publiées dans les référentiels suivants. (Puisque Spring JDBC et MyBatis sont mélangés et vérifiés, il existe quelques différences par rapport à la description de l'entrée.)

Créer un projet de développement

Tout d'abord, dans SPRING INITIALIZR, sélectionnez «H2», «JDBC» et «MyBatis (uniquement lors de l'utilisation de MyBatis)» comme dépendances pour créer un projet. (Cette entrée est basée sur le projet Maven) Ensuite, ajoutez "spring-data-jdbc" au projet créé par SPRING INITIALIZR. Lors de l'utilisation de Spring Data JDBC dans la série Spring Boot 2.0, il est nécessaire de spécifier la version Lovelace-RELEASE de spring-data-releasetrain dans la propriété fournie par Spring Boot comme suit.

pom.xml


<properties>
	<spring-data-releasetrain.version>Lovelace-RELEASE</spring-data-releasetrain.version>
</properties>

pom.xml


<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-jdbc</artifactId>
</dependency>

Configuration de la base de données

Préparez un DDL pour créer une table.

src/main/resources/schema.sql


CREATE TABLE IF NOT EXISTS todo (
	id IDENTITY
	,title TEXT NOT NULL
	,details TEXT
	,finished BOOLEAN NOT NULL
);

Créer un objet de domaine

Créez un objet Todo qui représente l'enregistrement dans la table TODO. Ajoutez «@ Id» à la propriété qui contient la valeur de clé.

src/main/java/com/example/demo/domain/Todo.java


package com.example.demo.domain;

import org.springframework.data.annotation.Id;

public class Todo {
	@Id
	private int id;
	private String title;
	private String details;
	private boolean finished;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getDetails() {
		return details;
	}

	public void setDetails(String details) {
		this.details = details;
	}

	public boolean isFinished() {
		return finished;
	}

	public void setFinished(boolean finished) {
		this.finished = finished;
	}

}

Créer un référentiel

Créez une interface de référentiel pour manipuler les objets du domaine. Le but est d'hériter du Croud Repository fourni par Spring Data.

src/main/java/com/example/demo/repository/TodoRepository.java


package com.example.demo.repository;

import org.springframework.data.repository.CrudRepository;

import com.example.demo.domain.Todo;

public interface TodoRepository extends CrudRepository<Todo, Integer> {

}

En faisant cela ... Vous pouvez utiliser l'objet Todo en utilisant les méthodes suivantes définies dans CrudRepository.

Référence: Extrait du référentiel Crud


package org.springframework.data.repository;

import java.util.Optional;

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
	<S extends T> S save(S entity);
	<S extends T> Iterable<S> saveAll(Iterable<S> entities);
	Optional<T> findById(ID id);
	boolean existsById(ID id);
	Iterable<T> findAll();
	Iterable<T> findAllById(Iterable<ID> ids);
	long count();
	void deleteById(ID id);
	void delete(T entity);
	void deleteAll(Iterable<? extends T> entities);
	void deleteAll();
}

Comment exécuter SQL

Spring Data JDBC fournit DataAccessStrategy comme interface pour résumer la méthode d'exécution de SQL, et pour le moment," Spring JDBC (NamedParameterJdbcOperations) implémentation" et "MyBatis implementation" sont intégrés.

Utilisation de l'implémentation Spring JDBC

Si vous utilisez l'implémentation Spring JDBC, le SQL à exécuter lorsque vous appelez la méthode définie dans CrudRepository est généré automatiquement (= vous n'avez pas besoin d'écrire du SQL pour les opérations CRUD).

Exemple de définition de bean

Créez une classe de configuration avec @ EnableJdbcRepositories et @Import (JdbcConfiguration.class). Cependant ... Si vous voulez changer le bean défini dans JdbcConfiguration, vous devez créer une classe de configuration qui hérite de JdbcConfiguration et l'enregistrer comme conteneur DI. Par exemple, si vous avez besoin de conversions de type qui ne sont pas prises en charge par défaut, vous pouvez remplacer la méthode jdbcCustomConversions et renvoyer JdbcCustomConversions avec n'importe quel Converter spécifié. Dans cette entrée, Converter est ajouté pour convertir le type TEXT ( Clob) de la base de données H2 en String. Au fait ... Si vous utilisez VARCHAR au lieu de TEXT, vous n'avez pas besoin de remplacer la méthode jdbcCustomConversions.

@EnableJdbcRepositories
@Configuration
public class SpringDataJdbcConfig extends JdbcConfiguration {

	@Override
	protected JdbcCustomConversions jdbcCustomConversions() { 
		return new JdbcCustomConversions(Collections.singletonList(new Converter<Clob, String>() {
			@Override
			public String convert(Clob clob) {
				try {
					return clob == null ? null : clob.getSubString(1L, (int) clob.length());
				} catch (SQLException e) {
					throw new IllegalStateException(e);
				}
			}
		}));
	}

}

Note:

NamingStrategy est fourni en tant qu'interface pour déterminer la stratégie de dénomination des noms de colonne et des noms de propriété. Par défaut, NamingStrategy.INSTANCE est utilisé, mais vous pouvez changer le comportement par défaut en définissant un bean pour NamingStrategy. ~~ De plus, NamingStrategy qui prend en charge les cas de serpent (délimiteurs de soulignement) a été ajouté en prenant en charge DATAJDBC-184 (lors de son utilisation) Définition du haricot requise). ~~ Le comportement par défaut est traité comme un cas de serpent (délimiteur de soulignement) (le comportement par défaut a été modifié pour prendre en charge DATAJDBC-206).

Utilisation de l'implémentation MyBatis

Lors de l'utilisation de l'implémentation MyBatis, il est nécessaire de définir le SQL à exécuter lorsque la méthode définie dans CrudRepository est appelée côté MyBatis. (= Il est également nécessaire d'écrire du SQL pour les opérations CRUD).

Exemple de définition de bean

Créez une classe de configuration avec @ EnableJdbcRepositories et @Import (JdbcConfiguration.class), et définissez un Bean de MyBatisDataAccessStrategy (implémentation MyBatis) comme DataAccessStrategy.

@EnableJdbcRepositories
@Import(JdbcConfiguration.class)
@Configuration
public class SpringDataJdbcConfig {
	@Bean
	DataAccessStrategy dataAccessStrategy(SqlSession sqlSession) {
		return new MyBatisDataAccessStrategy(sqlSession);
	}
}

NOTE:

06/02/2018: Avec le support de DATAJDBC-161, l'objet à passer à l'argument constructeur de MyBatisDataAccessStrategy est passé de SqlSessionFactory à SqlSession. (En fait changé en SqlSessionTemplate).

Exemple de paramétrage MyBatis

Définissez l'emplacement et l'alias de type du fichier XML Mapper.

src/main/resources/application.properties


mybatis.mapper-locations=classpath:/com/example/demo/mapper/*Mapper.xml
mybatis.type-aliases-package=com.example.demo.domain

Exemple de définition SQL

Définissez le SQL correspondant à la méthode de CrudRepository. Lors de l'utilisation de MyBatis via Spring Data JDBC, il est nécessaire de connaître certaines règles spéciales lors de la définition de SQL.

NOTE:

2018-04-03: Avec le support de DATAJDBC-178, en définissant une instance arbitraire NamespaceStrategy dans MyBatisDataAccessStrategy, le nom Vous pouvez modifier la convention de dénomination des espaces.

src/main/resources/com/example/demo/mapper/TodoMapper.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="com.example.demo.domain.TodoMapper">

	<!-- statements for CrudRepository method -->
	<insert id="insert" useGeneratedKeys="true" keyProperty="instance.id">
		INSERT INTO todo 
			(title, details, finished)
		VALUES 
			(#{instance.title}, #{instance.details}, #{instance.finished})
	</insert>
	<update id="update">
		UPDATE todo SET
			title = #{instance.title}, details = #{instance.details}, finished = #{instance.finished}
		WHERE
			id = #{instance.id}
	</update>
	<delete id="delete">
		DELETE FROM todo WHERE id = #{id}
	</delete>
	<delete id="deleteAll">
		DELETE FROM todo
	</delete>
	<select id="existsById" resultType="_boolean">
		SELECT count(*) FROM todo WHERE id = #{id}
	</select>
	<select id="findById" resultType="Todo">
		SELECT
			id, title, details, finished 
		FROM
			todo
		WHERE
			id = #{id}
	</select>
	<select id="findAll" resultType="Todo">
		SELECT
			id, title, details, finished
		FROM
			todo
		ORDER BY
			id
	</select>
	<select id="findAllById" resultType="Todo">
		SELECT
			id, title, details, finished
		FROM
			todo
		<where>
			<foreach collection="id" item="idValue" open="id in("
				separator="," close=")">
				#{idValue}
			</foreach>
		</where>
		ORDER BY
			id
	</select>
	<select id="count" resultType="_long">
		SELECT count(*) FROM todo
	</select>

</mapper>

Référence: Extrait de MyBatisContext


package org.springframework.data.jdbc.mybatis;

import java.util.Map;

public class MyBatisContext {

	private final Object id;
	private final Object instance;
	private final Class domainType;
	private final Map<String, Object> additonalValues;

	public MyBatisContext(Object id, Object instance, Class domainType, Map<String, Object> additonalValues) {
		this.id = id;
		this.instance = instance;
		this.domainType = domainType;
		this.additonalValues = additonalValues;
	}

	public Object getId() {
		return id;
	}

	public Object getInstance() {
		return instance;
	}

	public Class getDomainType() {
		return domainType;
	}

	public Object get(String key) {
		return additonalValues.get(key);
	}
}

Mise en œuvre combinée

Je ne vais pas l'expliquer dans cette entrée + Je ne l'ai pas vérifié, mais il semble qu'il soit possible d'utiliser plusieurs implémentations (par exemple Spring JDBC et MyBatis) ensemble en utilisant CascadingDataAccessStrategy.

Utilisation du référentiel

Le référentiel Spring Data JDBC est injecté et utilisé comme tout autre projet Spring Data.

@Autowired
private TodoRepository todoRepository;

@Test
public void insertAndFineById() {
	Todo newTodo = new Todo();
	newTodo.setTitle("soirée à boire");
	newTodo.setDetails("Ginza 19:00");
	todoRepository.save(newTodo);

	Optional<Todo> todo = todoRepository.findById(newTodo.getId());
	Assertions.assertThat(todo.isPresent()).isTrue();
	Assertions.assertThat(todo.get().getId()).isEqualTo(newTodo.getId());
	Assertions.assertThat(todo.get().getTitle()).isEqualTo(newTodo.getTitle());
	Assertions.assertThat(todo.get().getDetails()).isEqualTo(newTodo.getDetails());
	Assertions.assertThat(todo.get().isFinished()).isFalse();
}

Ajout de la méthode @ Query

Toute requête peut être exécutée (en utilisant la fonction Spring JDBC) en ajoutant une méthode avec @ Query au référentiel.

~~WARNING:~~ ~~ Avec l'implémentation actuelle, il n'est pas possible d'exécuter la mise à jour SQL (il semble qu'il y ait des plans pour la supporter). ~~

NOTE: 10/03/2018: DATAJDBC-182 prend également en charge l'exécution de la mise à jour SQL.

src/main/java/com/example/demo/repository/TodoRepository.java


package com.example.demo.repository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import com.example.demo.domain.Todo;

public interface TodoRepository extends CrudRepository<Todo, Integer> {

	@Query("SELECT * FROM todo WHERE id = :id")
	Optional<Todo> findOptionalById(@Param("id") Integer id);

	@Query("SELECT * FROM todo WHERE id = :id")
	Todo findEntityById(@Param("id") Integer id);

	@Query("SELECT * FROM todo ORDER BY id")
	Stream<Todo> findAllStream();

	@Query("SELECT * FROM todo ORDER BY id")
	List<Todo> findAllList();

	@Query("SELECT count(*) FROM todo WHERE finished = :finished")
	long countByFinished(@Param("finished") Boolean finished);

	@Query("SELECT count(*) FROM todo WHERE finished = :finished")
	boolean existsByFinished(@Param("finished") Boolean finished);

	@Query("SELECT current_timestamp()")
	LocalDateTime currentDateTime();

	@Modifying
	@Query("UPDATE todo SET finished = :finished WHERE id = :id")
	boolean updateFinishedById(@Param("id") Integer id, @Param("finished") boolean finished);

}

NOTE:

@ Param peut être omis en spécifiant l'option -parameters du compilateur Java.

Actuellement, les types de retour pris en charge sont

Et ʻorg.springframework.data.domain.Page `et ʻorg.springframework.data.domain.Slice ʻ ne sont pas pris en charge (si vous voulez traiter ces types comme des valeurs de retour, "Ajouter une opération personnalisée" décrite plus loin est nécessaire).

Les valeurs de retour prises en charge pour les méthodes de mise à jour sont

Est.

~~WARNING:~~ ~~ Dans l'implémentation actuelle, il n'est pas possible de spécifier un type autre que la classe de domaine comme une valeur numérique (ʻint, long, etc.) ou une valeur booléenne comme valeur de retour (c'est-à-dire, la méthode ... @ Query derecording. Vous ne pouvez pas spécifier un SQL pour obtenir le nombre ou un SQL pour vérifier l'existence d'enregistrements). Il peut être géré par la méthode introduite dans "Ajout d'opérations personnalisées" ci-dessous, mais ... J'ai l'impression que je manque de considération, alors je vais lui donner un problème. ⇒ [DATAJDBC-175](https://jira.spring.io/browse/DATAJDBC-175) ~~ ↓ 2018-03-08: Il est désormais possible de renvoyer des types autres que des classes de domaine (dits types simples) tels que des nombres (ʻint, long, etc.) et des valeurs booléennes comme valeurs de retour! !! En interne ... SingleColumnRowMapper + La résolution du type de données Spring est effectuée en coopération avec ConversionService appliqué à JDBC. À propos, Spring Framework 5.0.4.RELEASE ou supérieur est requis pour cette prise en charge.

Ajouter des opérations personnalisées

"Mécanisme d'ajout d'opérations personnalisées (méthodes personnalisées)" dans Spring Data Ce mécanisme peut également être utilisé dans Spring Data JDBC.

Créer une interface personnalisée

Définissez une interface pour définir des opérations personnalisées (méthodes personnalisées).

src/main/java/com/example/demo/repository/CustomizedTodoRepository.java


package com.example.demo.repository;

import com.example.demo.domain.Todo;

public interface CustomizedTodoRepository {

	Iterable<Todo> findAllByFinished(boolean finished);

}

Héritez de l'interface créée avec Todo Repository.

src/main/java/com/example/demo/repository/TodoRepository.java


package com.example.demo.repository;

import org.springframework.data.repository.CrudRepository;

import com.example.demo.domain.Todo;

public interface TodoRepository extends CrudRepository<Todo, Integer>, CustomizedTodoRepository {

}

Création d'une implémentation Spring JDBC

Lorsque vous utilisez Spring JDBC, créez la classe d'implémentation suivante.

src/main/java/com/example/demo/repository/CustomizedTodoRepositoryImpl.java


package com.example.demo.repository;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;

import com.example.demo.domain.Todo;

public class CustomizedTodoRepositoryImpl implements CustomizedTodoRepository {

	private static final RowMapper<Todo> ROW_MAPPER = new BeanPropertyRowMapper<>(Todo.class);

	private final NamedParameterJdbcOperations namedParameterJdbcOperations;

	public CustomizedTodoRepositorySpringJdbcImpl(NamedParameterJdbcOperations namedParameterJdbcOperations) {
		this.namedParameterJdbcOperations = namedParameterJdbcOperations;
	}

	public Iterable<Todo> findAllByFinished(boolean finished) {
		return this.namedParameterJdbcOperations.query(
				"SELECT id, title, details, finished FROM todo WHERE finished = :finished ORDER BY id",
				new MapSqlParameterSource("finished", finished), ROW_MAPPER);
	}

}

Créer une implémentation MyBatis

Lorsque vous utilisez MyBatis, créez la classe d'implémentation suivante.

src/main/java/com/example/demo/repository/CustomizedTodoRepositoryImpl.java


package com.example.demo.repository;

import org.apache.ibatis.session.SqlSession;

import com.example.demo.domain.Todo;

public class CustomizedTodoRepositoryImpl implements CustomizedTodoRepository {

	private final String NAMESPACE = Todo.class.getName() + "Mapper";

	private final SqlSession sqlSession;

	public CustomizedTodoRepositoryMyBatisImpl(SqlSession sqlSession) {
		this.sqlSession = sqlSession;
	}

	public Iterable<Todo> findAllByFinished(boolean finished) {
		return this.sqlSession.selectList(NAMESPACE + ".findAllByFinished", finished);
	}

}

Ajoutez également la définition SQL.

src/main/resources/com/example/demo/mapper/TodoMapper.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="com.example.demo.domain.TodoMapper">

	<!-- ... -->

	<!-- statements for custom repository method -->
	<select id="findAllByFinished" resultType="Todo">
		SELECT
			id, title, details, finished
		FROM
			todo
		WHERE
			finished = #{finished}
		ORDER BY
			id
	</select>

</mapper>

Opération de persistance de l'objet associé (1: 1 ou 1: N)

Spring Data JDBC prend en charge les opérations de persistance sur les objets associés qui ont une relation 1: 1 ou 1: N. Cependant, l'état du support diffère entre l'implémentation Spring JDBC et l'implémentation MyBatis. Si vous jetez un coup d'œil rapide ... Le statut de prise en charge des opérations de mise à jour est le même. Cependant, pour le fonctionnement du système de référence lors de l'utilisation de MyBatis, l'implémentation côté MyBatis (jointure de table + 1: 1/1: N mapping utilisant l'association et la collection) est requise.

Configuration de la base de données

Créez une table pour conserver les objets associés.

src/main/resources/schema.sql


CREATE TABLE IF NOT EXISTS activity (
	id IDENTITY
	,todo INTEGER NOT NULL --Colonne qui stocke l'ID de l'objet de domaine
	,todo_key INTEGER NOT NULL --Colonne qui stocke la clé d'identification (et la clé de tri) des objets associés dans l'objet de domaine
	,content TEXT NOT NULL
	,at TIMESTAMP NOT NULL
);

Dans l'implémentation par défaut de Spring Data JDBC, le nom de colonne de "colonne qui stocke l'ID de l'objet de domaine" est "nom de classe de l'objet de domaine", et le nom de colonne de "colonne qui stocke la clé d'identification de l'objet associé dans l'objet de domaine" est Il devient "colonne qui stocke l'ID de l'objet domaine +" _key "".

Création d'objets associés

Créez un objet de domaine qui représente l'activité de TODO et associez-le à l'objet Todo.

src/main/java/com/example/demo/domain/Activity.java


package com.example.demo.domain;

import org.springframework.data.annotation.Id;

import java.time.LocalDateTime;

public class Activity {
	@Id
	private int id;
	private String content;
	private LocalDateTime at;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public LocalDateTime getAt() {
		return at;
	}

	public void setAt(LocalDateTime at) {
		this.at = at;
	}
}

src/main/java/com/example/demo/domain/Todo.java


public class Todo {
	// ...
	private List<Activity> activities;
	// ...
	public List<Activity> getActivities() {
		return activities;
	}
	public void setActivities(List<Activity> activities) {
		this.activities = activities;
	}
}

Exemple d'exécution d'opération CRUD

Ici, l'objet de domaine qui contient l'objet associé avec une relation 1: N est exploité en utilisant la méthode définie dans CrudRepository.

Exemple d'exécution d'opération CRUD


@Test
public void oneToMany() {

	// Insert
	Todo newTodo = new Todo();
	newTodo.setTitle("soirée à boire");
	newTodo.setDetails("Ginza 19:00");
	Activity activity1 = new Activity();
	activity1.setContent("Created");
	activity1.setAt(LocalDateTime.now());

	Activity activity2 = new Activity();
	activity2.setContent("Started");
	activity2.setAt(LocalDateTime.now());

	newTodo.setActivities(Arrays.asList(activity1, activity2));

	todoRepository.save(newTodo);

	// Assert for inserting
	Optional<Todo> loadedTodo = todoRepository.findById(newTodo.getId());
	Assertions.assertThat(loadedTodo.isPresent()).isTrue();
	loadedTodo.ifPresent(todo -> {
		Assertions.assertThat(todo.getId()).isEqualTo(newTodo.getId());
		Assertions.assertThat(todo.getTitle()).isEqualTo(newTodo.getTitle());
		Assertions.assertThat(todo.getDetails()).isEqualTo(newTodo.getDetails());
		Assertions.assertThat(todo.isFinished()).isFalse();
		Assertions.assertThat(todo.getActivities()).hasSize(2);
		Assertions.assertThat(todo.getActivities().get(0).getContent()).isEqualTo(activity1.getContent());
		Assertions.assertThat(todo.getActivities().get(1).getContent()).isEqualTo(activity2.getContent());

	});

	// Update
	Activity activity3 = new Activity();
	activity3.setContent("Changed Title");
	activity3.setAt(LocalDateTime.now());
	loadedTodo.ifPresent(todo -> {
		todo.setTitle("[Change] " + todo.getTitle());
		todo.getActivities().add(activity3);
	});
	todoRepository.save(loadedTodo.get());

	// Assert for updating
	loadedTodo = todoRepository.findById(newTodo.getId());
	Assertions.assertThat(loadedTodo.isPresent()).isTrue();
	loadedTodo.ifPresent(todo -> {
		Assertions.assertThat(todo.getTitle()).isEqualTo("[Change] " + newTodo.getTitle());
		Assertions.assertThat(todo.getActivities()).hasSize(3);
		Assertions.assertThat(todo.getActivities().get(0).getContent()).isEqualTo(activity1.getContent());
		Assertions.assertThat(todo.getActivities().get(1).getContent()).isEqualTo(activity2.getContent());
		Assertions.assertThat(todo.getActivities().get(2).getContent()).isEqualTo(activity3.getContent());
	});

	// Delete
	todoRepository.deleteById(newTodo.getId());

	// Assert for deleting
	Assertions.assertThat(todoRepository.findById(newTodo.getId())).isNotPresent();

}

Utilisation de l'implémentation Spring JDBC

Si vous utilisez l'implémentation Spring JDBC, vous n'avez rien à faire. Si vous appelez simplement la méthode CrudRepository, Spring Data JDBC générera et exécutera le SQL.

Utilisation de l'implémentation MyBatis

Si vous utilisez l'implémentation MyBatis, vous devez définir SQL dans le fichier XML Mapper.

Premièrement ... Définissez le SQL pour l'insertion d'objets associés

src/main/resources/com/example/demo/mapper/ActivityMapper.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="com.example.demo.domain.ActivityMapper">
	<insert id="insert" useGeneratedKeys="true" keyProperty="instance.id">
		INSERT INTO activity
			(todo, todo_key, content, at)
		VALUES
			(#{additonalValues.Todo}, #{additonalValues.Todo_key}, #{instance.content}, #{instance.at})
	</insert>
</mapper>

Le point ici est que "l'ID de l'objet de domaine" et la "clé d'identification (et clé de tri) de l'objet associé dans l'objet de domaine" sont stockés dans la propriété ʻadditonalValuesde typeMap`. , Le nom de clé où la valeur est stockée est la même règle que le nom de colonne.

Suivant ... Définissez SQL pour les objets liés à DELETE. Ce SQL est également appelé lors de la mise à jour d'un objet de domaine. En d'autres termes ... Tous les objets associés avec une relation 1: N sont SUPPRIMÉS puis à nouveau INSÉRÉS.

src/main/resources/com/example/demo/mapper/TodoMapper.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="com.example.demo.domain.TodoMapper">
	<!-- ... -->
	<delete id="delete-activities">
		DELETE FROM activity WHERE todo = #{id}
	</delete>
	<delete id="deleteAll-activities">
		DELETE FROM activity WHERE todo = #{id}
	</delete>
	<!-- ... -->
</mapper>

Enfin ... Modifiez pour obtenir des objets liés dans le SQL qui font référence à l'objet de domaine. Plus précisément ... Rejoignez les tables contenant les informations des objets associés et mappez les objets associés aux objets du domaine à l'aide de la fonction ResultMap de MyBatis.

src/main/resources/com/example/demo/mapper/TodoMapper.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="com.example.demo.domain.ActivityMapper">
	<!-- ... -->
	<select id="findById" resultMap="todoMap">
		SELECT
			t.id, t.title, t.details, t.finished
			, a.id as activity_id, a.content as activity_content, a.at as activity_at
		FROM
			todo t
		LEFT OUTER JOIN activity a ON a.todo = t.id
		WHERE
			t.id = #{id}
		ORDER BY
			a.todo_key
	</select>
	<resultMap id="todoMap" type="Todo">
		<id column="id" property="id"/>
		<result column="title" property="title"/>
		<result column="details" property="details"/>
		<result column="finished" property="finished"/>
		<collection property="activities" columnPrefix="activity_" ofType="Activity">
			<id column="id" property="id"/>
			<result column="content" property="content"/>
			<result column="at" property="at"/>
		</collection>
	</resultMap>
	<!-- ... -->
</mapper>

Renommer la colonne pour la colonne d'association

Si vous n'aimez pas les noms de colonnes générés par l'implémentation par défaut de Spring Data JDBC ... Vous pouvez le changer en définissant la classe d'implémentation de NamingStrategy comme un bean. Ici, nous allons présenter comment changer Todo en todo_id et Todo_key en le nom de la colonne sort_order.

Premièrement ... Modifiez le nom de la colonne de la table.

src/main/resources/schema.sql


CREATE TABLE IF NOT EXISTS activity (
	id IDENTITY
	,todo_id INTEGER NOT NULL --Changer le nom de la colonne
	,sort_order INTEGER NOT NULL --Changer le nom de la colonne
	,content TEXT NOT NULL
	,at TIMESTAMP NOT NULL
);

Next ... Bean définition de la classe d'implémentation de NamingStrategy.

@Bean
NamingStrategy namingStrategy() {
	return new NamingStrategy(){
		@Override
		public String getReverseColumnName(RelationalPersistentProperty property) {
			return NamingStrategy.super.getReverseColumnName(property).toLowerCase() + "_id";
		}
		@Override
		public String getKeyColumn(RelationalPersistentProperty property) {
			return "sort_order";
		}
	};
}

Si vous utilisez l'implémentation MyBatis, vous devrez également modifier le SQL.

src/main/resources/com/example/demo/mapper/ActivityMapper.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="com.example.demo.domain.ActivityMapper">
	<insert id="insert" useGeneratedKeys="true" keyProperty="instance.id">
		INSERT INTO activity
			(todo_id, sort_order, content, at)
		VALUES
			(#{additonalValues.todo_id}, #{additonalValues.sort_order}, #{instance.content}, #{instance.at})
	</insert>
</mapper>

src/main/resources/com/example/demo/mapper/TodoMapper.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="com.example.demo.domain.ActivityMapper">
	<!-- ... -->
	<delete id="delete-activities">
		DELETE FROM activity WHERE todo_id = #{id}
	</delete>
	<delete id="deleteAll-activities">
		DELETE FROM activity WHERE todo_id = #{id}
	</delete>
	<!-- ... -->
	<select id="findById" resultMap="todoMap">
		SELECT
			t.id, t.title, t.details, t.finished
			, a.id as activity_id, a.content as activity_content, a.at as activity_at
		FROM
			todo t
		LEFT OUTER JOIN activity a ON a.todo_id = t.id
		WHERE
			t.id = #{id}
		ORDER BY
			a.sort_order
	</select>
	<!-- ... -->
</mapper>

Définition des valeurs pour les colonnes d'audit (fonction d'audit)

Dans Spring Data, "Mécanisme de définition des valeurs dans la colonne (colonne d'audit) qui contient quand, qui et quand et les données mises à jour (dernière mise à jour) -data / commons / docs / 2.1.0.RELEASE / reference / html / # audit), et cette fonction peut également être utilisée avec Spring Data JDBC.

IMPORTANT:

Au moment de Spring Data JDBC 1.0.0.RELEASE (Lovelace), il y a une partie que la fonction d'audit pour les objets imbriqués (1: 1, 1: N) ne peut pas être prise en charge. Cela semble être un problème du côté de Spring Data Commons, pas du côté de Spring Data JDBC.

  • [DATACMNS-1297] (https://jira.spring.io/projects/DATACMNS/issues/DATACMNS-1297): Une erreur se produit toujours lorsque la propriété d'audit existe dans la classe du côté N qui a une relation 1: N (données) Entraînera une erreur même s'il y a 0 cas)
  • [DATACMNS-1296] (https://jira.spring.io/projects/DATACMNS/issues/DATACMNS-1296) : Une erreur se produit si l'objet lui-même est «nul» lorsque la propriété d'audit existe dans la classe imbriquée.

Activer la fonction d'audit

Pour utiliser la fonction d'audit, ajoutez @ org.springframework.data.jdbc.repository.config.EnableJdbcAuditing à la classe de configuration.

@EnableJdbcAuditing //ajouter à
@Import(JdbcConfiguration.class)
@EnableJdbcRepositories
@Configuration
public class SpringDataJdbcConfig {
    // ...
}

Une classe qui implémente l'interface ʻorg.springframework.data.domain.AuditorAware` pour enregistrer qui a créé et mis à jour

package com.example.demo;

import org.springframework.data.domain.AuditorAware;

import java.util.Optional;

public class MyAuditorAware implements AuditorAware<String> {

	static ThreadLocal<String> currentUser = ThreadLocal.withInitial(() -> "default");

	public Optional<String> getCurrentAuditor() {
		return Optional.ofNullable(currentUser.get());
	}

}

NOTE:

Ici, l'implémentation est telle que le nom d'utilisateur défini dans la variable thread-local est simplement renvoyé en tant que créateur / dernier programme de mise à jour des données, mais lors du développement d'une application réelle, Spring Security etc. Il est courant de renvoyer le nom d'utilisateur de connexion géré par la fonction d'authentification de.

Et enregistrez-le dans le contexte de l'application.

@EnableJdbcAuditing
@Import(JdbcConfiguration.class)
@EnableJdbcRepositories
@Configuration
public class SpringDataJdbcConfig {
    // ...
    @Bean
    AuditorAware<String> auditorAware() {
        return new MyAuditorAware();
    }
    // ...
}

De plus, si vous voulez changer la méthode d'acquisition du temps "quand" de l'implémentation par défaut (CurrentDateTimeProvider =LocalDateTime.now ()), appliquez un objet qui implémente l'interface ʻorg.springframework.data.auditing.DateTimeProvider. Spécifiez le BeanID de l'objet enregistré dans le contexte et enregistré dans l'attribut dateTimeProviderRef de @ EnableJdbcAuditing`.

@EnableJdbcAuditing(dateTimeProviderRef = "dateTimeProvider")
@Import(JdbcConfiguration.class)
@EnableJdbcRepositories
@Configuration
public class SpringDataJdbcConfig {
    // ...
    @Bean
    DateTimeProvider dateTimeProvider(ObjectProvider<Clock> clockObjectProvider) {
        return () -> Optional.of(LocalDateTime.now(clockObjectProvider.getIfAvailable(Clock::systemDefaultZone)));
    }
    // ...
}

Ajout de colonnes pour l'audit

Après avoir activé la fonction d'audit, ajoutez d'abord une colonne d'audit au tableau.

CREATE TABLE IF NOT EXISTS todo (
	id IDENTITY
	,title TEXT NOT NULL
	,details TEXT
	,finished BOOLEAN NOT NULL
	,created_at TIMESTAMP
	,created_by VARCHAR(64)
	,last_updated_at TIMESTAMP
	,last_updated_by VARCHAR(64)
);

Utilisation de la fonctionnalité d'audit basée sur les annotations

À partir de Spring Data JDBC 1.0.0.RELEASE (Lovelace), Fonction d'audit basée sur les annotations est prise en charge. .RELEASE / reference / html / # auditing.annotations )seulement.

Lorsque vous utilisez la fonction d'audit basée sur les annotations, ajoutez une propriété à la valeur de la colonne d'audit et ajoutez l'annotation suivante à la colonne cible.

~~WARNING:~~

~~ Depuis Spring Data JDBC 1.0 M3 (Lovelace), cela ne fonctionne pas correctement si le type de propriété avec @ Id est une primitive. (DATAJDBC-216) ~~ -> 2018-05-18 Pris en charge: grin:

package com.example.demo.domain;

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.relational.core.mapping.Column;

import java.time.LocalDateTime;
import java.util.List;

public class Todo {
	@Id
	private int id;
	private String title;
	private String details;
	private boolean finished;

	//Date et heure de création
	@CreatedDate
	@Column("created_at")
	private LocalDateTime createdAt;

	//Auteur
	@CreatedBy
	@Column("created_by")
	private String createdBy;

	//Dernière modification
	@LastModifiedDate
	@Column("last_updated_at")
	private LocalDateTime lastUpdatedAt;

	//Dernière mise à jour
	@LastModifiedBy
	@Column("last_updated_by")
	private String lastUpdatedBy;
	private List<Activity> activities;

	// setters/getters

}

NOTE:

Bien que non lié à la fonction d'audit, @ Column est une annotation prise en charge après la sortie de Spring Data JDBC 1.0 M3 (Lovelace) (DATAJDBC-106 DONNÉES JDBC-106)). ~~ Au fait, s'il y a certaines règles telles que le mappage entre les colonnes de cas de serpent et les noms de propriété de cas de chameau, vous pouvez utiliser NamingStrategy pour absorber la différence de noms. ~~ (→ DATAJDBC-206 Avec le support, le mappage entre le nom de la colonne de cas de serpent et le nom de propriété de cas de chameau devient l'opération par défaut. devenu)

Fonction d'audit basée sur l'interface

La fonctionnalité d'audit basée sur l'interface n'est pas prise en charge à partir de Spring Data JDBC 1.0.0.RELEASE (Lovelace). En effet, Spring Data JDBC ne prend pas encore en charge la propriété ʻOptional` (DATAJDBC-205).

Coopération Spring Boot

Pour le moment ... Il semble y avoir spring-data-jdbc-boot-starter dans le référentiel personnel du développeur. Il n'est déployé nulle part pour le moment, vous devez donc l'installer et l'utiliser dans votre référentiel local. (Je ne l'ai pas utilisé cette fois pour le moment) ↓ Spring Boot 2.1 (2.1.0.M4) fournira AutoConfigure et Starter!

NOTE:

J'ai écrit "Essayez spring-boot-starter-data-jdbc (2.1.0.BUILD-SNAPSHOT)".

Résumé

Je pense que c'est une bibliothèque qui continue de se développer et de grandir, alors j'aimerais garder un œil sur les tendances. Je pense que ce sera assez pratique si vous pouvez définir des méthodes personnalisées dans l'interface Repository et spécifier SQL avec des annotations (en ce qui concerne README, il semble y avoir un plan pour le supporter ⇒ pris en charge !!). Et ce serait formidable si la coopération avec Spring Data REST est prise en charge. ↓ Enfin la version 1.0.0 est officiellement sortie! !! C'est toujours une bibliothèque en développement, mais je pense qu'elle a beaucoup grandi par rapport à quand j'ai écrit cette entrée (2018/01/08). [Référence Spring Data REST (Spring Data REST 3.1.0.RELEASE)](https://docs.spring.io/spring-data/rest/docs/3.1.0.RELEASE/reference/html/#getting-started En ce qui concerne .bootstrap), il semble que le support officiel ne soit pas encore disponible (je vais vous donner un problème ...).

Recommended Posts

J'ai essayé Spring Data JDBC 1.0.0.BUILD-SNAPSHOT (-> 1.0.0.RELEASE)
[J'ai essayé] Tutoriel de printemps
J'ai essayé Spring Batch
J'ai essayé de démarrer avec Spring Data JPA
J'ai essayé la machine Spring State
J'ai essayé le guide d'introduction de Spring Boot [Accès aux données avec JPA]
J'ai essayé d'utiliser Spring + Mybatis + DbUnit
J'ai essayé GraphQL avec Spring Boot
J'ai essayé Flyway avec Spring Boot
J'ai essayé de me connecter à MySQL en utilisant le modèle JDBC avec Spring MVC
J'ai essayé l'initialisation paresseuse avec Spring Boot 2.2.0
J'ai essayé de mettre Tomcat
J'ai essayé youtubeDataApi.
J'ai essayé de refactoriser ①
J'ai besoin de la validation de Spring Data pour Pageable ~
J'ai essayé FizzBuzz.
J'ai essayé JHipster 5.1
J'ai essayé d'implémenter le téléchargement de fichiers avec Spring MVC
Compatibilité de Spring JDBC et My Batis avec Spring Data JDBC (provisoire)
05. J'ai essayé de supprimer la source de Spring Boot
J'ai essayé de réduire la capacité de Spring Boot
J'ai essayé d'exécuter Autoware
J'ai essayé d'utiliser Gson
J'ai essayé d'utiliser TestNG
J'ai essayé d'utiliser Galasa
J'ai essayé node-jt400 (Programmes)
J'ai essayé node-jt400 (exécuter)
J'ai essayé node-jt400 (Transactions)
piège dynamodb de données de ressort
Essayez d'utiliser Spring JDBC
[JDBC] J'ai essayé d'accéder à la base de données SQLite3 depuis Java.
J'ai essayé de vérifier ceci et celui de Spring @ Transactional
J'ai essayé de démarrer avec Swagger en utilisant Spring Boot