[JAVA] Exemple d'application Web qui gère plusieurs bases de données avec Spring Boot 1.5

Aperçu

Ceci est un exemple de code d'une application Web qui gère plusieurs bases de données à l'aide de Spring Boot.

environnement

référence

Base de données

Comme le montre la figure ci-dessous, il existe trois bases de données gérées par l'application, l'une (test_dev3) est une base de données commune à toutes les applications et les deux autres (test_dev1, test_dev2) sont traitées comme multi-locataires. Les schémas de base de données de test_dev1 et test_dev2 sont les mêmes, mais les données à stocker sont différentes.

Dans cet exemple d'application, la valeur du paramètre de requête bascule entre les bases de données test_dev1 et test_dev2. De plus, puisqu'il s'agit d'un exemple d'application, la table à gérer n'a pas de signification particulière (appropriée). De plus, JpaTransactionManager ne semble pas être capable d'exécuter des transactions couvrant plusieurs bases de données (sources de données).

Dessin structurel

ds.png

Paramètres de la source de données

Définissez la source de données dans application.yml comme suit.

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

Code de configuration de la source de données

test_dev1

Paramètres de source de données pour le client A. L'implémentation consiste simplement à créer la source de données à partir des paramètres du fichier de propriétés. Le gestionnaire d'entité et le gestionnaire de transactions sont implémentés dans différentes classes.

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

Paramètres de source de données pour le client B. Il s'agit de la même implémentation que le locataire 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;
    }
}

Définition de la source de données qui regroupe test_dev1 et test_dev2

Implémentez le gestionnaire d'entités et le gestionnaire de transactions qui font référence à la source de données pour chaque client implémenté ci-dessus. Le point de mise en œuvre est

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();

        //Définir la source de données pour basculer
        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;
    }
}

Paramètres pour basculer entre test_dev1 et test_dev2

Implémentez le processus d'héritage et de commutation de AbstractRoutingDataSource de 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;
        }
        //Mappage des locataires et des sources de données
        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();
    }
}

Enum qui gère les types de locataires.

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

Changer de source de données avec AOP

Il existe plusieurs façons de changer de source de données, mais cet exemple d'application utilise la fonctionnalité Spring AOP. Défini pour la méthode qui a l'argument «tenant» de la classe avec l'annotation suivante. La source de données est commutée par la valeur de cet argument appelé tenant.

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

Ajustez l'annotation Order afin que la source de données soit commutée avant la transaction. Le point de mise en œuvre est

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

Source de données pour test_dev3

Comme il n'est pas nécessaire de basculer la source de données commune à toutes les applications, cette classe implémente même le gestionnaire d'entités et le gestionnaire de transactions. À propos, l'annotation principale est ajoutée afin que ce paramètre devienne la valeur par défaut.

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é et référentiel

Comme indiqué ci-dessous, les packages pour les clients et les applications courantes sont gérés séparément. Notez que les classes de service supérieures aux entités et aux référentiels n'ont pas besoin d'être séparées car il n'est pas nécessaire de séparer les packages.

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

entité

Il n'y a rien de spécial dans l'implémentation, il suffit de séparer les packages.

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

Dépôt

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

Contrôleurs et services

manette

Basculez la source de données avec la valeur du paramètre de demande «tenant».

Exemple


/todo/list?tenant=<Code locataire>

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

un service

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

Il y a quelques points dans la mise en œuvre.

(1) et (3) sont les implémentations requises pour changer de source de données dans AOP.

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

        //Code de test de délai d'expiration
        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

Exemple d'application Web qui gère plusieurs bases de données avec Spring Boot 1.5
Développement d'applications Spring Boot dans Eclipse
Implémenter l'application Spring Boot dans Gradle
Exemple de code qui utilise le moteur de modèle Moustache avec Spring Boot
Démarrez le développement d'applications Web avec Spring Boot
Exécutez l'application WEB avec Spring Boot + Thymeleaf
Déployer automatiquement des applications Web développées en Java à l'aide de Jenkins [Spring Boot App Edition]
Cookie SameSite dans Spring Boot (Spring Web MVC + Tomcat)
Connectez-vous aux applications Web Spring Boot sur la plate-forme Microsoft ID
Disponibilité de l'application Spring Boot 2.3
Obtenez plusieurs ressources qui correspondent au modèle au printemps
L'histoire du port devenant inutilisable dans l'exemple de programme Spring Boot
Définir le paramètre contextuel dans Spring Boot
Accélérez les tests des validateurs qui nécessitent DI dans Spring Boot
Multi-projets Spring Boot 2 avec Gradle
Application Java CICS-Run - (4) Application Spring Boot
Changements majeurs dans Spring Boot 1.5
NoHttpResponseException dans Spring Boot + WireMock
Application Spring Boot qui spécifie les paramètres de connexion à la base de données avec des paramètres
[Java] Exemple de projet de développement d'applications Web avec Spring Boot
Développement facile d'applications Web avec STS et Spring Boot. Dans 10 minutes.
Créons une application Web de gestion de livres avec Spring Boot part1
Créons une application Web de gestion de livres avec Spring Boot part3
Créons une application Web de gestion de livres avec Spring Boot part2
Spring Boot Hello World dans Eclipse
Points de révision du code de l'application Spring Boot
Spring Boot: exemple de projet d'API Restful
Créer une application d'enquête avec Spring Boot
Implémenter l'API REST avec Spring Boot
Qu'est-ce que @Autowired dans Spring Boot?
Un mémo qui a touché Spring Boot
Comment utiliser Thymeleaf avec Spring Boot
Développement d'applications Web Spring Boot2 avec connexion Visual Studio Code SQL Server
Une histoire sur un projet Spring Boot écrit en Java qui prend en charge Kotlin
Les champs auto-câblés dans une classe qui hérite de TextWebSocketHandler dans Spring Boot deviennent NULL
Un débutant Java a essayé de créer une application Web simple à l'aide de Spring Boot
Développement d'applications Web Spring Boot2 avec création de Visual Studio Code Hello World
WAR l'application WEB par Spring Boot et la déployer sur le serveur Tomcat
Mon mémorandum que je veux faire ValidationMessages.properties UTF8 dans Spring Boot
L'histoire selon laquelle le servlet n'a pas pu être chargé dans l'application Web Java
Traitement lors du démarrage d'une application avec Spring Boot
Lancer un (ancien) projet Spring Boot avec IntelliJ
Créer une image Spring Boot + Docker avec Gradle
Priorité d'accès aux fichiers statiques dans Spring Boot
Sortie du journal Spring Boot au format json
Mémorandum de téléchargement de fichier local avec Spring Boot
Desserrer la vérification de la syntaxe de Thymeleaf dans Spring Boot
[Entraine toi! ] Affichez Hello World avec Spring Boot
Lancez l'application Nginx + Spring Boot avec docker-compose
Utiliser la méthode de requête DynamoDB avec Spring Boot
DI SessionScope Bean dans le filtre Spring Boot 2
Rôle de JSP dans les applications Web [Java]
Modifier le délai d'expiration de la session dans Spring Boot
Exemple de code pour le contrôle de la base de données par transaction déclarative avec Spring Boot + Spring Data JPA
J'ai essayé de cloner une application Web pleine de bugs avec Spring Boot
Comment définir des variables d'environnement dans le fichier de propriétés de l'application Spring Boot