[JAVA] Basculer dynamiquement la base de données à laquelle se connecter

J'ai dû rechercher et mettre à jour plusieurs bases de données en une seule demande, alors j'ai cherché comment le faire. 2018/05/20 ** J'ai créé un article en utilisant narayanaJTA ici. ** **

0. Aperçu

Objectif de réalisation

environnement

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>tomcat-jdbc</artifactId>
		</dependency>

1. Écrivez les informations de connexion dans le fichier yaml et lisez-le.

Créer DataSourceProperties.java et DataSourcePropertyDetail.java utilisés pour obtenir les informations de connexion et lire les informations de ʻapplication.yml`

package com.example.demo.common;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("demo.datasource")
public class DataSourceProperties {

	/**
 * Conservez la valeur initiale des informations de connexion
	 */
	private DataSourcePropertyDetail base;

	/**
 * Gardez une liste des informations de connexion. Des informations insuffisantes sont définies à partir de la valeur initiale
	 */
	private List<DataSourcePropertyDetail> list;

 / ** setter getter omis * /

}

Pour la valeur réelle du paramètre, récupérez celle définie dans DataSourcePropertyDetail.java. Cette fois, définissez les quatre minimum de driverClassName, url, nom d'utilisateur, mot de passe et defaultAutoCommit. Préparez également un constructeur pour une copie complète en tenant compte du cas de l'ajout d'informations supplémentaires générées dans la logique.

package com.example.demo.common;

import java.util.Objects;

public class DataSourcePropertyDetail {

	private String name;

	private String driverClassName;
	private String url;
	private String username;
	private String password;
	private Boolean defaultAutoCommit;

	public DataSourcePropertyDetail(){
	}

	public DataSourcePropertyDetail(DataSourcePropertyDetail prop){
		if (Objects.isNull(prop)) {
			return;
		}
		this.driverClassName = prop.driverClassName;
		this.url = prop.url;
		this.username = prop.username;
		this.password = prop.password;
		this.defaultAutoCommit = prop.defaultAutoCommit;
	}

 / ** setter getter omis * /

}

Ajoutez les informations pour lire les données DataSourceProperties etDataSourcePropertyDetail créées ci-dessus à ʻapplication.yml`.

demo:
  datasource:
    base:
      driver-class-name: org.postgresql.Driver
      url: jdbc:postgresql://localhost:5432/postgres
      username: postgres
      password: postgres
      default-auto-commit: false
    list:
    - name: postgres
    - name: user
      url: jdbc:postgresql://localhost:5432/user
      username: user
      password: user
    - name: database
      url: jdbc:postgresql://localhost:5432/database
      username: database
      password: database

2. En fait, changer de source de données

Deux parties sont nécessaires pour changer de source de données. Préparez une partie qui crée une source de données à partir des informations de connexion et une partie qui détermine la source de données à utiliser au moment de la connexion.

2.1 Parties qui créent une source de données à partir des informations de connexion

Fondamentalement, le traitement nécessaire peut être décrit dans DataSourceConfig, mais comme il y a certains traitements que vous souhaitez utiliser à plusieurs reprises cette fois, DataSourceUtil est prêt à résumer le traitement à utiliser dans le futur.

package com.example.demo.common;

import java.util.HashMap;

import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Configuration
public class DataSourceConfig {

	@Autowired
	DataSourceProperties properties;

	@Bean
	@Primary
	public DynamicRoutingDataSourceResolver getRoutingdataSource() {
		DynamicRoutingDataSourceResolver resolver = new DynamicRoutingDataSourceResolver();
		DataSource defDataSource = DataSourceUtil.getDataSource(properties.getBase(), properties.getBase());
		Map<Object, Object> datasources = new HashMap<Object,Object>();
		datasources.put(properties.getBase().getName(), defDataSource);
		properties.getList().forEach(prop -> datasources.put(prop.getName(), getDataSource(prop)));

		resolver.setTargetDataSources(datasources);

		resolver.setDefaultTargetDataSource(defDataSource);

		return resolver;
	}

	private DataSource getDataSource(DataSourcePropertyDetail prop) {
		return DataSourceUtil.getDataSource(properties.getBase(), prop);
	}
}

package com.example.demo.common;

import java.util.Objects;
import java.util.function.Consumer;

import org.apache.tomcat.jdbc.pool.DataSource;

