[JAVA] Beispiel einer Webanwendung, die mit Spring Boot 1.5 mehrere Datenbanken verwaltet

Überblick

Dies ist ein Beispielcode einer Webanwendung, die mehrere Datenbanken mit Spring Boot verarbeitet.

Umgebung

Referenz

Datenbank

Es wird davon ausgegangen, dass die Anwendung drei Datenbanken verwaltet, eine (test_dev3) ist eine Datenbank, die allen Anwendungen gemeinsam ist, und die anderen beiden (test_dev1, test_dev2) werden als mandantenfähige Daten behandelt. Die Datenbankschemata von test_dev1 und test_dev2 sind identisch, die zu speichernden Daten sind jedoch unterschiedlich.

In dieser Beispielanwendung wechselt der Wert des Anforderungsparameters zwischen den Datenbanken test_dev1 und test_dev2. Da es sich um eine Beispielanwendung handelt, hat die zu behandelnde Tabelle keine besondere Bedeutung (angemessen). Darüber hinaus scheint JpaTransactionManager nicht in der Lage zu sein, Transaktionen auszuführen, die mehrere Datenbanken (Datenquellen) umfassen.

Strukturzeichnung

ds.png

Datenquelleneinstellungen

Stellen Sie die Datenquelle in application.yml wie folgt ein.

application.yml


spring:
  datasource:
    dev1:
      url: jdbc:postgresql://localhost:5432/test_dev1
      username: user_dev1
      password: pass1
      driverClassName: org.postgresql.Driver
      defaultAutoCommit: false
      defaultReadOnly: false
      validationQuery: SELECT 'dev1'
      timeBetweenEvictionRunsMillis: 3600000
    dev2:
      url: jdbc:postgresql://localhost:5432/test_dev2
      username: user_dev2
      password: pass2
      driverClassName: org.postgresql.Driver
      defaultAutoCommit: false
      defaultReadOnly: false
      validationQuery: SELECT 'dev2'
      timeBetweenEvictionRunsMillis: 3600000
    dev3:
      url: jdbc:postgresql://localhost:5432/test_dev3
      username: user_dev3
      password: pass3
      driverClassName: org.postgresql.Driver
      defaultAutoCommit: false
      defaultReadOnly: false
      validationQuery: SELECT 'dev3'
      timeBetweenEvictionRunsMillis: 3600000
  jpa:
    showSql: true
    formatSql: true

logging:
  level:
    root: INFO
    org.springframework: INFO
#    org.springframework.orm.jpa.JpaTransactionManager: DEBUG
#    org.springframework.transaction: TRACE
    org.hibernate: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
#    org.hibernate.transaction: DEBUG
#    org.hibernate.jpa.internal: DEBUG
    org.hibernate.event.internal: DEBUG
    org.hibernate.engine.transaction.internal: DEBUG
    org.hibernate.internal.util: DEBUG

Datenquellen-Konfigurationscode

test_dev1

Datenquelleneinstellungen für Mandant A. Die Implementierung besteht lediglich darin, die Datenquelle aus den Einstellungen in der Eigenschaftendatei zu erstellen. Entity Manager und Transaktionsmanager werden in verschiedenen Klassen implementiert.

Dev1DataSourceConfigure


package com.example.datasource;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class Dev1DataSourceConfigure {
    public static final String DEV1_DATASOURCE = "dev1DataSource";

    @ConfigurationProperties(prefix = "spring.datasource.dev1")
    @Bean(DEV1_DATASOURCE)
    public DataSource dataSource() {
        DataSource dev1 = DataSourceBuilder.create().build();
        return dev1;
    }
}

test_dev2

Datenquelleneinstellungen für Mandant B. Es ist die gleiche Implementierung wie Mieter A.

Dev2DataSourceConfigure


package com.example.datasource;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class Dev2DataSourceConfigure {
    public static final String DEV2_DATASOURCE = "dev2DataSource";

    @ConfigurationProperties(prefix = "spring.datasource.dev2")
    @Bean(DEV2_DATASOURCE)
    public DataSource dataSource() {
        DataSource dev2 = DataSourceBuilder.create().build();
        return dev2;
    }
}

