事务的概念
什么是事务
事务逻辑上的一组操作。组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
事务特性
原子性:强调事务的不可分割
一致性:事务的执行前后数据完整性保持一致。
隔离性:一个事务执行过程中,不应该受到其它事务的干扰
持久性:事务一旦执行,数据就持久到数据库
如果不考虑隔离性引发的安全性问题
脏读:一个事务读到了另一个事物未提交的数据
不可重复读:一个事务读到了另一个事务提交的update的数据导致多次查询结果不一致。
虚度:一个事务读到了另个一个事务已经提交的insert的数据导致多次查询结果不一致。
平台事务管理器
JdbcDaoSupport学习
如果想用Spring来管理事务需要用到Spring相关的类和API
PlatformTransactionManager接口
image.png如果用Spring管理事物 第一步就得配置 PlatformTransactionManager
这个接口。平台事务管理器(真正的管理事务类)。该类有具体的实现类,根据不同的框架,需要选择不同的实现类
TransactionDefinition
事物的定义信息接口(事物的隔离级别、传播行为,超时,只读)
- 事务的隔离级别,一般使用默认的
事务隔离级别常量
static int ISOLATION_DEFAULT - 采用数据库的默认隔离级别
- 事务的传播行为常量
PROPAGATION_REQUIRED -- A中有事务,使用A中的事物,如果没有,B就会开启一个新的事物,将A包含进来。(保证AB在同一个事物) 默认值。
事务管理的使用
提示:这里使用Spring的JDBC模板方式操作数据库。连接池使用的c3p0。
- 第一步 导入Jar包
-
第二步 搭建项目结构
image.png -
配置JDBC的模板 跟连接池
<!-- c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&characterEncoding=utf8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置JDBC模板类 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
引用属性
<property name="dataSource" ref="dataSource"></property>
</bean>
- 配置业务层和持久层
<!-- 配置业务层和持久层 -->
<bean id="accountService" class="com.coderhong.demo1.AccountServiceIml">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.coderhong.demo1.AccountDaoImpl">
<property name="jdbcTemplate" ref="org.springframework.jdbc.core.JdbcTemplate"></property>
</bean>
Dao操作数据库,可以将JDBC模板注入到Dao中
- 在dao添加JDBC模板属性跟set方法不是注解方式,必须提供set方法
// 继承 JdbcDaoSupport
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate template) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* 扣钱
*/
@Override
public void outMoney(String out, double money) {
this.getJdbcTemplate().update("update t_account set money = money - ? where name=?", money, out);
}
/**
* 加钱
*/
@Override
public void inMoney(String in, double money) {
this.getJdbcTemplate().update("update t_account set money = money + ? where name=?", money, in);
}
}
- 修改配置文件中将JDBC模板注入到Dao中注入
<bean id="accountDao" class="com.coderhong.demo1.AccountDaoImpl">
<property name="jdbcTemplate" ref="org.springframework.jdbc.core.JdbcTemplate"></property>
</bean>
以上配置及代码完成了业务层注入了dao,dao注入的JDBC模板。可以开发没问题。但是有一个不好的地方,就是我们每一个模块的dao都需要内部写一个JDBC模板成员并提供set方法,并且在配置文件中,到注入模板。
这个时候,Spring为我们提供了一个父类JdbcDaoSupport,到我们让我们的dao继承JdbcDaoSupport,就会报错。原因是JdbcDaoSupport这个父类已经有了这个jdbcTemplate属性并提供了set方法。
JdbcDaoSupportdao继承了JdbcDaoSupport,需要注释掉JDBC模板属性.在到使用模板通过父类的getJdbcTemplate()
获取JDBC模板。
import org.springframework.jdbc.core.support.JdbcDaoSupport;
// 继承 JdbcDaoSupport
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
/* 继承父类省略 父类已经实现
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate template) {
this.jdbcTemplate = jdbcTemplate;
}
*/
/**
* 扣钱
*/
@Override
public void outMoney(String out, double money) {
this.getJdbcTemplate().update("update t_account set money = money - ? where name=?", money, out);
}
/**
* 加钱
*/
@Override
public void inMoney(String in, double money) {
this.getJdbcTemplate().update("update t_account set money = money + ? where name=?", money, in);
}
}
这样就就解决了只要我们编写dao继承JdbcDaoSupport,配置文件注入JDBC模板就可以了。不用再Dao内部引入JDBC模板属性跟set方法。
现在整体的流程如下:
image.png
在阅读JdbcDaoSupport源码发现下面代码:
那就是说我们的dao继承了JdbcDaoSupport可以不用注入Jdbc模板。如果内有,直接帮我们创建,并将连接池放入Jdbc模板。可见在配置文件中以后我们不需要配置jdbc模板,之间在到注入连接池。这些前提都是dao继承JdbcDaoSupport。
修改配置配文件
<!-- c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&characterEncoding=utf8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置业务层和持久层 -->
<bean id="accountService" class="com.coderhong.demo1.AccountServiceIml">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.coderhong.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
现在的结构:
image.png
到这里就是最终方案:
- 编写的dao继承JdbcDaoSupport
- dao中配置文件注入连接池。不需要提供jdbc模板属性跟set方法,父类中已有。
事务管理的引入
如上面的案例,我们使用了JDBC模板来操作转账
我们看下业务层代码
public class AccountServiceIml implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void pay(final String out, final String in, final double monye) {
// 扣钱
accountDao.outMoney(out, monye);
//int a = 10 /0 ;
// 加钱
accountDao.inMoney(in, monye);
}
这里存在的问题:
业务层的扣钱跟加钱是两个不同的事务,一旦中间出现异常数据就出现问题,不会回滚。
这里就需要事物来管理了。
Spring框架事务管理分类
- 手动编写事务管理代码 (不推荐)
- 声明方式事务管理(底层采用AOP技术)
手动编写事务管理代码 (了解)
不管使用哪种事务分类,都是使用了Spring事务管理接口PlatformTransactionManager
来管理事务,所以需要使用到接口的实现类。
提示:这里使用的是JDBC模板,需要使用的实现类是DataSourceTransactionManager
第一步 配置事务管理器
平台事务管理器需要连接池
因为连接池中有连接,平台事务管理器需要拿到连接才可以管理连接。
<!-- 配置平台管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接池中有 连接 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
如果使用手动编码的方式,Spring提供了一个类TransactionTemplate
模板类。
使用这个类操作事物管理。
配置:
<!-- 配置平台管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接池中有 连接 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 手动编码 提供了模板类 使用该类管理事物比较简单版 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
现在整体的结构
Snip20171203_57.png
我们操作TransactionTemplate
这个类,其实就是底层AOP技术操作事务管理器
DataSourceTransactionManager。
以上配置完成了,需要在Service中注入TransactionTemplate
。使用TransactionTemplate管理事务。
<!-- 配置业务层和持久层 -->
<bean id="accountService" class="com.coderhong.demo1.AccountServiceIml">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
在Service使用模板类
public class AccountServiceIml implements AccountService {
// dao
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
// 事务模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void pay(final String out, final String in, final double monye) {
// 事物的执行
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
// 扣钱
accountDao.outMoney(out, monye);
// int a = 10 /0 ;
// 加钱
accountDao.inMoney(in, monye);
}
});
}
}
声明方式事务管理(底层采用AOP技术)
两种方式
- 基于AspectJ的XML方式
- 基于AspetJ的注解方式
基于AspectJ的XML方式
- 配置
<!-- 配置连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&characterEncoding=utf8"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置平台管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接池中有 连接 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 声明式事物 (采用XML配置文件的方式) -->
<!-- 先配置通知 -->
<tx:advice id="MyAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 可以给方法设置方法属性(隔离级别 传播行为) -->
<tx:method name="pay" propagation="REQUIRED"/>
<!-- 可以添加多个方法 -->
</tx:attributes>
</tx:advice>
!-- 配置AOP
如果是自己编写的aop 使用<aop:aspect></aop:aspect>这个切面
如果使用Spring提供的切面 <aop:advisor advice-ref=""/>
-->
<aop:config>
<!-- aop:advisor是spring框架提供的通知 -->
<aop:advisor advice-ref="MyAdvice" pointcut="execution(public * com.coderhong.demo2.AccountServiceIml.pay(..))"/>
</aop:config>
<!-- 配置业务层和持久层 -->
<bean id="accountService" class="com.coderhong.demo2.AccountServiceIml">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.coderhong.demo2.AccountDaoImpl">
<!-- <property name="jdbcTemplate" ref="org.springframework.jdbc.core.JdbcTemplate"></property> -->
<property name="dataSource" ref="dataSource"></property>
</bean>
关系图:
image.png
然后配置完成业务层跟dao层的代码就很简单了。
业务层
public class AccountServiceIml implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void pay(final String out, final String in, final double monye) {
// 扣钱
accountDao.outMoney(out, monye);
// int a = 10 /0 ;
// 加钱
accountDao.inMoney(in, monye);
}
}
dao层
// 继承 JdbcDaoSupport
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
/**
* 扣钱
*/
@Override
public void outMoney(String out, double money) {
this.getJdbcTemplate().update("update t_account set money = money - ? where name=?", money, out);
}
/**
* 加钱
*/
@Override
public void inMoney(String in, double money) {
this.getJdbcTemplate().update("update t_account set money = money + ? where name=?", money, in);
}
}
基于AspetJ的注解方式
配置文件- 开启事务注解
<!-- dbcp连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_day03?useUnicode=true&characterEncoding=utf8"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置平台管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接池中有 连接 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事物的注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置业务层和持久层 -->
<bean id="accountService" class="com.coderhong.demo3.AccountServiceIml">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.coderhong.demo3.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
在业务层类添加注解@Transactional
代表给类的所有方法全部都有了事物
业务层的代码
@Transactional
public class AccountServiceIml implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void pay(final String out, final String in, final double monye) {
// 扣钱
accountDao.outMoney(out, monye);
// int a = 10 /0 ;
// 加钱
accountDao.inMoney(in, monye);
}
}
dao层代码不变。
网友评论