网站建设难吗,外贸网站优化排名,申请好域名后怎么建设网站,饲料网站源码文章目录1. Spring 事务简介2. Spring 事务角色3. Spring 事务属性3.1 事务配置3.2 案例#xff1a;转账业务追加日志3.3 事务传播行为1. Spring 事务简介
Spring 事务作用#xff1a;在数据层或业务层保障一系列的数据库操作同成功、同失败。
数据层有事务我们可以理解转账业务追加日志3.3 事务传播行为1. Spring 事务简介
Spring 事务作用在数据层或业务层保障一系列的数据库操作同成功、同失败。
数据层有事务我们可以理解为什么业务层也需要处理事务呢?
举个简单的例子 转账业务会有两次数据层的调用一次是转入者加钱一次是转出者减钱 把事务放在数据层加钱和减钱就有两个事务 如果先加钱后减钱且加钱之后其他地方出现异常就会出现加钱成功减钱失败的结果。即没办法保证加钱和减钱同时成功或者同时失败 这个时候就需要将事务放在业务层进行处理。
不用事务时我们是这样处理的 Dao接口
public interface AccountDao {Update(update tbl_account set money money #{money} where name #{name})void inMoney(Param(name) String name, Param(money) Double money);Update(update tbl_account set money money - #{money} where name #{name})void outMoney(Param(name) String name, Param(money) Double money);
}Service 接口和实现类
public interface AccountService {/*** 转账操作* param out 转出方* param in 转入方* param money 余额*/void transfer(String out, String in, Double money);
}Service
public class AccountServiceImpl implements AccountService {Autowired//按类型注入private AccountDao accountDao;Overridepublic void transfer(String out, String in, Double money) {accountDao.outMoney(out, money);accountDao.inMoney(in, money);}
}测试
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classes SpringConfig.class)
public class AccountServiceTest {Autowiredprivate AccountService accountService;Testpublic void testTransfer() {accountService.transfer(Tom, Jerry, 100D);}
}上述代码运行时会执行转账操作Tom 的账户减少 100Jerry 的账户增加 100。
这是正常情况下的运行结果但是如果在转账的过程中出现了异常如 这个时候就模拟了转账过程中出现异常的情况正确的操作应该是 Tom 和 Jerry 账户中的钱不变。但是真正运行后会发现并没有像我们想象的那样Tom账户中的钱变少了而 Jerry 账户中的钱没变。
当程序出问题后我们需要让事务回滚且这个事务应该加在业务层上。Spring 的事务管理就是用来解决这类问题的。
Spring事务管理具体的实现步骤为:
(1) 在需要被事务管理的方法上添加注解
Service
public class AccountServiceImpl implements AccountService {Autowired//按类型注入private AccountDao accountDao;OverrideTransactional//开启事务public void transfer(String out, String in, Double money) {accountDao.outMoney(out, money);accountDao.inMoney(in, money);}
}注意: Transactional 可以写在接口类上、接口方法上、实现类上和实现类方法上 写在接口类上该接口的所有实现类的所有方法都会有事务 写在接口方法上该接口的所有实现类的该方法都会有事务 写在实现类上该类中的所有方法都会有事务 写在实现类方法上该方法上有事务 建议写在实现类或实现类的方法上
(2) 在 JdbcConfig 类中配置事务管理器
public class JdbcConfig {Value(${jdbc.driver})private String driver;Value(${jdbc.url})private String url;Value(${jdbc.username})private String userName;Value(${jdbc.password})private String password;Bean//把方法的返回值定义成一个beanpublic DataSource dataSource(){DruidDataSource ds new DruidDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(userName);ds.setPassword(password);return ds;}//配置事务管理器mybatis使用的是jdbc事务Bean//把方法的返回值定义成一个beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager transactionManager new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}注意事务管理器要根据使用技术进行选择Mybatis 框架使用的是 JDBC 事务可以直接使用 DataSourceTransactionManager。
(3) 开启事务注解
Configuration//该类是配置类
ComponentScan(com.itheima)//扫描这个包下的类找bean
PropertySource(jdbc.properties)
Import({JdbcConfig.class,MybatisConfig.class})//引入这些类中的bean
//开启注解式事务驱动
EnableTransactionManagement
public class SpringConfig {}(4) 测试
会发现在转账业务出现错误后事务就可以控制回滚保证数据的正确性。
2. Spring 事务角色
本节重点理解两个概念分别是事务管理员和事务协调员。
(1) 未开启Spring事务之前:
AccountDao 的 outMoney 因为是修改操作会开启一个事务 T1 AccountDao 的 inMoney 因为是修改操作会开启一个事务 T2 AccountService 的 transfer 没有事务。
运行过程中如果没有抛出异常则 T1 和 T2 都正常提交数据正确 如果在两个方法中间抛出异常T1 因为执行成功提交事务T2 因为抛异常不会被执行就会导致数据出现错误。
(2) 开启 Spring 事务管理后 transfer上添加了 Transactional 注解在该方法上就会有一个事务 T。 AccountDao 的 outMoney 方法的事务 T1 加入到 transfer 的事务 T 中 AccountDao 的 inMoney 方法的事务 T2 加入到 transfer 的事务 T 中。 这样就保证他们在同一个事务中当业务层中出现异常整个事务就会回滚保证数据的准确性。
通过上面例子的分析可以得到如下概念:
事务管理员发起事务方在 Spring 中通常指代业务层开启事务的方法如transfer 方法 事务协调员加入事务方在 Spring 中通常指代数据层方法如outMoney、inMoney 方法也可以是业务层方法。
注意: 目前的事务管理的前提是 DataSourceTransactionManager 和 SqlSessionFactoryBean 使用的是同一个数据源。
3. Spring 事务属性
3.1 事务配置 上面这些属性都可以在Transactional注解的参数上进行设置。
readOnlytrue 只读事务false 读写事务增删改要设为 false查询设为 true。timeout设置超时时间单位秒在多长时间之内事务没有提交成功就自动回滚-1 表示不设置超时时间。rollbackFor当出现指定异常时进行事务回滚。noRollbackFor当出现指定异常时不进行事务回滚。rollbackForClassName 等同于 rollbackFor只不过属性为异常的类全名字符串。noRollbackForClassName 等同于 noRollbackFor只不过属性为异常的类全名字符串。isolation 设置事务的隔离级别 DEFAULT默认隔离级别, 会采用数据库的隔离级别 READ_UNCOMMITTED读未提交 READ_COMMITTED读已提交 REPEATABLE_READ重复读取 SERIALIZABLE串行化
rollbackFor 是当出现指定异常时进行事务回滚对于异常事务不应该都回滚么为什么还要指定?
这块需要更正一个知识点并不是所有的异常都会回滚事务比如下面的代码就不会回滚导致转出账户减钱成功转入账户加钱失败。 出现这个问题的原因是Spring 事务只对 Error 异常和 RuntimeException 异常及其子类进行事务回滚其他的异常类型不回滚。IOException 就是其他的异常类型所以不回滚。
此时就可以使用 rollbackFor 属性来设置 IOException 异常回滚 3.2 案例转账业务追加日志
需求实现任意两个账户间转账操作并对每次转账操作在数据库进行留痕 需求微缩A账户减钱B账户加钱数据库记录日志
分析 ①基于转账操作案例添加日志模块实现数据库中记录日志 ②业务层转账操作transfer调用减钱、加钱与记录日志功能
实现效果预期 无论转账操作是否成功均进行转账操作的日志留痕
该环境是基于转账环境来完成的下面在其基础上继续往下写
(1) 创建日志表
create table tbl_log(id int primary key auto_increment,info varchar(255),createDate datetime
)(2) 添加 LogDao 接口
public interface LogDao {Insert(insert into tbl_log (info,createDate) values(#{info},now()))void log(String info);
}(3) 添加 LogService 接口与实现类
public interface LogService {void log(String out, String in, Double money);
}Service
public class LogServiceImpl implements LogService {Autowiredprivate LogDao logDao;Transactionalpublic void log(String out,String in,Double money ) {logDao.log(转账操作由out到in,金额money);}
}(4) 在转账的业务中添加记录日志
Service
public class AccountServiceImpl implements AccountService {Autowired//按类型注入private AccountDao accountDao;Autowiredprivate LogService logService;//添加日志相关Override//开启事务出现IOException时回滚Transactionalpublic void transfer(String out, String in, Double money){try{accountDao.outMoney(out,money);//int i 1/0;accountDao.inMoney(in,money);}finally {logService.log(out,in,money);//添加日志相关}}
}当程序正常运行tbl_account 表中转账成功tbl_log 表中日志记录成功 当转账业务之间出现异常(int i 1/0)转账失败tbl_account 成功回滚但 tbl_log 表未添加数据。
这个结果和我们想要的不一样什么原因该如何解决 失败原因日志的记录与转账操作隶属同一个事务同成功同失败。 理想效果无论转账操作是否成功日志必须保留
3.3 事务传播行为
前面讲过 Spring 事务会把 T1、T2、T3 都加入到事务 T 中。 所以当转账失败后所有的事务都回滚导致日志没有记录下来。 这与需求不符此时就想能不能让 log 方法单独是一个事务呢。 要想解决这个问题就需要用到事务传播行为。 事务传播行为事务协调员对事务管理员所携带事务的处理态度。
具体如何解决就需要用到 propagation 属性。
Service
public class LogServiceImpl implements LogService {Autowiredprivate LogDao logDao;//propagation设置事务属性传播行为设置为当前操作需要新事务Transactional(propagation Propagation.REQUIRES_NEW)public void log(String out,String in,Double money) {logDao.log(转账操作由out到in,金额money);}
}经过上面的修改当转账失败时tbl_log 表中的日志记录也会成功。但是记录的内容也是 “转账操作由Tom到Jerry,金额50.0”这显然不合理。于是 AccountServiceImpl 中做如下修改
Service
public class AccountServiceImpl implements AccountService {Autowired//按类型注入private AccountDao accountDao;Autowiredprivate LogService logService;//添加日志相关OverrideTransactionalpublic void transfer(String out, String in, Double money){try{accountDao.outMoney(out,money);//int i 1/0;accountDao.inMoney(in,money);logService.log(out,in,money);//添加日志相关}catch (Exception e){logService.log(out,in失败,0D);//添加日志相关throw new RuntimeException();//使事务回滚}}
}这样转账失败时记录的日志就变为 “转账操作由Tom到Jerry失败,金额0.0“。
【注意】当 catch 捕获了异常时事务不会回滚。如果非得写 catch需要 catch 后 throw new RuntimeException() 让事务回滚。
事务传播行为的可选值