Festlegen der Datenquelle, die test_dev1 und test_dev2 bündelt

Implementieren Sie den Entitätsmanager und den Transaktionsmanager, die sich auf die Datenquelle für jeden oben implementierten Mandanten beziehen. Der Punkt der Implementierung ist

MultiDataSourceConfigure


package com.example.datasource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

import static com.example.datasource.Dev1DataSourceConfigure.DEV1_DATASOURCE;
import static com.example.datasource.Dev2DataSourceConfigure.DEV2_DATASOURCE;
import static com.example.datasource.MultiDataSourceConfigure.ENTITY_MANAGER;
import static com.example.datasource.MultiDataSourceConfigure.REPOSITORY_PACKAGE;
import static com.example.datasource.MultiDataSourceConfigure.TRANSACTION_MANAGER;

@Configuration
@EnableTransactionManagement(order = 100)                                                 // (1)
@EnableJpaRepositories(
    basePackages = {REPOSITORY_PACKAGE},
    entityManagerFactoryRef = ENTITY_MANAGER,
    transactionManagerRef = TRANSACTION_MANAGER
)
public class MultiDataSourceConfigure {

    public static final String REPOSITORY_PACKAGE = "com.example.repository.tenantds";
    public static final String ENTITY_PACKAGES = "com.example.entity.tenantds";

    public static final String ENTITY_MANAGER = "multiEntityManagerFactory";
    public static final String TRANSACTION_MANAGER = "multiTransactionManager";

    public static final String MULTI_DATASOURCE_PU = "multiDataSourcePersistenceUnit";
    public static final String MULTI_DATASOURCE = "multiDataSource";

    @Autowired
    @Qualifier(DEV1_DATASOURCE)
    private DataSource dev1;

    @Autowired
    @Qualifier(DEV2_DATASOURCE)
    private DataSource dev2;

    @Bean(MULTI_DATASOURCE)
    public RoutingDataSourceResolver multiDataSource() {                                    // (2)
        RoutingDataSourceResolver resolver = new RoutingDataSourceResolver();

        //Stellen Sie die zu wechselnde Datenquelle ein
        Map<Object, Object> dataSources = new HashMap<Object,Object>();
        dataSources.put(DEV1_DATASOURCE, dev1);
        dataSources.put(DEV2_DATASOURCE, dev2);

        resolver.setTargetDataSources(dataSources);
        resolver.setDefaultTargetDataSource(dev1);
        return resolver;
    }

    @Bean(ENTITY_MANAGER)
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        LocalContainerEntityManagerFactoryBean emf = builder
                .dataSource(multiDataSource())
                .persistenceUnit(MULTI_DATASOURCE_PU)
                .packages(ENTITY_PACKAGES)
                .build();
        return emf;
    }

    @Bean(TRANSACTION_MANAGER)
    public PlatformTransactionManager transactionManager(
            @Autowired @Qualifier(ENTITY_MANAGER) EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager jtm = new JpaTransactionManager();
        jtm.setEntityManagerFactory(entityManagerFactory);
        //jtm.setDataSource(multiDataSource());
        return jtm;
    }
}

Einstellungen zum Umschalten zwischen test_dev1 und test_dev2

Implementieren Sie den Prozess des Erbens und Wechselns der AbstractRoutingDataSource des Spring Framework.

RoutingDataSourceResolver


package com.example.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import static com.example.datasource.Dev1DataSourceConfigure.DEV1_DATASOURCE;
import static com.example.datasource.Dev2DataSourceConfigure.DEV2_DATASOURCE;

public class RoutingDataSourceResolver extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        if (MultiDataSourceContextHolder.getTenantType() == null) {
            return DEV1_DATASOURCE;
        }
        //Mandanten- und Datenquellenzuordnung
        switch (MultiDataSourceContextHolder.getTenantType()) {
            case TENANT_A:
                return DEV1_DATASOURCE;
            case TENANT_B:
                return DEV2_DATASOURCE;
            default:
                throw new RuntimeException("unknown tenant");
        }
    }
}

MultiDataSourceContextHolder


package com.example.datasource;