public class DataSourceUtil {

	public static DataSource getDataSource(DataSourcePropertyDetail base, DataSourcePropertyDetail prop) {
		DataSource datasource = new org.apache.tomcat.jdbc.pool.DataSource();
		skipIfNull(defaultIfEmpty(prop.getDriverClassName(), base.getDriverClassName()), datasource::setDriverClassName);
		skipIfNull(defaultIfEmpty(prop.getUrl(), base.getUrl()), datasource::setUrl);
		skipIfNull(defaultIfEmpty(prop.getUsername(), base.getUsername()), datasource::setUsername);
		skipIfNull(defaultIfEmpty(prop.getPassword(), base.getPassword()), datasource::setPassword);
		skipIfNull(defaultIfEmpty(prop.getDefaultAutoCommit(), base.getDefaultAutoCommit()), datasource::setDefaultAutoCommit);
		return datasource;
	}

	public static<T> T defaultIfEmpty(T obj, T def) {
		if (Objects.isNull(obj)) {
			return def;
		}

		return obj;
	}

	public static <T> void skipIfNull(T obj, Consumer<T> consumer) {
		if (Objects.isNull(obj)) {
			return;
		}
		consumer.accept(obj);
	}
}

2.2 Parties qui déterminent la source de données à utiliser lors de la connexion

Trois pour se préparer ʻDynamicRoutingDataSourceResolver qui hérite de AbstractRoutingDataSource DataBaseSelectHolder qui contient les informations de connexion sélectionnées sur le thread local DataBaseSelectInfo, qui est une structure pour les informations de connexion

package com.example.demo.common;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicRoutingDataSourceResolver extends AbstractRoutingDataSource {

	@Autowired
	DataSourceProperties properties;

	@Override
	protected Object determineCurrentLookupKey() {
		DataBaseSelectInfo key = DataBaseSelectHolder.getDataBaseInstanceInfo();
		if (Objects.isNull(key)) {
			return null;
		}
		return key.getDataSourceName();
	}

	private DataBaseSelectInfo determineCurrentLookupKeyRaw() {
		return DataBaseSelectHolder.getDataBaseInstanceInfo();
	}

	@Override
	protected DataSource determineTargetDataSource() {
		DataBaseSelectInfo info = determineCurrentLookupKeyRaw();
 // S'il y a des informations supplémentaires, créez une source de données basée sur les informations supplémentaires
 // Appelle la méthode de la classe héritière s'il n'y a pas d'informations supplémentaires
		if (info != null && info.isAdditional()) {
			return createNewDataSouce(info);
		}
		return super.determineTargetDataSource();
	}

 // Créer une source de données avec des informations supplémentaires basées sur les propriétés spécifiées
	private DataSource createNewDataSouce(DataBaseSelectInfo info) {
		Optional<DataSourcePropertyDetail> op = properties.getList()
			.stream()
			.filter(prop -> Objects.equals(prop.getName(), info.getDataSourceName()))
			.findFirst();
		if (op.isPresent()) {
			DataSourcePropertyDetail prop = new DataSourcePropertyDetail(op.get());

			DataSourceUtil.skipIfNull(info.getAdditionalUrl(),
				 (url) -> prop.setUrl(prop.getUrl() + url));
			DataSourceUtil.skipIfNull(info.getAdditionalUserName(),
				 (name) -> prop.setUsername(prop.getUsername() + name));
			DataSourceUtil.skipIfNull(info.getAdditionalPassword(),
				 (password) -> prop.setPassword(prop.getPassword() + password));

			return DataSourceUtil.getDataSource(properties.getBase(), prop);
		}
		return super.determineTargetDataSource();
	}
}



package com.example.demo.common;

import org.springframework.util.Assert;

public class DataBaseSelectHolder {
	 private static ThreadLocal<DataBaseSelectInfo> contextHolder = new ThreadLocal<DataBaseSelectInfo>();

	    public static void setDataBaseInstanceInfo(DataBaseSelectInfo datasource) {
	        Assert.notNull(datasource, "datasource cannot be null.");
	        contextHolder.set(datasource);
	    }

	    public static void setDataBaseInstanceInfo(String dataSourceName) {
	        Assert.notNull(dataSourceName, "dataSourceName cannot be null.");
	        contextHolder.set(new DataBaseSelectInfo(dataSourceName));
	    }

