Points à noter dans la gestion des transactions Spring

La plupart des gens doivent être confus lorsqu'ils entrent en contact pour la première fois avec des transactions de bases de données, et leur regard confus révèle leur soif de connaissances. Ici, je vais vous donner quelques connaissances scientifiques populaires. :

  • notion de transaction
  • caractéristiques des transactions
  • Prise en charge du moteur de stockage de base de données MySQL
  • Modifier le moteur de stockage de base de données Mysql
  • transaction de configuration de printemps
  • code d'essai
  • Avis

notion de transaction

La transaction est une unité de contrôle de concurrence et une séquence d'opérations définie par l'utilisateur. Soit toutes ces opérations sont effectuées, soit aucune d’entre elles n’est effectuée, et elles constituent une unité de travail intégrale. Les transactions lient un ensemble d'opérations logiquement liées afin que le serveur maintienne l'intégrité des données.
COMMIT : indique la soumission, c'est-à-dire toutes les opérations de validation d'une transaction. Plus précisément, toutes les mises à jour de la base de données dans la transaction sont réécrites dans la base de données physique sur le disque et la transaction se termine normalement.
ROLLBACK signifie restauration, c'est-à-dire qu'une sorte d'échec se produit pendant l'exécution de la transaction et que la transaction ne peut pas continuer. Le système annule toutes les opérations terminées sur la base de données dans la transaction et revient à l'état dans lequel la transaction a commencé.

ex : A utilise la banque en ligne pour transférer de l'argent en ligne vers B <Ceci est un scénario>
Premier cas : Le montant de l'utilisateur A diminue et le montant de l'utilisateur B augmente <cas normal>
Deuxième cas : Le montant de l'utilisateur diminue et une exception se produit sur le chemin de l'utilisateur B. montant Pas d'augmentation <situation anormale>
Troisième situation : Le montant de l'utilisateur A ne diminue pas mais le montant de l'utilisateur B augmente <situation anormale>
. . .
À l'exception du premier cas, les autres sont des exceptions ; dans ce cas, il faut utiliser des transactions. Soit vous le faites, soit vous ne le faites pas , c'est-à-dire : si le compte A diminue, le compte B doit augmenter, sinon le compte A ne diminuera pas.

caractéristiques des transactions

Les quatre caractéristiques des transactions (ACID en abrégé) : atomicité, cohérence, isolement et durabilité

Atomicité : Toutes les opérations d'une transaction sont indivisibles dans la base de données, soit toutes sont terminées, soit aucune n'est exécutée.

Cohérence : les résultats de l'exécution de plusieurs transactions exécutées en parallèle doivent être cohérents avec les résultats de l'exécution en série dans un certain ordre.

Isolement : l'exécution d'une transaction n'est pas interférée par d'autres transactions et les résultats intermédiaires de l'exécution de la transaction doivent être transparents pour les autres transactions.

Durabilité : pour toute transaction validée, le système doit garantir que les modifications apportées à la base de données par la transaction ne sont pas perdues, même en cas de défaillance de la base de données.

Prise en charge du moteur de stockage de base de données MySQL