public class MultiDataSourceContextHolder {
    private static ThreadLocal<TenantType> contextHolder = new ThreadLocal<>();

    public static void setTenantType(TenantType tenantType) {
        if (tenantType == null) {
            throw new NullPointerException();
        }
        contextHolder.set(tenantType);
    }

    public static TenantType getTenantType() {
        return contextHolder.get();
    }

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

Aufzählung, die Mandantentypen verwaltet.

TenantType


package com.example.datasource;

import java.util.Arrays;
import java.util.Optional;

public enum TenantType {
    /* test_dev1 */
    TENANT_A("aaa"),
    /* test_dev2 */
    TENANT_B("bbb")
    ;

    private final String tenantName;

    TenantType(String tenantName) {
        this.tenantName = tenantName;
    }

    public String getTenantName() {
        return this.tenantName;
    }

    public static TenantType byName(final String tenantName) {
        Optional<TenantType> tenantType = Arrays.stream(TenantType.values())
                .filter(t -> t.getTenantName().equals(tenantName))
                .findFirst();
        if (!tenantType.isPresent()) {
            throw new IllegalStateException("unknown tenant name : [" + tenantName + "]");
        }
        return tenantType.get();
    }
}

Datenquellen mit AOP wechseln

Es gibt verschiedene Möglichkeiten, Datenquellen zu wechseln, aber diese Beispielanwendung verwendet die Spring AOP-Funktion. Legen Sie für die Methode fest, die das Argument "Tenant" der Klasse mit der folgenden Anmerkung enthält. Die Datenquelle wird durch den Wert dieses Arguments namens Tenant umgeschaltet.

SwitchingDataSource


package com.example.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface SwitchingDataSource {
}

Passen Sie die Auftragsanmerkung so an, dass die Datenquelle vor der Transaktion umgeschaltet wird. Der Punkt der Implementierung ist

SwitchingDataSourceAop


package com.example.aop;

import com.example.datasource.MultiDataSourceContextHolder;
import com.example.datasource.TenantType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(99)                                                                   // (1)
@Aspect
@Component
public class SwitchingDataSourceAop {

    @Around("@annotation(swds) && args(tenant,..)")                          // (2)
    public Object switchingForMethod(ProceedingJoinPoint pjp, SwitchingDataSource swds, String tenant) throws Throwable {
        try {
            switching(tenant);
            Object result = pjp.proceed();
            return result;
        } finally {
            clear();
        }
    }

    @Around("@within(swds) && args(tenant,..)")                              // (3)
    public Object switchingForClass(ProceedingJoinPoint pjp, SwitchingDataSource swds, String tenant) throws Throwable {
        try {
            switching(tenant);
            Object result = pjp.proceed();
            return result;
        } finally {
            clear();
        }
    }

    private void switching(String tenant) {
        TenantType tenantType = TenantType.byName(tenant);
        MultiDataSourceContextHolder.setTenantType(tenantType);
    }
    private void clear() {
        MultiDataSourceContextHolder.clearTenantType();
    }
}

Datenquelle für test_dev3

Da die für alle Anwendungen gemeinsame Datenquelle nicht gewechselt werden muss, implementiert diese Klasse sogar den Entitätsmanager und den Transaktionsmanager. Übrigens wird die primäre Anmerkung hinzugefügt, damit diese Einstellung zur Standardeinstellung wird.

SingleDataSourceConfigure


package com.example.datasource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import java.util.Properties;

import static com.example.datasource.SingleDataSourceConfigure.ENTITY_MANAGER;
import static com.example.datasource.SingleDataSourceConfigure.REPOSITORY_PACKAGES;
import static com.example.datasource.SingleDataSourceConfigure.TRANSACTION_MANAGER;

@Configuration
@EnableTransactionManagement(order = 101)
@EnableJpaRepositories(
    basePackages = {REPOSITORY_PACKAGES},
    entityManagerFactoryRef = ENTITY_MANAGER,
    transactionManagerRef = TRANSACTION_MANAGER
)
public class SingleDataSourceConfigure {

    public static final String REPOSITORY_PACKAGES = "com.example.repository.appds";
    public static final String ENTITY_PACKAGES = "com.example.entity.appds";

