作者: 欢迎观看我的文章, 注定了我们今生有缘分! 我已从事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();
}
}