【Java框架】Spring框架(三)——Spring整合Mybatis及Spring声明式事务

news2024/9/21 10:42:39

目录

  • 回顾Mybatis和新对象
    • 思路整理
  • Spring和MyBatis的整合步骤
    • 1. 创建Web工程,导入Spring和MyBatis的相关依赖
    • 2. 建立开发目录结构,创建实体类
    • 3. 创建数据访问接口和SQL映射语句文件
    • 4. 使用Spring配置文件配置数据源
      • 4.1 database.properties
      • 4.2spring配置文件applicationContext.xml
    • 5. 使用Spring配置文件创建SqlSessionFactory
    • 6. 配置MyBatis应用配置文件mybatis-config.xml
    • 7. 创建业务接口和业务实现类
    • 8.配置扫描扫描service层bean
    • 9.配置MapperScannerConfigurer
    • 10. 测试
  • spring整合Mybatis后的事务处理
    • 声明式事务
      • 示例(配置方式,本章主要用注解来实现声明式事务)
    • Spring声明式事务对应事务管理器接口
  • 注解实现声明式事务
    • @Transactional的工作原理
    • 添加注解配置
  • propagation:事务传播机制
      • REQUIRED(默认值)
      • SUPPORTS
      • REQUIRES_NEW
      • MANDATORY
      • NESTED
      • NOT_SUPPORTED
      • NEVER
  • 并发场景下,事务引发的问题
    • 脏读(Dirty Read)
      • 产生原因
      • 解决方案
      • 总结
    • 丢失修改(Lost of Modify)
      • 产生原因
      • 解决方案
      • 总结
    • 死锁(Deadlock)
      • 产生原因
      • 解决方案
      • 总结
    • 不可重复读(Unrepeatableread)
      • 产生原因
      • 解决方案
      • 总结
    • 幻读(Phantom read)
      • 产生原因
      • 解决方案
      • 总结
  • Spring声明式事务中的事务隔离:isolation
      • DEFAULT(默认值)
      • READ_COMMITTED:读已提交
      • READ_UNCOMMITTED:读未提交
      • REPEATABLE_READ:可重复读
      • SERIALIZABLE:可串行化
    • 补充
    • 事务属性

回顾Mybatis和新对象

在这里插入图片描述
倒着来看

  • 执行mapper.xml中的SQL需要先有sqlSession对象
  • sqlSession对象需要sqlSessionFactory创建
  • sqlSessionFactory需要SqlSessionFactoryBuilder创建
  • SqlSessionFactoryBuilder需要自己new出来
  • 即使是sqlSession对象,在使用时也是跟业务代码紧耦合的

思路整理

  • spring解决的就是手动new 对象和紧耦合的问题,何况Mybatis核心中的这些对象呢!
  • 以上流程可以全部移交给Spring来处理
  • 读取配置文件、组件的创建、组件之间的依赖关系以及整个框架的生命周期都由Spring容器统一管理
  • Spring框架整合其他框架的本质就是通过IOC和AOP把其他框架交给Spring框架管理,最终建立一个低耦合的应用架构

Spring和MyBatis的整合步骤

1. 创建Web工程,导入Spring和MyBatis的相关依赖

    <!--数据库驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    <!-- 阿里数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.0.9</version>
    </dependency>
    <!--Mybatis-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.2</version>
    </dependency>
    <!--日志包-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.25</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
      <scope>provided</scope>
    </dependency>

    <!-- spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <!-- aop依赖 -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

    <!-- spring+mybatis整合 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.2</version>
    </dependency>

2. 建立开发目录结构,创建实体类

在这里插入图片描述

package cn.smbms.pojo;

import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.util.Date;

/**
 * @author: zjl
 * @datetime: 2024/3/23
 * @desc:
 */
@Data
@ToString
public class User implements Serializable {
    private long id;
    private String userCode;
    private String userName;
    private String userPassword;
    private int gender;
    private Date birthday;
    private String phone;
    private String address;
    private int userRole;
}

3. 创建数据访问接口和SQL映射语句文件

