银行转账业务是一个典型的事务,我们先介绍基本概念,这样后面的代码看起来就比较容易理解了。

一、事务处理

“事务处理”这个名字的概念,相信大多数朋友都有所了解。我们看一下维基百科中关于“事务处理”的定义:

“在计算机科学中,事务是无法被分割的操作,事务处理就是被分割为个体的信息处理。事务必须作为一个完整的单元成功或失败,不可能存在部分完成的事务。”

看着是不是有点绕?

好,我们换个视角看“事务处理”,从数据库视角。

在数据库操作中,事务是由一条或者多条SQL语句组成的一个工作单元。只有当事中的所有操作都正常完成,整个事务才被提交到数据库,如果意向操作没有完成,则整个事务被撤销。

例如zhangsan给lisi转200元钱,对应的SQL是;

UPDATE ACCOUNT set MONEY=MONEY-200 WHERE NAME='zhangsan';

UPDATE ACCOUNT set MONEY=MONEY+200 WHERE NAME='lisi';

转钱这件事,就是一个事务处理。所以,事务是以数据为核心的,而数据是存在关系型数据库上的。因此,应用如何操作数据库就成为事务处理中的关键。

Java应用操作数据库的模块是JDBC。

二、JDBC

JBDC的全称为:Java Database Connectivity,是一套用于执行SQL语句的Java API。应用程序可以通过这套API连接到关系型数据库,并使用SQL语句来完成对数据库的查询、更新、删除工作。

我们先看一下JDBC API的内容。

在下面的三色框中,中间部分是JDBC常用的API。

第一部分是DataSource接口的实现;

第三名部分是DBUtils工具库;

JDBC的实现细节如下图所示,包含三大部分:

(1)JDBC驱动管理器:负责注册特定的JDBC驱动器,通过java.sql.DeviceManager类实现。

(2)Java驱动器API;其中最主要的接口是:java.sql.Driver接口。

(3)JDBC驱动器:一种数据库驱动,由数据库厂商创建。JDBC启动器实现了JDBC驱动器API,负责与特定的数据库链接,以及处理通信细节。

一个完整的JDBC分为六大步骤:

通过DriverManager加载并注册DB驱动

通过DeiverManager获取数据库连接(DB connection)

通过Connection对象获取Statement对象。

使用Statement执行SQL语句,生成结果集ResultSet。

操作结果集,取出结果。

释放数据库资源

需要注意的是:由于Driver类的源码中,已经在静态代码块中完成了数据库驱动的注册,所有为了避免数据库驱动被重复注册,主要在程序中加载驱动类即可。

下面我们查看一个完成的JDBC示例。下图中的标号,就对应上文的6个步骤。我们看到第六步释放了Statement和Connection资源。

三、JDBC批处理

在实际开发中,经常需要向数据库发送多条SQL语句。这时如果逐条执行这些SQL语句,效率会很低。为此,JDBC提供了批处理机制,可以同时执行多条SQL语句。Statement和PreparedStatement都实现了批处理。

PreparedStatement是Statement的一个子类。PreparedStatement对象可以对SQL语句进行预编译。当相同的SQL语句再次执行时,数据库只需使用缓冲区的数据,而不需要对SQL语句再次编译,从而有效提高了数据的访问效率。

我们查看Statement加载和执行批处理的代码,如下图红框所示:

我们再看通过PreparedStatement加载和执行批处理的代码:

很明显,PreparedStatement的方式更灵活。

需要注意的是:Statement和PreparedStatement的executeBactch()方法的范围值都是int[]类型的,所以能够进行批处理的SQL语句必须是INSERT、UPDATE、DELETE等返回值为int类型的SQK语句。

四、JDBC如何处理事务

针对JDBC处理事务的操作,在Connection接口中,提供了三个相关的方法,具体如下:

1. setAutoCommit(boolean autoCommit):设置是否自动提交事务。

2. commit(): 提交事务