Il existe quatre moteurs de stockage pour MySQL : MyIsAm, InnoDB, MEMORY et MERGE. Chacun des quatre a ses propres avantages. La sélection de la technologie permet de choisir différentes méthodes en fonction de différents besoins. MonIsAm
MyISAM est le moteur de stockage par défaut pour MySQL. MyISAM ne prend pas en charge les transactions ni les clés étrangères, mais sa vitesse d'accès est rapide et il n'y a aucune exigence d'intégrité des transactions.
Les tables MyISAM prennent également en charge 3 formats de stockage différents
tableau statique
Les tables statiques sont le format de stockage par défaut. Les champs des tables statiques sont des champs de longueur non variable. Les
avantages sont : le stockage est très rapide, facile à mettre en cache et facile à récupérer en cas de panne. Les
inconvénients sont : cela prend généralement plus d'espace que les tables dynamiques. (Remarque : lors du stockage, lorsque la largeur de la colonne est insuffisante, des espaces sont utilisés pour la compenser. Ces espaces ne seront pas obtenus lors de l'accès)
tableau dynamique
Les champs des tables dynamiques sont de longueur variable. L'avantage est qu'ils occupent relativement peu de place. Cependant, les mises à jour et suppressions fréquentes des enregistrements entraîneront une fragmentation. Les performances doivent être améliorées régulièrement et la récupération en cas de panne est
relativement difficile.
Tableau compressé
Les tables compressées occupent un petit espace disque et chaque enregistrement est compressé individuellement, il y a donc très peu de frais d'accès.
InnoDB
Le moteur de stockage InnoDB assure la sécurité des transactions avec des capacités de validation, de restauration et de récupération après incident. Cependant, par rapport au moteur de stockage MyISAM,
InnoDB écrit moins efficacement et occupe plus d'espace disque pour conserver les données et les index.
De plus, MySQL ne prend en charge qu'InnoDB en tant que moteur de stockage de clé étrangère. Lors de la création d'une clé étrangère, la
table attachée doit avoir un index correspondant, et la sous-table créera automatiquement un index correspondant lors de la création d'une clé étrangère. (La clé étrangère de la table associée doit être la clé primaire de la table associée)
InnoDB est idéal pour une utilisation dans des tables à forte concurrence et de nombreuses opérations de mise à jour. Tables qui doivent utiliser des transactions. Tableaux avec les exigences pour la reprise après sinistre automatisée.
MÉMOIRE
Le moteur de stockage MEMORY utilise le contenu stocké en mémoire pour créer des tables.
Chaque table MEMORY correspond en réalité à un seul fichier disque. L'accès aux tables de type MEMORY est très rapide car ses données sont stockées en mémoire et les index HASH sont utilisés par défaut.
Mais une fois le service arrêté, les données du tableau seront perdues. Le moteur de stockage de mémoire est utilisé dans les situations où une vitesse rapide est requise et des données temporaires sont requises.
FUSIONNER
Le moteur de stockage de fusion est une combinaison d'un groupe de tables MyISAM. Les structures de ces tables MyISAM doivent être exactement les mêmes. Il n'y a aucune donnée dans la table MERGE. La table de type MERGE peut être interrogée, mise à jour et supprimée. Ces
opérations sont en fait effectués sur la table interne MyISAM. pour fonctionner. Pour l'opération d'insertion dans la table MERGE, la table insérée est définie selon la clause INSERT_METHOD. Elle peut avoir trois valeurs différentes. La première et la dernière valeurs font que l'opération d'insertion agit sur la première ou la dernière table en conséquence. Définissez cette clause ou définissez-le sur NON,
indiquant que la table MERGE ne peut pas être insérée. Vous pouvez effectuer une opération de suppression sur la table MERGE. Cette opération supprime uniquement la définition de la table MERGE et n'a aucun impact sur les tables internes. MERGE conserve deux fichiers commençant par le nom de la table MERGE sur le disque : le fichier .frm stocke la définition de la table ; le fichier .MRG contient des informations sur la table combinée, y compris les tables dont la table MERGE est composée et la base de sa composition. insertion de données.
La table MERGE peut être modifiée en modifiant le fichier .MRG, mais elle doit être rafraîchie en vidant la table après modification.

Les méthodes principales du moteur Mysql sont ces quatre, et vous pouvez choisir celle correspondante en fonction des conditions.

Modifier le moteur de stockage de base de données Mysql

show engines; #显示数据库是否支持InnoDB
Changer la méthode 1 : Modifier le fichier de configuration my.cnf
Ouvrez my.cnf, ajoutez default-storage-engine=InnoDB à la fin de [mysqld], redémarrez le service de base de données et modifiez le moteur par défaut de la base de données en InnoDB.
Méthode de changement 2 : à préciser lors de la création du tableau
create table tableName( id int primary key, name varchar(50) )type=InnoDB;
Changer la méthode 3 : Modifier après avoir créé la table
alter table tableName ENGINE=InnoDB; #mysql5.0以后用这种方式
alter table tableName type = InnoDB; #mysql5.0之前用这种方式

