用户从控制台输入付款人名、收款人名、转账金额来进行转账,代码进行数据更新并返回转账结果。
数据准备:
CREATE TABLE balance(
userid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(64),
balance DOUBLE
);
INSERT INTO balance VALUES(1,'李青',10000),(2,'亚索',10000);
三层思想将Java程序分离成对数据的访问层、业务控制层、视图显示层,即:
DAO:(data access object)数据访问层
Service:业务层
Web层/View层:展示给用户
分层的目的是(高内聚、低耦合):
这种思想下的基础Java工程一般由如下的包组成(以此案例为例):
com.jdbc.dao:数据操作层
com.jdbc.domain:用于存放JavaBean类
com.jdbc.service:业务层
com.jdbc.util:工具包
com.jdbc.view:展示层
java.lang.ThreadLocal 该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是一个Map,key存放的当前线程,value存放需要共享的数据。
该类主要包括了两个方法,set(T)和get();当调用set方法向该类的对象添加数据时,这个数据将会被存放在当前线程为key的Map中,只有在同一个线程中调用get方法时,才能够成功获取到正确的数据。
先不考虑三层思想,通过C3P0连接池、事务、DBUtils工具包实现该案例的大致步骤如下:
a、从C3P0连接池获取连接对象
b、通过连接对象开启事务
c、通过DBUtils工具包中的QueryRunner类来实现转出方的扣款和转入方的入款。(此处需要注意的是事务中的所有单元只能由同一个连接对象来操作,故在使用该类时,不能使用将DataSource连接池对象作为参数的有参构造方法,而是使用无参构造,在执行具体数据操作时将连接对象作为参数传递,只要保证两次数据操作的连接对象为同一个即可)
d、操作完成后,提交事务或者回滚事务
再考虑三层思想:
DAO层:即数据操作层,此处主要执行的是数据的更新操作
Web/View层:即视图层,供用户录入数据,获取结果
Service层:即业务层,该层从Web层获取数据,对DAO层进行操作,获取操作结果,最后再将结果反馈到Web层
由于上述步骤c中需要获取同一个连接对象来进行两次操作数据库,故a步骤应该交由Service层操作,而c步骤要由DAO层来完成。b、d步骤,即开启、提交、回滚操作,可以被抽取到工具类中去。Web/View层从控制台获取数据即可。
再考虑多人转账的情况,即多线程的情况,对于任何一个线程,都必须要保证数据访问层对数据库的两次操作由同一个连接对象来完成,故可以借助TreadLocal类,将线程和连接对象封装到TreadLocal类对象中。由于业务层需要获取连接,数据访问层也要获取连接,且业务层需要进行事务的操作,故此处可以将对事务的操作和对连接对象的封装放在同一个工具类中,该工具类将连接对象和线程绑定,且提供对当前线程对应连接对象的事务的操作,在数据操作层中不需要再关注两次操作的连接对象是否为同一个,只需要直接通过该工具类获取连接对象即可,线程Key会自动保证连接对象相同。需要提供的方法主要包括:当前线程连接对象的事务的开启、当前线程连接对象的事务的提交、当前线程连接对象的事务的回滚、获取当前线程连接对象。
代码结构如下,将按照工具类→数据操作层→业务层→视图层的顺序粘贴代码
package com.jdbc.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* C3P0连接池工具类
* 使用C3P0连接池步骤:
* 1、导入c3p0-版本号.jar和mchange-commons-java-版本号.jar
* 2、在项目中新建c3p0-config.xml配置文件,放在src下
* 3、使用ComboPooledDataSource类来获取ComboPooledDataSource对象(连接池对象,此处无需手动读取配置文件)
* 4、使用DataSource对象调用getConnection()方法即可获取到连接池对象
* @author Administrator
*
*/
public class C3P0Utils {
private static ComboPooledDataSource ds = new ComboPooledDataSource();
/**
* 获取C3P0连接池连接对象
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/**
* 返回C3P0连接池对象
* @return
*/
public static ComboPooledDataSource getDataSource() {
return ds;
}
/**
* 关闭资源
* @param conn
* @param st
* @param rs
*/
public static void closeAll(Connection conn,Statement st,ResultSet rs){
//负责关闭
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(st != null){
try {
st.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.jdbc.util;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 事务管理类,将Service中对连接和事务的操作剥离出来,作为单独的方法放在这个类中
* 将线程与连接对象绑定
* @author limeng
*
*/
public class TransactionManagerUtils {
private static ThreadLocal tl = new ThreadLocal();
/**
* 获取连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
Connection con = tl.get();
if(con==null) {//说明当前线程尚未加入连接对象,需要获取连接对象赋值
tl.set(C3P0Utils.getConnection());
return tl.get();
}else {
return con;
}
}
/**
* 开启事务
* @throws SQLException
*/
public static void startTransaction() throws SQLException {
getConnection().setAutoCommit(false);;
}
/**
* 提交事务
* @throws SQLException
*/
public static void commitTransaction() throws SQLException {
getConnection().commit();
}
/**
* 回滚事务
* @throws SQLException
*/
public static void rollBackTransaction() throws SQLException {
getConnection().rollback();
}
/**
* 关闭连接
* @throws SQLException
*/
public static void close() throws SQLException{
getConnection().close();
}
}
package com.jdbc.dao;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import com.jdbc.util.TransactionManagerUtils;
/**
* 转账案例操作数据库类(DAO层)
* @author limeng
*
*/
public class TransferDao {
public static boolean cutBalance(String payerName,double amount) throws SQLException {
Connection con = TransactionManagerUtils.getConnection();
QueryRunner qr = new QueryRunner();
String sql = "update balance set balance = balance-? where username = ?";
int result = qr.update(con, sql,amount,payerName);
if(result!=0) {
return true;
}else {
return false;
}
}
public static boolean addBalance(String payeeName,double amount) throws SQLException {
Connection con = TransactionManagerUtils.getConnection();
QueryRunner qr = new QueryRunner();
String sql = "update balance set balance = balance+? where username = ?";
int result = qr.update(con, sql, amount,payeeName);
if(result!=0) {
return true;
}else {
return false;
}
}
}
package com.jdbc.service;
import java.sql.SQLException;
import com.jdbc.dao.TransferDao;
import com.jdbc.util.TransactionManagerUtils;
/**
* 转账案例Service层
* @author Administrator
*
*/
public class TransferService {
public static boolean transfer(String payerName,String payeeName,double amount){
boolean successTransfer = false;
try {
TransactionManagerUtils.startTransaction();
boolean cut = TransferDao.cutBalance(payerName, amount);
boolean add = TransferDao.addBalance(payeeName, amount);
if(cut&&add){
TransactionManagerUtils.commitTransaction();
successTransfer = true;
}else {
TransactionManagerUtils.rollBackTransaction();
}
} catch (Exception e) {
e.printStackTrace();
try {
TransactionManagerUtils.rollBackTransaction();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
try {
TransactionManagerUtils.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return successTransfer;
}
}
package com.jdbc.view;
import java.util.Scanner;
import com.jdbc.service.TransferService;
/**
* 转账用户视图层
* @author Administrator
*
*/
public class TransferView {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String payerName = "";
String payeeName = "";
double amount = 0;
System.out.println("请输入付款人姓名");
payerName = scan.nextLine();
System.out.println("请输入收款人姓名");
payeeName = scan.nextLine();
System.out.println("请输入转账金额");
amount = Double.parseDouble(scan.nextLine());
boolean success = TransferService.transfer(payerName, payeeName, amount);
if(success) {
System.out.println("转账成功");
}else {
System.out.println("转账失败");
}
scan.close();
}
}
运行结果示例如下: