事务概念
1、什么是事务
(1)事务是数据库操作的最基本的单元,逻辑上是一组操作,要么都成功,如果有一个失败则所有操作都失败
2、事务四个特性(ACID)
(1)原子性
- 一次事务提交,事务里的一组操作,要么全部提交成功,要么全部失败回滚,不能一部分成功,一部分失败
(2)一致性 - 事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。
(3)隔离性 - 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
(4)持久性 - 即一个事务一旦提交,它对数据库中数据的改变应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务操作环境
1、定义一个演示场景
比如银行转账,张三从自己的账户上转账100元到李四的账户上,此时数据库需要进行变化,张三的账户需要减100,李四的账户应该加100元。
2、创建一个测试表
3、注入DataSource和JdbcTemplate
<?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"
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">
<!-- 注入组件扫描类 -->
<context:component-scan base-package="com.company.base.spring9"/>
<!-- 引入外部配置文件 -->
<context:property-placeholder location="classpath:orm.properties"/>
<!-- 注入数据源信息 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 注入jdbcTemplate的操作bean -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
4、创建service和dao
service层定义一个转账的方法,而dao定义一个账户加钱和一个账户减钱的方法
@Service
public class BandCardService {
@Autowired
private BandCardDao bandCardDao;
// 账户操作
/**
*
* @param fromName money转出的账户
* @param targetName money转入的账户
* @param money 转入的金额
*/
public void operAccount(String fromName, String targetName, int money) {
bandCardDao.reduceAccount(fromName, money);
bandCardDao.addAccount(targetName, money);
}
}
public interface BandCardDao {
void addAccount(String name, int money);
void reduceAccount(String name, int money);
}
@Repository
public class BandCardDaoImpl implements BandCardDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addAccount(String name, int money) {
String sql = "update band_card set money = money + ? where name = ?";
jdbcTemplate.update(sql, new Object[]{money, name});
}
@Override
public void reduceAccount(String name, int money) {
String sql = "update band_card set money = money - ? where name = ?";
jdbcTemplate.update(sql, new Object[]{money, name});
}
}
5、进行测试
public class TestBandCard {
@Test
public void test() {
ApplicationContext context =
new ClassPathXmlApplicationContext("tx.xml");
BandCardService service = context.getBean("bandCardService", BandCardService.class);
service.operAccount("zhangsan", "lisi", 100);
}
}
事务场景引入
(1)一组操作,有的执行成功,有的执行失败了
public void operAccount(String fromName, String targetName, int money) {
bandCardDao.reduceAccount(fromName, money);
int i = 10/0;
bandCardDao.addAccount(targetName, money);
}
执行结果:抛出异常,zhangsan的账户减钱成功了,而lisi的账户由于异常抛出而未执行。
(2)事务执行的流程
Spring事务管理
1、事务一般添加到JavaEE三层结构里面的Service业务逻辑层
2、Spring进行事务管理操作
(1)编程式事务管理
<!-- 创建事务管理器 -->
<bean id="txMangaer" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
public void operAccount(String fromName, String targetName, int money) {
// 1、开启事务
//默认事务定义,例如隔离级别,传播行为等
TransactionDefinition tf = new DefaultTransactionDefinition() ;
//开启事务transaction
TransactionStatus transaction = txManager.getTransaction(tf);
try {
// 2、进行业务操作
bandCardDao.reduceAccount(fromName, money);
// 模拟异常
int i = 10/0;
bandCardDao.addAccount(targetName, money);
// 3、没有遇到异常,正常提交事务
txManager.commit(transaction);
System.out.println("没有遇到异常,正常提交事务");
} catch (Exception e) {
// 4、遇到异常,事务进行回滚
txManager.rollback(transaction);
System.out.println("遇到异常,事务回滚");
}
}
进行测试
(2)声明式事务管理
3、声明式事务管理
(1)基于注解方式
(2)基于xml配置文件的方式
4、在Spring进行声明式事务管理,底层使用AOP原理
5、Spring事务管理API
(1)提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
基于注解方式
1、引入事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
2、引入tx命名空间和开启注解
<!-- 开启事务注解,指定事务管理器 -->
<tx:annotation-driven transaction-manager="txManager"/>
3、@Transactional注解的作用
(1)添加到类上,为这个类类中所有的方法添加事务
(2)添加到方法上,为这个方法添加事务
声明式事务管理参数配置
1、propagation:事务传播行为
是指当一个事务方法被另外一个事务方法调用时,这个事务方法如何进行
(1)REQUIRED(需要事务):如果有事务在运行,当前的方法的就在这个事务内运行,如果当前没有事务,则启动一个新的事务,并在自己的事务内运行
(2)REQUIRED_NEW(需要新的事务):无论当前是否有事务,都会启动一个新的事务,并在自己的事务内运行。并且如果当前有事务,会将当前事务挂起。
(3)SUPPORTS(支持事务,非强制性):如果有事务,则在事务中运行,如果没有事务,则以非事务的方式运行
(4)NOT_SUPPORTS(不支持事务,有则挂起):不支持事务,如果有事务,则将当前事务挂起,以非事务的方式运行
(5)MANDATORY(强制性的,必须要有事务,没有就异常):当前的方法必须在事务中运行,如果没有事务则会抛出异常
(6)NEVER(绝不,不能有事务,有则异常):不允许在事务中运行,如果当前有事务,则抛出异常
(7)NESTED(嵌套的,有则变嵌套,无则新建):如果有事务在运行,则当前方法以嵌套事务的方式运行,否则就启动一个新的事务,并在自己的事务内运行
2、ioslation:事务隔离级别
(1)隔离性
如果事务之间不进行隔离的话,那么事务并发时,会导致事务之间会互相影响。
(2)隔离性导致的问题
- 脏读:一个未提交的事务读取到另一个未提交事务中的数据
- 不可重复读:一个未提交的事务读取到另一个已提交事务的数据
- 幻读:由于修改会被限制,所以不会产生不可重复读问题,但是会由于插入或删除导致产生幻读
(3)隔离级别 - 1、Read Uncommitted (读未提交)
顾名思义,就是可以读到未提交的内容。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。如无特殊情况,基本是不会使用这种隔离级别的。 - 2、READ Commintted (读已提交)
读提交,顾名思义,就是只能读到已经提交了的内容。这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。这里多说点:那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?
这就要说道另一个机制“快照(snapshot)”,而这种既能保证一致性又不加锁的读也被称为“快照读(Snapshot Read)”
假设没有“快照读”,那么当一个更新的事务没有提交时,另一个对更新数据进行查询的事务会因为无法查询而被阻塞,这种情况下,并发能力就相当的差。而“快照读”就可以完成高并发的查询,不过,“读提交”只能避免“脏读”,并不能避免“不可重复读”和“幻读”。 - 3、REPEATABLE READ(可重复读)
可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。
在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。 - 4、Serializable (串行化):
这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。
这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。
3、timeout:超时时间 - 1、事务需要在一定时间内进行提交,如果不提交就进行回滚
- 2、默认值是-1,设置时间以秒为单位进行计算
4、readOnly:是否只读 - 1、读:查询操作,写:添加、修改、删除操作
- 2、readOnly默认为false,表示可以进行增删改查的操作
- 3、设置readOnly是true后,只能进行查询操作
5、rollbackFor:回滚 - 设置出现哪些异常需要回滚
6、noRollbackFor:不回滚 - 设置出现哪些异常不需要回滚
XML声明式事务管理
1、在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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注入组件扫描类 -->
<context:component-scan base-package="com.company.base.spring9"/>
<!-- 引入外部配置文件 -->
<context:property-placeholder location="classpath:orm.properties"/>
<!-- 注入数据源信息 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 注入jdbcTemplate的操作bean -->
<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>
<!-- 配置通知 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 指定哪种规则的方法上面添加事务 -->
<tx:method name="operAccount" isolation="REPEATABLE_READ"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面和切入点 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="tx" expression="execution(* com.company.base.spring9.service.BandCardService.*(..))"/>
<!-- 配置通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx"/>
</aop:config>
</beans>
完全注解实现声明式管理
/**
* @ClassName SpringConfigTx
* @Description TOOD
* @Autor
* @Date
* @Version 1.0
**/
@Configuration
@ComponentScan(basePackages = {"com.company.base.spring10"})
@EnableTransactionManagement // 开启注解
@PropertySource(value = "classpath:orm.properties") // 引入数据源配置信息文件
public class SpringConfigTx {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 注入数据源dataSource
* @return
*/
@Bean
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
/**
* 注入jdbcTemplate
* @param dataSource 会自动从IOC容器中找到我们自定义的dataSource
* @return
*/
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
/**
* 注入事务管理器
* @param dataSource
* @return
*/
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}