Résumé de l'échec de Springboot+mybatis-plus+dynamic-datasource lors du changement de source de données

Springboot+mybatis-plus+dynamic-datasource+Druid résumé de l'échec de la commutation de sources de données multi-sources

0. Préface

Background Dynamic-DataSource est un excellent composant de gestion de sources de données multi-sources open source par @小锅盖 de l'équipe baomidou (baomidou). Il est très pratique et puissant, et il peut être utilisé immédiatement dans Spring Boot. Mais il existe également de nombreux petits problèmes, principalement dus à l'échec de la commutation de plusieurs sources de données. Notre entreprise est également fortement dépendante dynamic-datasourceet certains problèmes ont également été résolus. Organisez et résumez par la présente. Tout d’abord, soulignons quelques problèmes que vous pourrez examiner.

  1. Prise en charge des sous-threads pour hériter du code source de données du thread principal https://github.com/baomidou/dynamic-datasource/issues/502
  2. De plus, @DS utilise la base de données postgrel et le changement de source de données échoue https://github.com/baomidou/dynamic-datasource/issues/385
  3. "Commutation imbriquée de sources de données multicouches, invalide lorsque les transactions sont activées" https://github.com/baomidou/dynamic-datasource/issues/248
  4. Concernant les raisons de l'échec du changement de source de données sous-thread, certains étudiants ont également donné les meilleures pratiques de leurs propres projets dans github Issues. Cependant, compte tenu de la polyvalence de ce composant et du manque de scénarios asynchrones, l'auteur a donné la réponse : , "Utiliser moins asynchrone, je ne veux pas prendre en charge.". Ce camarade de classe continue toujours à prêcher et à énumérer les faits, peut-être que @小锅街 corrigera parfaitement ce défaut dans la prochaine version, alors restez à l'écoute
    insérer la description de l'image ici

insérer la description de l'image ici

1. Résumé du scénario de défaillance de la source de données du commutateur de source de données dynamique

1. Dans le cas de l'intégration Spring-Batch, le changement de source de données est anormal

Adresse du problème github https://github.com/baomidou/dynamic-datasource/issues/340

Solution:

La méthode donnée par l'auteur, ne pas utiliser de transactions ou de transactions séparées lors de la lecture, et ouvrir les transactions séparément lors de l'écriture
insérer la description de l'image ici

2. L'utilisation des annotations de transaction natives Spring entraîne l'échec de la commutation entre plusieurs sources de données

Solution

Vous devrez peut-être vérifier les classes et les méthodes impliquées dans le lien d'appel pour voir s'il existe une annotation @Transactional. Si vous devez assurer la cohérence des transactions de plusieurs bases de données si nécessaire, vous devez utiliser une solution de transactions distribuées. L'auteur de Seate a soumis un PR dans mybatis-plus pour résoudre ce problème, utilisez simplement @DSTransactional.

Analyse de principe :

Dans le projet Spring, Spring fournit la fonction de gestion des transactions, qui est principalement réalisée via l'annotation @Transactional. Dans la méthode annotée par @Transactional, Spring maintiendra un ConnectionHolder, qui contient la connexion à la base de données utilisée par la transaction en cours, ce qui garantit que dans cette transaction, toutes les opérations de base de données sont terminées dans une connexion à la base de données, ce qui garantit également l'atomicité des transactions.

Mais pour le moment, si vous utilisez une source de données dynamique pour changer de source de données, des problèmes surviendront. Parce que Dynamic-DataSource change de source de données avant d'appeler la méthode via AOP. Mais dans la méthode d'annotation @Transactional, Spring a déjà obtenu une connexion à partir d'une certaine source de données, et cette connexion ne changera pas tout au long de la transaction. Par conséquent, lorsque vous souhaitez basculer vers une autre source de données quelque part dans cette transaction, bien que la source de données dynamique puisse changer de source de données, elle ne peut plus modifier la connexion à la base de données obtenue par la transaction Spring, ce qui entraîne l'opération de base de données réelle. base de données originale.