package cn.smbms.mapper;

public interface UserMapper {
    int selectUserCount();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.smbms.mapper.UserMapper">
    <select id="selectUserCount" resultType="int">
        SELECT COUNT(1) FROM SMBMS_USER
    </select>
</mapper>

4. 使用Spring配置文件配置数据源

4.1 database.properties

jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/smbms?useSSL=false&characterEncoding=utf-8
jdbc.username = root
jdbc.password = 123456
maxActive=20
initialSize=1
maxWait=60000
minIdle=1
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
filters=stat

4.2spring配置文件applicationContext.xml

    <!--引入properties文件-->
    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="location">
            <value>classpath:database.properties</value>
        </property>
    </bean>
    <!--配置DataSource-->
<!--    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <!--最大连接池数量-->
        <property name="maxActive" value="${maxActive}" />
        <!--初始化时建立物理连接的个数-->
        <property name="initialSize" value="${initialSize}" />
        <!--获取连接时最大等待时间,单位毫秒-->
        <property name="maxWait" value="${maxWait}" />
        <!--最小连接池数量-->
        <property name="minIdle" value="${minIdle}" />
        <!--检测连接是否有效的超时时间,单位:秒-->
        <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
        <!--连接保持空闲而不被驱逐的最小时间-->
        <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
        <!--申请连接的时候检测-->
        <property name="testWhileIdle" value="${testWhileIdle}" />
        <!--申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
        <property name="testOnBorrow" value="${testOnBorrow}" />
        <!--归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
        <property name="testOnReturn" value="${testOnReturn}" />
        <!--属性类型是字符串,通过别名的方式配置扩展插件-->
        <property name="filters" value="${filters}" />
    </bean>

5. 使用Spring配置文件创建SqlSessionFactory

    <!-- 配置SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 引用数据源组件 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 引用MyBatis配置文件中的配置 -->
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
        <!-- 配置SQL映射文件信息 -->
        <property name="mapperLocations">
            <list>
                <value>classpath:mybatis/mapper/*.xml</value>
            </list>
        </property>
    </bean>

6. 配置MyBatis应用配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- Mybatis核心配置文件 -->
<configuration>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <typeAliases>
        <package name="cn.smbms.pojo"/>
    </typeAliases>
</configuration>

7. 创建业务接口和业务实现类

package cn.smbms.service;

public interface UserService {
    int getUserCount();
}
package cn.smbms.service;

import cn.smbms.mapper.UserMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author: zjl
 * @datetime: 2024/4/16
 * @desc:
 */
@Service
public class UserServiceImpl implements UserService{
    @Resource
    private UserMapper userMapper;
    @Override
    public int getUserCount() {
        return userMapper.selectUserCount();
    }
}

8.配置扫描扫描service层bean

<context:component-scan base-package="cn.smbms.service"/>

9.配置MapperScannerConfigurer

自动扫描指定包下的Mapper接口,并将它们直接注册为MapperFactoryBean
在这里插入图片描述

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.smbms.mapper" />
    </bean>

10. 测试

package cn.smbms.test;

import cn.smbms.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: zjl
 * @datetime: 2024/4/16
 * @desc:
 */
public class MyTest {

    @Test
    public void test(){
        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = act.getBean(UserService.class);
        System.out.println(userService.getUserCount());
    }
}

spring整合Mybatis后的事务处理

  • 编程式事务:硬编码方式,代码繁琐,且破坏分层,代码不易维护
  • 声明式事务:采用AOP的方式实现,Spring提供了声明式事务支持

声明式事务

  • 声明式事务关注的核心问题是:对哪些方法,采取什么样的事务策略
  • 配置步骤
    • 导入tx和aop命名空间
    • 定义事务管理器Bean,并为其注入数据源Bean
    • 通过<tx:advice>配置事务增强,绑定事务管理器并针对不同方法定义事务规则
    • 配置切面,将事务增强与方法切入点组合

示例(配置方式,本章主要用注解来实现声明式事务)

<!-- 使用xml方式实现声明式事务 -->
     <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" propagation="SUPPORTS" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="del*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice> 
    <!-- 定义切面 -->
     <aop:config>
        <aop:pointcut id="serviceMethod"
            expression="execution(* cn.smbms.service..*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
    </aop:config> 
 <!-- 定义事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

Spring声明式事务对应事务管理器接口

DataSourceTransactionManager类(事务管理器)中的主要方法:

  • doBegin():开启事务
  • doSuspend():挂起事务
  • doResume():恢复挂起的事务
  • doCommit():提交事务
  • doRollback():回滚事务

注解实现声明式事务

  • @Transactional注解添加声明式事务
  • 可以加在方法上,表示对该方法实现声明式事务
  • 可以加载类上,表示对该类中所有的方法实现声明式事务

@Transactional的工作原理

  • @Transactional是基于AOP实现的,AOP又是基于动态代理实现的。
  • 如果目标对象实现了接口,默认情况下就会采用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB的动态代理。
  • @Transactional在开始执行业务之前,通过代理先开始事务,在执行成功之后再提交事务。如果中途遇见异常,则回滚事务。
  • @Transactional实现思路预览:
    在这里插入图片描述
  • @Transactional具体执行细节如下图所示:
    在这里插入图片描述

添加注解配置

    <!--<tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" propagation="SUPPORTS" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="del*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    &lt;!&ndash; 定义切面 &ndash;&gt;
    <aop:config>
        <aop:pointcut id="serviceMethod"
                      expression="execution(* cn.smbms.service..*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
    </aop:config>-->
    <!-- 定义事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 使用注解实现声明式事务 -->
    <tx:annotation-driven />

propagation:事务传播机制

事务的传播机制解决的是一个事务在多个节点(方法)中传递的问题:
在这里插入图片描述

REQUIRED(默认值)

  • REQUIRED是默认的事务传播行为。
  • 如果当前存在事务,那么该方法将会在该事务中运行;如果当前没有事务,那么它会启动一个新的事务。
    在这里插入图片描述
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 
    // 使用REQUIRED传播机制
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 方法A的业务代码...
        // 调用方法B
        serviceB.methodB();
        // 如果methodB出现错误,那么methodA和methodB的操作都会回滚。
    }
}
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodB() {
        // 方法B的业务代码...
    }
}

SUPPORTS

如果当前存在事务,那么该方法将会在该事务中运行;如果当前没有事务,那么它可以以非事务方式执行。
在这里插入图片描述

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodA() {
        // 方法A的业务代码...
        serviceB.methodB();
        // 无论methodB是否出现错误,methodA的操作都不会回滚。
    }
}
 
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodB() {
        // 方法B的业务代码...
    }
}

REQUIRES_NEW

REQUIRES_NEW传播行为总是会启动一个新的事务。如果有一个事务正在运行,那么这个事务将会被挂起。

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodA() {
        // 方法A的业务代码...
        serviceB.methodB();
        // methodB在新的事务中运行,无论是否出错,都不会影响methodA的事务。
    }
}
 
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
       public void methodB() {
        // 方法B的业务代码...
    }
}

MANDATORY

MANDATORY传播行为要求方法必须在一个现有的事务中执行,如果没有事务就抛出异常。

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 
    @Transactional(propagation = Propagation.MANDATORY)
    public void methodA() {
        // 方法A的业务代码...
        serviceB.methodB();
        // 如果没有现有事务,会抛出异常。
    }
}
 
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.MANDATORY)
    public void methodB() {
        // 方法B的业务代码...
    }
}

NESTED

NESTED传播行为在一个嵌套事务中执行,如果一个事务正在运行,那么它将在一个嵌套事务中执行。这个嵌套事务是可以独立提交或回滚的。
在这里插入图片描述

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 
    @Transactional(propagation = Propagation.NESTED)
    public void methodA() {
        // 方法A的业务代码...
        serviceB.methodB();
        // methodB在嵌套事务中运行,如果出错,只有嵌套事务会回滚,不会影响methodA的事务。
    }
}
 
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.NESTED)
    public void methodB() {
        // 方法B的业务代码...
    }
}

