JDBC——ThreadLocal原理、完成转账案例

目录

  • 分层思想
  • 使用JDBC事务分层完成转账案例
  • ThreadLocal的原理
  • 编写ConnectionManager工具类
  • 优化转账案例

分层思想

跳转到目录
JDBC——ThreadLocal原理、完成转账案例_第1张图片

  • 开发中,常使用分层思想
    • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
  • 不同层级结构彼此平等
  • 分层的目的是:
    • 解耦
    • 可维护性
    • 可扩展性
    • 可重用性
  • 不同层次,使用不同的包表示
    • com.sunny公司域名倒写
    • com.sunny.dao dao层
    • com.sunny.service service层
    • com.sunny.domain javabean
    • com.sunny.utils 工具

使用JDBC事务分层完成转账案例

跳转到目录
JDBC——ThreadLocal原理、完成转账案例_第2张图片

  • AccountDAO
/*
    dao层定义AccountDao类,操作数据库
    进账方法:
        名称: in
        参数: 连接对象,进账账户,进账金额
        注意:连接对象,进账账户,进账金额由service层传递

     实现步骤:
        1.创建QueryRunner对象
        2.定义sql语句
        3.执行sql语句

    出账方法:
        名称: out
        参数: 连接对象,出账账户,出账金额
        注意:连接对象,出账账户,出账金额由service层传递

     实现步骤:
        1.创建QueryRunner对象
        2.定义sql语句
        3.执行sql语句


    注意:
        1.dao层不负责异常处理,有异常直接抛出
        2.dao层不负责事务管理

 */
public class AccountDAO {

    // 进账方法
    public void inAccount(Connection con, String inName, double inMoney) throws Exception{
        QueryRunner queryRunner = new QueryRunner();
        String sql = "UPDATE account SET money = money + ? WHERE name = ?";
        // 执行sql语句
        queryRunner.update(con, sql, inMoney, inName);
    }

    //出账方法
    public void outAccount(Connection con,String outName,double outMoney) throws SQLException {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner();

        //2.定义sql语句
        String sql = "update account set money = money - ? where name = ?";
        //3.执行sql语句
        qr.update(con,sql,outMoney,outName);
    }

/*
    //这种做法是有问题的,因为in和out方法,并没有使用service层传递进来的Connection连接对象
    //而是自己获取连接对象,导致service层和dao层使用的不是一个Connection对象,事务管理出现了问题
    //进账方法
    public void in(Connection con,String inName,double inMoney) throws SQLException {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //2.定义sql语句
        String sql = "update account set money = money + ? where name = ?";

        //3.执行sql语句
        qr.update(C3P0Util.getConnection(),sql,inMoney,inName);

    }
    //出账方法
    public void out(Connection con,String outName,double outMoney) throws SQLException {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner();

        //2.定义sql语句
        String sql = "update account set money = money - ? where name = ?";
        //3.执行sql语句
        qr.update(C3P0Util.getConnection(),sql,outMoney,outName);
    }
    */
}
  • AccountService
