Spring中的事务
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。 DataSource、 TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为 SessionFactory,TransactionManager的实现为HibernateTransactionManager。
一.事务的4个特性:
原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做,要么全部做。 一致性:数据不会因为事务的执行而遭到破坏。
隔离性:一个事务的执行,不受其他事务(进程)的干扰。既并发执行的个事务之间互不干扰。
持久性:一个事务一旦提交,它对数据库的改变将是永久的。
二:Spring事务的隔离级别
1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。
3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
除了防止脏读,不可重复读外,还避免了幻像读。
什么是脏数据,脏读,不可重复读,幻觉读?
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,
另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一
个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。
那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据
可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及
到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,
以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
三:Spring事务类型详解:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
四.事务的实现方式:
实现方式共有两种:编码方式;声明式事务管理方式。
基于AOP技术实现的声明式事务管理,实质就是:在方法执行前后进行拦截,然后在目标方法开始之前创建并加入事务,执行完目标方法后根据执行情况提交或回滚事务。
声明式事务管理又有两种方式:基于XML配置文件的方式;另一个是在业务方法上进行@Transactional注解,将事务规则应用到业务逻辑中。
五.实现事务的案例
下面模拟一个用户(Account)用钱来买股票(Stock),当用户出钱买股要是错误,就需要我们的事务了。
源码介绍(Spring框架基本jar包全的情况下):
1.首先我们要用到事务的jar包,我用的是:
spring-tx-4.2.0.RELEASE.jar
2.Account.java
package cn.tx.entity; /** * 账户 * @author zhangzong * */ public class Account { private Integer aid;//账户编号 private String aname;//用户姓名 private int balance;//账户金额 public Integer getAid() { return aid; } public void setAid(Integer aid) { this.aid = aid; } public String getAname() { return aname; } public void setAname(String aname) { this.aname = aname; } public int getBalance() { return balance; } public void setBalance(int balance) { this.balance = balance; } }
3.Stock.java
package cn.tx.entity; /** * 股票类 * @author zhangzong * */ public class Stock { private Integer sid;//股票编号 private String sname;//股票名称 private int count;//股数 public Integer getSid() { return sid; } public void setSid(Integer sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
4.AccountDao.java
package cn.tx.dao; //账户接口 import cn.tx.entity.Account; public interface AccountDao { /** * 新增账户 * @param account * @return */ public int createAccount(Account account); /** * 对账户的操作(钱买股,股收钱) * @param id 账户的编号 * @param money 发费的钱财 * @param isYesOrNo 是买股,还是收钱 * @return 受影响的行数 */ public int updateBalance(int id,int money,boolean isYesOrNo); /** * 根据编号查询余额 * @param id 编号 * @return 余额 */ public int selectOfBalance(int id); }
5.StockDao.java
package cn.tx.dao; //股票接口 import cn.tx.entity.Stock; public interface StockDao { /** * 创建股票 * @param stock 股票对象 * @return 受影响行数 */ public int createStock(Stock stock); /** * 对股票的操作(钱买股,股收钱) * @param id 股票编号 * @param num 变化的数量 * @param isYesOrNo 是买股,还是收钱 * @return 受影响的行数 */ public int updateCount(int id,int num,boolean isYesOrNo); }
6.AccountDaoImpl.java
package cn.tx.dao.impl; //实现了AccountDao接口的实现类 import org.springframework.jdbc.core.support.JdbcDaoSupport; import cn.tx.dao.AccountDao; import cn.tx.entity.Account; //JdbcDaoSupport是JDBC数据访问对象的超类 public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public int createAccount(Account account) { String sql = "insert into account(aname,balance) values(?,?)"; int count = this.getJdbcTemplate().update(sql, account.getAname(), account.getBalance()); return count; } @Override public int updateBalance(int id, int money, boolean isBuyOrNot) { String sql = null; // isBuyOrNot 为真,金额减少 if (isBuyOrNot) { sql = "update account set balance=balance-? where aid=?"; } else { sql = "update account set balance=balance+? where aid=?"; } int count = this.getJdbcTemplate().update(sql, money, id); return count; } @Override public int selectOfBalance(int id) { String sql = "select balance from account where aid=?"; Double money = this.getJdbcTemplate().queryForObject(sql, new Object[] { id }, Double.class); return money.intValue(); } }
7.StockDaoImpl.java
package cn.tx.dao.impl; //实现了StockDao接口的实现类 import org.springframework.jdbc.core.support.JdbcDaoSupport; import cn.tx.dao.StockDao; import cn.tx.entity.Stock; //JdbcDaoSupport是JDBC数据访问对象的超类 public class StockDaoImpl extends JdbcDaoSupport implements StockDao { @Override public int createStock(Stock stock) { String sql="insert into Stock(sname,count) values(?,?)"; int count = this.getJdbcTemplate().update(sql,stock.getSname(),stock.getCount()); return count; } @Override public int updateCount(int id, int num, boolean isYesOrNo) { String sql=null; if (isYesOrNo) { sql="update Stock set count+=? where sid=?"; }else { sql="update Stock set count-=? where sid=?"; } int count = this.getJdbcTemplate().update(sql,num,id); return count; } }
8.AccountService.java
package cn.tx.service; //用户操作业务接口(用钱买股) import cn.tx.entity.Account; import cn.tx.entity.Stock; import cn.tx.util.StockException; public interface AccountService { /** * 新增账户 * @param account * @return */ public int createAccount(Account account); /** * 对账户的操作(钱买股,股收钱) * @param id 账户的编号 * @param money 发费的钱财 * @param isYesOrNo 是买股,还是收钱 * @return 收影响的行数 */ public int updateBalance(int id,int money,boolean isYesOrNo); /** * 创建股票 * @param stock 股票对象 * @return 受影响行数 */ public int createStock(Stock stock); /** * 对股票的操作(钱买股,股收钱) * @param id 股票编号 * @param num 变化的数量 * @param isYesOrNo 是买股,还是收钱 * @return 受影响的行数 */ public int updateCount(int id,int num,boolean isYesOrNo); /** * 购买股票的方法 * @param aid 账户编号 * @param money 发费的金额 * @param sid 股票的编号 * @param num 所买股数 * @throws StockException */ public void buyStock(int aid,int money,int sid,int num) throws StockException; /** * 根据编号查询余额 * @param id 编号 * @return 余额 */ public int selectOfBalance(int id); }
9.AccountServiceImpl.java
package cn.tx.service.impl; //用户操作实现类 import cn.tx.dao.AccountDao; import cn.tx.dao.StockDao; import cn.tx.entity.Account; import cn.tx.entity.Stock; import cn.tx.service.AccountService; import cn.tx.util.StockException; public class AccountServiceImpl implements AccountService { //植入账户接口 private AccountDao adao; //植入股票接口 private StockDao sdao; @Override public int createAccount(Account account) { // TODO Auto-generated method stub return adao.createAccount(account); } @Override public int updateBalance(int id, int money, boolean isYesOrNo) { // TODO Auto-generated method stub return adao.updateBalance(id, money, isYesOrNo); } @Override public int createStock(Stock stock) { // TODO Auto-generated method stub return sdao.createStock(stock); } @Override public int updateCount(int id, int num, boolean isYesOrNo) { // TODO Auto-generated method stub return sdao.updateCount(id, num, isYesOrNo); } @Override public int selectOfBalance(int id) { // TODO Auto-generated method stub return adao.selectOfBalance(id); } //@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=StockException.class) public void buyStock(int aid,int money,int sid,int num) throws StockException{ boolean isBuy=true;//默认为钱买股 //更改账户信息 adao.updateBalance(aid, money, isBuy); //模拟异常,如果没钱或钱为负数 if(adao.selectOfBalance(aid)<=0){ throw new StockException("异常发生了。。。。。"); } //更改股票信息 sdao.updateCount(sid, num, isBuy); } public AccountDao getAdao() { return adao; } public void setAdao(AccountDao adao) { this.adao = adao; } public StockDao getSdao() { return sdao; } public void setSdao(StockDao sdao) { this.sdao = sdao; } }
10.StockException.java(制造的一个异常类)
package cn.tx.util; /** * 构造一个检查异常 * @author zhangzong * */ public class StockException extends Exception { /** * */ private static final long serialVersionUID = 1L; public StockException() { super(); } public StockException(String message) { super(message); } }
11.applicationContext.xml(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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 1.dao --> <bean id="accountDao" class="cn.tx.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="stockDao" class="cn.tx.dao.impl.StockDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2.service --> <bean id="accountService" class="cn.tx.service.impl.AccountServiceImpl"> <property name="adao" ref="accountDao"></property> <property name="sdao" ref="stockDao"></property> </bean> <!-- 3.c3p0数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 4.注册jdbc属性 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!--******************************事务配置 ********************************* --> <!-- 注册事务管理器 --> <bean id="mytxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- *****************获得事务代理************** --> <!--方法一: 事务自动代理 :此方法要结合注解使用,在AccountServiceImpl的buyStock上 --> <!--<tx:annotation-driven transaction-manager="mytxManager"/> --> <!-- 方法二:TransactionProxyFactoryBean 生成事务代理 --> <!-- <bean id="stockServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="accountService"></property> <property name="transactionManager" ref="mytxManager"></property> <property name="transactionAttributes"> <props> 四种隔离级别 传播属性 required <prop key="create*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop> <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-StockException </prop> </props> </property> </bean> --> <!-- 方法三 : aop**************** --> <tx:advice id="txAdvice" transaction-manager="mytxManager"> <tx:attributes> <tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED" /> <tx:method name="buyStock" isolation="DEFAULT" propagation="REQUIRED" rollback-for="StockException" /> </tx:attributes> </tx:advice> <!-- aop配置 --> <aop:config> <aop:pointcut expression="execution(* *..service.*.*(..))" id="stockPointcut" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="stockPointcut"/> </aop:config> </beans>
12.jdbc.properties(连接数据库的配置)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\:///mybook
jdbc.username=root
jdbc.password=1234
13.log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
14.MyTest.java
package cn.tx.test; //测试类 import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.tx.entity.Account; import cn.tx.entity.Stock; import cn.tx.service.AccountService; import cn.tx.util.StockException; public class MyTest { //测试余额 @Test public void selectOfbalance(){ ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); AccountService service = (AccountService) ctx.getBean("accountService"); int balance = service.selectOfBalance(1); System.out.println(balance); } @Test // 购买股票测试 public void buyStockTest() throws StockException { ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); AccountService service = (AccountService) ctx.getBean("accountService"); // 花200 块 买 2股 service.buyStock(1, 200, 1, 2); } @Test // 开户测试 public void addData() { ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); AccountService service = (AccountService) ctx.getBean("accountService"); // 银行卡账户 Account account = new Account(); account.setAname("傻瞿亮"); account.setBalance(1000); service.createAccount(account); Stock stock = new Stock(); stock.setSname("脑残教育"); stock.setCount(5); service.createStock(stock); } }