MySQL实战系列 -- 9. ThreadLocal

386 阅读6分钟

作者: 欢迎观看我的文章, 注定了我们今生有缘分! 我已从事Java开发教育十多年, 怀着教书育人的情怀, 正在让更多的同学们少走弯路, 改变千万IT人的命运!

期待同学动动你的小手给'霈哥'点赞、加关注、分享给更多的朋友共同学习交流, 每天持续更新离不开你的支持! 3Q

欢迎关注我的B站,可观看本文章配套视频~~~ 欢迎关注我的公众号,获取更多资料~~~


学习目标

  • 能够理解代码分层开发的思想
  • 能够理解ThreadLocal的作用

第二章 JavaEE分层开发思想

2.1 分层开发思想

  • 开发中,常使用分层思想

    • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
  • 分层的目的是:

    • 解耦
    • 可维护性
  • 可重用性

  • 不同层次,使用不同的包表示

    • com.baidu.map 公司域名倒写 com.baidu.map.
    • com.baidu.map.dao dao层
    • com.baidu.map.service service层
    • com.baidu.map.pojo javabean
    • com.baidu.map.utils 工具

2.2 案例:使用分层开发思想, 完成转账

分析

代码实现

  • 步骤1:编写入口程序
/**
 * 程序执行的入口
 */
public class TestAccount {
    /*
    1. 模拟数据  扣款人  收款人  金额
    2. 调用AccountService中 转账方法
    3. 打印 转账后的结果 ( 成功,  失败)
     */
    public static void main(String[] args) {
        // 1. 模拟数据  扣款人  收款人  金额
        String outUser = "jack"; //扣款人
        String inUser = "rose"; //收款人
        double money = 1000; // 金额
        //2. 调用AccountService中 转账方法
        AccountService service = new AccountService();
        try {
            service.transfer(outUser, inUser, money);
            // 打印 转账后的结果 ( 成功,  失败)
            System.out.println("转账成功");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("转账失败");
        }
    }
}

  • service层
public class AccountService {

    /**
     * 转账方法
     * 参数1 : 扣款人
     * 参数2 : 收款人
     * 参数3 : 金额
     */
    public void transfer(String outUser, String inUser, double money) throws Exception{
        /*
        事务控制, 保证 转账的操作 结果的一致性
        1. 获取Connection对象
        2. 手动开启事务
        3. 执行 减钱 加钱操作
        4. 提交事务
        5. 如果出现异常, 回滚事务
         */
        Connection conn = null;
        try {
            // 1. 获取Connection对象
            conn = DruidUtils.getConnetion();
            //2. 手动开启事务
            conn.setAutoCommit(false);
            //3. 执行 减钱 加钱操作
            //调用dao层方法 完成 减钱 加钱操作
            AccountDao dao = new AccountDao();
            //减钱
            dao.out(conn, outUser, money);

            //模拟异常的操作
            int n = 1/0;

            //加钱操作
            dao.in(conn, inUser, money);

            // 4. 提交事务
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 5. 如果出现异常, 回滚事务
            conn.rollback();
        }

    }
}
  • dao层
public class AccountDao {

    /**
     * 扣钱方法
     * @param outUser 扣钱人
     * @param money 金额
     */
    public void out(Connection conn, String outUser, double money) throws SQLException {
        String sql = "update account set money=money-? where name=?";

        QueryRunner qr = new QueryRunner();
        qr.update(conn, sql, money, outUser);
    }

    /**
     * 加钱方法
     * @param inUser 收钱人
     * @param money 金额
     */
    public void in(Connection conn, String inUser, double money) throws SQLException {
        String sql = "update account set money=money+? where name=?";

        QueryRunner qr = new QueryRunner();
        qr.update(conn, sql, money, inUser);
    }

}

第三章 ThreadLocal

3.1 事务传递Connection引发的思考

在“事务传递Connection参数案例”中,我们必须传递Connection对象,才可以完成整个事务操作。如果不传递参数,是否可以完成?

在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在当前线程中共享数据。

java.lang.ThreadLocal 该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。

3.2 ThreadLocal类的使用

java.lang.ThreadLocal 该类提供了线程局部(thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是相当于一个Map,key存放的当前线程,value存放需要共享的数据。

时间线程A(已开启)线程B(已开启)
T1创建ThreadLocal对象
T2调用set("霈哥") 存入值
T3调用get() 获取值
T4调用get()获取值, 结果为null

举例

public class ThreadLocalDemo {

    public static void main(String[] args) {
        ThreadLocal<String>  mainThread = new ThreadLocal<>();

        mainThread.set("霈哥");

        System.out.println(mainThread.get());//霈哥

        new Thread(()->{
            System.out.println(mainThread.get());//null
        }).start();
    }
}

结论:向ThreadLocal对象中添加的数据只能在当前线程下使用。

3.3 案例:使用ThreadLocal完成转账

分析

代码实现

工具类

/**
 * 阿里巴巴的连接池 Druid 工具类
 */
public class DruidUtils {
    /*
    1. 加载 druid.properties 配置文件
    2. 创建 Druid 连接池对象
    3. 提供 获得 连接池对象的方法
    4. 提供 通过ThreadLocal 从连接池中 获取连接对象Connection的 方法
    */

    public static DataSource ds = null;

    //给当前线程绑定 连接
    private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();

    static {
        try {
            //1. 加载 druid.properties 配置文件
            InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties prop = new Properties();
            prop.load(is);
            //2. 创建 Druid 连接池对象
           ds = DruidDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
    3. 提供 获得 连接池对象的方法
     */
    public static DataSource getDataSource(){
        return ds;
    }

    /*
    4. 提供 通过ThreadLocal 从连接池中 获取连接对象Connection的 方法
    步骤: 
    1.从ThreadLocal获得连接
    2.如果没有, 从连接池获得连接, 并保存到ThreadLocal中
    3.获得连接,返回即可
     */
    /**
     * 获取数据源中的连接对象
     */
    public static Connection getConection() throws SQLException {
        //#1从当前线程中, 获得已经绑定的连接
        Connection conn = threadLocal.get();
        //休眠一秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(conn == null){
            //#2 第一次获得,绑定内容 – 从连接池获得
            conn = dataSource.getConnection();
            //#3 将连接存 ThreadLocal
            threadLocal.set(conn);
            System.out.println(Thread.currentThread().getName()+"====第一次获得连接对象,并存入ThreadLocal===="+ conn);
        }
        return conn;
    }
}

service层

/**
 * 业务类
 */
public class AccountService {

    /**
     * 转账
     * @param outUser 汇款人
     * @param inUser 收款人
     * @param money 金额
     */
    public void transfer(String outUser, String inUser, double money) {
        /**
         * 1.获得连接
         * 2.开始事务
         * 3.具体的sql操作(加钱, 减钱)
         * 4.提交事务 ,释放资源
         * 5.如果出现异常, 回滚事务释放资源
         */
        Connection conection = null;
        try {
            conection = DruidUtils.getConection();
            conection.setAutoCommit(false);
            System.out.println(Thread.currentThread().getName()+"====在AccountService的transfer方法中, 获取到了连接对象===="+ conection);

            //TODO 具体的sql操作(加钱, 减钱)
            AccountDao dao = new AccountDao();
            //减钱
            dao.out(outUser, money);
            //模拟异常
            //int i = 1/0;
            //加钱
            dao.in(inUser, money);

            //提交事务 ,释放资源
            DbUtils.commitAndCloseQuietly(conection);

        } catch (Exception e) {
            //如果出现异常, 回滚事务释放资源
            DbUtils.rollbackAndCloseQuietly(conection);
            //抛出异常
            throw new RuntimeException(e);
        }
    }
}

dao层

/**
 * 操作数据库表
 */
public class AccountDao {

    private QueryRunner qr = new QueryRunner();

    /**
     * 减钱
     * @param outUser 扣款人
     * @param money 金额
     */
    public void out(String outUser, double money) throws SQLException {
        //获取Threadlocal中的连接对象
        Connection conn = DruidUtils.getConection();
        System.out.println(Thread.currentThread().getName()+"====在AccountDao的out方法中, 获取到了连接对象===="+ conn);


        String sql = "update account set money = money-? where name=?";
        qr.update(conn, sql, money, outUser);
    }

    /**
     * 加钱
     * @param inUser 收款人
     * @param money 金额
     */
    public void in(String inUser, double money) throws SQLException {
        //获取Threadlocal中的连接对象
        Connection conn = DruidUtils.getConection();
        System.out.println(Thread.currentThread().getName()+"====在AccountDao的in方法中, 获取到了连接对象===="+ conn);


        String sql = "update account set money= money+? where name=?";
        qr.update(conn, sql, money, inUser);
    }
}

测试类

public class TestAccount {
    public static void main(String[] args) {
        /**
        1. 模拟数据  扣款人  收款人  金额
        2. 调用AccountService中 转账方法
        3. 打印 转账后的结果 ( 成功,  失败)
         */
        //1. 模拟数据  扣款人  收款人  金额
        //扣款人
        String[] outUserArray = {"zhang3","wang5","tian7"};

        //收款人
        String[] inUserArray = {"li4","zhao6","wang8"};

        double money = 1000; //金额

        //调用转账方法, 启动3个线程, 完成不同账户的转账操作
        AccountService service = new AccountService();
        for (int i = 0; i < 2; i++) {
            transfer(service, outUserArray[i], inUserArray[i], money);
        }

    }

    public static void transfer(AccountService service, String outUser, String inUser, Double money){
        new Thread(()->{
            try{
                //TODO 调用AccountService中 转账方法
                service.transfer(outUser, inUser, money);

                System.out.println("转账成功");
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("转账失败");
            }
        }).start();
    }
}