3.rollback(): 撤销事务。

接下来,我们通过一个案例展示J如何通过JDBC进行事务处理。

首先看一个工具类:JDBCUtils。

这个工具类避免我们每次操作都要书写:加载数据库驱动、检查数据连接以及关闭数据的连接的代码:

接下来,新建一个类,用于在两个账号之间转账。

从下图可以看出,本类使用了工具类JDBCUtils中的方法访问数据库。

JDBC关闭事务自动提交、提交事务、回滚事务的位置,我用红框标出来了,比较好理解。

五、集中式事务与分布式事务的本质

事务(Transaction )的核心是操作数据。事务必须要满足ACID的标准。而集中式架构,是很容易保证事务的ACID的。ACID中比较难实现的是C,实时一致性。集中式事务实现的方式就是传统应用架构如JavaEE+关系型数据库。我们也管这种事务叫本地事务。这种事务可以实现强一致性。

分布式架构,或者说分布式事务。分布式事务比较难以实现实时一致性,因此ACID不再适用。业内Bianc便提出了CAP和BASE等理论。所以说,集中式和分布式事务的根本区别是在于维护数据的强一致还是最终一致。

CAP 理论指出:无法设计一种分布式协议,使得同时完全具备CAP 三个属性,即1)该种协议 下的副本始终是强一致性,2)服务始终是可用的,3)协议可以容忍任何网络分区异常;分布式系统 协议只能在CAP 这三者间所有折中。

CAP 理论的定义很简单,CAP 三个字母分别代表了分布式系统中三个相互矛盾的属性:

Consistency (一致性):CAP 理论中的副本一致性特指强一致性;

Availiablity(可用性):指系统在出现异常时已经可以提供服务;

Tolerance to the partition of network (分区容忍):指系统可以对网络分区。这种异常情 况进行容错处理;

CAP 理论 的意义就在于明确提出了不要去妄图设计一种对CAP 三大属性都完全拥有的完美系统,因为这种系 统在理论上就已经被证明不存在。

BASE理论指的是:

Basically Available(基本可用)

Soft state(软状态)

Eventually consistent(最终一致性)

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

也就是说:在集中式事务中,我们可以实现完美的ACID。在分布式事务中,在得到分布式的收益下,我们也需要接受不完美。前文我们也提到过,事务处理中,比较难实现的是实时一致性,因此分布式系统的核心理念是:保证数据的最终一致性即可。

在CAP/Base理论的前提下,分布式事务的实现方式,主要分为2PC、补偿事务、TCC。2PC通常是事件驱动架构,补偿事务、TCC通常是通过代码实现,实现难度较高。因此,2PC应用更为广泛。

传统单机应用一般都会使用一个关系型数据库,好处是应用可以使用 ACID transactions。为保证一致性我们只需要:开始一个事务,改变(插入,删除,更新)很多行,然后提交事务(如果有异常时回滚事务)。

随着组织规模不断扩大,业务量不断增长,单机应用和数据库已经不足以支持庞大的业务量和数据量,这个时候需要对应用和数据库进行拆分,就出现了一个应用需要同时访问两个或两个以上的数据库情况。开始我们用分布式事务来保证一致性,也就是我们常说的两阶段提交协议(2PC)。

两阶段提交顾名思义它分成两个阶段,先由一方进行提议(propose)并收集其他节点的反馈(vote),再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator),其他参与决议节点称为参与者(participants, 或cohorts)。

在阶段1中,coordinator发起一个提议,分别问询各participant是否接受。

在阶段2中,coordinator根据participant的反馈,提交或中止事务,如果participant全部同意则提交,只要有一个participant不同意就中止。

近年来兴起的微服务,也大量使用2PC,即事件驱动的分布式架构。事件驱动架构中,我们会使用到消息中间件如AMQ、Kafka等。

关于AMQ和Kafka容器化实现,可以参考:

https://www.ibm.com/developerworks/cn/cloud/library/cl-lo-building-distributed-message-platform-based-on-openshift/index.html