NOT_SUPPORTED

NOT_SUPPORTED传播行为总是以非事务方式执行,如果有一个事务正在运行,那么这个事务将会被挂起。

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void methodA() {
        // 方法A的业务代码...
        serviceB.methodB();
        // 无论methodB是否出错,methodA的操作都不会回滚,因为它们不在同一个事务中。
    }
}
 
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void methodB() {
        // 方法B的业务代码...
    }
}

NEVER

NEVER传播行为要求方法以非事务方式执行,如果有一个事务正在运行,将会抛出异常。
在这里插入图片描述

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
 
    @Transactional(propagation = Propagation.NEVER)
    public void methodA() {
        // 方法A的业务代码...
        serviceB.methodB();
        // 如果有事务正在运行,会抛出异常。
    }
}
 
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.NEVER)
    public void methodB() {
        // 方法B的业务代码...
    }
}

并发场景下,事务引发的问题

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但是可能会导致以下的问题。

脏读(Dirty Read)

当一个事务正在访问数据并且对数据进行了修改,此时还未提交到数据库中,这时另一个事务也访问并使用了这个数据,由于上个事务还未提交,此时他读到的就是“脏数据”,根据“脏数据”所做的操作可能时不正确的。

产生原因

  1. 并发事务未提交: 一个事务在进行更新操作但尚未提交时,另一个事务已经读取了被更新的数据。这样读取到的数据就是未提交的“脏数据”。

  2. 缺乏事务隔离机制: 如果数据库系统的事务隔离级别设置较低,如读取未提交数据(Read Uncommitted),则允许事务读取到其他事务尚未提交的数据,从而导致脏读问题。

解决方案

  1. 使用事务隔离级别: 将数据库的事务隔离级别设置为合适的级别,如可重复读(Repeatable Read)或串行化(Serializable),这样可以确保一个事务在读取数据时不会读取到其他事务尚未提交的数据,从而避免脏读问题的发生。

  2. 加锁: 在进行读取操作时,可以使用行级锁或表级锁来锁定数据,防止其他事务对数据进行修改,确保读取到的数据是一致的。

  3. 优化事务设计: 在设计应用程序时,避免长时间持有事务或数据库连接,尽量缩短事务的执行时间,减少脏读发生的可能性。

  4. 谨慎使用未提交读(Read Uncommitted): 如果必须使用未提交读隔离级别,应该在应用程序中谨慎使用,并且清楚了解可能带来的风险。

总结

脏读问题可能导致系统产生错误的结果,因此在设计和实现数据库系统时,必须采取适当的措施来防止脏读的发生。通过设置合适的事务隔离级别、加锁以及优化事务设计,可以有效地避免脏读问题,确保系统的数据一致性和完整性

丢失修改(Lost of Modify)

  • 指一个事务读取到一个数据,另一个事务也访问了该数据。那么在第一个事务修改了这个数据后,第二个事务也进行了修改,此时第一个事务的修改结果就被覆盖了,也就是丢失了,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
  • 当两个或多个事务同时修改同一数据时,后提交的事务可能会覆盖前一个事务所做的修改,导致前一个事务的更新被丢失,这可能会导致数据不一致性或意外的结果

产生原因

  1. 并发更新: 多个事务同时对同一数据进行更新操作,由于没有正确的并发控制机制,可能导致后提交的事务覆盖了先前事务的修改。
  2. 缺乏锁机制: 如果系统没有实现有效的锁机制来保护共享数据,不同的事务可能会同时对同一数据进行修改,从而导致丢失更新问题。

