学科分类
目录
Java Web

DBUtils处理事务

上一小节,用DBUtils完成了对数据库增删改查的操作,其中使用了QueryRunner类中有参数的构造方法,参数即数据源,这时,框架会自动创建数据库连接,并释放连接。但这是处理一般操作的时候,当要进行事务处理时,连接的创建和释放就要由程序员自己实现了。本小节将结合案例针对用DBUtils框架处理事务进行详细的讲解。

为了讲解DBUtils如何处理事务,接下来向大家模拟银行之间的转账业务。具体步骤如下:

(1) 建立所需的数据表account作为账目记录表,并添加数据,具体语句如下:

USE chapter03;

CREATE TABLE account(

  id int primary key auto_increment,

  name varchar(40),

  money float

); 

INSERT INTO account(name,money) VALUES('a',1000);

INSERT INTO account(name,money) VALUES('b',1000);

上述SQL语句执行成功后,使用SELECT语句查询account表中的数据,SQL语句的执行结果如下:

mysql> select * from account;

+----+------+-------+

| id | name | money |

+----+------+-------+

| 1 | a  | 1000 |

| 2 | b  | 1000 |

+----+------+-------+

2 rows in set (0.03 sec)

(2) 创建实体类Account,具体代码如下:

 1  package cn.itcast.jdbc.example.domain;

 2  public class Account {

 3    private int id;

 4    private String name;

 5    private float money;

 6    public int getId() {

 7       return id;

 8    }

 9    public void setId(int id) {

 10      this.id = id;

 11    }

 12    public String getName() {

 13      return name;

 14    }

 15    public void setName(String name) {

 16      this.name = name;

 17    }

 18    public float getMoney() {

 19      return money;

 20    }

 21    public void setMoney(float money) {

 22      this.money = money;

 23    }

 24 }

创建类JDBCUtils,该类封装了创建连接、开启事务、关闭事务等方法。需要注意的是,请求中的一个事务涉及多个数据库操作,如果这些操作中的Connection 是从连接池获得的话,两个 DAO操作就用到了两个Connection,这样的话是没有办法完成一个事务的。因此,需要借助ThreadLocal类。

ThreadLocal类的作用是在一个线程里记录变量。我们可以生成一个连接放在这个线程中,只要是这个线程中的任何对象都可以共享这个连接,当线程结束后就删除这个连接。这样就保证了一个事务,一个连接。具体实现如例1所示。

例1 JDBCUtils.java

 1  package cn.itcast.jdbc.utils;

 2  import java.sql.Connection;

 3  import java.sql.SQLException;

 4  import javax.sql.DataSource;

 5  import com.mchange.v2.c3p0.ComboPooledDataSource;

 6  public class JDBCUtils {

 7    // 创建一个ThreadLocal 对象,以当前线程作为key

 8    private static ThreadLocal<Connection> threadLocal = 

 9        new ThreadLocal<Connection>();

 10   // 从c3p0-config.xml配置文件中读取默认的数据库配置,生成c3p0数据源

 11   private static DataSource ds = new ComboPooledDataSource();

 12   // 返回数据源对象

 13   public static DataSource getDataSource() {

 14     return ds;

 15   }

 16   // 获取c3p0数据库连接池中的连接对象

 17   public static Connection getConnection() throws SQLException {

 18     Connection conn = threadLocal.get();

 19     if (conn == null) {

 20       conn = ds.getConnection();

 21       threadLocal.set(conn);

 22     }

 23     return conn;

 24   }

 25   // 开启事务

 26   public static void startTransaction() {

 27     try {

 28       // 获得链接

 29       Connection conn = getConnection();

 30       // 开启事务

 31       conn.setAutoCommit(false);

 32     } catch (SQLException e) {

 33       e.printStackTrace();

 34     }

 35   }

 36   // 提交事务

 37   public static void commit() {

 38     try {

 39       // 获得链接

 40       Connection conn = threadLocal.get();

 41       // 提交事务

 42       if (conn != null)

 43         conn.commit();

 44     } catch (SQLException e) {

 45       e.printStackTrace();

 46     }

 47   }

 48   // 回滚事务

 49   public static void rollback() {

 50     try {

 51       // 获得链接

 52       Connection conn = threadLocal.get();

 53       // 回滚事务

 54       if (conn != null)

 55         conn.rollback();

 56     } catch (SQLException e) {

 57       e.printStackTrace();

 58     }

 59   }

 60   // 关闭数据库连接,释放资源

 61   public static void close() {

 62     // 获得链接

 63     Connection conn = threadLocal.get();

 64     // 关闭事务

 65     if (conn != null) {

 66       try {

 67         conn.close();

 68       } catch (SQLException e) {

 69         e.printStackTrace();

 70       } finally {

 71         // 从集合中移除当前绑定的连接

 72         threadLocal.remove();

 73         conn = null;

 74       }

 75     }

 76   }

 77 }

