[JAVA] Wechseln Sie dynamisch die Datenbank, zu der eine Verbindung hergestellt werden soll

Ich musste mehrere Datenbanken in einer Anfrage suchen und aktualisieren, also habe ich untersucht, wie das geht. 2018/05/20 ** Ich habe einen Artikel mit narayanaJTA [hier] erstellt (https://qiita.com/suimyakunosoko/items/afc3f97ffaed78d91adf). ** ** **

0. Übersicht

Realisierungsziel

Umgebung

		<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. Schreiben Sie die Verbindungsinformationen in die yaml-Datei und lesen Sie sie.

Erstellen Sie "DataSourceProperties.java" und "DataSourcePropertyDetail.java", um Verbindungsinformationen abzurufen und die Informationen von "application.yml" zu lesen

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 {

	/**
 * Behalten Sie den Anfangswert der Verbindungsinformationen bei
	 */
	private DataSourcePropertyDetail base;

	/**
 * Führen Sie eine Liste mit Verbindungsinformationen. Ab dem Anfangswert werden nicht genügend Informationen festgelegt
	 */
	private List<DataSourcePropertyDetail> list;

 / ** Getter Setter weggelassen * /

}

Rufen Sie für den tatsächlichen Einstellungswert den in DataSourcePropertyDetail.java definierten Wert auf. Definieren Sie dieses Mal mindestens vier Treibernamen, URL, Benutzername, Kennwort und defaultAutoCommit. Bereiten Sie außerdem einen Konstruktor für die Tiefenkopie vor, um den Fall des Hinzufügens zusätzlicher in der Logik generierter Informationen zu berücksichtigen.

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;
	}

 / ** Getter Setter weggelassen * /

}

Fügen Sie die Informationen zum Lesen der oben erstellten "DataSourceProperties" und "DataSourcePropertyDetail" zu "application.yml" hinzu.

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. Wechseln Sie tatsächlich die Datenquelle

Zum Umschalten der Datenquelle sind zwei Teile erforderlich. Bereiten Sie einen Teil vor, der eine Datenquelle aus Verbindungsinformationen erstellt, und einen Teil, der die Datenquelle bestimmt, die zum Zeitpunkt der Verbindung verwendet werden soll.

2.1 Teile, die aus Verbindungsinformationen eine Datenquelle erstellen

Grundsätzlich kann die erforderliche Verarbeitung in "DataSourceConfig" beschrieben werden. Da es jedoch einige Verarbeitungen gibt, die Sie dieses Mal wiederholt verwenden möchten, ist "DataSourceUtil" bereit, die in Zukunft zu verwendende Verarbeitung zusammenzufassen.

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 Teile, die die Datenquelle bestimmen, die beim Verbinden verwendet werden soll

Drei zur Vorbereitung DynamicRoutingDataSourceResolver, der AbstractRoutingDataSource erbt DataBaseSelectHolder, der die ausgewählten Verbindungsinformationen im lokalen Thread enthält DataBaseSelectInfo, eine Struktur für Verbindungsinformationen

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();
 // Wenn zusätzliche Informationen vorhanden sind, erstellen Sie eine Datenquelle basierend auf den zusätzlichen Informationen
 // Rufe die Methode der ererbenden Klasse auf, wenn keine zusätzlichen Informationen vorhanden sind
		if (info != null && info.isAdditional()) {
			return createNewDataSouce(info);
		}
		return super.determineTargetDataSource();
	}

 // Eine Datenquelle mit zusätzlichen Informationen basierend auf den angegebenen Eigenschaften erstellen
	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;
	}

 / ** Getter Setter weggelassen * /

	/**
 * Methode zur Bestimmung des Vorhandenseins oder Nichtvorhandenseins zusätzlicher Informationen
	 */
	public boolean isAdditional() {
		return !(Objects.isNull(this.additionalUrl) 
			&& Objects.isNull(this.additionalUserName) 
			&& Objects.isNull(this.additionalPassword));
	}

}

3. Eigentlich verwenden

Erstellen Sie Controller, Service und DAO und betreiben Sie sie tatsächlich. Verwenden Sie Propagation.REQUIRES_NEW, um die aktuelle Transaktion anzuhalten und eine weitere Transaktion für die Transactional-Eigenschaft zu erstellen.

Bestätigen Sie mit "DemoController # exception", dass es zum Zeitpunkt der Ausnahme zurückgesetzt wird Überprüfen Sie, ob mehrere DBs mit "DemoController # demo" aktualisiert wurden

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";
	}

}

