本文探讨JDBC需要解决的问题及如何解决和设计的,包括:

  • JDBC要解决的问题
  • 数据库事务
  • JDBC的架构设计
  • JDBC代码注意点
  • Spring是如何处理事务
  • 什么是事务的传播特性
  • Redis事务与数据库事务的区别

问题

在《架构风格:万金油CS与分层》中提到,三层架构一般分为:

  • Presentation tier 表现层
  • Logic tier 业务逻辑层
  • Data access tier 数据访问层

那业务逻辑层与数据访问层是如何通信的呢?(假设数据访问层使用的是数据库进行数据持久化,这也是目前比较普遍的做法)

单看「业务逻辑层」和「数据访问层」,这是明显的CS风格:

  • 「业务逻辑层」是Client
  • 「数据访问层」是Server

CS风格的约束如下:

  • Server组件提供了一组服务,并监听对这些服务的请求(这里就是数据库服务),
  • Client组件通过一个连接器将请求发送到Server,希望执行一个服务(执行sql),
  • Server可以拒绝这个请求,也可以执行这个请求并将响应发送回Client(sql执行结果)。

除了上面的约束外,针对数据库访问而言,还应该包括如下约束:

  • 兼容各种数据库。不能每个数据库都有一套接口,这样会导致业务逻辑层与数据访问层的紧耦合。
  • 事务支持。需要支持数据库事务,否则数据会出现混乱。

设计

针对上面的约束,可以抽象出四个组件:

  • 连接组件:对与数据访问层的连接的抽象
  • 执行组件:执行对数据库的操作的抽象
  • 结果组件:对返回操作后的结果的抽象
  • 适配组件:适配各个数据库,就是对各个数据库的抽象

这就是JDBC的结构:

  • Connection:连接组件。提供统一的API建立数据库连接。
  • Statement:执行组件。针对各种执行有不同的实现,Statement,PreparedStatement,CallableStatement。提供统一的API执行sql。
  • ResultSet:结果组件,对结果的抽象,提供统一的API操作结果数据。
  • Driver:适配组件,对各个不同的数据库操作进行适配,统一为一套对外一致的API。各个Driver统一由DriverManager进行管理。

基于上面的设计,也就统一了JDBC操作数据库的流程:

  • 注册驱动
  • 建立连接
  • 创建执行对象
  • 执行语句
  • 处理结果
  • 释放资源

如果你google一下JDBC代码,一般会得到类似下面的代码:

 // 1.注册驱动
Class.forName("com.mysql.jdbc.Driver"); // 这一行代码实际早就不需要了!!!// 2.建立连接Connection conn = DriverManager.getConnection(url, user, password);// 3.创建执行对象Statement st = conn.createStatement();// 4.执行语句ResultSet rs = st.executeQuery("select * from table1");// 5.处理结果while (rs.next()) { ......}// 6.释放资源rs.close();st.close();conn.close();

实际上,早就已经不需要第一步「注册驱动」的代码了!在google第一页的十条结果中,只有一条结果提到了此问题!

你可以尝试一下,将第一行代码注释掉,程序还是能正常的执行。原因就是现在的JDBC驱动一般都使用了SPI来自动的注册驱动了,不再需要手动的去注册了。

SPI你可以认为是Java原生提供的的依赖注入!以mysql的驱动为例:

  • 在mysql的驱动包META-INF目录下,有一个services目录
  • 里面有个文件,叫java.sql.Driver。里面的内容为
  • com.mysql.jdbc.Driver
  • com.mysql.fabric.jdbc.FabricMySQLDriver
  • 名称是接口,文件里的内容是实现
  • JVM会根据上面的配置,去找对应的类,并把它加载进去
  • DriverManager通过ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);这行代码加载驱动,将其加到驱动列表中进行管理。实现注册驱动的逻辑

性能优化

从上面的代码可以看到,每次进行sql查询的时候,都需要创建连接。我们都知道创建连接是个很耗时的操作,如何能降低创建连接的开销呢?

在《非功能性约束之性能(1)-性能银弹:缓存》中,已经给出答案了。就是引入「缓存」!

这里的「缓存」就是连接池!即:

  • 先创建一批连接,一般是在应用启动时
  • 当需要连接的时候,直接从连接池中获取
  • 操作完了之后,将连接再还到连接池中

如果你了解IO多路复用或EDA架构风格,那么你可能会有个疑问。为什么数据库连接不使用IO多路复用技术呢?

IO多路复用或EDA架构风格可以参考《EDA风格与Reactor模式》!

其实在《EDA风格与Reactor模式》里已经给出了答案:如果请求数据量太大或处理时间很长!即使是主从Reactor模型,线程池也会被耗尽!耗尽后,导致后续的请求积压。