解决方案

  1. 使用锁机制: 引入锁机制,如行级锁或表级锁,可以确保同时只有一个事务可以对特定的数据进行修改操作,从而避免丢失更新的问题。
  2. 乐观并发控制: 采用乐观并发控制方法,如版本控制或时间戳控制。每个事务在修改数据时,先获取数据的版本信息或时间戳,并在提交时检查数据是否发生变化,如果发生变化则进行回滚或者重新尝试。
  3. 使用事务隔离级别: 设置合适的事务隔离级别,如可重复读或串行化隔离级别,以确保一个事务在读取和修改数据时,不会被其他事务的更新所影响,从而避免丢失更新问题。
  4. 应用程序设计: 在应用程序设计阶段,尽量避免长时间持有数据库连接或事务,减少并发操作的可能性,从而减少丢失更新问题的发生。

总结

丢失更新问题在并发事务处理中是一个常见的挑战,但通过合适的并发控制机制和事务管理策略,可以有效地解决这一问题,确保数据的一致性和完整性。数据库管理员和开发人员需要充分了解丢失更新问题的原因和解决方法,并在设计和实现数据库系统时采取相应的措施,以提高系统的稳定性和可靠性。

死锁(Deadlock)

  • 两个或多个事务相互等待对方释放资源,导致系统无法继续执行。这种情况下,只能通过终止其中一个事务或者回滚来解决死锁。

产生原因

  1. 资源竞争: 多个事务同时请求获取相同的资源,但由于资源被其他事务占用而无法立即获取,导致事务之间相互等待。
  2. 循环等待: 事务之间存在循环的资源依赖关系,每个事务都在等待其他事务所持有的资源,形成了循环等待的局面。

解决方案

  1. 加锁顺序: 设计良好的应用程序应该按照相同的顺序请求和释放资源,从而降低死锁发生的可能性。通过统一的加锁顺序,可以减少资源竞争和循环等待的情况。

  2. 超时机制: 引入超时机制,当事务在一定时间内无法获取所需资源时,自动释放已经获取的资源并进行回滚操作,从而打破死锁的局面。

  3. 检测和回滚: 实现死锁检测算法,定期检测系统中是否存在死锁,并采取自动回滚或者手动干预的方式来解除死锁。

  4. 事务监控: 监控事务的执行情况,及时发现可能导致死锁的事务,并对其进行优化或者调整,从而降低死锁的发生概率。

总结

死锁是数据库并发处理中的一个重要问题,需要引起开发人员和数据库管理员的高度重视。通过合理设计事务和加锁机制、实现死锁检测和处理算法,以及进行事务监控和优化,可以有效地预防和解决死锁问题,确保数据库系统的稳定性和可靠性。

不可重复读(Unrepeatableread)

一个事务在读取某个数据后,另一个事务修改了该数据并提交。当第一个事务再次读取同一数据时,得到的结果与之前不一致。因此称为不可重复读。

产生原因

  1. 并发事务更新: 当一个事务在读取数据后,另一个事务对同一行数据进行了更新操作,导致第一个事务在后续读取同一行数据时,得到了不一致的结果。
  2. 并发事务删除: 当一个事务在读取数据后,另一个事务对同一行数据进行了删除操作,导致第一个事务在后续读取同一行数据时,发现数据已经不存在了。

解决方案

  1. 使用合适的事务隔离级别: 将数据库的事务隔离级别设置为合适的级别,如可重复读(Repeatable Read)或串行化(Serializable),这样可以确保一个事务在读取数据时,不会受到其他事务的更新或删除操作的影响,从而避免不可重复读问题的发生。

  2. 加锁: 在进行读取操作时,可以使用行级锁或表级锁来锁定数据,防止其他事务对数据进行修改或删除,确保读取到的数据是一致的。

  3. 优化事务设计: 在设计应用程序时,尽量减少事务的持续时间,缩短事务执行的时间窗口,从而减少并发操作对数据的影响。

总结

不可重复读问题可能导致系统产生不一致的结果,因此在设计和实现数据库系统时,必须采取适当的措施来防止不可重复读的发生。通过设置合适的事务隔离级别、加锁以及优化事务设计,可以有效地避免不可重复读问题,确保系统的数据一致性和完整性。

幻读(Phantom read)

幻读与不可重复读类似,它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