Ich weiß nicht, ob es sich um die Spezifikation von Spring Transactional oder um die Spezifikation von AOP selbst handelt, da ich dies nicht untersucht habe. Da die Transaktion jedoch zum Zeitpunkt des Methodenaufrufs in derselben Instanz nicht ausgeführt wird, ist die DB-Umschaltung innerhalb der Transaktion wie folgt Sie verwenden die Methode. --Transactional wird der DAO-Klassenmethode selbst hinzugefügt

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() {
 // DB mit Schlüssel-Duplizierungsfehler zurückgesetzt
		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 Das SELECT-Ergebnis von ~~ user01 wird in postgres eingefügt, aber im zweiten INSERT tritt eine Ausnahme auf und es wird zurückgesetzt. ~~ ** Die Testdaten sind schlecht und es wird tatsächlich kein Rollback durchgeführt. ** ** **

DB vor der Implementierung

key value
key value
user001 Benutzer 001 testen

DB nach der Implementierung

key value
key value
user001 Benutzer 001 testen

3.2 DemoController#demo http://localhost:8080/demo Fügen Sie die SELECT-Ergebnisse von user01 und user02 in postgres ein. Dann SELECT postgres und INSERT in die Datenbank innerhalb derselben Transaktion.

DB vor der Implementierung

key value
key value
database database
key value
user001 Benutzer 001 testen
key value
user002 Benutzer 002 testen

DB nach der Implementierung

key value
user001 Benutzer 001 testen
user002 Benutzer 002 testen
key value
database database
user001 Benutzer 001 testen
user002 Benutzer 002 testen
key value
user001 Benutzer 001 testen
key value
user002 Benutzer 002 testen

9. Siehe

[Spring Boot] Dynamische Änderung der Datenquelle

Probieren Sie das Propagierungsattribut (Propagation) der Transaktion (@Transactional) mit Spring Boot aus

Recommended Posts

Wechseln Sie dynamisch die Datenbank, zu der eine Verbindung hergestellt werden soll
[Java / PostgreSQL] Verbinden Sie die WEB-Anwendung mit der Datenbank
Bitten Sie Sota, die Werte in der Datenbank zurückzusprechen
So löschen Sie die Datenbank beim Neuerstellen der App
Code zum Verbinden von Rails 3 mit PostgreSQL 10
3. Erstellen Sie eine Datenbank für den Zugriff über das Webmodul
[Java] Stellen Sie eine Verbindung zu MySQL her
Ändern Sie den Primärschlüssel der Datenbank (MySQL) in eine beliebige Spalte.
Wischen Sie, um die Bildschirme zu wechseln
[Ausgabe] Informationen zur Datenbank
Schalten Sie den Writer dynamisch entsprechend dem Lesewert mit Spring-Batch
Java-Datenbankverbindung Java-MySQL-Verbindung ⑤: Stellen Sie eine Verbindung zur Datenbank (MySQL) her, um Tabelleninformationen zu suchen und Suchergebnisse anzuzeigen / September 2017
So ändern Sie dynamisch den von MyBatis erfassten Spaltennamen
[JDBC] Ich habe versucht, von Java aus auf die SQLite3-Datenbank zuzugreifen.
So verbinden Sie die Zeichenfolgen in der Liste durch Kommas getrennt
Stellen Sie mit spring boot + spring jpa eine Verbindung zur Datenbank her und führen Sie die CRUD-Operation durch
Dynamisches Wechseln von JDKs beim Erstellen von Java mit Gradle
So wechseln Sie Java in der OpenJDK-Ära auf dem Mac
So wechseln Sie in Netty dynamisch zwischen FIN und RST
[Docker] So trennen Sie eine mit der Datenbank verbundene Sitzung zwangsweise [Postgres]
[Schienen] Anzeigen von Informationen, die in der Datenbank gespeichert sind
9 Entspricht dem Rückgabewert
JavaFX - Dynamische Zuordnung von Controller zu fxml
Wechseln Sie von slim3-gen zu slim3-gen-jsr269
12 Entspricht der while-Anweisung
Stellen Sie eine Verbindung von Java zu PostgreSQL her
4 Fügen Sie dem Interpreter println hinzu
Stellen Sie mit Java eine Verbindung zur Datenbank her
Stellen Sie mit Java eine Verbindung zu MySQL 8 her
Verbinden Sie CentOS 7 mit L2TP VPN
Lassen Sie uns die switch-Anweisung verstehen!
Verbinde dich mit Eclipse mit Orakel!
[Android] Verbindung zu MySQL herstellen (unvollendet)
Eingabe in die Java-Konsole
[Swift] So ändern Sie dynamisch die Höhe der Symbolleiste auf der Tastatur
Ubuntu unter WSL2: Unter Unix kann keine Verbindung zum Docker-Daemon hergestellt werden
Herstellen einer Verbindung zu einer Datenbank mit Java (Teil 1) Möglicherweise die grundlegende Methode
Verbinden Sie IoT-Geräte mithilfe von Gateway- und Subgeräteszenarien mit der Cloud