transaction de configuration de printemps

Voici deux méthodes, l'une est la méthode d'injection automatique AOP et l'autre est la méthode basée sur les annotations.

Méthode d'injection automatique
Lors du cross-cuting AOP, les transactions sont injectées là où cela est nécessaire selon les règles. Il en résulte que des endroits inutiles peuvent également être injectés. Provoque un gaspillage de ressources. L'avantage est que vous n'avez pas à l'oublier et que vous n'avez pas à vous soucier de savoir s'il existe des lieux qui ne sont pas gérés par la transaction.
    <!-- 配置数据源 使用的是Druid数据源 -->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />

        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />

        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize"
            value="33" />
        <!-- 用来检测有效sql -->
        <property name="validationQuery" value="${validationQuery}" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="testWhileIdle" value="true" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="25200000" />
        <!-- 打开removeAbandoned功能 -->
        <property name="removeAbandoned" value="true" />
        <!-- 1800秒,也就是30分钟 -->
        <property name="removeAbandonedTimeout" value="1800" />
        <!-- 关闭abanded连接时输出错误日志 -->
        <property name="logAbandoned" value="true" />
        <!-- 监控数据库 -->
        <property name="filters" value="mergeStat" />
    </bean>
    <!-- myBatis文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 添加mybatis的配置 -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
        <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
        <property name="mapperLocations" value="classpath:com/tanrice/dao/impl/*.xml" />
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.tanrice.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 配置事物 -->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="append*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="modify*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="edit*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="delete*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="remove*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="repair" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="delAndRepair" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="get*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="find*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="load*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="search*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="datagrid*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
        </tx:attributes>
    </tx:advice>

    <!-- Spring aop事务管理 -->
    <aop:config>
        <aop:pointcut id="transactionPointcut" expression="execution(* com.projectname.service..*Impl.*(..))" />
        <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" />
    </aop:config>
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
         <constructor-arg index="0" ref="sqlSessionFactory" /> 
    </bean>
Approche basée sur les annotations
La méthode basée sur les annotations consiste à ajouter l'annotation @Transactional à la classe ou à la méthode qui doit être utilisée. L'avantage est de réduire le gaspillage de ressources. L'inconvénient est que parfois cela est oublié. Une fois oubliées, certaines méthodes n'auront aucune transaction.
    <!-- 配置数据源 使用的是Druid数据源 -->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />

        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />

        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize"
            value="33" />
        <!-- 用来检测有效sql -->
        <property name="validationQuery" value="${validationQuery}" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="testWhileIdle" value="true" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="25200000" />
        <!-- 打开removeAbandoned功能 -->
        <property name="removeAbandoned" value="true" />
        <!-- 1800秒,也就是30分钟 -->
        <property name="removeAbandonedTimeout" value="1800" />
        <!-- 关闭abanded连接时输出错误日志 -->
        <property name="logAbandoned" value="true" />
        <!-- 监控数据库 -->
        <property name="filters" value="mergeStat" />
    </bean>

    <!-- myBatis文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 添加mybatis的配置 -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
        <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
        <property name="mapperLocations" value="classpath:com/tanrice/dao/impl/*.xml" />
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.tanrice.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>


    <!-- 注解的方式配置事务 -->
    <!-- 在需要的地方配置 -->
    <tx:annotation-driven transaction-manager="transactionManager"  proxy-target-class="true"/>

    <!-- 注解方式配置事物 -->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="append*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="modify*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="edit*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="delete*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="remove*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="repair" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="delAndRepair" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="get*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="find*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="load*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="search*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="datagrid*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
        </tx:attributes>
    </tx:advice>
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
         <constructor-arg index="0" ref="sqlSessionFactory" /> 
    </bean>
@Service
@Transactional
public class UserTaskServiceImpl implements UserTaskService{
    
    

    @Autowired
    private UserTaskDao userTaskDao;

    @Autowired
    private TaskDao taskDao;

