Préface
Spring spécifie 7 types de comportements de propagation des transactions dans l'interface TransactionDefinition. Le comportement de propagation des transactions est une fonction d'amélioration des transactions propres au framework Spring, et il n'appartient pas au comportement de la base de données du fournisseur réel de transaction. Il s'agit d'une boîte à outils puissante que Spring nous fournit, et l'utilisation de lignes de propagation de transaction peut fournir de nombreuses commodités pour nos efforts de développement. Mais les gens ont beaucoup de malentendus à ce sujet, et vous devez avoir entendu la rumeur selon laquelle "l'activité de la méthode de service est préférable de ne pas être imbriquée". Pour utiliser correctement les outils, vous devez d'abord comprendre les outils. Cet article présente en détail les sept comportements de propagation de transaction et présente les principaux exemples de code du contenu.
Concepts de base
1. Qu'est-ce que le comportement de communication des transactions?
Le comportement de propagation des transactions est utilisé pour décrire comment les transactions se propagent lorsque les méthodes modifiées par un certain comportement de propagation des transactions sont imbriquées dans une autre méthode.
Utilisez le pseudo-code pour expliquer:
public void methoda () {methodb (); // dosomething} @transaction (propagation = xxx) public void methodb () {// dosomething} methodA() dans le code appelle methodB() dans Nested, et le comportement de propagation de transaction de methodB() est déterminé par @Transaction(Propagation=XXX) . Il convient de noter ici que methodA() ne démarre pas la transaction, et la méthode de modification d'un certain comportement de propagation de transaction ne doit pas être appelée dans la méthode périphérique de démarrage de la transaction.
2. Sept comportements de propagation des transactions au printemps
| Type de comportement de propagation des transactions | illustrer |
|---|---|
| Propagation_requure | S'il n'y a pas de transaction actuellement, créez une nouvelle transaction et s'il y a déjà une transaction, ajoutez-la à la transaction. C'est le choix le plus courant. |
| Propagation_supports | Prend en charge la transaction actuelle et s'il n'y a actuellement aucune transaction, elle sera exécutée de manière non transactionnelle. |
| Propagation_mandatoire | Utilisez la transaction actuelle et lancez une exception en cas de transaction actuellement. |
| Propagation_requires_new | Créer une nouvelle transaction. Si la transaction existe actuellement, suspendez la transaction actuelle. |
| Propagation_not_supported | Exécutez les opérations de manière non transactionnelle et si une transaction existe actuellement, la transaction actuelle est suspendue. |
| Propagation_never | S'exécute de manière non transactionnelle et lance une exception si une transaction existe actuellement. |
| Propagation_nesed | Si une transaction existe actuellement, elle est exécutée dans une transaction imbriquée. S'il n'y a actuellement aucune transaction, effectuez une opération similaire à Propagation_Required. |
La définition est très simple et facile à comprendre. Passons à la section Test de code pour vérifier si notre compréhension est correcte.
Vérification du code
Le code de cet article est présenté en deux couches dans une structure traditionnelle à trois couches, à savoir le service et la couche DAO. Spring est responsable de l'injection de dépendances et de la gestion des transactions d'annotation. La couche DAO est implémentée par MyBatis. Vous pouvez également utiliser n'importe quelle méthode préférée, telle que Hibernate, JPA, JDBCTemplate, etc. La base de données utilise la base de données MySQL, et vous pouvez également utiliser n'importe quelle base de données compatible avec les transactions, qui n'affectera pas les résultats de vérification.
Nous créons d'abord deux tables dans la base de données:
User1
Créer un tableau `user1` (` id` entier non signé pas null auto_increment, `name` varchar (45) pas null default '', clé primaire (` id`)) moteur = innodb;
user2
Créer la table `user2` (` id` entier non signé pas null auto_increment, `name` varchar (45) pas null default '', clé primaire (` id`)) moteur = innodb;
Écrivez ensuite le code de calque Bean et DAO correspondant:
User1
classe publique User1 {ID entier privé; nom de chaîne privé; // Les méthodes Get and Set sont omises ...}User2
classe publique User2 {ID entier privé; nom de chaîne privé; // Les méthodes Get and Set sont omises ...}User1mapper
Interface publique User1Mapper {int insert (User1 Record); User1 selectByPrimaryKey (INTER ID); // D'autres méthodes sont omises ...}User2mapper
Interface publique User2Mapper {int insert (User2 Record); User2 selectByPrimaryKey (INTER ID); // D'autres méthodes sont omises ...}Enfin, le code de vérification spécifique est implémenté par la couche de service, et nous la répertorierons dans les situations suivantes.
1.Propagation_Required
Nous ajoutons des attributs Propagation.REQUIRED .
Méthode User1Service:
@ServicePublic Class User1ServiceIMPL implémente user1Service {// Omit autre ... @Override @Transactional (propagation = propagation.requied) public void addRequired (user1 user) {user1mapper.insert (user); }}Méthode User2Service:
@ServicePublic Class user2ServiceIMPl implémente user2Service {// omettre d'autres ... @Override @Transactional (propagation = propagation.requage) public void addRequired (user2 user) {user2Mapper.Insert (user); } @Override @TransActional (propagation = propagation.requered) public void addRequiredException (user2 user) {user2mapper.insert (user); lancer un nouveau runtimeException (); }} 1.1 Scène 1
Cette méthode périphérique de scénario ne permet pas les transactions.
Méthode de vérification 1:
@Override public void nottransaction_exception_required_required () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequired (User2); lancer un nouveau runtimeException (); }Méthode de vérification 2:
@Override public void nottransaction_required_required_exception () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequiredException (user2); }Exécutez les méthodes de vérification séparément et les résultats:
Analyse des résultats de la base de données du numéro de série de la méthode de vérification
| Numéro de série de la méthode de vérification | Résultats de la base de données | Analyse des résultats |
|---|---|---|
| 1 | "Zhang San" et "Li Si" sont tous deux insérés. | La méthode périphérique n'a pas démarré la transaction et l'insertion des méthodes "Zhang San" et "Li si" s'exécute indépendamment dans leurs propres transactions. La méthode périphérique anormale n'affecte pas l'insertion interne des méthodes "Zhang San" et "Li si". |
| 2 | "Zhang San" est inséré, mais "Li si" n'est pas inséré. | La méthode périphérique n'a pas de transactions, et les méthodes d'insertion de "Zhang San" et "Li si" sont toutes deux exécutées indépendamment dans leurs propres transactions, donc l'insertion de la méthode "Li Si" ne fera que reculer la méthode "Li si", et l'insertion de la méthode "Zhang San" ne sera pas affectée. |
Conclusion: Grâce à ces deux méthodes, nous prouvons que la méthode interne modifiée par propagation.Required ouvrira récemment ses propres transactions lorsque la méthode périphérique n'ouvre pas la transaction, et les transactions ouvertes sont indépendantes les unes des autres et ne s'interfèrent pas entre elles.
1.2 Scène 2
La méthode périphérique démarre la transaction, qui est un scénario avec un taux d'utilisation relativement élevé.
Méthode de vérification 1:
@Override @Transactional (propagation = propagation.requured) public void transaction_exception_required_required () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequired (User2); lancer un nouveau runtimeException (); }Méthode de vérification 2:
@Override @Transactional (propagation = propagation.requured) public void transaction_required_required_exception () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequiredException (user2); }Méthode de vérification 3:
@Transactional @Override public void transaction_required_required_exception_try () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); essayez {user2Service.adDrequiredException (user2); } catch (exception e) {System.out.println ("Method Rollback"); }}Exécutez les méthodes de vérification séparément et les résultats:
| Numéro de série de la méthode de vérification | Résultats de la base de données | Analyse des résultats |
|---|---|---|
| 1 | "Zhang San" et "Li Si" n'ont pas été insérés. | La méthode périphérique démarre la transaction, la méthode interne rejoint la transaction de la méthode périphérique, la méthode périphérique recule et la méthode interne doit également être annulée. |
| 2 | "Zhang San" et "Li Si" n'ont pas été insérés. | La méthode périphérique ouvre la transaction, la méthode interne ajoute la transaction de la méthode périphérique, la méthode interne jette un recul d'exception et la méthode périphérique perçoit l'exception provoquant la transaction globale de la transaction. |
| 3 | "Zhang San" et "Li Si" n'ont pas été insérés. | La méthode périphérique ouvre la transaction, la méthode interne rejoint la transaction de la méthode périphérique et la méthode interne lance un recul d'exception. Même si la méthode est capturée et n'est pas perçue par la méthode périphérique, toute la transaction est toujours en arrière. |
Conclusion: Les résultats expérimentaux ci-dessus montrent que lorsque la méthode périphérique ouvre la transaction, les méthodes internes modifiées par Propagation.REQUIRED sera ajoutée à la transaction de la méthode périphérique. Toutes les méthodes internes et les méthodes périphériques modifiées par Propagation.REQUIRED Require appartiennent à la même transaction. Tant qu'une méthode recule, la transaction entière sera annulée.
2.propagation_requires_new
Nous ajoutons l'attribut Propagation.REQUIRES_NEW aux méthodes correspondantes de l'utilisateur1Service et user2Service.
Méthode User1Service:
@ServicePublic Class user1ServiceIMPl implémente user1Service {// omettre d'autres ... @Override @Transactional (propagation = propagation.requires_new) public void addRequiresNew (user1 user) {user1mapper.insert (user); } @Override @TransActional (propagation = propagation.requered) public void addRequired (user1 user) {user1mapper.insert (user); }}Méthode User2Service:
@ServicePublic class user2ServiceIMPl implémente user2Service {// omettre autre ... @Override @Transactional (propagation = propagation.requires_new) public void addRequireSNew (user2 user) {user2mapper.insert (user); } @Override @Transactional (propagation = propagation.requires_new) public void addRequiresNewException (user2 user) {user2mapper.insert (user); lancer un nouveau runtimeException (); }} 2.1 Scène 1
La méthode périphérique n'activait pas les transactions.
Méthode de vérification 1:
@Override public void nottransaction_exception_requiresnew_requiresnew () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequireSNew (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequireSNew (User2); lancer un nouveau runtimeException (); }Méthode de vérification 2:
@Override public void nottransaction_requireresnew_requiresnew_exception () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequireSNew (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequireSNewException (user2); }Exécutez les méthodes de vérification séparément et les résultats:
| Numéro de série de la méthode de vérification | Résultats de la base de données | Analyse des résultats |
|---|---|---|
| 1 | "Zhang San" est inséré et "Li si" est inséré. | La méthode périphérique n'a aucune transaction. L'insertion des méthodes "Zhang San" et "Li si" est exécutée indépendamment dans leurs propres transactions. Le recul d'exception de la méthode périphérique n'affectera pas la méthode interne. |
| 2 | "Zhang San" est inséré, "Li si" n'est pas inséré | La méthode périphérique ne démarre pas la transaction. L'insertion de la méthode "Zhang San" et l'insertion de la méthode "li si" démarrent respectivement leurs propres transactions. L'insertion de la méthode "li si" lance un recul d'exception et d'autres transactions ne sont pas affectées. |
Conclusion: Grâce à ces deux méthodes, nous prouvons que la méthode interne modifiée par Propagation.REQUIRES_NEW commencera récemment ses propres transactions, et les transactions ouvertes sont indépendantes les unes des autres et n'interfèrent pas entre elles.
2.2 Scène 2
La méthode périphérique démarre la transaction.
Méthode de vérification 1:
@Override @Transactional (propagation = propagation.requured) public void transaction_exception_required_requiresnew_requireSnew () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequireSNew (User2); User2 user3 = new User2 (); user3.SetName ("Wang Wu"); user2Service.adDrequireSNew (User3); lancer un nouveau runtimeException (); }Méthode de vérification 2:
@Override @Transactional (propagation = propagation.requured) public void transaction_required_requiresnew_requiresnew_exception () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequireSNew (User2); User2 user3 = new User2 (); user3.SetName ("Wang Wu"); user2Service.adDrequireSNewException (User3); }Méthode de vérification 3:
@Override @Transactional (propagation = propagation.requured) public void transaction_required_requiresnew_requiresnew_exception_try () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.adDrequired (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.adDrequireSNew (User2); User2 user3 = new User2 (); user3.SetName ("Wang Wu"); essayez {user2Service.adDrequiresNewException (user3); } catch (exception e) {System.out.println ("Rollingback"); }}Exécutez les méthodes de vérification séparément et les résultats:
| Numéro de série de la méthode de vérification | Résultats de la base de données | Analyse des résultats |
|---|---|---|
| 1 | "Zhang San" n'a pas été inséré, "Li Si" a été inséré et "Wang Wu" a été inséré. | La méthode périphérique démarre la transaction, insère une transaction de la méthode "Zhang SAN" et de la méthode périphérique, insère respectivement la méthode "li si" et la méthode "Wang Wu" dans la transaction nouvellement créée indépendante. La méthode périphérique lance une exception et ne fait que reculer la même transaction que la méthode périphérique, de sorte que la méthode d'insertion de la méthode "Zhang SAN" recule. |
| 2 | "Zhang San" n'a pas été inséré, "Li Si" a été inséré et "Wang Wu" n'a pas été inséré. | La méthode périphérique démarre la transaction, insère une transaction de la méthode "Zhang SAN" et de la méthode périphérique, insère la méthode "li si" et la méthode "Wang Wu" dans de nouvelles transactions indépendantes. Lorsque la méthode "Wang Wu" est insérée, la transaction insérée dans la méthode "Wang Wu" est annulée. L'exception continue d'être jetée et est perçue par la méthode périphérique. La transaction de la méthode périphérique est également reculée, de sorte que la méthode "Zhang San" est également reculée. |
| 3 | "Zhang San" est inséré, "Li si" est inséré, et "Wang Wu" n'est pas inséré. | La méthode périphérique démarre la transaction, insère une transaction de la méthode "Zhang SAN" et de la méthode périphérique, insère la méthode "li si" et la méthode "Wang Wu" dans de nouvelles transactions indépendantes. La méthode "Wang Wu" est insérée et la transaction qui insère la méthode "Wang Wu" est annulée. L'exception est capturée et ne sera pas perçue par la méthode périphérique. La transaction de la méthode périphérique n'est pas annulée, de sorte que l'insertion de la méthode "Zhang SAN" est insérée avec succès. |
Conclusion: Lorsque la méthode périphérique ouvre la transaction, la méthode interne modifiée par Propagation.REQUIRES_NEW ouvrira toujours des transactions indépendantes séparément, et elle est également indépendante des transactions de méthode externe. La méthode interne, la méthode interne et les transactions de méthode externe sont indépendantes les unes des autres et n'interfèrent pas entre elles.
3.Propagation_nesed
Nous ajoutons des attributs Propagation.NESTED aux méthodes correspondantes de l'utilisateur1Service et user2Service.
Méthode User1Service:
@ServicePublic Class User1ServiceImplt implémente user1Service {// omettre autre ... @Override @Transactional (propagation = propagation.nest) public void adddeSéed (user1 user) {user1mapper.insert (user); }}Méthode User2Service:
@ServicePublic class user2ServiceImplt implémente user2Service {// omettre autre ... @Override @Transactional (propagation = propagation.nest) public void adddeSéed (user2 user) {user2mapper.insert (user); } @Override @Transactional (propagation = propagation.nest) public void addNestedException (user2 user) {user2mapper.insert (user); lancer un nouveau runtimeException (); }} 3.1 Scène 1
Cette méthode périphérique de scénario ne permet pas les transactions.
Méthode de vérification 1:
@Override public void nottransaction_exception_nest_nest () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.AddNested (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.AddNested (user2); lancer un nouveau runtimeException (); }Méthode de vérification 2:
@Override public void nottransaction_neested_nest_exception () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.AddNested (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.AddNestedException (user2); }Exécutez les méthodes de vérification séparément et les résultats:
| Numéro de série de la méthode de vérification | Résultats de la base de données | Analyse des résultats |
|---|---|---|
| 1 | "Zhang San" et "Li Si" sont tous deux insérés. | La méthode périphérique n'a pas démarré la transaction et l'insertion des méthodes "Zhang San" et "Li si" s'exécute indépendamment dans leurs propres transactions. La méthode périphérique anormale n'affecte pas l'insertion interne des méthodes "Zhang San" et "Li si". |
| 2 | "Zhang San" est inséré, mais "Li si" n'est pas inséré. | La méthode périphérique n'a pas de transactions, et les méthodes d'insertion de "Zhang San" et "Li si" sont toutes deux exécutées indépendamment dans leurs propres transactions, donc l'insertion de la méthode "Li Si" ne fera que reculer la méthode "Li si", et l'insertion de la méthode "Zhang San" ne sera pas affectée. |
Conclusion: Grâce à ces deux méthodes, nous prouvons que Propagation.REQUIRED Propagation.NESTED . Les méthodes internes modifiées recommenceront leurs propres transactions et les transactions ouvertes sont indépendantes les unes des autres et n'interfèrent pas entre elles.
3.2 Scène 2
La méthode périphérique démarre la transaction.
Méthode de vérification 1:
@Transactional @Override public void transaction_exception_nest_nest () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.AddNested (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.AddNested (user2); lancer un nouveau runtimeException (); }Méthode de vérification 2:
@Transactional @Override public void transaction_nesed_nest_exception () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.AddNested (User1); User2 user2 = new User2 (); user2.setName ("li si"); user2Service.AddNestedException (user2); }Méthode de vérification 3:
@Transactional @Override public void Transaction_NeeShed_nest_exception_try () {user1 user1 = new User1 (); user1.setName ("Zhang San"); user1Service.AddNested (User1); User2 user2 = new User2 (); user2.setName ("li si"); essayez {user2Service.AddNestedException (user2); } catch (exception e) {System.out.println ("Method Rollback"); }}Exécutez les méthodes de vérification séparément et les résultats:
| Numéro de série de la méthode de vérification | Résultats de la base de données | Analyse des résultats |
|---|---|---|
| 1 | "Zhang San" et "Li Si" n'ont pas été insérés. | La méthode périphérique démarre la transaction et la transaction interne est une sous-transaction de la transaction périphérique. La méthode périphérique recule et la méthode interne doit également être annulée. |
| 2 | "Zhang San" et "Li Si" n'ont pas été insérés. | La méthode périphérique démarre la transaction et la transaction interne est une sous-transaction de la transaction périphérique. La méthode interne lance un recul d'exception et la méthode périphérique perçoit l'exception provoquant la réduction de la transaction globale. |
| 3 | "Zhang San" est inséré et "Li si" n'est pas inséré. | La méthode périphérique démarre la transaction et la transaction interne est une sous-transaction de la transaction périphérique. Insérez la méthode interne "Zhang San" pour lancer une exception, et la transaction enfant peut être annulée séparément. |
Conclusion: Les résultats des tests ci-dessus montrent que lorsque la méthode périphérique ouvre la transaction, la méthode interne modifiée par Propagation.NESTED appartient à la sous-transaction de la transaction externe. La transaction principale périphérique recule et la sous-transaction doit reculer. La sous-transaction interne peut être annulée séparément sans affecter la transaction principale périphérique et d'autres sous-transactions.
4. similitudes et similitudes de requis, requise_new, imbriqué
D'après la comparaison de "1.2 Scene 2" et "3.2 Scene 2", nous pouvons voir:
Les méthodes internes modifiées par imbriquées et requises sont deux transactions de méthode périphérique. Si la méthode périphérique lance une exception, les transactions des deux méthodes seront annulées. Cependant, requis rejoint les transactions de méthode périphérique, il appartient donc à la même transaction que les transactions périphériques. Une fois que la transaction requise a lancé une exception et est retirée, les transactions de méthode périphérique seront également annulées. Nested est une sous-transaction de la méthode périphérique et a un point de sauvegarde séparé, de sorte que la méthode imbriquée lance une exception et est annulée, ce qui n'affectera pas la transaction de la méthode périphérique.
D'après la comparaison de "2.2 Scene 2" et "3.2 Scene 2", nous pouvons voir:
Les transactions à la fois imbriquées et nécessaires peuvent faire reculer les transactions de méthode interne sans affecter les transactions de méthode périphérique. Cependant, comme le imbriqué est une transaction imbriquée, une fois la méthode périphérique qui a été annulée, les sous-transactions qui sont des transactions de méthode périphérique seront également annulées. Required_new est implémenté en ouvrant une nouvelle transaction. Les transactions internes et les transactions périphériques sont deux transactions. Le retour en arrière des transactions périphériques n'affectera pas les transactions internes.
5. Autres comportements de propagation des transactions
Compte tenu du problème de la longueur de l'article, les tests d'autres comportements de propagation des transactions ne seront pas décrits ici. Les lecteurs intéressés peuvent rechercher le code de test et les explications de résultats correspondants dans le code source. Portail: https: //github.com/tmtse/tran ...
Cas d'utilisation de simulation
Après avoir introduit tant de comportements de communication de transaction, comment les appliquer dans notre travail réel? Permettez-moi de vous donner un exemple:
Supposons que nous ayons une méthode enregistrée, dans laquelle la méthode d'ajout de points est appelée. Si nous voulons ajouter des points pour ne pas affecter le processus d'enregistrement (c'est-à-dire que le retour en arrière n'a pas ajouté de points ne peut pas faire en sorte que la méthode d'enregistrement Rollback), nous écrivons ceci:
@Service public class userServiceImplt implémente userService {@Transactional public void Register (utilisateur utilisateur) {try {membreSHiPPointService.addpoint (point de point); } catch (exception e) {// omettre ...} // omettre ...} // omettre ...} Nous stipulons également que la défaillance d'enregistrement affectera addPoint() (la méthode d'enregistrement Rollback nécessite également un retour en arrière), de sorte que addPoint() doit être implémentée comme ceci:
@Service public classe MembresHippointServiceIMPl implémente MemberHippointService {@Transactional (propagation = propagation.nest) public void addPoint (point de point) {try {RecordService.AdDrecord (enregistrement d'enregistrement); } catch (exception e) {// omettre ...} // omettre ...} // omettre ...} Nous avons remarqué que addRecord() addPoint() , qui est utilisée pour enregistrer les journaux. Sa mise en œuvre est la suivante:
@Service public class RecordServiceImpl implémente RecordService {@Transactional (propagation = propagation.not_supported) public void addrecord (enregistrement d'enregistrement) {// omit ...} // omit ...} Nous avons remarqué propagation = Propagation.NOT_SUPPORTED dans addRecord() , car elle n'est pas exacte pour le journal, et on peut être plus ou moins, donc addRecord() elle-même et la méthode périphérique addPoint() addRecord() addRecord() provoquera pas une exception ne affectera pas la méthode de addPoint() périphérique.
Grâce à cet exemple, je crois que tout le monde a une compréhension plus intuitive de l'utilisation du comportement de communication des transactions. La combinaison de divers attributs peut en effet rendre notre mise en œuvre commerciale plus flexible et diversifiée.
en conclusion
Grâce à l'introduction ci-dessus, je crois que tout le monde a une compréhension plus approfondie du comportement de communication des transactions de printemps, et j'espère que votre travail de développement quotidien sera utile.
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.