	    public static void setDataBaseInstanceInfo(String dataSourceName, String additionalUrl,
			String additionalUserName, String additionalPassword) {
	        Assert.notNull(dataSourceName, "dataSourceName cannot be null.");
	        contextHolder.set(new DataBaseSelectInfo(dataSourceName, additionalUrl,
			additionalUserName, additionalPassword));
	    }

	    public static DataBaseSelectInfo getDataBaseInstanceInfo() {
	        return contextHolder.get();
	    }

	    public static void clear() {
	        contextHolder.remove();
	    }
}


package com.example.demo.common;

import java.util.Objects;

public class DataBaseSelectInfo {

	private String dataSourceName;

	private String additionalUrl;

	private String additionalUserName;

	private String additionalPassword;

	public DataBaseSelectInfo(String dataSourceName, String additionalUrl
		, String additionalUserName, String additionalPassword) {
		this.dataSourceName = dataSourceName;
		this.additionalUrl = additionalUrl;
		this.additionalUserName = additionalUserName;
		this.additionalPassword = additionalPassword;
	}

 / ** setter getter omis * /

	/**
 * Méthode de détermination de la présence ou de l'absence d'informations complémentaires
	 */
	public boolean isAdditional() {
		return !(Objects.isNull(this.additionalUrl) 
			&& Objects.isNull(this.additionalUserName) 
			&& Objects.isNull(this.additionalPassword));
	}

}

3. En fait, utilisez

Créez un contrôleur, un service et un DAO et faites-les réellement fonctionner. Utilisez Propagation.REQUIRES_NEW pour suspendre la transaction en cours et créer une autre transaction pour la propriété Transactional.

Confirmez qu'il est annulé au moment de l'exception avec DemoController # exception Vérifiez que plusieurs bases de données ont été mises à jour avec DemoController # demo

package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;

import com.example.demo.common.DataBaseSelectHolder;
import com.example.demo.service.DemoService;

@Controller
@Transactional(propagation=Propagation.REQUIRES_NEW)
public class DemoController {

	@Autowired
	private DemoService service;

	@RequestMapping("exception")
	public String exception() {
		DataBaseSelectHolder.setDataBaseInstanceInfo("postgres");
		service.insertException();
		return "empty";
	}

	@RequestMapping("demo")
	public String demo() {
		DataBaseSelectHolder.setDataBaseInstanceInfo("postgres");
		service.insert();
		return "empty";
	}

}

Je ne sais pas s'il s'agit de la spécification de Spring Transactional ou de la spécification d'AOP elle-même car je n'ai pas étudié, mais comme la transaction n'est pas effectuée au moment de l'appel de méthode dans le même cas, la commutation de base de données dans la transaction est la suivante Vous utilisez la méthode. --Transactional est ajouté à la méthode de classe DAO elle-même

package com.example.demo.service;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.common.DataBaseSelectHolder;
import com.example.demo.dao.DemoDAO;

@Service
@Transactional(propagation=Propagation.REQUIRES_NEW)
public class DemoService {

	@Autowired
	private DemoDAO demoDao;

	@Autowired
	private DemoService self;

	public void insertException() {
 // La base de données a été restaurée avec une erreur de duplication de clé
		DataBaseSelectHolder.setDataBaseInstanceInfo("database");
		List<Map<String, Object>> list = demoDao.selectTransactional();
		list.stream().forEach(demoDao::insert);
		list.stream().forEach(demoDao::insert);
	}

	public void insert() {

		DataBaseSelectHolder.setDataBaseInstanceInfo("user", "001", null, null);
		List<Map<String, Object>> list1 = demoDao.selectTransactional();
		list1.stream().forEach(demoDao::insert);

		DataBaseSelectHolder.setDataBaseInstanceInfo("user", "002", null, null);
		List<Map<String, Object>> list2 = self.getList();
		list2.stream().forEach(demoDao::insert);

		DataBaseSelectHolder.setDataBaseInstanceInfo("database");
		List<Map<String, Object>> list3 = demoDao.select();
		self.insertOther(list3);
	}

	public List<Map<String, Object>> getList() {
		return demoDao.select();
	}

	public void insertOther(List<Map<String, Object>> list) {
		list.stream().forEach(demoDao::insert);
	}

}
package com.example.demo.dao;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Mapper
public interface DemoDAO {

