一、Service业务层概述1.1 什么是业务层 ServiceDAO 层只负责单纯数据库 CRUD 操作Service 业务层用来实现完整业务功能面向用户提供业务能力。用户每一次操作对应一个业务功能用户办理账户转账业务用户注册账号网站首页数据展示用户登录校验Service 层主要职责可以调用 DAO 层 1 次或者多次数据库操作完成业务数据运算、逻辑判断管理事务核心保证一组数据库操作要么全部成功要么全部失败。开发规范业务接口命名XXXService业务实现类命名XXXServiceImpl原则一张业务表对应一套 Service 接口 实现类。业务分层关系用户交互视图层 →Service 业务层→ DAO 数据访问层 → 数据库二、实战案例账户转账业务2.1 业务需求A 账户向 B 账户转账扣减转出账户余额增加转入账户余额。 业务执行步骤校验转出账户登录信息获取转账金额转出账户扣钱update校验转入账户信息转入账户加钱update。⚠️问题如果扣钱之后程序突然异常崩溃加钱操作没有执行。会出现钱凭空消失 解决方案事务把两次 update 放到同一个事务中要么全部成功要么全部回滚。2.2 开发整体步骤三层开发标准流程创建数据库账户表编写实体类 Account和数据库表字段一一对应DAO 层编写AccountDao接口 AccountDaoImpl实现类封装增删改查Service 层编写AccountService接口 AccountServiceImpl实现类编写转账业务控制事务JUnit 单元测试测试业务功能。2.3 初步编写转账业务原生事务控制JDBC 默认事务是自动提交模式每执行一条 SQL 就自动 commit。 事务完全由Connection连接对象进行管理。表格Connection 事务方法说明conn.setAutoCommit(false)关闭自动提交开启手动事务写在业务最开始conn.commit()业务全部正常手动提交事务持久化所有修改conn.rollback()出现异常回滚事务撤销全部修改基础 service 伪代码Override public void transfer(Integer fromCardId, String password, Double money, Integer toCardId) { Connection conn null; try { // 1.获取连接关闭自动提交开启手动事务 conn JDBCUtil.getConn(); conn.setAutoCommit(false); AccountDao dao new AccountDaoImpl(); //业务逻辑 //①校验转出账户 Account fromAcc dao.selectByCardId(fromCardId); //②校验密码、余额判断 //③转出账户扣钱 //④转入账户加钱 //全部业务执行完毕提交事务 conn.commit(); } catch (Exception e) { try { //发生任何异常执行回滚 conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); throw new RuntimeException(转账业务失败,e); }finally { JDBCUtil.close(null,conn); } }2.4 重大坑点事务控制失效❌现象Service 开启事务调用 DAO 的多个 update一旦出现异常不会回滚事务失效。✅根因Service 层拿了一个 Connection 连接 DAO 层内部调用JDBCUtil.getConn()拿到全新的另外一个 Connection 对象。 多个 DAO 使用不同的数据库连接每个连接各自独立事务事务完全隔离Service 的 commit/rollback 管不到 DAO 的连接。两种解决方案方案一把 Connection 当做参数传入 DAO 方法不推荐DAO 所有 CRUD 方法增加参数Connection connService 把自己的连接传给 DAO 使用。缺点污染 DAO 接口每一个 DAO 方法都要多传连接参数代码侵入严重业务代码到处传递 conn耦合高。并且 DAO 不能内部关闭连接连接要交给 Service 层关闭。方案二ThreadLocal 线程本地变量【推荐】核心思路把 Connection 绑定到当前线程上同一个线程中不管 Service、DAO调用工具类都拿到同一个 Connection 对象。 线程之间互相隔离A 线程的连接不会被 B 线程拿到。三、ThreadLocal 原理 改造 JDBCUtil 工具类3.1 ThreadLocal 是什么ThreadLocalT线程本地变量。将对象绑定到当前执行线程同一个线程任意位置调用get()都拿到同一个对象不同线程之间数据互相隔离互不干扰业务结束调用remove()释放线程绑定对象防止内存泄漏。核心 API表格方法功能set(obj)将对象绑定到当前线程get()获取当前线程绑定的对象remove()删除当前线程绑定的对象释放资源3.2 重构 JDBCUtil 工具类增加 ThreadLocalimport java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; public class JDBCUtil2 { //ThreadLocal绑定当前线程的Connection对象 private static ThreadLocalConnection tol new ThreadLocal(); private static Properties prop new Properties(); static { try { InputStream in JDBCUtil2.class.getResourceAsStream(/jdbc.properties); prop.load(in); Class.forName(prop.getProperty(jdbc.driverClassName)); }catch (Exception e){ throw new RuntimeException(配置文件加载失败,e); } } /** * 获取数据库连接同一个线程多次调用返回同一个connection */ public static Connection getConn(){ Connection conn tol.get(); if(conn null){ //当前线程还没有绑定连接新建连接 try { conn DriverManager.getConnection( prop.getProperty(jdbc.url), prop.getProperty(jdbc.username), prop.getProperty(jdbc.password) ); //绑定到当前线程 tol.set(conn); }catch (Exception e){ throw new RuntimeException(获取数据库连接失败,e); } } return conn; } /** * 关闭资源 * 重点如果conn来自ThreadLocal关闭之后必须执行tol.remove() */ public static void close(ResultSet rs, Statement stm, Connection conn){ try { if(rs ! null) rs.close(); if(stm ! null) stm.close(); if(conn ! null){ conn.close(); //清除当前线程绑定的连接释放防止内存泄漏 tol.remove(); } }catch (Exception e){ e.printStackTrace(); } } //重载没有resultSet场景DAO增删改调用 public static void close(Statement stm,Connection conn){ close(null,stm,conn); } }重要约束DAO 层代码不可以关闭 Connection 连接由 Service 业务层统一关闭DAO 只关闭 Statement/ResultSet。 只有业务全部结束才执行 close同时执行tol.remove()清除线程绑定。3.3 改造之后事务完整流程Service 业务层调用JDBCUtil2.getConn()拿到连接绑定当前线程conn.setAutoCommit(false)关闭自动提交开启手动事务Service 多次调用 DAO 层DAO 内部调用JDBCUtil2.getConn()拿到线程绑定的同一个连接业务全部正常Service 执行conn.commit()提交捕获异常Service 执行conn.rollback()回滚finally 调用 close关闭连接tol.remove()清除线程绑定。✅ 现在所有 DAO 复用同一个 Connection事务完全生效拓展Spring 框架底层事务管理本质原理也是依靠 ThreadLocal 绑定数据库连接。四、JavaWeb 经典三层架构表格分层职责重点说明视图层 View和用户交互接收输入展示结果Swing / Servlet / 前端页面业务层 Service实现业务逻辑、数据运算、事务控制接口 实现类调用 DAO 层事务在这里开启、提交、回滚数据访问层 DAO只负责数据库 CRUD 增删改查不处理业务不处理事务不能关闭 Connection 连接三层架构开发好处各司其职职责分离解耦合修改某一层其他层不受影响利于团队分工协作开发方便单元测试。开发顺序建表 → 实体类 → DAO 接口 实现类 → Service 接口 实现类 → JUnit 单元测试。五、动手波Account 账户业务5.1 定义 AccountService 业务接口import java.util.List; public interface AccountService { // 查询全部账户 ListAccount queryAll(); //新增账户 void addAccount(Account acc); //修改账户 void modifyAccount(Account acc); //删除账户 void removeAccount(Integer cardId); //根据卡号查询账户 Account queryByCardId(Integer cardId); /** * 多条件动态查询 * username、balance、mobile参数不为空就作为查询条件为空不参与条件拼接 */ ListAccount queryByTiaojian(String username, Double balance, String mobile); //转账业务 void transfer(Integer fromCardId,String password,Double money,Integer toCardId); }可以尝试编写AccountServiceImpl实现上述全部接口使用重构后的JDBCUtil2ThreadLocal 管理连接转账业务完整实现事务使用 JUnit 单元测试每一个业务方法。六、重点知识总结Service 业务层实现业务逻辑事务写在 Service 层不是 DAO 层JDBC 默认自动提交事务setAutoCommit(false)开启手动事务commit()提交rollback()回滚事务失效最常见原因多次获取不同 Connection 连接ThreadLocal绑定对象到当前线程线程隔离同一个线程拿到同一个 ConnectionDAO 层只关闭 Statement、ResultSetConnection 交给 Service 层关闭同时调用 remove 清除线程绑定防止内存泄漏三层架构View 视图层 / Service 业务层 / DAO 数据访问层开发顺序表→实体→DAO→Service→单元测试。 以上为本篇 JDBC 学习笔记若有更好的建议欢迎评论区商讨交流欢迎各位大佬指正、补充不同实现思路。
