- 在学习之前要想一个问题:我学这个用来干嘛?不然学了没有用!
- 大家应该都知道,对于数据库来说,一个连接对应于一个事务,一个事务可以包含多个DML操作(增删改查)
- 举一个转账的例子,张三给李四转账1000元,就相当于张三减1000李四加1000,想要转账成功肯定是这二个操作同时成功,不可能是张三减了1000,李四账户上没有收到1000吧!
- 用三层结构来解释就是service的操作都是来自于dao层的操作,而dao层就是二个操作:张三减1000,李四加1000,而这二个操作会产生二个连接对象connection,上面有说过一个连接相当于一个事务,而在二个连接就不是同一个事务了就不能实现同时张三减1000,李四加1000的操作,所以这个时候我们要应用ThreadLocal
- 简单介绍一下ThreadLocal,它可以为每个线程复制一个副本。用上面的例子来说:当代码访问数据源的时候会产生一个connection连接,当张三减1000的时候,并没有提交事务。所以当李四加1000的时候代码也会去访问数据源,可数据源的连接已经被张三加1000给用了,所以这个时候ThreadLocal就会复制一个副本连接出来给李四来加1000,就实现了一个连接对应多个事务
接下来废话不多说,直接看代码
数据库的账号表:

cardid是账户卡号; name是账户户主;balance是账号余额
实体类:
public class Account {
private int cardid;
private String name;
private int balance;
public int getCardid() {
return cardid;
}
public void setCardid(int cardid) {
this.cardid = cardid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
}
工具类:c3p0连接池
public static DataSource getDataSoure(){
//传入的参数是配置文件中的name的值
ComboPooledDataSource c3p0 = new ComboPooledDataSource("haiyang");
return c3p0;
}
c3p0连接池xml配置
<c3p0-config>
<!--默认配置-->
<default-config>
<property name="user">simple</property>
<property name="password">123456</property>
<property name="driverClass">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
<property name="jdbcUrl">jdbc:sqlserver://localhost:1433;databaseName=Student</property>
</default-config>
<!--配置连接池-->
<named-config name="haiyang">
<property name="user">simple</property>
<property name="password">123456</property>
<property name="driverClass">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
<property name="jdbcUrl">jdbc:sqlserver://localhost:1433;databaseName=User</property>
</named-config>
</c3p0-config>
工具类:辅助事务
public class JDBCUtil {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//获取链接
public static Connection getConnection() throws SQLException {
Connection conn = threadLocal.get();//第一次去数据源中获取链接
if (conn == null){//如果没有链接
conn = c3p0.getDataSoure().getConnection();//从连接池中获取数据源从而获取链接
threadLocal.set(conn);//把链接放进数据源中
}
return conn;
}
//开启事务
public static void beginThreadLocal() throws SQLException {
Connection conn = getConnection();
conn.setAutoCommit(false);//设为手动提交,开启事务
}
//事务成功
public static void CommitThreadLocal() throws SQLException {
Connection conn = getConnection();
if (conn != null)//所有的对象.方法全部都要排空
conn.commit();
}
//事务失败,回滚事务
public static void rollbackThreadLocal() throws SQLException {
Connection conn = getConnection();
if (conn != null)
conn.rollback();
}
//关闭
public static void close() throws SQLException{
Connection conn = getConnection();
if (conn != null)
conn.close();
threadLocal.remove();
}
}
Dao层接口
public interface AccountDao {
//根据卡号查询账户
Account QueryByid(int cardid) throws Exception;
//修改账户
void updateAccount(Account account) throws Exception;
}
Dao层实现类
public class AccountDaoimpl implements AccountDao {
@Override
public Account QueryByid(int cardid) throws Exception{
Connection conn = JDBCUtil.getConnection();
QueryRunner runner = new QueryRunner();//手动提交事务,无参
Account account = runner.query(conn, "select * from account where cardid = ?", new BeanHandler<Account>(Account.class), cardid);
return account;
}
@Override
public void updateAccount(Account account) throws Exception{
Connection conn = JDBCUtil.getConnection();
QueryRunner runner = new QueryRunner();
runner.update(conn,"update account set balance = ? where cardid = ?",new Object[]{account.getBalance(),account.getCardid()});
}
}
service接口
//业务:转账
public interface AccountService {
//第一个参数:付款人,第二个参数:收款人,第三个参数:转账金额
void transfer(int formcarid,int tocarid,int money);
}
service实现类
public class AccountServiceimpl implements AccountService {
@Override
public void transfer(int formcarid, int tocarid, int money) {
AccountDaoimpl accountDao = new AccountDaoimpl();
try {
//开启事务
JDBCUtil.beginThreadLocal();
//DML操作
//先根据卡号查询账户
Account formname = accountDao.QueryByid(formcarid);//付款方
Account toname = accountDao.QueryByid(tocarid);//收款方
if(formname.getBalance() > money){//判断付款方是否有足够的余额
//转账操作
//付款方
int formbalance = formname.getBalance() - money;
formname.setBalance(formbalance);
formname.setCardid(formcarid);
accountDao.updateAccount(formname);
//收款方
int tobalance = toname.getBalance() + money;
toname.setBalance(tobalance);
toname.setCardid(tocarid);
accountDao.updateAccount(toname);
//事务成功
System.out.println("转账成功!");
JDBCUtil.CommitThreadLocal();
}else {
System.out.println("余额不足,转账失败!");
}
} catch (Exception e) {
try {
JDBCUtil.rollbackThreadLocal();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
System.out.println("转账失败!");
}finally {
try {
JDBCUtil.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试类
public class test {
public static void main(String[] args) {
AccountServiceimpl accountService = new AccountServiceimpl();
accountService.transfer(1111,2222,2000);
}
}
一开始我设置张三8000元,李四8000元

运行结果

再次查询数据库

张三成功给李四转账2000元