关系数据库的底层原理决定了其性能瓶颈在于磁盘读写,也就是说如果使用IO多路复用,瓶颈会出现在执行线程(数据库查询的磁盘操作),而不在IO通信。使用IO多路复用并不会提高数据库的访问,而且编程模型要复杂得多。

而redis能使用IO多路复用的原因是它主要是内存操作,速度要比磁盘操作快得多,瓶颈更可能出在IO通信上。

什么是事务

JDBC相关的另一个大的主题就是事务!

满足ACID四个特性的数据库操作,称为数据库事务

这里的事务指数据库本地事务,不包括分布式事务,分布式事务后续讨论!

四个特性包括:

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中

其中,隔离性又分为四种隔离级别,隔离级别从严格到宽松依次为:

  • 可串行性(Serializability):所有的事务依次逐个执行。这是最严格的的隔离级别,实际就是对事务进行排队,一个一个的执行,保证了每个事务的独立性,但是严重的影响了性能。如果有一个事务的执行耗时很长,那么后面的事务就全部等待。此隔离级别基本不会使用。
  • 可重复读取(Repeatable Read):一个事务在整个过程中可以多次重复执行某个查询。相对宽松的隔离级别,即在一个事务中某个查询,多次执行时,所得到的数据内容是一样的,但是得到的数据条数不一定是一样的,即所谓的幻读。
  • 已提交读(Read Committed):一个事务只能读取另一个事务已经提交的数据。更宽松的隔离级别,此隔离级别可能引起幻读和不可重复读。这是大部分数据库的默认隔离级别。
  • 未提交读(Read Uncommitted):一个事务可以读取另一个事务修改但还没有提交的数据。最宽松的隔离级别,此隔离级别可能引起幻读,不可重复读,脏读。

  • 幻读(行):一个事务中多次读取,取到另外一个事务已经提交的新增数据(表级)
  • 不可重复读:多次读取同一个数据返回结果有所不同(行级)
  • 脏读:一个事务读取到另外一个事务未提交的更新的数据(行级)

事务在代码中的体现就是conn.setAutoCommit(false),conn.commit(),conn.rollback():

......
// 2.建立连接Connection conn = DriverManager.getConnection(url, user, password);// 关闭自动提交,开启事务conn.setAutoCommit(false);Statement st = conn.createStatement();try { ResultSet rs = st.executeQuery("select * from table1"); // 提交事务 conn.commit();} catch(Exception e) { conn.rollback(); // 出现异常进行事务回滚}......

Spring中的事务

Spring通过Transactional注解来简化了事务的管理。

Transactional对事务的处理流程如下:

  • 开启事务
  • 将conn对象保存到ThreadLocal中
  • 执行sql。「这里就是你写代码的地方」
  • 从ThreadLocal中取回conn对象
  • 提交事务

Transactional的属性包括:

  • isolation:配置事务的隔离级别
  • readOnly:配置只读。默认false。需要事务才能生效
  • rollbackFor:配置事务回滚。默认在遇到RuntimeException时进行回滚
  • propagation:配置事务的传播特性

事务的传播特性:

  • propagation=Propagation.NOT_SUPPORTED:针对某个方法不开启事务
  • propagation=Propagation.REQUIRED:如果有事务,那么加入事务,没有的话新创建一个, 默认的事务支持
  • propagation=Propagation.REQUIREDS_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
  • propagation=Propagation.MANDATORY:必须在一个已有的事务中执行,否则抛出异常
  • propagation=Propagation.NEVER:不能在一个事务中执行,就是当前必须没有事务,否则抛出异常
  • propagation=Propagation.SUPPORTS:其他bean调用这个方法,如果在其他bean中声明了事务,就是用事务。没有声明,就不用事务。
  • propagation=Propagation.NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动的事务,则按照REQUIRED属性执行,它使用一个单独的事务
  • propagation=Propagation.REQUIRED,readOnly=true:只读,不能更新,删除
  • propagation=Propagation.REQUIRED,timeout=30:超时30秒
  • propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT:数据库隔离级别

事务总结

redis的事务

从表面看redis事务和jdbc事务很类似,都是下面的流程:

  • 开始事务「redis通过MULTI指令执行」
  • 输入需要执行的命令
  • 执行事务「redis通过EXEC指令执行」

实际上有本质的区别。redis的事务实际上就是指令的批量执行,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

总结

本文通过业务逻辑层与数据持久层的通信约束,来阐述JDBC的设计及总结本地事务的相关知识点,同时与redis做了一个简单的比较。

