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). ** ** **
<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>
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
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.
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);
}
}
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));
}
}
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. ** ** **
key | value |
---|---|
key | value |
---|---|
user001 | Benutzer 001 testen |
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.
key | value |
---|---|
key | value |
---|---|
database | database |
key | value |
---|---|
user001 | Benutzer 001 testen |
key | value |
---|---|
user002 | Benutzer 002 testen |
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 |
[Spring Boot] Dynamische Änderung der Datenquelle
Recommended Posts