关于分布式的技术细节,请参考:

六、数据库连接池

在前文中,我们提到了Java应用通过JDBC操作数据库,共分为六大步骤,其中重要的一个环节就是创建访问数据库连接(Connection)。

我们知道,每次创建和断开Connection对象都会消耗一定的时间和I/O资源。为了避免频繁地创建数据库连接,工程师么提出了数据库连接池技术。他负责分配、管理是释放数据库连接,他允许应用程序复用现有的数据库连接,而不是重新建立,如下图所示:

数据库连接池在初始化时,会创建一定数量的数据库连接放到连接池中,当应用程序访问DB时,不是直接创建Connection,而是先从连接池申请Connection(如果池子中没有则新建Conection),使用完毕后,连接池会自动回收。

JDBC提供javax.sql.DataSource接口用于获取DB的Connection。获取Connection有两个方法:

Connection getConenction()

Connection getConnection(String username, String password)

这两个重载的方法都可以获取Connection对象,第一个是通过无参方式创建与DB的连接,第二种方法是通过传入登录信息等内容创建与数据库的连接。

我们通常把实现了javax.sql.DataSource接口的类称为数据源,而数据源中包含数据库连接池。我们用一张形象的图说明数据、数据库、数据源的关系:

现在最常用的数据源有两种:

DBCP

C3P0

七、DBCP

DBCP全称是:DataBase Connection Pool。它是Apache下的开源项目,也是Tomcat使用的连接池组件。

单独使用DBCP的话,需要单独导入一下两个包:

1.commons-dbcp.jar包:DBCP数据源的实现包,包含操作数据库连接信息和数据库连接池初始化信息的方法,并实现了javax.sql.DataSource接口的getConnection()方法。

2.commons-pool.jar:为commons-dbcp.jar中的方法提供支持。

commons-dbcp.jar中包含两个核心的类:BasicDataSource、BasicSourceFactory。

BasicDataSource是BasicSource接口的实现类,包括设置数据源对象的方法:

BasicDataSource可以直接创建数据源对象。我们通过代码进行说明。

我们查看下图红框的标识:我们导入了BasicDataSource类、通过BasicDataSource()方法创建数据源实例bds、通过bds加载mysql的驱动、通过bds设置数据库URL/用户名/密码、将bds赋值ds、通过ds获取数据库连接。

BasicSourceFactory工厂类读取配置文件,然后创建数据对象,然后获取连接对象。

我们先查看配置文件:

我们查看代码,关注红框的标识:

我们导入Properities/DataSource/BasicDataSourceFactory类、创建配置文件对象prop、将配置文件内容加载到prop中、以prop为参数通过BasicSourceFactory创建数据源对象ds、通过ds创建数据库连接对象conn、获取数据库连接信息metaData、通过metaData打印相关信息。

八、C3P0

C3P0是最流行的开源数据库连接池,实现了DataSource数据源接口,支持JDBC2和JDBC3的标准规范。开源框架Hibernate和Spring都是使用C3P0数据源。

C3P0的核心类是ComboPooledDataSource,它是DataSource接口类的实现。

C3P0的核心类ComboPooledDataSource的常用方法如下:

ComboPooledDataSource有两个构造方法:

无参构造方法:ComboPooledDataSource()

有参构造方法:ComboPooledDataSource(String configName)

我们首先利用无参构造方法,展示通过ComboPooledDataSource类直接创建数据源对象:

代码输出结果如下所示:

接下来,我们利用有参构造方法,读取c3p0-config.xml的配置文件(必须是这个名字,而且放到该项目的src目录下),创建数据源对象,然后获取数据库连接对象。

我们首先查看参数文件内容:

我们查看代码:

代码执行结果如下:

九、DButils工具类

为了简单使用JDBC,Apache提供了一个工具类库commons-dbutils。它是操作数据库的一个组件,实现了对JDBC的封装,简化了代码工作量。

