Spring04
# spring 事务
1)事务回顾
1.1)什么是事务?
事务指数据库中多个操作合并在一起形成的操作序列
1.2)事务的作用
1.当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C)(要么操作前状态,要么操作后状态)。
2.当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)。
-
事务特征(ACID)
-
原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
-
一致性(Consistency)事务前后数据的完整性必须保持一致
-
隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
-
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
-
1.3)事务的隔离级
-
脏读:允许读取未提交的信息
- 原因:Read uncommitted
解决方案: (表级读锁)
- 不可重复读:读取过程中单个数据发生了变化
- 解决方案: Repeatable read (行级写锁)
- 幻读:读取过程中数据条目发生了变化
- 解决方案: Serializable(表级写锁)
2)事务管理
2.1)Spring事务核心对象
-
J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
-
Spring为业务层提供了整套的事务解决方案
-
PlatformTransactionManager
-
TransactionDefinition
-
TransactionStatus
-
2.2)PlatformTransactionManager
-
平台事务管理器实现类
-
DataSourceTransactionManager 适用于Spring JDBC或MyBatis
-
HibernateTransactionManager 适用于Hibernate3.0及以上版本
-
JpaTransactionManager 适用于JPA
-
JdoTransactionManager 适用于JDO
-
JtaTransactionManager 适用于JTA
-
-
JPA(Java Persistence API)Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行
-
JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
-
JTA(Java Transaction API)Java EE 标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO 都可以参与到一个JTA事务中
此接口定义了事务的基本操作
-
获取事务 :
TransactionStatus getTransaction(TransactionDefinition definition)
-
提交事务 :
void commit(TransactionStatus status)
-
回滚事务 :
void rollback(TransactionStatus status)
2.3)TransactionDefinition
此接口定义了事务的基本信息
-
获取事务定义名称
String getName()
-
获取事务的读写属性
boolean isReadOnly()
-
获取事务隔离级别
int getIsolationLevel()
-
获事务超时时间
int getTimeout()
-
获取事务传播行为特征
int getPropagationBehavior()
2.4)TransactionStatus
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
2.5)事务控制方式
-
编程式
-
声明式(XML)
-
声明式(注解)
2.6)案例说明
2.6.1)案例说明
银行转账业务说明
银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。
2.6.2)案例环境(基于Spring、Mybatis整合)
- 业务层接口提供转账操作
/**
* 转账操作
* @param outName 出账用户名
* @param inName 入账用户名
* @param money 转账金额
*/
public void transfer(String outName,String inName,Double money);
- 业务层实现提供转账操作
public void transfer(String outName,String inName,Double money){
accountDao.inMoney(outName,money); accountDao.outMoney(inName,money);
}
- 数据层提供对应的入账与出账操作
<update id="inMoney">
update account set money = money + #{money} where name = #{name}
</update>
<update id="outMoney">
update account set money = money - #{money} where name = #{name}
</update>
2.6.3)编程式事务
public void transfer(String outName,String inName,Double money){
//创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置与数据层相同的数据源
dstm.setDataSource(dataSource);
//创建事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
//创建事务状态对象,用于控制事务执行
TransactionStatus ts = dstm.getTransaction(td);
accountDao.inMoney(outName,money);
int i = 1/0; //模拟业务层事务过程中出现错误
accountDao.outMoney(inName,money);
//提交事务
dstm.commit(ts);
}
2.7)使用AOP控制事务
将业务层的事务处理功能抽取出来制作成AOP通知,利用环绕通知运行期动态织入
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
配置AOP通知类,并注入dataSource
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
使用环绕通知将通知类织入到原始业务对象执行过程中
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="tx" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
2.8声明式事务(XML)
AOP配置事务是否具有特例性?
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
使用tx命名空间配置事务专属通知类
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
</tx:attributes>
</tx:advice>
使用aop:advisor在AOP配置中引用事务专属通知类
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
2.8.1)aop:advice与aop:advisor区别
-
aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系
-
aop:advisor配置的通知类必须实现通知接口
-
MethodBeforeAdvice
-
AfterReturningAdvice
-
ThrowsAdvice
-
……
-
2.8.2)tx配置----tx:advice
-
名称:tx:advice
-
类型:标签
-
归属:beans标签
-
作用:专用于声明事务通知
-
格式:
<beans> <tx:advice id="txAdvice" transaction-manager="txManager"> </tx:advice> </beans>
-
基本属性:
-
id :用于配置aop时指定通知器的id
-
transaction-manager :指定事务管理器bean
-
2.8.3)tx配置----tx:attributes
-
名称:tx:attributes
-
类型:标签
-
归属:tx:advice标签
-
作用:定义通知属性
-
格式:
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> </tx:attributes> </tx:advice>
-
基本属性:
- 无
2.8.4)tx配置----tx:method
-
名称:tx:method
-
类型:标签
-
归属:tx:attribute标签
-
作用:设置具体的事务属性
-
格式:
<tx:attributes> <tx:method name="*" read-only="false" /> <tx:method name="get*" read-only="true" /> </tx:attributes>
-
说明:
通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性
tx:method属性
2.9)事务传播行为
-
事务管理员
-
事务协调员
- 事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度
2.10)事务传播行为
2.11)事务传播应用
-
场景A:生成订单业务
-
子业务S1:记录日志到数据库表X
-
子业务S2:保存订单数据到数据库表Y
-
子业务S3:……
-
如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
-
(S1需要新事务)
-
-
场景B:生成订单业务
-
背景1:订单号生成依赖数据库中一个专门用于控制订单号编号生成的表M获取
-
背景2:每次获取完订单号,表M中记录的编号自增1
-
子业务S1:从表M中获取订单编号
-
子业务S2:保存订单数据,订单编号来自于表M
-
子业务S3:……
-
如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
-
(S1需要新事务)
-
2.12)声明式事务(注解)
2.12.1)@Transactional
-
名称:@Transactional
-
类型:方法注解,类注解,接口注解
-
位置:方法定义上方,类定义上方,接口定义上方
-
作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
-
范例:
@Transactional( readOnly = false, timeout = -1, isolation = Isolation.DEFAULT, rollbackFor = {ArithmeticException.class, IOException.class}, noRollbackFor = {}, propagation = Propagation.REQUIRES_NEW )
2.12.2)tx:annotation-driven
-
名称:tx:annotation-driven
-
类型:标签
-
归属:beans标签
-
作用:开启事务注解驱动,并指定对应的事务管理器
-
范例:
<tx:annotation-driven transaction-manager="txManager"/>
2.13)声明式事务(纯注解驱动)
-
名称:@EnableTransactionManagement
-
类型:类注解
-
位置:Spring注解配置类上方
-
作用:开启注解驱动,等同XML格式中的注解驱动
-
范例:
@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class}) @EnableTransactionManagement public class SpringConfig { }
public class TransactionManagerConfig { @Bean public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
3)模板对象
3.1)Spring模块对象
-
TransactionTemplate
-
JdbcTemplate
-
RedisTemplate
-
RabbitTemplate
-
JmsTemplate
-
HibernateTemplate
-
RestTemplate
3.2)JdbcTemplate(了解)
提供标准的sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
3.3)NamedParameterJdbcTemplate(了解)
提供标准的具名sql语句操作API
public void save(Account account) {
String sql = "insert into account(name,money)values(:name,:money)";
Map pm = new HashMap();
pm.put("name",account.getName());
pm.put("money",account.getMoney());
jdbcTemplate.update(sql,pm);
}
3.4)RedisTemplate
RedisTemplate对象结构
public void changeMoney(Integer id, Double money) {
redisTemplate.opsForValue().set("account:id:"+id,money);
}
public Double findMondyById(Integer id) {
Object money = redisTemplate.opsForValue().get("account:id:" + id);
return new Double(money.toString());
}
4)事务底层原理解析
4.1)策略模式应用
策略模式(Strategy Pattern)使用不同策略的对象实现不同的行为方式,策略对象的变化导致行为的变化。
策略模式(Strategy Pattern)使用不同策略的对象实现不同的行为方式,策略对象的变化导致行为的变化。
202211月新增B站三更草堂spring-04部分笔记如下:
Spring-04
1.Spring整合Junit
①导入依赖
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- spring整合junit的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
② 编写测试类
在测试类上加上
@RunWith(SpringJUnit4ClassRunner.class)注解,指定让测试运行于Spring环境
@ContextConfiguration注解,指定Spring容器创建需要的配置文件或者配置类
@RunWith(SpringJUnit4ClassRunner.class)//让测试运行与Spring测试环境
@ContextConfiguration(locations = "classpath:配置文件1.xml")//设置Spring配置文件或者配置类(指定以什么配置文件来创建spring容器,如果不使用xml配置文件的而使用java配置类的方式的话则使用下面的@ContextConfiguration注解 效果也一样)
//@ContextConfiguration(classes = SpringConfig.class)
public class SpringTest {}
这样就不用创建一个spring容器去getbean,然后运行测试代码了
③注入对象进行测试
在测试类中注入要测试的对象,定义测试方法,在其中使用要测试的对象。
@RunWith(SpringJUnit4ClassRunner.class)//让测试运行与Spring测试环境
@ContextConfiguration(locations = "classpath:配置文件1.xml")//设置Spring配置文件或者配置类
//@ContextConfiguration(classes = SpringConfig.class)
public class SpringTest {
// 想测哪个对象,就注入哪个对象
@Autowired
private UserService userService;
//定义测试方法
@Test
public void testUserService() {
userService.findById(10);
}
}
2.Spring整合Mybatis
我们如果想把Mybatis整合到Spring中需要使用一个整合包mybatis-spring
官方文档:http://mybatis.org/spring/zh/index.html
①导入依赖
<!-- spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- mybatis整合到Spring的整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
②往容器中注入整合相关对象
<!--读取properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--创建连接池注入容器-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClassName" value="${jdbc.driver}"></property>
</bean>
<!--spring整合mybatis后控制的创建获取SqlSessionFactory的对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sessionFactory">
<!--配置连接池-->
<property name="dataSource" ref="dataSource"></property>
<!--配置mybatis配置文件的路径-->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>
<!--mapper扫描配置,扫描到的mapper对象会被注入Spring容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="mapperScannerConfigurer">
<property name="basePackage" value="com.sangeng.dao"></property>
</bean>
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">
<configuration>
<typeAliases>
<package name="com.sangeng.domain"></package>
</typeAliases>
</configuration>
③从容器中获取Mapper对象进行使用
@Autowired
private UserDao userDao;
3.Spring声明式事务
3.1 事务回顾
3.1.1 事务的概念
保证一组数据库的操作,要么同时成功,要么同时失败
3.1.2 四大特性
-
隔离性
多个事务之间要相互隔离,不能互相干扰
-
原子性
指事务是一个不可分割的整体,类似一个不可分割的原子
-
一致性
保障事务前后这组数据的状态是一致的。要么都是成功的,要么都是失败的。
-
持久性
指事务一旦被提交,这组操作修改的数据就真的的发生变化了。即使接下来数据库故障也不应该对其有影响。
3.2 实现声明式事务
如果我们自己去对事务进行控制的话我们就需要值原来核心代码的基础上加上事务控制相关的代码。而在我们的实际开发中这种事务控制的操作也是非常常见的。所以Spring提供了声明式事务的方式让我们去控制事务。
只要简单的加个注解(或者是xml配置)就可以实现事务控制,不需要事务控制的时候只需要去掉相应的注解即可。(这样就不用写以前JDBC原始的connection.setAutoCommit(false); con.rollback();等这些代码了)
有如下两种方式:
- 3.2.1 注解方式实现
- 3.2.2 xml方式实现
3.2.0 案例环境准备
①数据初始化
CREATE DATABASE /*!32312 IF NOT EXISTS*/`spring_db` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `spring_db`;
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT NULL,
`money` DOUBLE DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `account`(`id`,`name`,`money`) VALUES (1,'三更',100),(2,'草堂',100);
②Spring整合Mybatis
③创建Service和Dao
public interface AccountService {
/**
* 转账
* @param outId 转出账户的id
* @param inId 转出账户的id
* @param money 转账金额
*/
public void transfer(Integer outId,Integer inId,Double money);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccoutDao accoutDao;
public void transfer(Integer outId, Integer inId, Double money) {
//增加
accoutDao.updateMoney(inId,money);
//减少
accoutDao.updateMoney(outId,-money);
}
}
public interface AccoutDao {
void updateMoney(@Param("id") Integer id,@Param("updateMoney") Double updateMoney);
}
AccoutDao.xml如下:
<?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="com.sangeng.dao.AccoutDao">
<update id="updateMoney">
update account set money = money + #{updateMoney} where id = #{id}
</update>
</mapper>
3.2.1 注解方式实现
①配置事务管理器和事务注解驱动
在spring的配置文件中添加如下配置:
<!--把事务管理器注入Spring容器,需要配置一个连接池-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解驱动,配置使用的事务管理器-->
<tx:annotation-driven transaction-manager="txManager"/>
②添加注解
在需要进行事务控制的方法或者类上添加@Transactional注解就可以实现事务控制。
@Transactional
public void transfer(Integer outId, Integer inId, Double money) {
//增加
accoutDao.updateMoney(inId,money);
// System.out.println(1/0);
//减少
accoutDao.updateMoney(outId,-money);
}
注意:如果加在类上,这个类的所有方法都会受事务控制,如果加在方法上,就是那一个方法受事务控制。
注意,因为声明式事务底层是通过AOP实现的,所以最好把AOP相关依赖都加上。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
3.2.2 xml方式实现
①配置事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
②配置事务切面
<!--定义事务管理的通知类-->
<tx:advice transaction-manager="txManager" id="txAdvice">
<tx:attributes>
<tx:method name="trans*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.sangeng.service..*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
注意,因为声明式事务底层是通过AOP实现的,所以最好把AOP相关依赖都加上。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
3.3 属性配置
3.3.1 事务传播行为propagation
当事务方法嵌套调用时,需要控制是否开启新事务,可以使用事务传播行为来控制。
测试案例:
@Service
public class TestServiceImpl {
@Autowired
AccountService accountService;
@Transactional // 注意: 外层有事务注解
public void test(){
accountService.transfer(1,2,10D);
accountService.log();
}
}
public class AccountServiceImpl implements AccountService {
//...省略其他不相关代码
@Transactional // 注意: 内层也有事务注解
public void log() {
System.out.println("打印日志");
int i = 1/0;
}
}
假如出现了像上面那样的大小/内外 事务嵌套的情况下,有如下规则:
属性值 | 行为 |
---|---|
REQUIRED(必须要有) | 外层方法有事务,内层方法就加入(其实就是用同一个JDBC的connection连接)。外层没有,内层就新建 |
REQUIRES_NEW(必须要有新事务) | 外层方法有事务,内层方法新建(就是新建一个JDBC的connection连接,外层回滚不影响内层方法)。外层没有,内层也新建 |
SUPPORTS(支持有) | 外层方法有事务,内层方法就加入。外层没有,内层就也没有 |
NOT_SUPPORTED(支持没有) | 外层方法有事务,内层方法没有。外层没有,内层也没有 |
MANDATORY(强制要求外层有) | 外层方法有事务,内层方法加入。外层没有。内层就报错 |
NEVER(绝不允许有) | 外层方法有事务,内层方法就报错。外层没有。内层就也没有 |
例如:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transfer(Integer outId, Integer inId, Double money) {
//增加
accoutDao.updateMoney(inId,money);
//减少
accoutDao.updateMoney(outId,-money);
}
3.3.2 隔离级别isolation
Isolation.DEFAULT 表示使用数据库默认隔离级别
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void transfer(Integer outId, Integer inId, Double money) {
//增加
accoutDao.updateMoney(inId,money);
//减少
accoutDao.updateMoney(outId,-money);
}
3.3.3 只读readOnly
如果事务中的操作(方法代码)都是读操作,没涉及到对数据的写操作可以设置readOnly为true。这样可以提高效率。
@Transactional(readOnly = true)
public void log() {
System.out.println("打印日志");
int i = 1/0;
}