产生原因

  1. 并发事务插入或删除: 当一个事务在查询数据后,另一个事务对相同的条件的数据进行了插入或删除操作,导致第一个事务在后续查询相同条件的数据时,发现了新增或减少的数据,产生了幻读现象。

  2. 并发事务更新: 当一个事务在查询数据后,另一个事务对相同条件的数据进行了更新操作,导致第一个事务在后续查询相同条件的数据时,发现了数据内容的改变,也会产生幻读问题。

解决方案

  1. 使用合适的事务隔离级别: 将数据库的事务隔离级别设置为合适的级别,如串行化(Serializable),这样可以确保一个事务在读取数据时,不会受到其他事务的插入、更新或删除操作的影响,从而避免幻读问题的发生。

  2. 使用行级锁或范围锁: 在进行查询操作时,可以使用行级锁或范围锁来锁定数据,防止其他事务对数据进行插入、更新或删除操作,确保查询到的数据集是一致的。

  3. 优化事务设计: 在设计应用程序时,尽量减少事务的持续时间,缩短事务执行的时间窗口,从而减少并发操作对数据的影响,降低出现幻读问题的可能性。

总结

  • 幻读问题可能导致系统产生不一致的结果,因此在设计和实现数据库系统时,必须采取适当的措施来防止幻读的发生。通过设置合适的事务隔离级别、使用锁机制以及优化事务设计,可以有效地避免幻读问题,确保系统的数据一致性和完整性。
  • 不可重复度和幻读的区别:不可重复读的重点是修改,幻读的重点在于新增或者删除。
    • 例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
    • 例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

Spring声明式事务中的事务隔离:isolation

事务隔离级别:解决的是多个事务同时调用数据库的问题
在这里插入图片描述

DEFAULT(默认值)

READ_COMMITTED:读已提交

允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

READ_UNCOMMITTED:读未提交

最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

REPEATABLE_READ:可重复读

对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

SERIALIZABLE:可串行化

最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

√:会出现,×:不会出现

隔离级别脏读不可重复读幻影读
READ-UNCOMMITTED
READ-COMMITTED×
REPEATABLE-READ××
SERIALIZABLE×××

补充

  • MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)
  • 需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下,允许应用使用 Next-Key Lock 锁算法来避免幻读的产生。这与其他数据库系统(如 SQL Server)是不同的。所以说虽然 InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) ,但是可以通过应用加锁读(例如 select * from table for update 语句)来保证不会产生幻读,而这个加锁度使用到的机制就是 Next-Key Lock 锁算法。从而达到了 SQL 标准的 SERIALIZABLE(可串行化) 隔离级别。
  • 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读) 并不会有任何性能损失。
  • InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化) 隔离级别。

事务属性

  • timeout:事务超时时间,允许事务运行的最长时间,以秒为单位。默认值为-1,表示不超时
  • read-only:事务是否为只读,默认值为false
  • rollback-for:设定能够触发回滚的异常类型
    • Spring默认只在抛出runtime exception时才标识事务回滚
    • 可以通过全限定类名指定需要回滚事务的异常,多个类名用逗号隔开
  • no-rollback-for:设定不触发回滚的异常类型
    • Spring默认checked Exception不会触发事务回滚
    • 可以通过全限定类名指定不需回滚事务的异常,多个类名用英文逗号隔开
属性类型说明
propagation枚举型:Propagation可选的传播性设置。使用举例:
@Transactional(propagation=Propagation.REQUIRES_NEW)
isolation枚举型:Isolation可选的隔离性级别。使用举例:
@Transactional(isolation=Isolation.READ_COMMITTED)
readOnly布尔型是否为只读型事务。使用举例:@Transactional(readOnly=true)
timeoutint型(以秒为单位)事务超时。使用举例:Transactional(timeout=10)
rollbackFor一组 Class 类的实例,必须是Throwable的子类一组异常类,遇到时 必须 回滚。使用举例:@Transactional(rollbackFor={SQLException.class}),多个异常用逗号隔开
rollbackForClassName一组 Class 类的名字,必须是Throwable的子类一组异常类名,遇到时 必须 回滚。使用举例:@Transactional(rollbackForClassName={“SQLException”}),多个异常用逗号隔开
noRollbackFor一组 Class 类的实例,必须是Throwable的子类一组异常类,遇到时 必须不 回滚
noRollbackForClassName一组 Class 类的名字,必须是Throwable的子类一组异常类名,遇到时 必须不 回滚

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1611872.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx反向代理.NetCore开发的基于WebApi创建的gRPC服务