Solutions

  1. Ne laissez pas les opérations qui nécessitent de changer de source de données être effectuées dans la transaction, si cela n'est pas nécessaire, n'ajoutez pas le principe de transaction.
  2. Si cela doit être fait dans une transaction, vous devrez peut-être vérifier les classes et les méthodes impliquées dans le lien d'appel pour voir s'il existe une annotation @Transactional. S'il est nécessaire d'assurer la cohérence transactionnelle de plusieurs bases de données,Vous devez adopter une solution de transaction distribuée ou utiliser une annotation de transaction multi-sources de données locale @DSTransactional Ceci est écrit par l'auteur de Seata et il est très fiable
  3. Si vous ne le souhaitez pas @DSTransactional, vous pouvez utiliser le cadre d'implémentation de transactions global JTA (Java Transaction API) au lieu de "Spring Boot+Atomikos pour la gestion des transactions distribuées de plusieurs sources de données et exemples" pour gérer les transactions entre plusieurs sources de données. Cela nécessite la prise en charge du pilote XA correspondant. Mais cela apportera une certaine surcharge de performances, adaptée aux scénarios qui nécessitent une grande cohérence des données et doivent avoir une certaine expérience technique. Si vous souhaitez éviter des ennuis, utilisez l'annotation fournie par l'auteur @DSTransactional.

3. Causé par des appels de méthodes internes

Ce problème existe pour toutes les fonctions basées sur AOP. Alors appelez dans la méthode actuelle. Le changement de source de données ne sera pas déclenché. Je ne ferai pas ici une explication détaillée, mais vous pouvez parler des conditions et des principes de l'agence dynamique pour comprendre,

Au Spring, le principe de mise en œuvre du changement de source de données est basé sur le proxy AOP. Par conséquent, si vous appelez une autre méthode qui doit changer de source de données à l'intérieur de la méthode,

Solution

Extrayez la méthode qui doit basculer la source de données vers un autre service, puis appelez-la en externe.

4. Problèmes liés au cadre Shiro

Ceci est proposé dans le document officiel, et nous ne l'avons pas rencontré dans notre projet.
Lorsque vous utilisez le framework Shiro, si vous utilisez des classes injectées @Autowired, vous pouvez rencontrer des problèmes d'invalidation des annotations de transaction et des annotations de cache.

Analyse de principe

Dans le framework Spring, le processus d'instanciation et d'initialisation du Bean implique plusieurs points d'interception clés, notamment BeanFactoryPostProcessor, BeanPostProcessor, etc. Dans ce processus, l'ordre de chargement de ces points d'interception a sa stricte priorité :

  1. BeanFactoryPostProcessor : Le premier chargé, il sera intercepté après le chargement de BeanDefinition et avant l'instanciation (instanciation). À l'heure actuelle, toutes les informations de définition du Bean ont été chargées dans Spring, mais tous les Beans n'ont pas été instanciés.

  2. BeanPostProcessor : Par rapport à BeanFactoryPostProcessor, son temps de chargement est plus tardif et il sera intercepté lors des phases d'initialisation et de destruction du cycle de vie du Bean.

  3. Bean : L'ordre de chargement du Bean ordinaire est après les deux ci-dessus.

La clé du problème est l’ordre de chargement. Étant donné que ShiroFilterFactoryBean de Shiro est un BeanPostProcessor, il sera chargé plus tôt que les beans ordinaires (tels que UserController, UserService, etc.). Étant donné qu'AOP est implémenté via BeanPostProcessor, si ShiroFilterFactoryBean a été chargé avant le traitement AOP, alors le Bean injecté dans ShiroFilterFactoryBean ne peut pas être amélioré par AOP (comme l'amélioration de la transaction de l'annotation @Transactional), donc le Bean d'injection de dépendance est une question invalidée.

Par exemple, dans la chaîne de dépendances suivante :

ShiroFilterFactoryBean -> SecurityManager -> UserRealm -> IUserService

Les autres services dont dépend IUserService en aval, tels que MenuService, RoleService, etc., ne fonctionneront pas correctement pour les raisons ci-dessus. Les utilisateurs utilisant des annotations telles que @Transactional dans ces services n'auront pas l'effet escompté.

Solution

1. Obtenez manuellement la méthode Bean, qui peut être obtenue manuellement via ApplicationContext.getBean();
2. Utilisez l'annotation @Lazy pour retarder l'initialisation du Bean et instancier le Bean une fois le traitement AOP terminé.

@Component
public class UserRealm extends AuthorizingRealm {
    
    

    @Lazy
    @Autowired
    private IUserService userService;
	//... 省略其他无关的内容
}

5. Ordre d'initialisation PostConstruct

Il
s'agit d'une fonctionnalité du framework Spring, qui implique le cycle de vie de Spring et le processus d'initialisation du Bean.