	@Select("SELECT key, value FROM demo")
	List<Map<String, Object>> select();

	@Select("SELECT key, value FROM demo")
	@Transactional(propagation=Propagation.REQUIRES_NEW)
	List<Map<String, Object>> selectTransactional();

	@Insert("INSERT INTO demo (key, value) VALUES ( #{map.key}, #{map.value})")
	int insert(@Param("map")Map<String, Object> map);

	@Update("UPDATE demo SET value = #{map.value} WHERE key = #{map.key}")
	int update(@Param("map")Map<String, Object> map);
}

3.1 DemoController#exception http://localhost:8080/exception Le résultat SELECT de ~~ user01 est INSERT dans postgres, mais une exception se produit dans le deuxième INSERT et il est annulé. ~~ ** Les données de test sont incorrectes et aucune restauration n'est réellement effectuée. ** **

DB de pré-implémentation

key value
key value
user001 Utilisateur test 001

DB après mise en œuvre

key value
key value
user001 Utilisateur test 001

3.2 DemoController#demo http://localhost:8080/demo INSÉRER les résultats SELECT de user01 et user02 dans postgres. Puis SELECT postgres et INSERT dans la base de données au sein de la même transaction.

DB de pré-implémentation

key value
key value
database database
key value
user001 Utilisateur test 001
key value
user002 Utilisateur test 002

DB après mise en œuvre

key value
user001 Utilisateur test 001
user002 Utilisateur test 002
key value
database database
user001 Utilisateur test 001
user002 Utilisateur test 002
key value
user001 Utilisateur test 001
key value
user002 Utilisateur test 002

9. Voir

[Spring Boot] Changement de source de données dynamique

Essayez l'attribut de propagation (propagation) de la transaction (@Transactional) avec Spring Boot

Recommended Posts

Basculer dynamiquement la base de données à laquelle se connecter
[Java / PostgreSQL] Connectez l'application WEB à la base de données
Demandez à Sota de revenir sur les valeurs de la base de données
Comment effacer la base de données lors de la recréation de l'application
Code utilisé pour connecter Rails 3 à PostgreSQL 10
3. Créez une base de données à laquelle accéder à partir du module Web
[Java] Connectez-vous à MySQL
Changez la clé primaire de la base de données (MySQL) sur n'importe quelle colonne.
Balayez pour changer d'écran
[Sortie] À propos de la base de données
Commutez dynamiquement l'écrivain en fonction de la valeur du lecteur avec Spring-batch
Connexion Java-base de données Connexion Java-MySQL ⑤: Connectez-vous à la base de données (MySQL) pour rechercher des informations sur la table et afficher les résultats de la recherche / septembre 2017
Comment changer dynamiquement le nom de la colonne acquis par MyBatis
[JDBC] J'ai essayé d'accéder à la base de données SQLite3 depuis Java.
Comment connecter les chaînes de la liste séparées par des virgules
Connectez-vous à la base de données avec spring boot + spring jpa et effectuez l'opération CRUD
Comment changer dynamiquement les JDK lors de la construction de Java avec Gradle
Comment changer Java à l'ère d'OpenJDK sur Mac
Comment basculer dynamiquement entre FIN et RST dans Netty
[Docker] Comment déconnecter de force une session connectée à la base de données [Postgres]
[Rails] Comment afficher les informations stockées dans la base de données dans la vue
9 Correspond à la valeur de retour
JavaFX - Affectation dynamique du contrôleur à fxml
Passer de slim3-gen à slim3-gen-jsr269
12 Correspond à l'instruction while
Connectez-vous de Java à PostgreSQL
4 Ajoutez println à l'interpréteur
Connectez-vous à DB avec Java
Connectez-vous à MySQL 8 avec Java
Connectez CentOS 7 au VPN L2TP
Comprenons l'instruction switch!
Connectez-vous à Oracle avec Eclipse!
[Android] Connectez-vous à MySQL (non terminé)
Entrée dans la console Java
[Swift] Comment modifier dynamiquement la hauteur de la barre d'outils sur le clavier
Ubuntu sur WSL2: impossible de se connecter au démon Docker sous unix
Connexion à une base de données avec Java (partie 1) Peut-être la méthode de base
Connectez les appareils IoT au cloud à l'aide de scénarios de passerelle et de sous-appareil