一、本文中使用的工具: Vs2022使用.NET 8.0开发基于ASP.NET Core WebApi的gRPC服务; Nginx:1.25.5,下载地址:http://nginx.org/en/download.html 二、gRPC介绍: 由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。在vs2022中可以直接创建gRP…

C++|stack-queue-priority_queue(适配器+模拟实现+仿函数)

目录 一、容器适配器 1.1容器适配器概念的介绍 1.2stack和queue的底层结构 1.3deque容器的介绍 1.3.1deque的缺陷及为何选择他作为stack和queue的底层默认实现 二、stack的介绍和使用 2.1stack的介绍 2.2stack的使用 2.3stack的模拟实现 三、queue的介绍和使用 …

BioTech - 使用 Amber 工具 松弛(Relaxation) 蛋白质三维结构 (Python)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/137889532 Amber 工具在蛋白质 松弛(Relaxation) 过程中起着重要的作用。在分子动力学模拟中,蛋白质松弛是指模拟过程中蛋白质结构达到一个较为稳定的状态。这个过程通…

【记录】Python3|Selenium 下载 PDF 不预览不弹窗(2024年)

版本&#xff1a; Chrome 124Python 3.12Selenium 4.19.0 版本与我有差异不要紧&#xff0c;只要别差异太大比如 Chrome 用 57 之前的版本了&#xff0c;就可以看本文。 如果你从前完全没使用过、没安装过Selenium&#xff0c;可以参考这篇博客《【记录】Python3&#xff5c;Se…

EFK安装与使用!!!

一、将你的项目进行打包。 二、上传到docker&#xff0c; 启动项目 三、修改前端的代理路径 四、EFK相关配置 1、docker-compose.yml&#xff1a; version: 3 services:kibana:image: kibana:7.14.0ports:- "5601:5601"environment:- ELASTICSEARCH_HOSTShttp://19…

redhatcsa学习笔记--题目+答案

一、semanage命令 semanage命令 – 查询与修改安全上下文 semanage命令来自英文词组“SELinux manage”的缩写&#xff0c;其功能是用于查询与修改安全上下文。semanage的功能类似于chcon命令&#xff0c;它们都可以用于设置文件的SELinux安全上下文策略&#xff0c;而semana…

【火猫TV】NBA:汤普森不肯低头,库汤追组合几乎解体,他想留下只有一个办法

随着西部附加赛结束&#xff0c;拥有辉煌历史的勇士队彻底结束了自己本赛季的征程&#xff0c;面对国王队勇士被对手死死压制&#xff0c;除了库里之外其他球员都没有发挥出应有的水平。尤其是曾经的关键先生克莱汤普森&#xff0c;在比赛中竟然贡献了10投0中的尴尬数据&#x…

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available 解决办法&#xff1a;下载想安装的node压缩包&#xff0c;放入nvm对应目录。 2024年最新node压缩包地址&#xff1a;https://nodejs.org/dist/ 1、选择对应的node版本&#xff1a;例如&#xff0c;我选的…

Spring AOP(面向切面编程)

1.Spring AOP 简介 1.1 AOP概述 AOP 为 Aspect Oriented Programming 的缩写&#xff0c;意思为面向切面编程, 是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续&#xff0c;是Spring框架中的一个重要内容&#xff0c;是函数式编程的一…

亚信安全数据安全运营平台DSOP新版本发布 注入AI研判升维

在当今快速发展的数字经济时代&#xff0c;企业对于数据的依赖日益加深&#xff0c;数据安全已成为企业的生命线。亚信安全推出数据安全运营平台DSOP全新版本&#xff0c;正是为满足企业对数据安全的高度需求而设计。这款平台以其卓越的能力和技术优势&#xff0c;为企业的数据…

