20 :Time:43min
P114 spring AOP基于注解之全注解开发 start :
其它文件与上集相同
Spring6Config类:
package com.powernode.spring6.service;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
@Configuration//代替spring.xml文件
@ComponentScan("com.powernode.spring6.service")//组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true)//启用aspecttj的自动代理机制
public class Spring6Config {
}
测试类内容:
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
运行结果:
前环绕
前置通知
目标方法的方法名generate
前置通知:安全。。。
系统正在生成订单。。。
后置通知
最终通知
后环绕
P115 Spring AOP基于XML方式实现 start :
TimerAspect类中的内容:
package com.powernode.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
public class TimerAspect {
//通知
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//前环绕
long begin = System.currentTimeMillis();
//目标
joinPoint.proceed();
//后环绕
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
}
}
UserService类中的内容:
package com.powernode.spring6.service;
public class UserService {//目标对象
public void logout(){//目标方法
System.out.println("系统正在安全退出...");
}
}
spring配置文件中 的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--纳入spring ioc -->
<bean id="userService" class="com.powernode.spring6.service.UserService"></bean>
<bean id="timerAspect" class="com.powernode.spring6.service.TimerAspect"></bean>
<!--aop的配置 -->
<aop:config>
<!--切点表达式 -->
<aop:pointcut id="mypointcut" expression="execution(* com.powernode.spring6.service..*(..))"/>
<!--切面: 通知 + 切点 -->
<aop:aspect ref="timerAspect" >
<aop:around method="aroundAdvice" pointcut-ref="mypointcut" />
</aop:aspect>
</aop:config>
</beans>
测试类中的内容:
@Test
public void testXml(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.logout();
}
运行结果:
系统正在安全退出...
耗时10毫秒
Time:43min
P116 Spring AOP编程式事务解决方案 start:
15.5 AOP的实际案例:事务处理
项目中的事务控制是在所难免的。在一个业务流程当中,可能需要多条DML语句共同完成,为了保证数据的安全,这多条DML语句要么同时成功,要么同时失败。
这个控制事务的代码就是和业务逻辑没有关系的“交叉业务”。以上伪代码当中可以看到这些交叉业务的代码没有得到复用,并且如果这些交叉业务代码需要修改,那必然需要修改多处,难维护,怎么解决?可以采用AOP思想解决。可以把以上控制事务的代码作为环绕通知,切入到目标类的方法当中。
AccountService类中的内容:
package com.powernode.spring6.service;
import org.springframework.stereotype.Service;
@Service
public class AccountService {//目标对象
//目标方法
//转账 的业务方法
public void transfer(){
System.out.println("银行账户正在完成转账操作...");
}
//目标方法
//取款的业务方法
public void withdraw(){
System.out.println("正在取款,请稍后。。。");
}
}
OrderService类中的内容:
package com.powernode.spring6.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {//目标对象
//目标方法
//生成订单的业务方法
public void generate(){
System.out.println("正在生成订单...");
}
//目标方法
//取消订单的业务方法
public void cancel(){
System.out.println("订单已取消...");
String s = null;
s.toString();
}
}
TransactionAspect类中的内容:
package com.powernode.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TransactionAspect {
//编程式事务解决方案。
@Around("execution(* com.powernode.spring6.service..*(..))")
public void aroundAdvice(ProceedingJoinPoint joinPoint){
try {
//前环绕
System.out.println("开启事务");
//执行目标
joinPoint.proceed();
//后环绕
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务");
}
}
}
spring配置文件spring.xml文件中的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--组件扫描 -->
<context:component-scan base-package="com.powernode.spring6.service"/>
<!--启动自动代理 -->
<aop:aspectj-autoproxy />
</beans>
测试类中的 内容:
@Test
public void testTransaction(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
accountService.transfer();
accountService.withdraw();
orderService.generate();
orderService.cancel();
运行结果:
开启事务
银行账户正在完成转账操作...
提交事务
开启事务
正在取款,请稍后。。。
提交事务
开启事务
正在生成订单...
提交事务
开启事务
订单已取消...
回滚事务
P117 spring AOP安全日志解决方案 start:
15.6 AOP的实际案例:安全日志:
SecurityLogAspect类中的内容:
package com.powernode.spring6.biz;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
@Aspect
public class SecurityLogAspect {
@Pointcut("execution(* com.powernode.spring6.biz..save*(..))")
public void savePointcut(){}
@Pointcut("execution(* com.powernode.spring6.biz..delete*(..))")
public void deletePointcut(){}
@Pointcut("execution(* com.powernode.spring6.biz..modify*(..))")
public void modifyPointcut(){}
@Before("savePointcut() || deletePointcut() || modifyPointcut()")
public void beforeAdvice(JoinPoint joinPoint){
//系统时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String nowTime = sdf.format(new Date());
//输出日志信息
System.out.println(nowTime + " zhangsan : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
}
}
UserService类中的内容:
package com.powernode.spring6.biz;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void saveUser(){
System.out.println("新增用户信息");
}
public void deleteUser(){
System.out.println("删除用户信息");
}
public void modifyUser(){
System.out.println("修改用户信息");
}
public void getUser(){
System.out.println("获取用户信息");
}
}
VipService类中的内容:
package com.powernode.spring6.biz;
import org.springframework.stereotype.Service;
@Service
public class VipService {
public void saveVip(){
System.out.println("新增会员信息");
}
public void deleteVip(){
System.out.println("删除会员信息");
}
public void modifyVip(){
System.out.println("修改会员信息");
}
public void getVip(){
System.out.println("获取会员信息");
}
}
spring配置文件spring.mxl文件中的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--组件扫描 -->
<context:component-scan base-package="com.powernode.spring6"/>
<!--启动自动代理 -->
<aop:aspectj-autoproxy />
</beans>
测试类中的内容:
@Test
public void testSecuritylog(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
VipService vipService = applicationContext.getBean("vipService", VipService.class);
userService.saveUser();
userService.deleteUser();
userService.modifyUser();
userService.getUser();
vipService.saveVip();
vipService.deleteVip();
vipService.modifyVip();
vipService.getVip();
}
运行结果:
2024-03-21 14:16:06 851 zhangsan : com.powernode.spring6.biz.UserService.saveUser
新增用户信息
2024-03-21 14:16:06 866 zhangsan : com.powernode.spring6.biz.UserService.deleteUser
删除用户信息
2024-03-21 14:16:06 867 zhangsan : com.powernode.spring6.biz.UserService.modifyUser
修改用户信息
获取用户信息
2024-03-21 14:16:06 867 zhangsan : com.powernode.spring6.biz.VipService.saveVip
新增会员信息
2024-03-21 14:16:06 872 zhangsan : com.powernode.spring6.biz.VipService.deleteVip
删除会员信息
2024-03-21 14:16:06 873 zhangsan : com.powernode.spring6.biz.VipService.modifyVip
修改会员信息
获取会员信息
P118 Spring事务之事务概述
十六、Spring对事务的支持
16.1 事务概述
● 什么是事务
○ 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
○ 多条DML要么同时成功,要么同时失败,这叫做事务。
○ 事务:Transaction(tx)
● 事务的四个处理过程:
○ 第一步:开启事务 (start transaction)
○ 第二步:执行核心业务代码
○ 第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)
○ 第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)
● 事务的四个特性:
○ A 原子性:事务是最小的工作单元,不可再分。
○ C 一致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
○ I 隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。
○ D 持久性:持久性是事务结束的标志。
16.2 引入事务场景
以银行账户转账为例学习事务。两个账户act-001和act-002。act-001账户向act-002账户转账10000,必须同时成功,或者同时失败。(一个减成功,一个加成功, 这两条update语句必须同时成功,或同时失败。)
连接数据库的技术采用Spring框架的JdbcTemplate。
采用三层架构搭建:
P119 spring事务之引入事务场景 :
一张表会对应一个实体类(一般)
AccountDaoImpl类中的内容:
package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public Account selectByActno(String actno) {
String sql = "select actno, balance from t_act where actno = ?";
Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
return account;
}
@Override
public int update(Account act) {
String sql = "update t_act set balance = ? where actno = ?";
int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());
return count;
}
}
AccountDao接口中的内容:
package com.powernode.bank.dao;
import com.powernode.bank.pojo.Account;
/**
* 专门负责账号信息的CRUD操作。
* DAO中只执行SQL语句,没有任何业务逻辑。
* 也就是说DAO不和业务挂钩。
*/
public interface AccountDao {
/**
* 根据账号查询账户信息
* @param actno
* @return
*/
Account selectByActno(String actno);
/**
* 更新账户信息。
* @param act
* @return
*/
int update(Account act);
}
Account类中的内容:
package com.powernode.bank.pojo;
/**
* 银行账号类
*/
public class Account {
private String actno;
private Double balance;
public Account() {
}
public Account(String actno, Double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
}
AccountServiceImpl类中的内容:
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
//控制事务,因为在这个方法中要完成所有的转账业务。
@Override
public void transfer(String fromActno, String toActno, double money) {
//查询转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new RuntimeException("余额不足!!!");
}
//余额充足
Account toAct = accountDao.selectByActno(toActno);
//将内存中两个对象的余额先修改。
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//数据库更新
int count = accountDao.update(fromAct);
count += accountDao.update(toAct);
if(count != 2){
throw new RuntimeException("转账失败,联系银行!");
}
}
}
AccountService接口中的内容:
package com.powernode.bank.service;
/**
* 业务接口
* 事务就是在这个接口下控制的。
*/
public interface AccountService {
/**
* 转账业务方法
* @param fromActno 从这个账户转出
* @param toActno 转入这个账号
* @param money //转账金额
*/
void transfer(String fromActno,String toActno, double money);
}
P120 spring事务之演示转账失败:
P121 Spirng事务之事务管理器接口
16.3 Spring对事务的支持
Spring实现事务的两种方式
● 编程式事务
○ 通过编写代码的方式来实现事务的管理。
● 声明式事务
○ 基于注解方式
○ 基于XML配置方式
Spring事务管理API
Spring对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。所以Spring专门针对事务开发了一套API,API的核心接口如下:
PlatformTransactionManager接口:spring事务管理器的核心接口。在Spring6中它有两个实现:
● DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
● JtaTransactionManager:支持分布式事务管理。
如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务。(Spring内置写好了,可以直接用。)
P122 spring事务之注解方式:
@Transactional如果你写在类上,表示所有的方法都应用事务,如果用在方法上,说明只针对这个方法应用食物
AccountDaoImpl类中的内容:
package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public Account selectByActno(String actno) {
String sql = "select actno, balance from t_act where actno = ?";
Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
return account;
}
@Override
public int update(Account act) {
String sql = "update t_act set balance = ? where actno = ?";
int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());
return count;
}
}
AccountDao类中的内容:
package com.powernode.bank.dao;
import com.powernode.bank.pojo.Account;
/**
* 专门负责账号信息的CRUD操作。
* DAO中只执行SQL语句,没有任何业务逻辑。
* 也就是说DAO不和业务挂钩。
*/
public interface AccountDao {
/**
* 根据账号查询账户信息
* @param actno
* @return
*/
Account selectByActno(String actno);
/**
* 更新账户信息。
* @param act
* @return
*/
int update(Account act);
}
Account类中的内容:
package com.powernode.bank.pojo;
/**
* 银行账号类
*/
public class Account {
private String actno;
private Double balance;
public Account() {
}
public Account(String actno, Double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
}
AccountServiceImpl类中的内容:
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
//控制事务,因为在这个方法中要完成所有的转账业务。
@Override
//@Transactional
public void transfer(String fromActno, String toActno, double money) {
//第一步:开启事务
//第二步:执行核心业务逻辑
//查询转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new RuntimeException("余额不足!!!");
}
//余额充足
Account toAct = accountDao.selectByActno(toActno);
//将内存中两个对象的余额先修改。
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//数据库更新
int count = accountDao.update(fromAct);
//模拟异常
// String s =null;
// s.toString();
count += accountDao.update(toAct);
if(count != 2){
throw new RuntimeException("转账失败,联系银行!");
}
//第三步:如果执行业务流程过程中,没有异常,提交事务
//第四步:如果执行业务流程过程中,有异常,回滚事务
}
}
AccountService接口中 的内容:
package com.powernode.bank.service;
/**
* 业务接口
* 事务就是在这个接口下控制的。
*/
public interface AccountService {
/**
* 转账业务方法
* @param fromActno 从这个账户转出
* @param toActno 转入这个账号
* @param money //转账金额
*/
void transfer(String fromActno,String toActno, double money);
}
spring配置文件spring.xml文件中的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--组件扫描 -->
<context:component-scan base-package="com.powernode.bank" />
<!--配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" >
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
<!--配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!--配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--开启事务注解驱动器,开启事务注解。告诉Spring框架,采用注解的方式去控制事务。 -->
<tx:annotation-driven transaction-manager="txManager" />
</beans>
测试类中的内容:
@Test
public void testSpringTx(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
try{
accountService.transfer("act-001","act-002",10000);
System.out.println("转账成功");
}catch(Exception e){
e.printStackTrace();
}
}
P123 Spring事务之传播行为 :
事务属性
事务属性包括哪些
事务中的重点属性:
● 事务传播行为
● 事务隔离级别
● 事务超时
● 只读事务
● 设置出现哪些异常回滚事务
● 设置出现哪些异常不回滚事务
事务传播行为
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
事务传播行为在spring框架中被定义为枚举类型:
一共有七种传播行为:
● REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
● SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
● MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
● REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
● NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
● NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
● NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
P124 Spring事务之搭建传播行为测试程序
一定要集成Log4j2日志框架,在日志信息中可以看到更加详细的信息。
事务隔离级别
事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好。
数据库中读取数据存在的三大问题:(三大读问题)
● 脏读:读取到没有提交到数据库的数据,叫做脏读。
● 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。
● 幻读:读到的数据是假的。
事务隔离级别包括四个级别:
● 读未提交:READ_UNCOMMITTED
○ 这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。
● 读提交:READ_COMMITTED
○ 解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。
● 可重复读:REPEATABLE_READ
○ 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。
● 序列化:SERIALIZABLE
○ 解决了幻读问题,事务排队执行。不支持并发。
大家可以通过一个表格来记忆:
隔离级别 脏读 不可重复读 幻读
读未提交 有 有 有
读提交 无 有 有
可重复读 无 无 有
序列化 无 无 无
在Spring代码中如何设置隔离级别?
隔离级别在spring中以枚举类型存在:
P125 Spring事务之传播行为REQUIRED:
依赖文件内容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.powernode</groupId>
<artifactId>spring6-013-tx-bank</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!--仓库-->
<repositories>
<!--spring里程碑版本的仓库-->
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<!--依赖-->
<dependencies>
<!--spring context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--spring jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.13</version>
</dependency>
<!--@Resource注解-->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--log4j2的依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
log4j2.xml文件中的内容:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
-->
<root level="DEBUG">
<appender-ref ref="spring6log"/>
</root>
</loggers>
<appenders>
<!--输出日志信息到控制台 -->
<console name="spring6log" target="SYSTEM_OUT">
<!--控制日志输出的格式 -->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
</appenders>
</configuration>
AccountServiceImpl类中的内容:
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService")
//@Transactional
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
//控制事务,因为在这个方法中要完成所有的转账业务。
@Override
@Transactional
public void transfer(String fromActno, String toActno, double money) {
//第一步:开启事务
//第二步:执行核心业务逻辑
//查询转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money) {
throw new RuntimeException("余额不足!!!");
}
//余额充足
Account toAct = accountDao.selectByActno(toActno);
//将内存中两个对象的余额先修改。
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
//数据库更新
int count = accountDao.update(fromAct);
//模拟异常
// String s =null;
// s.toString();
count += accountDao.update(toAct);
if(count != 2){
throw new RuntimeException("转账失败,联系银行!");
}
//第三步:如果执行业务流程过程中,没有异常,提交事务
//第四步:如果执行业务流程过程中,有异常,回滚事务
//withdraw();
}
@Transactional(propagation = Propagation.REQUIRED)
public void withdraw(){
}
@Resource(name = "accountService2")
private AccountService accountService;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void save(Account act) {
//这里调用dao的insert方法。
accountDao.insert(act);//保存act-003账户
//创建账户对象
Account act2 = new Account("act-004",1000.0);
try{
accountService.save(act2);//保存act-004账户
}catch (Exception e){
}
}
}
AccountServiceImpl2类中的内容:
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService2")
public class AccountServiceImpl2 implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Override
public void transfer(String fromActno, String toActno, double money) {
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void save(Account act) {
accountDao.insert(act);
//模拟异常
String s = null;
s.toString();
//事儿还没有处理完,这个大括号当中的后续也许还有其他的DML语句。
}
}
测试类中的内容:
@Test
public void testPropagation(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
Account act = new Account("act-003",1000.0);
accountService.save(act);
}
其他文件未存储。
P126 Spring 事务之传播行为REQUIRES_NEW :
内容:略
P127 Spring事务之事务隔离级别的三大读问题:
事务隔离级别
事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好。
数据库中读取数据存在的三大问题:(三大读问题)
● 脏读:读取到没有提交到数据库的数据,叫做脏读。
● 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。
● 幻读:读到的数据是假的。
事务隔离级别包括四个级别:
● 读未提交:READ_UNCOMMITTED
○ 这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。
● 读提交:READ_COMMITTED
○ 解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。
● 可重复读:REPEATABLE_READ
○ 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。
● 序列化:SERIALIZABLE
○ 解决了幻读问题,事务排队执行。不支持并发。
大家可以通过一个表格来记忆:
隔离级别 脏读 不可重复读 幻读
读未提交 有 有 有
读提交 无 有 有
可重复读 无 无 有
序列化 无 无 无
在Spring代码中如何设置隔离级别?
隔离级别在spring中以枚举类型存在:
P127 3:24
Time:4.30h+40min=5h
视频网址:
https://www.bilibili.com/video/BV1Ft4y1g7Fb?p=127&vd_source=ff898217e3612fcbdefecfdc49657ab1