    public static final String ENTITY_MANAGER = "entityManagerFactory";
    public static final String TRANSACTION_MANAGER = "transactionManager";

    public static final String SINGLE_DATASOURCE_PU = "singleDataSourcePersistenceUnit";
    public static final String SINGLE_DATASOURCE = "singleDataSource";

    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.dev3")
    @Bean(SINGLE_DATASOURCE)
    public DataSource singleDataSource() {
        DataSource dataSource = DataSourceBuilder.create().build();
        return dataSource;
    }

    @Primary
    @Bean(ENTITY_MANAGER)
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder) {

        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(singleDataSource());
        emf.setPersistenceUnitName(SINGLE_DATASOURCE_PU);
        emf.setPackagesToScan(ENTITY_PACKAGES);

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        emf.setJpaVendorAdapter(vendorAdapter);
        emf.setJpaProperties(additionalProperties());

        return emf;
    }

    @Primary
    @Bean(TRANSACTION_MANAGER)
    public PlatformTransactionManager transactionManager(
            @Autowired @Qualifier(ENTITY_MANAGER) EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager jtm = new JpaTransactionManager();
        jtm.setEntityManagerFactory(entityManagerFactory);
        //jtm.setDataSource(singleDataSource());
        return jtm;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
        return new PersistenceExceptionTranslationPostProcessor();
    }

    private Properties additionalProperties() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.show_sql", "true");
        properties.setProperty("hibernate.format_sql", "true");
        properties.setProperty("hibernate.use_sql_comments", "true");
        properties.setProperty("hibernate.generate_statistics", "false");
        return properties;
    }

}

Entität und Repository

Wie unten gezeigt, werden die Pakete für Mandanten und allgemeine Anwendungen separat verwaltet. Serviceklassen, die höher als Entitäten und Repositorys sind, müssen nicht getrennt werden, da keine Pakete getrennt werden müssen.

com.example
  |
  +--- entity
  |      |
  |      +--- tenantds            //tenant datasource
  |      |      |
  |      |      +--- Todo
  |      |
  |      +--- appds               //application datasource
  |             |
  |             +--- Memo
  |
  +--- repository
  |      |
  |      +--- tenantds
  |      |      |
  |      |      +--- TodoRepository
  |      |
  |      +--- appds
  |             |
  |             +--- MemoRepository
  |
  +--- service
         |
         +--- impl
         |      |
         |      +--- TodoService
         |      +--- MemoService
         |
         +--- TodoServiceImpl
         +--- MemoServiceImpl

Entität

Die Implementierung hat nichts Besonderes, trennen Sie einfach die Pakete.

Todo


package com.example.entity.tenantds;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.util.Date;

@Entity
@Table(name="todo")
@SequenceGenerator(name = "todo_id_gen", sequenceName = "todo_id_seq", allocationSize = 1)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Todo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="title")
    private String title;
    @Column(name="done")
    private Boolean done;
    @Column(name="updated")
    @Temporal(TemporalType.TIMESTAMP)
    private Date updated;
}

Memo


package com.example.entity.appds;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.util.Date;

@Entity
@Table(name="memo")
@SequenceGenerator(name = "memo_id_gen", sequenceName = "memo_id_seq", allocationSize = 1)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Memo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="title")
    private String title;
    @Column(name="description")
    private String description;
    @Column(name="done")
    private Boolean done;
    @Column(name="updated")
    @Temporal(TemporalType.TIMESTAMP)
    private Date updated;
}

Repository

TodoRepository


package com.example.repository.tenantds;

import com.example.entity.multids.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;

import javax.persistence.LockModeType;

public interface TodoRepository extends JpaRepository<Todo, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Todo findById(Long id);
}

MemoRepository


package com.example.repository.appds;

import com.example.entity.singleds.Memo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;

import javax.persistence.LockModeType;

public interface MemoRepository extends JpaRepository<Memo, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Memo findById(Long id);
}

Controller und Dienstleistungen

Regler

Schalten Sie die Datenquelle mit dem Wert des Anforderungsparameters "Tenant" um.

Beispiel


/todo/list?tenant=<Mandantencode>

TodoController


package com.example.controller;