链表基础4——带头双向循环链表

什么是带头双向循环链表 我们直接看图片 定义结点类型 typedef int LTDataType;//存储的数据类型typedef struct ListNode {LTDataType data;//数据域struct ListNode* prev;//前驱指针struct ListNode* next;//后继指针 }ListNode;链表的初始化 //创建一个新结点 ListNod…

Java 非对称加密RSA应用实现

1.RSA介绍 RSA算法是一种非对称加密算法&#xff0c;与对称加密算法不同的是,RSA算法有两个不同的密钥&#xff0c;一个是公钥&#xff0c;一个是私钥。 RSA公开密钥密码体制是一种使用不同的加密密钥与解密密钥&#xff0c;“由已知加密密钥推导出解密密钥在计算上是不可行的…

王者荣耀快速提升等级

1、为什么要提升等级 等级越高&#xff0c;解锁的玩法越多 等级越高&#xff0c;解锁的铭文数量越多&#xff0c;铭文能提升英雄的技能属性 2、如何快速提升等级 通过快速赛、排位赛、人机赛等均可以获取经验值 通过经验翻倍卡&#xff0c;可以快速获取经验值 3、经验卡翻倍…

YOLOv8水稻病害检测系统(python代码,可以通过图片、视频或者摄像头三种路径进行检测)

1.效果视频&#xff1a;最新最全面的水稻病害检测创作&#xff08;yolov8模型&#xff0c;稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫等病害。&#xff09;_哔哩哔哩_bilibili 2.数据集介绍 水稻叶病害数据集&#xff08;目标检测&#xff0c;yolo…

信道的题目

调制信道分为恒参信道和随参信道。恒参信道举例&#xff1a;各种有线信道&#xff1b;中长波地波传播、卫星中继。随参信道举例&#xff1a;短波电离层反射信道、各种散射信道、移动通信信道。狭义信道分为有线信道和无线信道。广义信道包含调制信道和编码信道。调制信道中不包…

RK3568 android11 修改关机弹窗界面

需要修改关机弹窗界面&#xff0c;当前界面我已经按照客户需求去掉emergency 但是客户需要按其他区域可以实现返回&#xff0c;也就是点击黑色背景取消dialog 嗑代码发现黑色布局为&#xff1a; <node index"0" text"" resource-id"com.android.…

【R语言】混合图:小提琴图+箱线图

{ggstatsplot} 是 {ggplot2} 包的扩展&#xff0c;用于创建图形&#xff0c;其中包含信息丰富的绘图本身中包含的统计测试的详细信息。在典型的探索性数据分析工作流程中&#xff0c;数据可视化和统计建模是两个不同的阶段&#xff1a;可视化通知建模&#xff0c;而建模又可以建…

janus架构学习

基础介绍 Janus 是由Meetecho设计和开发的开源、通用的基于SFU架构的WebRTC流媒体服务器&#xff0c;它支持在Linux的服务器或MacOS上的机器进行编译和安装。Janus 是使用C语言进行编写的&#xff0c;它的性能十分优秀。 架构 janus为sfu架构 模块结构图 模块说明 core模…

C# Solidworks二次开发:获取模型、组件、主体的表面积相关API详解

大家好&#xff0c;今天要介绍模型、组件、主体的表面积相关API。 下面是今天要介绍的API: &#xff08;1&#xff09;第一个为SurfaceArea Property (IMassProperty)&#xff0c;这个API的含义为获取此模型的表面积&#xff0c;下面是官方的具体解释&#xff1a; 其没有输入…

如何解决DDoS攻击?群联科技做出回答。

DDoS攻击&#xff08;分布式拒绝服务攻击&#xff09;是一种恶意利用多台傀儡机协同发起大规模网络流量&#xff0c;旨在压垮目标系统或网络资源&#xff0c;使其无法正常服务的网络攻击手段。由于现代计算机和网络性能的提升&#xff0c;单点发起的DoS攻击已难以奏效&#xff…