JDBC的架构设计的更多相关文章

  1. 【转】Flume(NG)架构设计要点及配置实践

    Flume(NG)架构设计要点及配置实践   Flume NG是一个分布式.可靠.可用的系统,它能够将不同数据源的海量日志数据进行高效收集.聚合.移动,最后存储到一个中心化数据存储系统中.由原来的Fl ...

  2. MyBatis架构设计及源代码分析系列(一):MyBatis架构

    如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...

  3. 《深入了解mybatis原则》 MyBatis架构设计和案例研究

    MyBatis这是现在很流行ORM框架,这是非常强大.事实上现却比較简单.优雅. 本文主要讲述MyBatis的架构设计思路,而且讨论MyBatis的几个核心部件.然后结合一个select查询实例.深入 ...

  4. 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

    作者博客:http://blog.csdn.net/u010349169/article/category/2309433 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简 ...

  5. 旧调重弹Hibernate与Ibatis区别——深入架构设计

    对于一个粗学者而言一言概况就是:ibatis非常简单易学,hibernate相对较复杂,门槛较高.  但是,hibernate对数据库结构提供了较为完整的封装,hibernate的o/r mappin ...

  6. MySql(十四):MySql架构设计——可扩展性设计之数据切分

    一.前言 通过 MySQL Replication 功能所实现的扩展总是会受到数据库大小的限制,一旦数据库过于庞大,尤其是当写入过于频繁,很难由一台主机支撑的时候,我们还是会面临到扩展瓶颈.这时候,我 ...

  7. MySQL性能调优与架构设计——第 14 章 可扩展性设计之数据切分

    第 14 章 可扩展性设计之数据切分 前言 通过 MySQL Replication 功能所实现的扩展总是会受到数据库大小的限制,一旦数据库过于庞大,尤其是当写入过于频繁,很难由一台主机支撑的时候,我 ...

  8. 架构设计:系统间通信(20)——MQ:消息协议(下)

    (接上文<架构设计:系统间通信(19)--MQ:消息协议(上)>) 上篇文章中我们重点讨论了"协议"的重要性.并为各位读者介绍了Stomp协议和XMPP协议. 这两种协 ...

  9. 《深入理解mybatis原理1》 MyBatis的架构设计以及实例分析

    <深入理解mybatis原理> MyBatis的架构设计以及实例分析 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构 ...

随机推荐

  1. XSSFWorkbook

    支持2007以后的 此类与HSSFWorkbook(支持2007之前) 类似,读取文件时把全部的内容都存放到内存中,关闭输入流后. 内存与硬盘完全是毫无关系的两份数据,所有的操作都是对内存的操作,最后 ...

  2. CF习题集一

    CF习题集一 一.CF915E Physical Education Lessons 题目描述 \(Alex\)高中毕业了,他现在是大学新生.虽然他学习编程,但他还是要上体育课,这对他来说完全是一个意 ...

  3. java进阶(3)--接口

    一.基本概念 1.接口为引用数据类型,编译后也是class字节码文件 2.接口是完全抽象的,(抽象类是半抽象的),属于特殊的抽象类 3.接口定义方法:[修饰符列表]interface 接口名{} 4. ...

  4. 实验02——java两个数交换的三种解决方案

    package cn.tedu.demo;/** * @author 赵瑞鑫      E-mail:1922250303@qq.com * @version 1.0* @创建时间:2020年7月16 ...

  5. github开源文章生成pdf

    最近需要研究ELK,然后在网上发现了有本书写的不错,然后搜到是在 github 上开源过的.这本书的时间有点久了,就想通过源码自己来生成一个 pdf 我使用的是 ubuntu 系统 step1:安装 ...

  6. Docker初探之Windows篇

    一.什么是Docker? Docker是一个开源的应用容器引擎,可以轻松地为任何应用创建一个轻量级.可移植.自给自足的容器.开发者在本地编译测试通过的容器可以批量地在生产环境中部署,包括虚拟机和其他平 ...

  7. e分钟带你利用Python制作词云图

    随着大数据时代的来临,数据分析与可视化,显得越来越重要,今天给小伙伴们带来一种最常见的数据可视化图形-词云图的制作方法. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法 ...

  8. 20行代码教你用python给证件照换底色

    1.图片来源 该图片来源于百度图片,如果侵权,请联系我删除!图片仅用于知识交流.本文只是为了告诉大家:python其实有很多黑科技(牛逼的库),我们既可以用python处理工作中的一些事儿,同时我们也 ...

  9. 【计算机算法设计与分析】——SVM

    一.简介 支持向量机(support vector machines)是一种二分类模型,它的目的是寻找一个超平面来对样本进行分割,分割的原则是间隔最大化,最终转化为一个凸二次规划问题来求解.由简至繁的 ...

  10. 性能分析(5)- 软中断导致 CPU 使用率过高的案例

    性能分析小案例系列,可以通过下面链接查看哦 https://www.cnblogs.com/poloyy/category/1814570.html 前言 软中断基本原理,可参考这篇博客:https: ...