commons-dbutils包含两个核心类和一个接口:

两个核心类:

org.apache.commons.dbutils.DButils:主要用于关闭连接、装载JDBC驱动程序之类的常规工作提供方法。

org.apache.commons.dbutils.QueryRunner:主要用于执行增删改查操作。

一个接口:

org.apache.commons.dbutils.ResultSetHandler:主要用于处理ResultSet结果集。

十、DBuTils处理事务

我们如果使用连接池访问DB,就很难保证一个事务只用一个connection。但在不同的Connection中的数据无法共享。这就需要借助ThreadLocal类来实现在一个线程里记录变量。

我们可以生成一个Connection放在线程里,只要这个线程中的任何对象都可以共享这个链接,当线程结束后就删除这个Connection。

接下来我们模拟银行间的转账事务,来理解DBUtils的作用。

首先创建数据库表acoount作为账务记录表,插入数据:

查看account数据库表,内容如下,我们看到a账户有1000、B账户有1000:

创建实体类Account,通过以下代码我们可以看出这个实体类包含对Account表操作的方法(get/set)

创建类JDBCUtils这个类一共有三大段代码,该类封装了创建Connection、开启事务关闭事务的方法:

本类代码第二段定义了开始开启事务和提交事务的方法。我们看到开启事务前必须要先获取Connection。我们看到提交事务获取Connection是从threadLocal获取的。

第三段代码实现了回滚事务和关闭事务的方法。我们看到回滚和关闭事务前的Connection是从threadLocal获取的。

创建AccountDao类,该类封装了转账所需的数据库操作,包括查询用户、转入、转出操作,代码实现如下,下面代码实现了查询和Update SQL,下面代码导入了dbutils类:

创建Business类,这个类包含转账过程的逻辑方法,导入了封装事务操作的JDBCUtils类和封装数据库操作的AccountDao类,完成后转账操作,我们可以看到下面代码定义了一个名为Transfer的静态方法:

调用transfer方法,并注入参数,如下图所示:

执行结果如下,我们看到转账成功:

参考文献:

《Java Web程序开发入门》-清华大学出版社

java模仿银行账务业务_一个银行转账业务模型分析:大魏Java记5-7相关推荐

  1. java 井字棋 人机_一个井字棋tictactoe游戏的java实现 | Soo Smart!

    这是一个井字棋游戏的java实现.摘录于stackoverflow. 游戏规则很简单,只要一方棋子在水平线,垂直线或者对角线任意一条线上排列成功即为获胜. 作者原先的代码存在着一些问题: 代码如下: ...

  2. java模仿银行账务业务_Java基础案例 - 模拟银行存取款业务

    博学谷--让IT教学更简单,让IT学习更有效 模拟银行存取款业务 编写一个Java应用程序,模拟网上银行登录及存取款业务.登录时需判断银行卡号和银行卡密码,当输入的卡号和密码都正确时,登录成功,提示当 ...

  3. 【银行架构day1】一个银行的信息系统架构是什么样子

    这是<银行信息化丛书>读书笔记2.做企业架构意义很大,架构的治理更重要,因为要让架构发挥作用.企业架构开发.治理内容很多,框架理论体系就要很长的学习周期和实际经验.本篇是作者的实践总结,比 ...

  4. JAVA程序中怎么看线程的个数_一个文件中有10000个数,用Java实现一个多线程程序将这...

    18 推荐 运行结果: 编辑于 2015-07-16 17:20:57 回复(11) 12 自己重写了一下推荐答案,加了些注释方便理解 package threadpackage; import ja ...

  5. java写病毒程序代码_一个用JAVA写的清除EXE病毒文件的程序(转)

    Clear.java 这是一个主类,主要是负责运行程序和参数检查,不是核心 程序代码: import java.io.*; public class Clear{ public static void ...

  6. java 多态判断非空_跳槽涨薪季面试题之java基础(一)

    点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐] 为迎接金九银十跳槽涨薪季,小编汇总了java精编版面试题,大概从java基础.java8特性.多线程.spring.springboot. ...

  7. Java程序设计当中包的使用_【学习笔记】 唐大仕—Java程序设计 第4讲 类、包和接口之4.2 类的继承...

    [学习笔记] 唐大仕-Java程序设计 第4讲 类.包和接口之4.2 类的继承 super的使用 1.使用super访问父类的域和方法 注意:正是由于继承,使用this可以访问父类的域和方法.但是有时 ...

  8. java.线程池 线程数_如何在线程“ main”中修复异常java.lang.NoClassDefFoundError:Java中的org / slf4j / LoggerFactory...

    java.线程池 线程数 此错误表示您的代码或您在应用程序中使用的任何外部库都在使用SLF4J库 (一个开放源代码日志记录库),但无法找到所需的JAR文件,例如slf4j-api-1.7.2.jar因 ...

  9. 新手学java还是python知乎_编程初学者应该先学C++、Java还是Python?

    最近,看到这样的一个话题:"打算自学编程,但是不知道该先学哪门语言入门?编程初学者应该先学C++.Java还是Python?",作为一个新手,应该学什么语言入门比较好呢?相信这是困 ...

  10. java二级考试真题_计算机等级考试真题2(JAVA)

    1. D (A)类属于JAVA语言的引用数据类型. (B)接口属于JAVA语言的引用数据类型. (C)数组属于JAVA语言的引用数据类型. (D)double不属于JAVA语言的引用数据类型. 2. ...

