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

目录

  • 回顾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 mapperPUBLIC "-//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 configurationPUBLIC "-//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{@Resourceprivate UserMapper userMapper;@Overridepublic 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 {@Testpublic 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 {@Autowiredprivate ServiceB serviceB;// 使用REQUIRED传播机制@Transactional(propagation = Propagation.REQUIRED)public void methodA() {// 方法A的业务代码...// 调用方法BserviceB.methodB();// 如果methodB出现错误,那么methodA和methodB的操作都会回滚。}
}
@Service
public class ServiceB {@Transactional(propagation = Propagation.REQUIRED)public void methodB() {// 方法B的业务代码...}
}

SUPPORTS

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

@Service
public class ServiceA {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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.mzph.cn/news/824190.shtml

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

相关文章

全国产化无风扇嵌入式车载电脑在救护车远端诊断的行业应用

救护车远端诊断的行业应用 背景介绍 更加快速的为急症病人在第一时间开始进行诊断和治疗,是提高病人救助成功率的关键。因此&#xff0c;先进的救护系统正在思考&#xff0c;如何在病人进入救护车之后&#xff0c;立刻能够将救护车中各种检查仪器的信息快速的传回医院&#xf…

aws云靶场和一些杂记

aws靶场 在AWS靶场中&#xff0c;存在三个安全问题&#xff1a;1) 一个S3存储桶政策配置错误&#xff0c;允许公共访问&#xff0c;通过访问特定域名可获取flag。2) SQS消息队列的政策没有限制角色&#xff0c;允许发送和接收消息&#xff0c;通过aws sqs命令行工具的receive-…

JS-42-Node.js01-Node.js介绍

一、浏览器大战 众所周知&#xff0c;在Netscape设计出JavaScript后的短短几个月&#xff0c;JavaScript事实上已经是前端开发的唯一标准。 后来&#xff0c;微软通过IE击败了Netscape后一统桌面&#xff0c;结果几年时间&#xff0c;浏览器毫无进步。&#xff08;2001年推出…

C++面向对象程序设计-北京大学-郭炜【课程笔记(七)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;七&#xff09;】 1、类型转换运算符2、自增、自减运算符的重载3、继承和派生的基本概念3.1、基本概念3.2、派生类对象的内存空间 4、继承关系和复合关系4.1、继承关系的使用4.2、复合关系的使用 5、派生类覆盖基类成员6…

《论文阅读》基于情感原因感知的共情对话生成模型 2023 AAAI

《论文阅读》基于情感原因感知的共情对话生成模型 2023 AAAI 前言简介模型构架情绪推理器回复生成器实验结果前言 亲身阅读感受分享,细节画图解释,再也不用担心看不懂论文啦~ 无抄袭,无复制,纯手工敲击键盘~ 今天为大家带来的是《The Empathic Dialogue Generation Model…

npm ERR! code CERT_HAS_EXPIRED (创建vue过程)

npm ERR! code CERT_HAS_EXPIRED &#xff08;创建vue过程&#xff09; 起因&#xff1a;卸载 npm uninstall -g vue-cli时候发现报这个错误。 当我们创建vue之前&#xff0c;使用npm更新或者安装啥的时&#xff0c;出现此类提示&#xff0c;则表明&#xff0c;用来验证和网络加…

java读取Excel表格数据

java读取Excel表格数据 环境说明项目结构1.controller层2.service层实现层StudentModel.java类 使用的Maven依赖效果示例一效果示例二文档截图第一页第二页 postman请求说明其他说明 环境说明 jdk1.8&#xff0c;springboot2.5.3 项目结构 1.controller层 package com.exam…

Taro-vue微信小程序用户隐私保护

Taro-vue微信小程序用户隐私保护 一、在 微信公众平台的【设置】- 【服务内容与声明】 &#xff0c;设置用户隐私保护指引&#xff0c;添加项目需要的接口权限。 【用户隐私保护指引】提交之后&#xff0c;官方会进行审核。审核通过之后&#xff0c;对应的接口权限才会生效。 …

区块链安全应用----压力测试

通过Caliper进行压力测试程序 1.环境配置 第一步. 配置基本环境 部署Caliper的计算机需要有外网权限&#xff1b;操作系统版本需要满足以下要求&#xff1a;Ubuntu > 16.04、CentOS > 7或MacOS > 10.14&#xff1b;部署Caliper的计算机需要安装有以下软件&#xff…

【C语言回顾】函数

前言1. 函数的概念和分类2.库函数3. 自定义函数3.1 自定义函数的简单介绍3.2 自定义函数举例 4. 形参和实参4.1 形参4.2 实参4.3 形参和实参的关系4.3.1 理解4.3.2 举例代码和调试 5. 嵌套函数和链式访问5.1 嵌套函数5.2 链式访问 6. 函数的声明和定义6.1 单个文件6.2 多个文件…

ChatGPT在遥感领域中的应用

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。本课程重点介绍ChatGPT在遥感中的应用&#xff0c;人工智…

中文编程入门(Lua5.4.6中文版)第十三章 Lua 文件操作

在《Lua世界》的冒险旅途中&#xff0c;勇士们时常需要与神秘的文本卷轴打交道。为了更好地掌握这些知识宝藏&#xff0c;Lua I/O库提供了两种强大的探索模式&#xff1a;简单模式和完全模式&#xff0c;助你轻松应对各类文献挑战。 简单模式&#xff1a;初识卷轴 简单模式如…

C# aspose word实现模板方式打印及打印速度慢解决方法

1.引用dll nuget或者网上都有下载的方式。不过都要收费。下载地址&#xff1a;https://files.cnblogs.com/files/rolayblog/Tool.zip?t1713322422&downloadtrue 2.打印模板设计 新建一个doc文档&#xff0c;根据自己的需求画页面。 A、普通文本 在word中需要替换值的地方添…

《2024最新Java面试题及答案(带完整目录)》

获取链接&#xff1a;《2024最新Java面试题及答案&#xff08;带完整目录&#xff09;》 更多技术书籍&#xff1a;技术书籍分享&#xff0c;前端、后端、大数据、AI、人工智能... ​ ​ ​ 4.1.9.8. 可重入锁&#xff08;递归锁&#xff09; ...........................…

Oracle11.2.0.1,(CVE-2012-1675)漏洞解决方案

1.进入容器停止监听 docker exec -it -u 0 oracle11g bash su - oracle lsnrctl stop listener2.找到监听配置文件位置&#xff0c;修改监听文件 echo $ORACLE_HOMEvi network/admin/listener.ora #在文件底部添加 SECURE_REGISTER_LISTENER (IPC) #启动监听 lsnrctl start …

基于springboot+vue+Mysql的汽车租赁系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

idea运行报错:启动命令过长

JAVA项目&#xff0c;运行的时候报错 Command line is too long. Shorten the command line via JAR manifest or via a classpath file and rerun老问题了&#xff0c;记录一下 解决办法&#xff1a; 1、Edit Configurations 2、点击Modify options设置&#xff0c;勾选S…

PlistEdit Pro for Mac激活版:强大的Plist文件编辑工具

PlistEdit Pro for Mac是一款专为Mac用户设计的强大Plist文件编辑工具。Plist文件是苹果公司开发的一种XML文件格式&#xff0c;用于存储应用程序的配置信息和数据。这款软件为用户提供了直观、易用的界面&#xff0c;使编辑和管理Plist文件变得轻松简单。 PlistEdit Pro for M…

C++设计模式|创建型 3.抽象工厂模式

在上一篇文章中介绍了工厂模式&#xff0c;每个具体工厂负责生产一个专门的产品&#xff0c;其代码扩展性很好&#xff0c;这篇文章将介绍抽象工厂模式。 1.为什么要使用抽象工厂模式&#xff1f; 既然已经有了“工厂模式”&#xff0c;那为什么还会有抽象工厂模式呢&#xf…

生成人工智能体:人类行为的交互式模拟论文与源码架构解析(1)——场景故事介绍

生成NPC为交互应用程序创建逼真的人类行为模拟。在这项工作中&#xff0c;我们通过将二十五个NPC放置在一个沙盒环境中&#xff08;类似于The Sims&#xff0c;模拟人生&#xff09;&#xff0c;展示了生成NPC的能力。用户可以观察和干预NPC的日常计划、分享新闻、建立关系以及…