在例1中,可以注意到,在关闭连接时为什么不能直接将conn对象置空,而是先要从集合中移除当前绑定的连接?首先,获得连接是从threadLocal集合中拿出元素的地址复制给conn对象,那么集合中还有指向该连接的变量记住这个对象的地址。ThreadLocal集合为静态集合,所以只要虚拟机不关闭,静态变量就永远不释放。这样就会造成内存泄露。所以要先从集合中移除当前绑定的连接,再将conn对象置空,变为垃圾对象。

(3) 创建类AccountDao,该类封装了转账所需的数据库操作,包括查询用户,转入,转出操作,具体实现代码如例2所示。

例2 AccountDao.java

 1  package cn.itcast.jdbc.example.dao;

 2  import java.sql.Connection;

 3  import java.sql.SQLException;

 4  import org.apache.commons.dbutils.QueryRunner;

 5  import org.apache.commons.dbutils.handlers.BeanHandler;

 6  import cn.itcast.jdbc.example.domain.Account;

 7  import cn.itcast.jdbc.utils.C3p0Utils;

 8  import cn.itcast.jdbc.utils.JDBCUtils;

 9  public class AccountDao {

 10   public Account find(String name) throws SQLException {

 11     QueryRunner runner = new QueryRunner();

 12     Connection conn = JDBCUtils.getConnection();

 13     String sql = "select * from account where name=?";

 14     Account account = (Account) runner.query(conn, sql, new BeanHandler(

 15         Account.class), new Object[] { name });

 16     return account;

 17   }

 18   public void update(Account account) throws SQLException {

 19     QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());

 20     Connection conn = JDBCUtils.getConnection();

 21     String sql = "update account set money=? where name=?";

 22     runner.update(conn, sql,

 23         new Object[] { account.getMoney(), account.getName() });

 24   }

 25 }

(5)创建类Business,该类包括转账过程的逻辑方法,导入了封装事务操作的JDBCUtils类和封装数据库操作的AccountDao类,完成转账操作。具体代码如下:

 1  package cn.itcast.example;

 1  import java.sql.SQLException;

 2  import cn.itcast.jdbc.example.dao.AccountDao;

 3  import cn.itcast.jdbc.example.domain.Account;

 4  import cn.itcast.jdbc.utils.JDBCUtils;

 5  public class Business {

 6    public static void transfer(String sourceAccountName,

 7          String toAccountName, float money) {

 8      try {

 9        // 开启事务

 10       JDBCUtils.startTransaction();

 11       // 根据用户名查询数据并存入实体类对象中

 12       AccountDao dao = new AccountDao();

 13       Account accountfrom = dao.find(sourceAccountName);

 14       Account accountto = dao.find(toAccountName);

 15       // 完成转账操作

 16       if(money<accountfrom.getMoney()){

 17         accountfrom.setMoney(accountfrom.getMoney()-money);

 18       }else{

 19         System.out.println("转出账户余额不足");

 20       }    

 21       accountto.setMoney(accountto.getMoney()+money);

 22       dao.update(accountfrom);

 23       dao.update(accountto);

 24       // 提交事务

 25       JDBCUtils.commit();

 26       System.out.println("提交成功");

 27     } catch (SQLException e) {

 28       System.out.println("提交失败");

 29       JDBCUtils.rollback();

 30       e.printStackTrace();

 31     } finally {

 32       // 关闭事务

 33       JDBCUtils.close();

 34     }

 35   }

 36   public static void main(String[] args) throws SQLException {

 37     // 调用方法,实现a向b转账200元操作

 38     transfer("a", "b", 200);

 39   }

 40 }

运行类Business,执行结果如图1所示。

图1 运行结果

查询数据库account 表,查询结果如下:

mysql> select * from account;

+----+------+-------+

| id | name | money |

+----+------+-------+

| 1 | a  |  800 |

| 2 | b  | 1200 |

+----+------+-------+

2 rows in set (0.00 sec)

根据查询结果可以看出,转账功能已经成功实现了。也就是说,已经成功演示了DBUtils工具处理事务的整个过程。

点击此处
隐藏目录