Lors du processus de création d'un objet Bean par le conteneur Spring, il se déroulera dans l'ordre suivant :

1. Instanciation : utilisez le mécanisme de réflexion pour créer l'objet Bean correspondant en fonction du fichier de configuration.

2. Affectation d'attributs : selon le fichier de configuration, utilisez la méthode set pour attribuer des attributs.

3. Post-processeur Bean Avant : effectuez un traitement avant l'initialisation de l'objet Bean.

4. Initialisation : effectuez une initialisation personnalisée du Bean.

  • L'objet implémente l'interface InitializingBean et exécute la méthode afterPropertiesSet.

  • L'objet a une méthode configurée avec l'annotation @PostConstruct, qui sera exécutée.

  • La méthode d'initialisation spécifiée par init-method dans le fichier de configuration.
    5. Post-processeur Bean après : effectuez un traitement après l'initialisation de l'objet Bean.

Il ressort de la séquence ci-dessus que lorsque l'initialisation définie par l'utilisateur (y compris @PostConstruct, afterPropertiesSet, init-method) est exécutée, le post-processeur Bean (y compris AOP) n'a pas encore été exécuté, donc AOP ne peut pas être obtenu. à ce stade Objets proxy améliorés.

L'initialisation comprend : l'annotation PostConstruct, l'interface InitializingBean et la méthode d'initialisation personnalisée. A ce stade, tout AOP est inutile.

@Component
public class MyConfiguration {
    
    
    @Resource
    private UserMapper userMapper;
    @DS("slave")
    @PostConstruct
    public void init(){
    
    
        // 无法选择正确的数据源
        userMapper.selectById(1);
    }
}

Solution:

Écoutez l'événement d'achèvement du démarrage du conteneur et effectuez l'initialisation une fois le conteneur terminé.

@Component
public class MyConfiguration {
    
    

    @DS("slave")
    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
    
    
        // 成功选择正确的数据源
        userMapper.selectById(1);
    }
}

Code source Spring associé : `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean

6. La version Druid est trop basse

Si votre version de Druid est trop basse, un échec de changement de source de données peut se produire en cas de concurrence élevée.

Solution : mettez à jour votre version de Druid vers la version 1.1.22 et supérieure.

7. Les threads nouvellement ouverts provoquent le flux parallèle ParallelStream de (@Async ou java8 et d'autres méthodes.)

Dans les scénarios où de nouveaux threads doivent être ouverts, comme l'utilisation de @Async ou de ParallelStream de java8, le changement de source de données entraînera également des problèmes d'invalidation.
Lorsqu'il s'agit de tâches simultanées, l'annotation @Async de Spring ou ParallelStream de Java 8 sont nos outils couramment utilisés. Ils peuvent nous aider à traiter les tâches dans de nouveaux threads et à améliorer l'efficacité du programme. Cependant, étant donné que ces nouveaux threads sont indépendants du thread d'origine, le changement de source de données dans le thread d'origine ne peut pas être transféré vers le nouveau thread, ce qui peut amener le fonctionnement de la base de données dans le nouveau thread à utiliser toujours le thread d'origine. , la source de données ne peut pas être commutée correctement.

Solution

Ajoutez les annotations DS correspondantes à la méthode nouvellement ouverte. Ce problème peut être résolu en ajoutant l'annotation DS correspondante sur la nouvelle méthode. L'annotation DS peut spécifier la source de données utilisée par cette méthode, de sorte que même si cette méthode est exécutée dans un nouveau thread, elle utilisera toujours la source de données que nous avons spécifiée via l'annotation DS.

Par exemple:

@DS("second")
@Async
public void asyncMethod() {
    
    
    // 这个方法将在新的线程中执行,并使用"second"数据源
}

De même, pour la méthode exécutée par ParallelStream de java8, on peut également ajouter une annotation DS à la méthode pour spécifier la source de données :

@DS("second")
public void parallelMethod() {
    
    
    List<String> data = ...;
    data.parallelStream().forEach(item -> {
    
    
        asyncMethod() // 这个方法将在新的线程中执行,并使用"second"数据源
    });
}

3. Références

  1. dépôt GitHub de Dynamic-Datasource ↗ : dépôt GitHub officiel de Dynamic-DataSource, comprenant des ressources telles que du code source, de la documentation et des exemples.

Je suppose que tu aimes

Origine blog.csdn.net/wangshuai6707/article/details/132582997
conseillé
Classement