http://coderdream.iteye.com/blog/434561
在软件开发过程中,经常会遇到事务问题,下面我们来看看最简单的JDBC和Spring分别如何处理事务。
关于事务控制的场景当然是转账,我们使用的数据库是MySQL。
打开test数据库后,运行下面的数据库脚本:
- DROP TABLE IF EXISTS account;
- CREATE TABLE account (
- accountId int primary key auto_increment,
- accountname varchar(20),
- money int not null
- );
- INSERT INTO ACCOUNT(ACCOUNTNAME,MONEY) VALUES('zhangsan',100);
- INSERT INTO ACCOUNT(ACCOUNTNAME,MONEY) VALUES('lisi',100);
1、JDBC中的事务控制
代码1:AccountDAO.java
- package com.coderdream;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Statement;
- public class AccountDAO {
- public void transfer(Integer fromAccountId, Integer toAccountId, int money) {
- try {
- // 1. 注册驱动
- Class.forName("com.mysql.jdbc.Driver");
- // 2. 获取数据库的连接
- Connection conn = DriverManager.getConnection(
- "jdbc:mysql://localhost/test", "root", "1234");
- // 3. 获取表达式
- Statement stmt1 = conn.createStatement();
- Statement stmt2 = conn.createStatement();
- Statement stmt3 = conn.createStatement();
- Statement stmt4 = conn.createStatement();
- // 执行插入数据的 SQL
- ResultSet rs1 = stmt1
- .executeQuery("SELECT MONEY FROM ACCOUNT WHERE ACCOUNTID="
- + fromAccountId);
- // 5. 显示结果集里面的数据
- int money1 = 0;
- while (rs1.next()) {
- System.out.println(rs1.getInt(1));
- money1 = rs1.getInt(1);
- }
- // 修改
- money1 -= money;
- System.out.println("money1: " + money1);
- stmt2.executeUpdate("UPDATE ACCOUNT SET MONEY=" + money1
- + " WHERE ACCOUNTID=" + fromAccountId);
- // 执行插入数据的 SQL
- ResultSet rs2 = stmt3
- .executeQuery("SELECT MONEY FROM ACCOUNT WHERE ACCOUNTID="
- + toAccountId);
- // 5. 显示结果集里面的数据
- int money2 = 0;
- while (rs2.next()) {
- System.out.println(rs2.getInt(1));
- money2 = rs2.getInt(1);
- }
- // 修改
- money2 += money;
- System.out.println("money2: " + money2);
- stmt2.executeUpdate("UPDATE ACCOUNT SET MONEY=" + money2
- + " WHERE ACCOUNTID=" + toAccountId);
- // 6. 释放资源
- rs1.close();
- rs2.close();
- stmt1.close();
- stmt2.close();
- stmt3.close();
- stmt4.close();
- conn.close();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
代码2:AccountService.java
- package com.coderdream;
- public class AccountService {
- private AccountDAO accountDAO;
- /**
- * 通过 Spring 向 Service ͨszh注入 Dao
- *
- * @param accountDAO
- */
- public void setAccountDAO(AccountDAO accountDAO) {
- this.accountDAO = accountDAO;
- }
- /**
- * 转账
- *
- * @param fromAccountId
- * 转出帐号
- * @param toAccountId
- * 转入帐号
- * @param money
- * 转账金额
- */
- public void transfer(Integer fromAccountId, Integer toAccountId, int money) {
- accountDAO.transfer(fromAccountId, toAccountId, money);
- }
- }
代码3:Main.java
- package com.coderdream;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.FileSystemXmlApplicationContext;
- public class Main {
- /**
- * @param args
- */
- public static void main(String[] args) {
- ApplicationContext act = new FileSystemXmlApplicationContext(
- "src/applicationContext.xml");
- AccountService accountService = (AccountService) act
- .getBean("accountService");
- try {
- // 帐号1转账1元至帐号2
- accountService.transfer(1, 2, 1);//A
- } catch (Exception e) {
- System.out.println("转账失败!");
- }
- }
- }
代码4:applicationContext.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
- <bean id="accountDAO" class="com.coderdream.AccountDAO" />
- <bean id="accountService" class="com.coderdream.AccountService">
- <property name="accountDAO" ref="accountDAO"></property>
- </bean>
- </beans>
上面的代码是没有加事务控制的,如果把‘A’处的代码换成:
- // 帐号1转账1元至帐号3
- accountService.transfer(1, 3, 1);//A
则由于帐号3不存在,所以会出现问题,帐号1的金额会减1,而帐号2的金额不会变,转账的1元就“不翼而飞”了,所以必须在Dao层加入事务控制。
代码5:加入事务控制后的AccountDAO
- public class AccountDAO {
- public void transfer(Integer fromAccountId, Integer toAccountId, int money) {
- Connection conn = null;
- ResultSet rs1 = null;
- Integer rs2 = null;
- ResultSet rs3 = null;
- Integer rs4 = null;
- Statement stmt1 = null;
- Statement stmt2 = null;
- Statement stmt3 = null;
- Statement stmt4 = null;
- // 1. 注册驱动
- try {
- Class.forName("com.mysql.jdbc.Driver");
- // 2. 获取数据库的连接
- conn = DriverManager.getConnection("jdbc:mysql://localhost/test",
- "root", "1234");
- conn.setAutoCommit(false);
- // 3. 获取表达式
- stmt1 = conn.createStatement();
- stmt2 = conn.createStatement();
- stmt3 = conn.createStatement();
- stmt4 = conn.createStatement();
- // 执行插入数据的 SQL
- rs1 = stmt1
- .executeQuery("SELECT MONEY FROM ACCOUNT WHERE ACCOUNTID="
- + fromAccountId);
- // 5. 显示结果集里面的数据
- int money1 = 0;
- while (rs1.next()) {
- System.out.println(rs1.getInt(1));
- money1 = rs1.getInt(1);
- }
- // 修改
- money1 -= money;
- System.out.println("money1: " + money1);
- rs2 = stmt2.executeUpdate("UPDATE ACCOUNT SET MONEY=" + money1
- + " WHERE ACCOUNTID=" + fromAccountId);
- if (1 != rs2) {
- throw new Exception(" 转出失败,帐号: " + fromAccountId);
- }
- // 执行插入数据的 SQL
- rs3 = stmt3
- .executeQuery("SELECT MONEY FROM ACCOUNT WHERE ACCOUNTID="
- + toAccountId);
- // 5. 显示结果集里面的数据
- int money2 = 0;
- while (rs3.next()) {
- System.out.println(rs3.getInt(1));
- money2 = rs3.getInt(1);
- }
- // 修改
- money2 += money;
- System.out.println("money2: " + money2);
- rs4 = stmt2.executeUpdate("UPDATE ACCOUNT SET MONEY=" + money2
- + " WHERE ACCOUNTID=" + toAccountId);
- if (1 != rs4) {
- throw new Exception(" 转入失败,帐号: " + toAccountId);
- }
- conn.commit();
- System.out.println("转帐成功!");
- } catch (Exception e) {
- try {
- conn.rollback();
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- e.printStackTrace();
- }
- // 6. 释放资源
- finally {
- try {
- if (rs1 != null) {
- rs1.close();
- }
- if (rs3 != null) {
- rs3.close();
- }
- if (stmt1 != null) {
- stmt1.close();
- }
- if (stmt2 != null) {
- stmt2.close();
- }
- if (stmt3 != null) {
- stmt3.close();
- }
- if (stmt4 != null) {
- stmt4.close();
- }
- if (conn != null) {
- conn.close();
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
- }
2、Spring中的事务控制方式一:Hibernate的事务管理器托管
我们先来看看通过Spring+Hibernate来操作数据库的简单示例。
先通过 MyEclipse 生成Hibernate 需要的 Bean 及 hbm.xml文件:
代码6:Account.java
- package com.coderdream;
- /**
- * Account entity.
- *
- * @author MyEclipse Persistence Tools
- */
- public class Account implements java.io.Serializable {
- /**
- *
- */
- private static final long serialVersionUID = 909891879728703117L;
- private Integer accountId;
- private String accountname;
- private Integer money;
- // Property accessors
- public Integer getAccountId() {
- return this.accountId;
- }
- public void setAccountId(Integer accountId) {
- this.accountId = accountId;
- }
- public String getAccountname() {
- return this.accountname;
- }
- public void setAccountname(String accountname) {
- this.accountname = accountname;
- }
- public Integer getMoney() {
- return this.money;
- }
- public void setMoney(Integer money) {
- this.money = money;
- }
- // Constructors
- /** default constructor */
- public Account() {
- }
- /** full constructor */
- public Account(String accountname, Integer money) {
- this.accountname = accountname;
- this.money = money;
- }
- }
代码7:Account.hbm.xml
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <!--
- Mapping file autogenerated by MyEclipse Persistence Tools
- -->
- <hibernate-mapping>
- <class name="com.coderdream.Account" table="account" catalog="test">
- <id name="accountId" type="java.lang.Integer">
- <column name="accountId" />
- <generator class="native" />
- </id>
- <property name="accountname" type="java.lang.String">
- <column name="accountname" length="20" />
- </property>
- <property name="money" type="java.lang.Integer">
- <column name="money" length="20" />
- </property>
- </class>
- </hibernate-mapping>
代码8:
- package com.coderdream;
- import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
- public class AccountDAO extends HibernateDaoSupport {
- public void addMoney(Integer accountId, int money) {
- Account account = (Account) getHibernateTemplate().get(Account.class,
- accountId);
- account.setMoney(account.getMoney() + money);
- getHibernateTemplate().saveOrUpdate(account);
- }
- public void subMoney(Integer accountId, int money) {
- Account account = (Account) getHibernateTemplate().get(Account.class,
- accountId);
- account.setMoney(account.getMoney() - money);
- getHibernateTemplate().saveOrUpdate(account);
- }
- }
代码9:
- package com.coderdream;
- public class AccountService {
- private AccountDAO accountDAO;
- /**
- * 通过 Spring 将 DAO 注入到 Service
- *
- * @param accountDAO
- */
- public void setAccountDAO(AccountDAO accountDAO) {
- this.accountDAO = accountDAO;
- }
- /**
- * 转账方法包括两个原子方法:转出方法和转入方法
- *
- * @param fromAccountId
- * @param toAccountId
- * @param money
- */
- public void transfer(Integer fromAccountId, Integer toAccountId, int money) {
- accountDAO.subMoney(fromAccountId, money);
- accountDAO.addMoney(toAccountId, money);
- }
- }
代码10:
- package com.coderdream;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.FileSystemXmlApplicationContext;
- public class Main {
- /**
- * @param args
- */
- public static void main(String[] args) {
- ApplicationContext act = new FileSystemXmlApplicationContext(
- "src/applicationContext.xml");
- AccountService accountService = (AccountService) act
- .getBean("accountService");
- try {
- // 帐号1转账1元至帐号2
- accountService.transfer(1, 2, 1);// B
- } catch (Exception e) {
- System.out.println("转账失败");
- }
- }
- }
上面的代码同样没有加入事务控制,如果在‘B’处将转入的帐号设置为不存在的帐号3,同样会有问题,下面我们来加入事务控制,我们需要修改 applicationContext.xml 文件:
代码11:增加事务后的 applicationContext.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
- <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/test"></property>
- <property name="username" value="root"></property>
- <property name="password" value="1234"></property>
- </bean>
- <bean id="sessionFactory"
- class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
- <property name="dataSource">
- <ref bean="dataSource" />
- </property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">
- org.hibernate.dialect.MySQLDialect
- </prop>
- <prop key="hibernate.show_sql">true</prop>
- </props>
- </property>
- <property name="mappingResources">
- <list>
- <value>com/coderdream/Account.hbm.xml</value>
- </list>
- </property>
- </bean>
- <!-- 引用Hibernate的事务管理器 -->
- <bean id="transactionManager"
- class="org.springframework.orm.hibernate3.HibernateTransactionManager">
- <property name="sessionFactory" ref="sessionFactory"></property>
- </bean>
- <bean id="accountDAO" class="com.coderdream.AccountDAO">
- <property name="sessionFactory" ref="sessionFactory"></property>
- </bean>
- <!-- 通过事务管理器来管理Service -->
- <bean id="accountService"
- class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
- <property name="transactionManager" ref="transactionManager"></property>
- <property name="target">
- <bean class="com.coderdream.AccountService">
- <property name="accountDAO" ref="accountDAO"></property>
- </bean>
- </property>
- <property name="transactionAttributes">
- <props>
- <prop key="transfer">PROPAGATION_REQUIRED</prop>
- </props>
- </property>
- </bean>
- </beans>
3、Spring中的事务控制方式二:注解方式
当然,我们还可以通过注解的方式加入事务的控制。
我们需要先在 applicationContext.xml 声明事务控制器和注解:
代码12:applicationContext.xml
- <?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:jee="http://www.springframework.org/schema/jee"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
- <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/test"></property>
- <property name="username" value="root"></property>
- <property name="password" value="1234"></property>
- </bean>
- <bean id="sessionFactory"
- class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
- <property name="dataSource">
- <ref bean="dataSource" />
- </property>
- <property name="hibernateProperties">
- <props>
- <prop key="hibernate.dialect">
- org.hibernate.dialect.MySQLDialect
- </prop>
- <prop key="hibernate.show_sql">true</prop>
- </props>
- </property>
- <property name="mappingResources">
- <list>
- <value>com/coderdream/Account.hbm.xml</value>
- </list>
- </property>
- </bean>
- <!-- 引用Hibernate的事务管理器 -->
- <bean id="transactionManager"
- class="org.springframework.orm.hibernate3.HibernateTransactionManager">
- <property name="sessionFactory" ref="sessionFactory"></property>
- </bean>
- <!-- 使用annotation定义事务 -->
- <tx:annotation-driven transaction-manager="transactionManager" />
- <bean id="accountDAO" class="com.coderdream.AccountDAO">
- <property name="sessionFactory" ref="sessionFactory"></property>
- </bean>
- <bean id="accountService" class="com.coderdream.AccountService">
- <property name="accountDAO" ref="accountDAO"></property>
- </bean>
- </beans>
同时需要在Service层的要使用的方法上声明事务,如:
- @Transactional(readOnly = false)
如果只是对数据库进行查询操作,这里的“readOnly = true“,如果是”增/删/改“操作,则为 false。
代码13:在方法上加入“注解式”事务控制后的 AccountService.java
- package com.coderdream;
- public class AccountService {
- private AccountDAO accountDAO;
- /**
- * 通过 Spring 将 DAO 注入到 Service
- *
- * @param accountDAO
- */
- public void setAccountDAO(AccountDAO accountDAO) {
- this.accountDAO = accountDAO;
- }
- /**
- * 转账方法包括两个原子方法:转出方法和转入方法
- *
- * @param fromAccountId
- * @param toAccountId
- * @param money
- */
- @Transactional(readOnly = false)
- public void transfer(Integer fromAccountId, Integer toAccountId, int money) {
- accountDAO.subMoney(fromAccountId, money);
- accountDAO.addMoney(toAccountId, money);
- }
- }