import com.example.entity.tenantds.Todo;
import com.example.service.TodoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;

@RequestMapping(path = "todo")
@RestController
public class TodoController {

    @Autowired
    private TodoService service;

    @GetMapping(path = "list", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public List<Todo> list(
        @RequestParam(value = "tenant", required = true) String tenant) throws Exception {
        List<Todo> lists = service.list(tenant);
        return lists;
    }

    @GetMapping(path = "update", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Todo update(
        @RequestParam(value = "tenant", required = true) String tenant,
        @RequestParam(value = "id", required = true) Long id,
        @RequestParam(value = "title") String title,
        @RequestParam(value = "done", defaultValue = "FALSE") Boolean done,
        @RequestParam(value = "wt", defaultValue = "30") Long waittime) throws Exception {
        Todo todo = service.lockAndUpdate(tenant, id, title, done, new Date(), waittime);
        return todo;
    }

    @GetMapping(path = "insert", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Todo insert(
        @RequestParam(value = "tenant", required = true) String tenant,
        @RequestParam(name = "title", required = true) String title) throws Exception {
        Todo todo = service.insert(tenant, title, Boolean.FALSE, new Date());
        return todo;
    }
}

Bedienung

TodoService


package com.example.service;

import com.example.entity.tenantds.Todo;

import java.util.Date;
import java.util.List;

public interface TodoService {
    List<Todo> list(String tenant);
    Todo lockAndUpdate(String tenant, Long id, String title, Boolean done, Date updated, Long waittime) throws Exception;
    Todo insert(String tenant, String title, Boolean done, Date updated) throws Exception;
}

Es gibt einige Punkte in der Implementierung.

(1) und (3) sind die Implementierungen, die zum Wechseln von Datenquellen in AOP erforderlich sind.

TodoServiceImpl


package com.example.service.impl;

import com.example.aop.SwitchingDataSource;
import com.example.entity.tenantds.Todo;
import com.example.repository.tenantds.TodoRepository;
import com.example.service.TodoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.example.datasource.MultiDataSourceConfigure.TRANSACTION_MANAGER;

@Service
@SwitchingDataSource                                                                         // (1)
@Transactional(readOnly = true, timeout = 10, transactionManager = TRANSACTION_MANAGER)      // (2)
public class TodoServiceImpl implements TodoService {

    @Autowired
    private TodoRepository repository;

    @Override
    public List<Todo> list(String tenant) {                                                  // (3)
        Sort sort = new Sort(new Order(Sort.Direction.DESC, "updated"), new Order(Sort.Direction.DESC, "id"));
        List<Todo> lists = repository.findAll(sort);
        return lists;
    }

    @Transactional(readOnly = false, timeout = 120, rollbackFor = Exception.class, transactionManager = TRANSACTION_MANAGER)
    @Override
    public Todo lockAndUpdate(String tenant, Long id, String title, Boolean done, Date updated, Long waittime) throws Exception {

        Todo todo = repository.findById(id);

        todo.setTitle(title);
        todo.setDone(done);
        todo.setUpdated(updated);

        //Timeout-Testcode
        try {
            TimeUnit.SECONDS.sleep(waittime);
        } catch (InterruptedException e) {
            System.out.println("timeout:" + e.getMessage());
            throw new Exception("timeout");
        }

        repository.save(todo);

        return todo;
    }

    @Transactional(readOnly = false, rollbackFor = Exception.class, transactionManager = TRANSACTION_MANAGER)
    @Override
    public Todo insert(String tenant, String title, Boolean done, Date updated) throws Exception {
        Todo todo = Todo.builder().title(title).done(done).updated(updated).build();
        repository.save(todo);
        return todo;
    }
}

Recommended Posts

Beispiel einer Webanwendung, die mit Spring Boot 1.5 mehrere Datenbanken verwaltet
Spring Boot-Anwendungsentwicklung in Eclipse
Implementieren Sie die Spring Boot-Anwendung in Gradle
Beispielcode, der die Moustache-Vorlagen-Engine mit Spring Boot verwendet
Starten Sie die Entwicklung von Webanwendungen mit Spring Boot
Führen Sie die WEB-Anwendung mit Spring Boot + Thymeleaf aus
Automatische Bereitstellung von in Java entwickelten Webanwendungen mit Jenkins [Spring Boot App Edition]
SameSite-Cookie im Spring Boot (Spring Web MVC + Tomcat)
Melden Sie sich bei Spring Boot-Webanwendungen auf der Microsoft ID-Plattform an
Spring Boot 2.3 Verfügbarkeit von Anwendungen
Holen Sie sich im Frühjahr mehrere Ressourcen, die dem Muster entsprechen
Die Geschichte, dass der Port im Spring-Boot-Beispielprogramm unbrauchbar wird
Legen Sie den Kontextparameter in Spring Boot fest
Beschleunigen Sie das Testen von Validatoren, für die DI im Spring Boot erforderlich ist
Spring Boot 2 Multiprojekt mit Gradle
CICS-Java-Anwendung ausführen- (4) Spring Boot-App
Wichtige Änderungen in Spring Boot 1.5
NoHttpResponseException in Spring Boot + WireMock
Spring Boot-Anwendung, die DB-Verbindungseinstellungen mit Parametern angibt
[Java] Beispielprojekt zum Entwickeln von Webanwendungen mit Spring Boot
Einfache Entwicklung von Webanwendungen mit STS und Spring Boot. In 10 Minuten.
Erstellen wir eine Buchverwaltungs-Webanwendung mit Spring Boot part1
Lassen Sie uns mit Spring Boot part3 eine Webanwendung für die Buchverwaltung erstellen
Lassen Sie uns mit Spring Boot part2 eine Webanwendung für die Buchverwaltung erstellen
Frühlingsstiefel Hallo Welt in Eclipse
Überprüfungspunkte für den Spring Boot-Anwendungscode
Spring Boot: Restful API-Beispielprojekt
Erstellen Sie eine Anfrage-App mit Spring Boot
Implementieren Sie die REST-API mit Spring Boot
Was ist @Autowired im Spring Boot?
Ein Memo, das Spring Boot berührte
Verwendung von Thymeleaf mit Spring Boot
Spring Boot2-Webanwendungsentwicklung mit Visual Studio Code SQL Server-Verbindung
Eine Geschichte über ein in Java geschriebenes Spring Boot-Projekt, das Kotlin unterstützt
Autowired-Felder in einer Klasse, die TextWebSocketHandler in Spring Boot erbt, werden NULL
Java-Anfänger haben versucht, mit Spring Boot eine einfache Webanwendung zu erstellen
Spring Boot2-Webanwendungsentwicklung mit Visual Studio Code Hello World-Erstellung
WAR die WEB-Anwendung per Spring Boot und stellen Sie sie auf dem Tomcat-Server bereit
Mein Memorandum, dass ich ValidationMessages.properties UTF8 in Spring Boot erstellen möchte
Die Geschichte, dass das Servlet nicht in die Java-Webanwendung geladen werden konnte
Verarbeitung beim Starten einer Anwendung mit Spring Boot
Starten Sie mit IntelliJ ein (altes) Spring Boot-Projekt
Erstellen Sie mit Gradle ein Spring Boot + Docker-Image
Statische Dateizugriffspriorität beim Spring Boot
Spring Boot-Protokoll im JSON-Format ausgeben
Memorandum zum Herunterladen lokaler Dateien mit Spring Boot
Lösen Sie die Thymeleaf-Syntaxprüfung in Spring Boot
[Trainieren! ] Zeigen Sie Hello World mit Spring Boot an
Starten Sie die Nginx + Spring Boot-Anwendung mit Docker-Compose
Verwenden Sie die DynamoDB-Abfragemethode mit Spring Boot
DI SessionScope Bean im Spring Boot 2-Filter
Rolle von JSP in Webanwendungen [Java]
Ändern Sie das Sitzungszeitlimit in Spring Boot
Beispielcode für die DB-Steuerung durch deklarative Transaktion mit Spring Boot + Spring Data JPA
Ich habe versucht, eine Webanwendung voller Fehler mit Spring Boot zu klonen
So legen Sie Umgebungsvariablen in der Eigenschaftendatei der Spring-Boot-Anwendung fest