    public boolean updateUserTask(UserTask userTask,int id) {
        boolean flag = false;
        try{
            flag = userTaskDao.saveUserTask(userTask)>0?true:false;
            System.out.println("插入:"+flag);
            flag = taskDao.updateTaskLeftcountById(id)>0?true:false;
            System.out.println("修改:"+flag);
        }catch(Exception e){
            throw new RuntimeException();
        }
        return flag;
    }
}

Vous pouvez envisager d'utiliser vous-même les deux méthodes ci-dessus. J'ai écrit les commentaires dans le fichier de configuration XML plus en détail, je ne les expliquerai donc pas petit à petit.

code d'essai

La méthode de service a été écrite. L'essentiel est de coller le code de mybatis. J'ai délibérément écrit une instruction SQL mal écrite dans mybatis. Une erreur s'est produite lors de son exécution.

    <update id="updateTaskLeftcountById">
        <!-- leftcount为int类型,所以加'aas'的时候会出错 -->
        update t_tm_task_table set leftcount = leftcount+aas where id = #{id}
    </update>
    @org.junit.Test
    public void testTrx(){
        UserTask userTask = new UserTask();
        userTask.setFullname("wanda");
        userTask.setUserid(10071);
        userTask.setTaskid(10026);
        userTask.setGettime(new Date().getTime());
        userTask.setState(5);

        System.out.println("结果是:"+userTaskService.updateUserTask(userTask, 10026));
    }

Avis

Il y a plusieurs points à noter lors de l’utilisation des transactions.

  • Pourquoi ne pas configurer les transactions sur la couche dao
    • La raison la plus fondamentale : étant donné que la couche DAO exploite directement la base de données, il n'y a qu'une seule responsabilité et il n'est pas nécessaire de revenir en arrière. Le succès est le succès et l'échec est l'échec.
  • niveau d'isolement des transactions
    • Lecture non validée : ce que nous appelons une lecture sale, deux transactions simultanées, "Transaction A : le leader paie un salaire à singo", "Transaction B : singo interroge le compte de salaire", la transaction B lit les données non validées de la transaction A.
    • Lecture validée : ce que nous appelons lecture non répétable, deux transactions simultanées, "Transaction A : consommation de singo", "Transaction B : transfert en ligne de la femme de singo", la transaction A a lu les données à l'avance, la transaction B suit immédiatement Les données ont été mis à jour et la transaction a été validée. Lorsque la transaction A lit à nouveau les données, les données ont changé.
    • Lecture répétable Lecture répétable : peut éviter les lectures non répétables. Lorsque Singo prend la carte de salaire pour consommer, une fois que le système commence à lire les informations de la carte de salaire (c'est-à-dire que la transaction démarre), la femme de Singo ne peut pas modifier l'enregistrement, c'est-à-dire que la femme de Singo ne peut pas transférer d'argent pour le moment. Le niveau d'isolement par défaut de MySQL est Lecture répétable.
    • Sérialisation sérialisable : sérialisable est le niveau d'isolation des transactions le plus élevé. Il est également le plus cher et a de très faibles performances. Il est généralement rarement utilisé. À ce niveau, les transactions sont exécutées de manière séquentielle, ce qui évite non seulement les lectures sales et les lectures non répétables, mais évite aussi les fantômes.
    <!-- 注解方式配置事物 -->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="append*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="modify*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="edit*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="delete*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="remove*" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="repair" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="delAndRepair" propagation="REQUIRED" isolation="READ_COMMITTED" />
            <tx:method name="get*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="find*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="load*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="search*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="datagrid*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
            <tx:method name="*" propagation="SUPPORTS" isolation="READ_COMMITTED" />
        </tx:attributes>
    </tx:advice>

Il s'agit de l'isolation des transactions que j'ai configurée ci-dessus. <tx:method name="repair" propagation="REQUIRED" isolation="READ_COMMITTED" />Regardez l'élément de configuration de l'isolation. Il s'agit du niveau d'isolation configuré.

Je suppose que tu aimes

Origine blog.csdn.net/my_God_sky/article/details/53291138
conseillé
Classement