事务的基础入门
大家在不知道事务这个东西的时候,会不会在自己的某一次报错中“灵机一动”的想到这样一个问题。银行、微信、支付宝在
转账的过程中突然报错了,我的钱转过去了,而对方并没有收到我的钱。仔细想一想,那我是不是可以借着这个,来赢取白富美,
走向人生巅峰,我先定他一个小目标......,直到在很久以前的某一天,我知道了事务这个东西,额......
一、什么是事务
事务是一个很广泛的词,各行各业对这个词都有不同的理解,而对于计算机术语而言,它则代表着Transaction - 就是逻辑上的
一组操作,组成这组操作的各个单元要么全部成功,要么全都失败。在知道了事务的概念后,我还是晕晕的,满足什么样的特性
才可以称的是事务呢?
二、事物的四个特性
* 原子性(A):不可分割
组成事务的所有逻辑上的操作,要不就全都执行,要不就一个都不要执行(生死与共)。
* 一致性(C):事务在执行前后,要保证数据的一致
一致性可以简单的理解为2个人转账,转账前和转账后的两人金钱总和,不发生改变。这个栗子只是在描述最后的结果,而产生一致
性的原因,除了结果守恒以外还有些什么呢(数据库里是怎么保证一致性的)?存入数据库的数据,都是我们将现实里的东西,通过代码
的方式进行抽象化。将抽象化对象中,我们所需要的属性值,存入到数据库中(数据化)。为了方便辨别和使用,我们会给这个属性取
一个数据库里的名字(列名)。那么一个类的前后一致性一定是,这个类的所有根性都要前后一致,即 : 要保证实体完整性(主属性不
为空)、参照完整性(外键必须存在原表中)、用户自定义的完整性(自己为这个类定义的一些约束)。
* 隔离性(I):一个事务在执行的过程中,不应该受到其它事务的干扰
隔离性依赖于加锁或者多版本控制,在执行了一条插入的SQL,如加上个100万。如果该事物没有提交,其他的事物是不能读到这条
执行结果的。
* 持久性(D):事务一旦结束,数据持久化到数据库
三、事务的一些问题
如果不考虑事务的隔离性的,会发生一些什么样的问题呢?
* 脏读: 一个事务读取到另一个事务的未提交数据。
* 不可重复读: 一个事务读取到另一个事务提交的数据(主要是指update),会导致两次读取的结果不一致。
* 虚读(幻读): 一个事务读取到另一个事务提交的数据(主要是指insert),会导致两次读取结果不一致。
以上的这些问题可能会导致许许多多的问题,那么它还有救么?我们可以通过设置不同的隔离级别,来解决脏读、
不可重复读等问题。
四、事物的隔离级别
事务一空有四个隔离级别,分别是:
* READ_UNCOMMITED 读取未提交,它引发所有的隔离问题。
* READ_COMMITTED 读已提交,阻止脏读,可能发生不可重复读与虚读。
* REPEATABLE_READ 重复读 阻止脏读,不可重复读 可能发生虚读。
* SERIALIZABLE 串行化 解决所有问题 不允许两个事务,同时操作一个目标数据。(效率低下)
而在常用的数据库中 ORACLE 的默认的是事务隔离级别是 READ_COMMITTED,MYSQL 默认的事务隔离级别 REPEATABLE_READ。
五、Spring对事务管理的简单使用
Spring框架的一大亮点就是他对事物的支持非常友好,使用起来也很方便,Spring的事务管理有着四个优点:
* 提供一致的对于不同的事务管理的API
* 支持声明式事务管理
* 编程事务管理(使用较少)
* 优秀的整合与Spring的数据访问
Spring的事务管理主要是通过3个接口来提供支持的:
1、org.springframework.transaction.PlatformTransactionManager
这是一个事务管理器,可以来选择相关的平台(Jdbc、Hibernate、Jpa等),即:你要使用哪个平台的事务。
2、TransactionDefinition
它定义事务的一些相关信息,例如:隔离、传播、超时、只读。
* 隔离:设置事物的隔离级别,如:ISOLATION_READ_COMMITTED 可以解决脏读,会产生不可重复读与虚读。
* 传播:它解决的是两个被事务管理的方法互相调用问题。是程序内部维护的问题。如:PROPAGATION_REQUIRED(默认值)
两个操作处于同一个事务,如果之前没有事务,则新建一个事务。
* 超时:默认值是-1,它使用的是数据库默认的超时时间。
* 只读:它的值有两个true/false,如果选择true一般是在select操作时。
3、TransactionStatus
它定义了事务状态信息,在事务运行过程中,得到某个时间点的状态。
(一)基于XML配置声明式事务
在导入相关依赖之后还要在applicationContext.xml文件中添加aop与tx的名称空间。
<!-- 引入外部的properties文件,数据库连接配置文件--> <context:property-placeholder location="classpath:db.properties" /> <!-- 创建c3p0连接池,连接数据库 --> <bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- service --> <bean id="accountService" class="cn.lalala.service.AccountServiceImpl"> <!-- 引入Dao层依赖 --> <property name="accountDao" ref="accountDao"></property> </bean> <!-- dao dao层要继承JdbcDaoSupport类--> <bean id="accountDao" class="cn.lalala.dao.AccountDAOImpl"> <!-- 当注入dataSource后,底层会自动创建一个JdbcTemplate模板类,详情可以参看JdbcDaoSupport类的源码--> <property name="dataSource" ref="c3p0DataSource" /> </bean> <!-- 配置事务管理器,这里用的是jdbc的 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 要注入连接池,参数请看DataSourceTransactionManager的源码 --> <property name="dataSource" ref="c3p0DataSource"></property> </bean> <!-- 配置通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- name: 必须的 对哪些方法进行事务控制 isolation 可选 设置事务隔离级别 默认是DEFAULT propagation: 可选 设置事务传播 默认值 REQUIRED timeout 可选 超时时间 默认值-1 read-only 可选 默认值是false 如果不是只读,它可以对insert update delete操作,如果是只读不可以。 rollback-for 可选 可以设置一个异常,如果产生这个异常,触发事务回滚 no-rolback-for 可选 可以设置一个异常,如果产生这个异常,不会触发事务回滚 --> <!-- 要被事务管理的方法 --> <tx:method name="account" /> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <!-- 配置切点 --> <aop:pointcut expression="execution(* cn.lalala.service.IAccountService.account(..))" id="txPointcut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>
(二)基于注解配置事务
在配置好事务管理器和开启注解式事务后,只需在被事务管理的方法或类(整个类下的所有方法)使用@Transactional注解。
<!-- 开启注解式事务 --> <tx:annotation-driven transaction-manager="transactionManager"/>
注解式的配置和配置文件式的配置各有优缺点,注解式使用起来方便,但是不利于后期的维护,基于配置文件的则是配置
起来比较麻烦但是方便阅读,容易理解。