/**
* service层定义AccountService类,操作dao层的AccountDao类
* 转账方法:
* 名称: transfer
* 参数: 进账账户,出账账户,转账金额
* 注意:连接对象,service层自己创建,进账账户,出账账户,转账金额由view层传递
* 

* 步骤: * 1.创建AccountDao类的对象 * 2.获取连接对象 * 3.开启事务 * 4.调用方法(进账/出账的方法) * 5.方法正常执行,提交事务 * 6.方法执行出现问题,回滚事务 * 7.关闭资源 *

* 注意: * 1.service层负责异常处理,有异常try-catch-finally * 2.service层负责事务管理 */ public class AccountService { public void transfer(String inName, String outName, double transferMoney) { Connection con = null; try { // 创建AccountDAO对象 AccountDAO accountDAO = new AccountDAO(); con = DruidUtil.getConnection(); // 开启事务 con.setAutoCommit(false); // 调用方法(进账/出账) accountDAO.inAccount(con, inName, transferMoney); // 模拟异常 System.out.println(1 / 0); // 调用出账方法 accountDAO.outAccount(con, outName, transferMoney); // 提交事务 con.commit(); } catch (Exception e) { e.printStackTrace(); // 方法执行出现问题, 回滚事务 if (con != null) { try { con.rollback(); } catch (Exception e1) { e1.printStackTrace(); } } } finally { DruidUtil.close(null, null, con); } } }

  • AccountPage
/**
 * 转账页面:
 *  调用service层
 */
public class AccountPage {
    public static void main(String[] args) {
        String inName = "jerry";
        String outName = "tom";
        double transferMoney = 1000;

        // 调用service层
        AccountService service = new AccountService();
        service.transfer(inName, outName, transferMoney);
    }
}

ThreadLocal介绍

  • 实现共享局部变量
  • 上面的分析过程, 我们可以得知 view -> service -> dao 三层中都使用了同一个线程: main线程, 如果在这三层中, 使用同一个连接对象就可以解决操作的是同一个Connection连接对象了。

JDBC——ThreadLocal原理、完成转账案例_第3张图片
正如上图我们分析的这样, ThreadLocal内部使用的是Map集合, 一般Map集合有两个泛型 Map, 但是ThreadLocal只有一个泛型, 代表的是值的类型, 内部把键的类型已经规定为Thread, 也就是当前线程对象 作为键的值; 所以ThreadLocal类泛型T就表示的是当前线程对象绑定的值的类型。

ThreadLocal的原理

跳转到目录
JDBC——ThreadLocal原理、完成转账案例_第4张图片

  • java.lang.ThreadLocal<T>
    作用: 实现线程共享局部变量
    内部使用Map集合,Map集合有2个泛型,K: 键的类型,V代表值的类型
    ThreadLocal<T>类: 只有一个泛型,代表的是值的类型,内部把键的类型已经规定为了Thread,使用当前线程对象,作为键的值所以ThreadLocal<T>类,泛型T表示的是给当前线程对象绑定的值的类型。

    成员方法:
    public void set(T t): 给当前线程对象,绑定一个T类型的变量t
    相当于Map集合:
    map.put(Thread.currentThread,t)

    public T get(): 获取当前线程对象上,绑定的值, 因为键已经在内部确定了(当前线程对象)
    相当于Map集合:
    map.get(Thread.currentThread)

    public T remove(): 删除当前线程对象上绑定的值
    相当于Map集合:
    map.remove(Thread.currentThread)

public class ThreadLocalDemo {
    public static void main(String[] args) {
        // 创建ThreadLocal对象
        ThreadLocal<String> tl = new ThreadLocal<>();

        // 获取当前线程对象绑定的值
        String s = tl.get();
        System.out.println(s); // null, 因为当前线程对象还没有设置value值呢,所以为null

        // 给当前main线程对象(作为key),绑定值为"hello guizy"
        tl.set("hello guizy");
        s = tl.get();
        System.out.println(s);  // hello guizy

        // 创建SubThread类的对象
        SubThread st = new SubThread(tl);
        // 开启线程
        st.start();
    }
}


public class SubThread extends Thread {
    private ThreadLocal<String> tl;

    public SubThread(ThreadLocal<String> tl) {
        this.tl = tl;
    }

    @Override
    public void run() {
        // 获取当前线程对象上绑定的值
        String str = tl.get();
        System.out.println("run...." + str);

        // 给当前线程对象(key),绑定值为 "sub thread"
        tl.set("sub thread");
        str = tl.get();
        System.out.println("run...." + str);
    }
}

ConnectionManager工具类

跳转到目录

/**
 * 连接Connection对象管理工具类
 */
public class ConnectionManager {
    /**
     * 借助ThreadLocal实现线程中局部变量的数据共享
     */
    private static ThreadLocal<Connection> tl = new ThreadLocal();

    private ConnectionManager() {

    }

    /**
     * 定义静态方法,获取连接对象
     * 此方法,保证在同一个线程中获取到的是同一个Connection对象; 当下次再获取连接的时候,此时该线程对象中已经有连接对象了.
     */
    public static Connection getConnection() throws SQLException {
        // 首先从ThreadLocal对象中获取Connection对象
        Connection con = tl.get();
        // 判断con是否为null
        if (con == null) {
            // 先从数据库连接池中获取一个连接对象
            con = DruidUtil.getConnection();
            // 绑定到ThreadLocal中
            tl.set(con);
        }
        return con;
    }
    /*
        定义开启事务的方法, 使用的都是同一个连接对象来操作事务
     */
    public static void setAutoCommit() throws SQLException {
        Connection con = getConnection();
        con.setAutoCommit(false);
    }
    /*
        定义提交事务的方法
     */
    public static void commit() throws SQLException {
        Connection con = getConnection();
        con.commit();
    }
    /*
        定义回滚事务的方法
     */
    public static void rollback() throws SQLException {
        Connection con = getConnection();
        con.rollback();
        //从ThreadLocal对象中移除
        tl.remove();
    }
}

优化转账案例

跳转到目录

  • AccountDAO
/*
    dao层定义AccountDao类,操作数据库
    进账方法:
        名称: in
        参数: 进账账户,进账金额
        注意:进账账户,进账金额由service层传递

     实现步骤:
        1.创建QueryRunner对象
        2.定义sql语句
        3.执行sql语句

    出账方法:
        名称: out
        参数: 出账账户,出账金额
        注意: 出账账户,出账金额由service层传递

     实现步骤:
        1.创建QueryRunner对象
        2.定义sql语句
        3.执行sql语句


    注意:
        1.dao层不负责异常处理,有异常直接抛出
        2.dao层不负责事务管理
        3.连接对象从ConnectionManager工具类中获取,它保证同一个线程中获取到的是同一个连接对象

 */
public class AccountDao {
    //进账方法
    public void in(String inName,double inMoney) throws SQLException {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //2.定义sql语句
        String sql = "update account set money = money + ? where name = ?";

        //3.执行sql语句
        qr.update(ConnectionManager.getConnection(),sql,inMoney,inName);

    }
    //出账方法
    public void out(String outName,double outMoney) throws SQLException {
        //1.创建QueryRunner对象
        QueryRunner qr = new QueryRunner();

        //2.定义sql语句
        String sql = "update account set money = money - ? where name = ?";
        //3.执行sql语句
        qr.update(ConnectionManager.getConnection(),sql,outMoney,outName);
    }
}
  • AccountService
/*
    service层定义AccountService类,操作dao层的AccountDao类
    转账方法:
        名称: transfer
        参数: 进账账户,出账账户,转账金额
        注意:进账账户,出账账户,转账金额由view层传递
            连接对象,从ConnectionManager工具类中获取,它保证同一个线程中获取到的是同一个连接对象

    步骤:
        1.创建AccountDao类的对象
        2.获取连接对象
        3.开启事务
        4.调用方法(进账/出账的方法)
        5.方法正常执行,提交事务
        6.方法执行出现问题,回滚事务
        7.关闭资源

    注意:
        1.service层负责异常处理,有异常try-catch-finally
        2.service层负责事务管理
 */
public class AccountService {
    public void transfer(String inName,String outName,double transferMoney) {
        Connection con = null;
        try {
            //1.创建AccountDao类的对象
            AccountDao accountDao = new AccountDao();

            //2.获取连接对象
            con = ConnectionManager.getConnection();

            //3.开启事务
            ConnectionManager.setAutoCommit();

            //4.调用方法(进账/出账的方法)
            //调用进账方法
            accountDao.in(inName,transferMoney);

//            System.out.println(1/0);//出现异常

            //调用出账方法
            accountDao.out(outName,transferMoney);

            //5.方法正常执行,提交事务
            ConnectionManager.commit();


        } catch (Exception e) {
            e.printStackTrace();
            //6.方法执行出现问题,回滚事务
            try {
                ConnectionManager.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            //7.关闭资源
            DruidUtil.close(null, null, con);
        }
    }
}
  • AccountPage
public class AccountPage {
    public static void main(String[] args) {
        String inName = "jerry";
        String outName = "tom";
        double transferMoney = 1000;

        //调用service层
        //创建AccountService类的对象
        AccountService service = new AccountService();

        //AccountService类的对象调用transfer方法,完成转账
        service.transfer(inName,outName,transferMoney);
    }
}

你可能感兴趣的:(MySQL,JDBC,事务,ThreadLocal,转账案例)