




已阅读5页,还剩13页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
数据库事务的简单封装实现 上面一直在说数据源跟事务之间的关系 可是在写上面的东西的时候大脑中对这些东 西只是有一个模糊的概念 没法形成体系 更没法继续描述清楚 所以就把这件事情 暂时放下了 写了几天的算法 但是内心还是感觉有些东西需要完成 最近还是准备 继续写下去 在这里我决定重新整理一下思路 事务是我们最开始在接触数据库的时候学到的 在这里我们可以得出一个大胆的结论 那就是事务要由数据库来实现 那么我们在程序中是没有数据库这个概念的 我们的 操作是通过数据源的操作来实现的 所以在程序中 事务是和数据源绑定在一起的 我们的事务的保证要依赖于具体的数据源 那么 spring 要实现事务也要依赖于具体的数据源 所以这里 spring 最终能提供什么 样的事务要看具体的数据库和具体的数据源 Spring 在具体的操作的时候提供了很多不同的事务管理器 这些不同的事务管理器对 应操作不同的数据源类型 单个数据库的时候 我们使用 jdbc 的事务管理器是我们很 常见的一种方式 具体的代码设计如下 Class forName com mysql jdbc Driver newInstance Connection conn DriverManager getConnection jdbc mysql 127 0 0 1 3306 test 01 user root conn setAutoCommit false Statement stmt conn createStatement ResultSet rs stmt executeQuery select from user while rs next System out println rs getString 1 mit 大体的流程如上 首先将自动提交设为 false 这样我们就可以手动提交了 否则手 动提交时会抛异常的 之后去执行具体的操作 操作完成后手动提交业务 当然这里 要捕捉异常的 一旦出现异常 就应该执行 rollback Spring 的事务管理器也不会超出我们上面的过程 这里我们先讨论非分布式的事务 先从我们的使用上分析 看一下我们 service 和 dao 层的代码 会发现我们这里是没 有开启事务 也就是将自动提交设为 false 的代码 没有最后的 commit 过程或者 rollback 的过程 从这一点上来说如果让我们自己封装的话我相信大家也会想到用动 态代理来实现的 那么 spring 就可以采用 aop 来实现了 我们这里大体猜测一下过程就可以有一下几个 bean beforeMethod 的实现类 完成事务中开启的工作 执行 conn setAutoCommit false afterMethod 执行事务的提交工作 这里还有一个问题 出现异常怎么办 谁捕捉异常 这里没有其他的类可以捕捉异常 了 只剩下一个代理类了 所以在代理类中需要将这个异常捕捉并执行回滚的操作 那么我们的代理类的执行就会变成以下的过程 beforeMethod 开启事务 try method invoke 具体的实现的业务 也就是我们 dao 或者 service 中的代码 catch Exception e Conn rollback afterMethod 提交事务 根据我们的分析 可以设计出上面的框架 我们的上面的代码设计有没有问题 在单个业务 也就是只执行一次的方法里面是没有问题的 那么在什么情况下会有问 题呢 假设我们的业务代码拥有两个过程 分别为新建一个用户 在 message 表中插入一个 记录 这是两个业务 但是要求这两个业务具有原子性 我们上面的方式就出问题了 写到这里我想自己写一下 好像能说的清楚一些 在写这个之前我们需要获取数据源 顺便写一个简单的数据源的封装工具 这样我们就可以把整个过程说的清楚一些 顺 便能熟悉一下数据源的实现的大体流程 简单封装的数据库连接池的工具如下 public class DBPoolUtil private static LinkedList pool new LinkedList private static Lock lock new ReentrantLock private static Condition notEmpty lock newCondition private static Condition timeInterval lock newCondition private static String driverClassName com mysql jdbc Driver private static String url private static String username private static String password private static int INIT 10 初始连接数 private static int MAX 20 最大连接数 private static int MIN 10 最小连接数 private static int INCREMENT 5 每次增加数 private static volatile int current 0 保证读的时候读取内存中 最新的值 private static int TIMEWAIT 1000 等待时间 用于检测池中的连接是否有关闭或者无法使用的 若存在则将该连接移除链接 池 private static Thread dbKeeper null static try init catch Exception e e printStackTrace addConnection INIT dbKeeper new Thread new DBKeeper dbKeeper setDaemon true dbKeeper start private DBPoolUtil private static void init throws Exception File file new File resource dbconfig properties Properties prop new Properties prop load new FileInputStream file String driver prop getProperty driverClassName if driver null String url prop getProperty url if url null equals url throw new Exception url cannot be null DBPoolUtil url url String username prop getProperty username if username null String password prop getProperty password if password null String init prop getProperty init if init null String max prop getProperty max if max null String min prop getProperty min if min null String increment prop getProperty increment if increment null String timewait prop getProperty timewait if timewait null public static Connection getConnection lock lock if pool isEmpty catch InterruptedException e 如果被中断 这里不做处理 if pool isEmpty lock unlock return null else Connection conn pool poll lock unlock return conn Connection conn pool poll lock unlock return conn private static void addConnection int n lock lock try Class forName driverClassName newInstance for int i 0 i DBPoolUtil MIN DBPoolUtil INCREMENT for int i 0 i pool size DBPoolUtil MIN i Connection conn pool poll if conn isClosed conn close current catch SQLException e e printStackTrace finally lock unlock public static int getCurrentCount return current static class DBKeeper implements Runnable Override public void run while true lock lock for int i 0 i pool size i Connection conn pool get i try if conn isClosed pool remove conn catch SQLException e pool remove conn e printStackTrace try timeInterval await 2000 TimeUnit MILLISECONDS catch InterruptedException e 被中断此处不做处理 e printStackTrace lock unlock 这样连接池有了 我们需要实现一个代理层和相应的 dao 和 service 的实现了 这里 为了保证安全性和在多个方法中获取同一个数据源 我们需要使用 threadlocal 所以 我们需要创建一个 threadlocal 的类 其中封装着当前线程所对应的 Connection 了 public class SessionUtil private static ThreadLocal local new ThreadLocal public Connection initialValue return null public static Connection currentConnection Connection conn local get try if conn null conn isClosed conn DBPoolUtil getConnection local set conn catch SQLException e e printStackTrace return local get 我们这里将一个线程所获取的连接取名为 session 了 之后就该到了封装代理写 DAO 的操作了 public Map getById Integer id Map result new HashMap Connection conn SessionUtil currentConnection try conn setAutoCommit false Statement stmt conn createStatement ResultSet rs stmt executeQuery select id name from user where id id if rs next result put id rs getString 1 result put name rs getString 2 mit catch SQLException e try conn rollback catch SQLException e1 e1 printStackTrace e printStackTrace return result Dao 的调用应该是没有问题 上面这种是最原始的方式 我们需要把开启事务 提交 事务回滚事务的方法移动到代理类中 这里我们在把 beforeAdvice afterAdvice 的一个简单的封装类再拷贝过来 public class ProxyUtil public static interface Advice void beforeMethod Method method Object args Object target void afterMethod Method method Object args Object target private static class MyProxy implements InvocationHandler private Advice advice private Object target public void setAdvice Advice advice this advice advice public void setTarget Object target this target target Override public Object invoke Object arg0 Method method Object args throws Throwable if advice null advice beforeMethod method args target Object result method invoke target args if advice null advice afterMethod method args target return result SuppressWarnings unchecked public static T newProxy T target SuppressWarnings rawtypes Class intf Advice advice if target null try throw new Exception target cannot be null catch Exception e e printStackTrace MyProxy proxy new MyProxy proxy setAdvice advice proxy setTarget target target T Proxy newProxyInstance target getClass getClassLoader intf proxy return target 这个没有什么好说的 也比较容易理解 public class TransactionAdvice implements Advice Override public void beforeMethod Method method Object args Object target Connection conn SessionUtil currentConnection try conn setAutoCommit false catch SQLException e e printStackTrace Override public void afterMethod Method method Object args Object target Connection conn SessionUtil currentConnection try mit catch SQLException e e printStackTrace 这里封装了开启事务和提交事务的方法 都是比较简单的 那么 DAO 中的相应的部分 就可以去掉了 测试类就变成了如下的样子 public class Test public static void main String args Advice advice new TransactionAdvice UserDAO dao new UserDAOImpl UserDAO proxy ProxyUtil newProxy dao new Class UserDAO class advice System out println proxy getById 2 运行的结果 id 2 name wangh2 我们发现是正确的 这里还有一件事没解决 就是出了异常要自动回滚 能捕捉异常的地方只能在代理中 捕捉 可是在底层显示的异常怎么处理 我们都知道 如果出了异常 底层 catch 掉 的话顶层就没法捕捉了 若是底层抛出异常的话 则需要每个方法上都带着 throws 好麻烦 似乎没有解决方案了 不过有一类异常底层抛出后顶层不是必须要捕捉的 那就是运行时异常 所以我们采用的方案就是在底层碰到异常后全部捕捉 之后抛出 运行时异常 这样就能在代理中捕捉异常并回滚了 为了捕捉异常在代理类中 advice 接口添加了 exception 方法 同时在 invoke 方法 中捕捉异常 修改如下 public class ProxyUtil public static interface Advice void beforeMethod Method method Object args Object target void afterMethod Method method Object args Object target void exception Method method Object args Object target private static class MyProxy implements InvocationHandler private Advice advice private Object target public void setAdvice Advice advice this advice advice public void setTarget Object target this target target Override public Object invoke Object arg0 Method method Object args throws Throwable Object result null try if advice null advice beforeMethod method args target result method invoke target args if advice null advice afterMethod method args target catch Exception e if advice null advice exception method args target e printStackTrace return result SuppressWarnings unchecked public static T newProxy T target SuppressWarnings rawtypes Class intf Advice advice if target null try throw new Exception target cannot be null catch Exception e e printStackTrace MyProxy proxy new MyProxy proxy setAdvice advice proxy setTarget target target T Proxy newProxyInstance target getClass getClassLoader intf proxy return target 修改 dao 中的方法 并添加了一个 update 方法 代码如下 public class UserDAOImpl implements UserDAO Override public Map getById Integer id Map result new HashMap Connection conn SessionUtil currentConnection try Statement stmt conn createStatement ResultSet rs stmt executeQuery select id name from user where id id if rs next result put id rs getString 1 result put name rs getString 2 catch SQLException e throw new RuntimeException getById exception return result Override public void update Map user Connection conn SessionUtil currentConnection try Statement stmt conn createStatement String sql update user set name user get name where id user get id stmt executeUpdate sql catch SQLException e throw new RuntimeException update exception 在上面的代码我在测试的时候出现了下面这个异常 java lang reflect InvocationTargetException at sun reflect NativeMethodAccessorImpl invoke0 Native Method at sun reflect NativeMethodAccessorImpl invoke NativeMethodAccessorImpl j ava 57 at sun reflect DelegatingMethodAccessorImpl invoke DelegatingMethodAcce ssorImpl java 43 at java lang reflect Method invoke Method java 601 at cn spring mydb util ProxyUtil MyProxy invoke ProxyUtil java 32 at com sun proxy Proxy0 update Unknown Source at cn spring mydb test Test main Test java 18 Caused by java lang RuntimeException update exception at cn spring mydb dao impl UserDAOImpl update UserDAOImpl java 41 7 more 一下就蒙了有没有 异常抛出的有问题 不能看到提示信息 之后我把上一个异常直 接传到了下一个异常里面 也就是在抛出异常的时候把 getById exception 和 update exception 换成了e 错误信息就很明显了 Caused by java lang RuntimeException com mysql jdbc exceptions jdbc4 MySQLSyntaxErrorException Unknown column songbk1 in field list at cn spring mydb dao impl UserDAOImpl update UserDAOImpl java 41 7 more Caused by com mysql jdbc exceptions jdbc4 MySQLSyntaxErrorException Unknown column songbk1 in field list 我们就会猜测到是我们的 sql 语句出问题了 通过检查发现我们没有加引号 修改完成后我们发现数据库的值修改了 其实到现在我们都没有提出我们这么设计问题出在哪里 该怎么修改我们的代码 我们这里假设现在有两个更新的需求 要求两个更新同时成功或失败 我们这里是不 能做到的 我们修改一下代码让第二次更新失败回滚 第一次成功 当 id 为 2 时 更新将会失败回滚 其他的会成功 测试代码如下 运行的测试抛出的异常如下 我们发现数据库中的结果第一个更新了 这不是我们要的 如何能做到我们的需求呢 我们问题出在什么地方的 我们简单分析一下 我们在第一次更新完成后就提交了 第二次更新的出现异常回滚了 但是不能回滚到 提交的事务的开始去 所以我们就需要设计我们事务什么时候提交怎么提交 感觉要实现这个功能要写好多代码 还打算模拟一个简单的 spring 的事务管理来着 好麻烦 这里还要写一个事务管理器 一套反射用的简单工具 先写反射吧 我的设计思路如下 当遇到 OutTransaction 的时候我们就开启事务 并将当前的方 法保存起来 当最后提交的时候我们再次检查当前的方法 若为与开启事务的方法一 致我们就提交事务 否则的话我们就采用默认的数据库事务的处理方式 思路比较简单 这样就简单修改了一下 advice 的实现并添加了 TransactionManager 类 这个类就是一个简单的 Bean 看一下代码吧 public class TransactionAdvice implements Advice Override public void beforeMethod Method method Object args Object target SessionManager manager TransactionManager getSessionManager Method methods target getClass getMethods Method m null for Method t methods if t getName equals method getName m t OutTransaction anno m getAnnotation OutTransaction class if manager isHasTransaction try Connection conn manager getConn conn setAutoCommit false manager setHasTransaction true catch SQLException e e printStackTrace Override public void afterMethod Method method Object args Object target SessionManager manager TransactionManager getSessionManager if manager isHasTransaction catch SQLException e e printStackTrace Override public void exception Method method Object args Object target SessionManager manager TransactionManager getSessionManager if manager isHasTransaction try manager getConn rollback catch SQLException e e printStackTrace public class SessionManager private Connection conn private boolean hasTransaction private Method startMethod public SessionManager Connection conn boolean hasTransaction super this conn conn this hasTransaction hasTransaction public Connection getConn return conn public void setConn Connection conn this conn conn public boolean isHasTransaction return hasTransaction public void setHasT
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 基础强化自考专业(市场营销学)试题带答案(基础题)
- 2025年度精密仪器委托生产合作协议
- 2025年旅游景区场地租赁合同补充协议范本
- 2025成都个人车辆租赁合同示范文本
- 2025年度水电安装工程结算与支付合同范本
- 2025版互联网+教育项目投资协议书
- 2025版商用净水设备租赁与环保责任保险合同
- 2025大厦环保材料装修工程招标合同
- 2025版高尔夫球场租赁及配套设施使用合同
- 2025版人力资源和社会保障局0001号企业退休人员管理服务合同
- 主题阅读1:大自然的文字
- 电梯周期日常维护保养项目表
- 浙江省火力发电企业名录2019最新版
- 国际贸易理论与实务ppt课件(完整版)
- GB∕T 6546-2021 瓦楞纸板边压强度的测定
- 历史选择性必修1 国家制度与社会治理(思考点学思之窗问题探究)参考答案
- 学前儿童发展心理学(第3版-张永红)教学课件1754
- 中职《机械基础》全套课件(完整版)
- 保监会保险机构高级管理人员任职资格考试题库(附标准规范答案)
- 部编人教版九年级语文上册教学计划及教学进度表
- 干法——稻盛和夫
评论
0/150
提交评论