最新文章

  1. 和我一起学 Selenium WebDriver
  2. mysql 默认时间字段 1067,mysql替datetime类型字段设置默认值default
  3. 面试技巧:16个经典面试问题回答思路(转)
  4. [bzoj1086][SCOI2005]王室联邦
  5. VB在XP/2K 任务管理器的进程列表中隐藏当前进程
  6. 如果你在aws ec2上安装php7x 的时候提示 libwebp 错误,可以试一下下面这个代码...
  7. Maven Oracle JDBC
  8. 【BearChild】
  9. 关于统计分析软件Spss统计个案数和实际数据的个案数不一致问题
  10. ajax无刷新验证用户名
  11. 51单片机 (九)定时器
  12. tnl 网络游戏架构底层深入分析
  13. 笔记本计算机的清洁保养知识,笔记本电脑保养小知识
  14. 【26天高效学习Java编程】Day22:Java中的属性集-缓冲流-转换流-序列化流详解
  15. win10家庭版远程桌面控制解决
  16. charles抓包一直返回unkown的解决方法
  17. 千米与英里转化的c语言程序,C ++程序将公里/小时转换为英里/小时,反之亦然...
  18. 虚拟现实在医学领域的应用和发展前景
  19. 基于动态径向基函数(DRBF)代理模型的优化策略
  20. 蓝桥杯练习(第二十三天)

热门文章

  1. 【优化调度】基于matlab粒子群算法求解燃机冷热电优化联供问题【含Matlab源码 330期】
  2. 【CVRP】基于matlab遗传算法求解带容量的车辆路径规划问题【含Matlab源码 162期】
  3. sql取逗号隔开的值_想学好Excel函数?你得先搞懂函数公式中逗号和星号的区别...
  4. json输出count如何计算_基于 Kafka + Flink + Redis 的电商大屏实时计算案例
  5. 深度学习行人检测简介_深度学习简介
  6. kafka topic:1_Topic️主题建模:超越令牌输出
  7. 怎么升级Android Studio版本,Android studio 2 版本升级 Android studio 3 版本注意事项
  8. 关于卷积,池化,前向传播,反向传播,全连接层,通道数的一些概念
  9. 经典傅里叶算法小集合 附完整c代码
  10. Elasticsearch的索引模块(正排索引、倒排索引、索引分析模块Analyzer、索引和搜索、停用词、中文分词器)...