JDBCTemplate与模板设计方法(二)
前言:上一篇博客介绍了模板方法模式,并且给出了一个小demo,简单对模板方法进行了实现,接下来我们把目光转向spring的源码JDBCTemplate,看一看spring是如何对jdbc进行高度封装的。
本篇博客的目录:
一:传统的jdbc存在的缺陷和不足
二:JDBCTemplate的实现原理
三:JDBCTemplate的使用方法
五:总结
正文:
一:传统的jdbc存在的缺陷和不足
1.1首先我们来一下以往我们使用jdbc进行数据库操作的代码是什么样的?
public class UpdateDateBase {
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/jdbc_db";
static final String USER = "root";
static final String PASS = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(DB_URL, USER, PASS);
stmt = conn.createStatement();
String sql = "UPDATE student " +"SET age = 22 WHERE id in (100, 101)";
stmt.executeUpdate(sql);
sql = "SELECT id, first, last, age FROM student";
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()){
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
}
rs.close();
}catch(SQLException se){
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(stmt!=null)
conn.close();
}catch(SQLException se){
}
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
}
}
这里只是做了一个简单的操作,对数据进行更新,再查询显示出来,可以看到这么一大长串,假如我们再进行一个删除操作,还得把这些步骤再来一遍,天呐,头都要大了,写代码最烦的就是写重复烦躁的代码。这就是传统jdbc的编码,存在以下不足:
1:对外暴露的细节太多,比如把数据库名、密码、用户名、Ip等都暴露出来,万一代码落入到还有不轨企图的人手里,只要看一下你的jdbc,它就可以得到你数据库的所有信息。其实安全这一点对企业来说非常重要,如果没有参加工作的童鞋可能还意识不到这一点
2:冗余、重复的代码太多,都是做一些重复机械的操作,首先是获取驱动、得到连接、生成Statement,再编译sql,执行,取结果遍历,最后还得再关闭结果集、关闭连接、关闭statement,防止抛异常,还得考虑事务,万一出错还得考虑回滚等等。
3:代码编写比较繁复,这其中万一有一个步骤出错了,比如忘记关闭数据库连接池了,这样就会发生很多莫名奇妙的问题,比如再次更新数据库发现没有变化,这就是没有关闭连接池导致的。这种细节性操作很容易酿造大的事情,程序员需要关注的细节过多
4: 向方法中的sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应,这其中万一出错了,就会导致无法操作数据库
二:JDBCTemplate源码分析
2.1:关于JDBCTemplate:它是这样一个类,它把我们对数据库的操作jdbc代码高度封装,并且采用了模板方法的设计思想,将其抽象成回调接口和工具类调用的方法,把特定的步骤通过工具类来组装,这样可以实现固定步骤的高度可重用。只需要编写一次,我们就可以进行重复利用起来。
2.2:JDBCTemplate的继承关系

上面是它的继承关闭图,它继承自抽象类JdbcAccessor和JdbcOperations接口。其中JdbcAccessor设定了处理数据源DataSoucre和sql的异常检验操作,而JdbcOperations则负责具体的查询query、update操作、批处理batchUpdate等,同时继承自两个父类,让JDBCTemplate具有操作数据库和处理数据源、获取异常的能力。
2.3:JDBCTemplate的成员变量

这些成员变量是它本身的,我来翻译一下它的含义:
private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";//前缀
private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";//计数前缀
private NativeJdbcExtractor nativeJdbcExtractor;//本地JDBC执行器
private boolean ignoreWarnings = true;//是否忽略警告
private int fetchSize = 0;//查询到的大小
private int maxRows = 0;//最大行数
private int queryTimeout = 0;//查询时间
private boolean skipResultsProcessing = false;//是否跳过结果处理
private boolean skipUndeclaredResults = false;//是否跳过非公共结果
private boolean resultsMapCaseInsensitive = false;//Map结果的是否大小写敏感
但是因为它继承了一个抽象类和接口,它同样享有JdbcAccesor的成员变量,可以看到DataSoucre已经注入了,但它是private的,虽然可以继承,但是不能使用,不过可以引用JdbcAccesor中的setter方法进行设值

父类中的这些很简单,基本上可以很直接的看出来,它们分别是日志记录器、数据源、sql异常转换器、是否懒加载,这些成员变量是全局的,也就是说它们存在着线程安全的问题,为此Spring还专门采用了单例加锁的设计模式来获取exceptionTranslator对象,以下是源码:

可以看到它使用了synchronize关键字,这个锁是方法级别上的,获取exceptionTranslator对象,根据dataSoure是否为空来判断实现的不同子类SQLErrorCodeSQLExceptionTranslator(需要DataSource)和SQLStateSQLExceptionTranslator(不需要DataSource);
2.4:jdbcTemplate的构造方法

可以看到它有三个构造方法,标记为①的是个空构造方法,这个不用多说了。第二个它的话它引用了JdbcAccesor注入了DataSource,然后调用了父类中的属性设置后的方法this.arterPropertiesSet(),我们来看看它的源码:

可以看到这是和懒加载有关的一个方法,没有懒加载,就会调用本类中的方法调用2.3中讲的方法获取异常转换器中
第三个构造方法,它在第二个的基础上增加了设置LazyInit的字段,设置它是否支持懒加载,关于懒加载:它是一种节省资源提高效率的做法,数据只有在真正用到的时候才去加载它。
2.5:jdbcTemplate执行sql原理
我们来看一下它执行sql的方法exexute的源码:

首先它是一个日志记录,级别在debug,然后方法中声明了一个类,这个类继承了StatementCallback接口和sqlProvider接口,这个接口只有一个方法,主要负责的就是用Statement这个对象执行sql。

最后再把这个接口传入execute重载方法,我们来看一下它对接口作了哪些处理:

我们来分析一下这段源码,它主要是获取connection对象,再通过对象赋值,通过本地jdbc提取器再次获取,然后再通过该对象创建Statement,再进行设值,再通过对象赋值给另一个对象,至于Spring中为什么经常出现这种代码-指的是对象赋值转接,这里可以理解为对象的一个复制过程,然后再做重新的处理。再调用接口中的doInstatement方法执行sql获取结果,又再次出现把对象转接过去,对结果进行赋值,然后在catch代码块中关闭Statement和释放连接,再抛出异常,在finally块中确保关闭Statement和Connection.
2.6:jdbcTemplate的update方法源码分析

我们看到这就是它的update方法,首先是一个断言机制:sql必须不为null,关于断言就是一种假设机制,假设程序运行到这里对参数进行校验,如果不满足的话,将会抛出AssertionError异常。然后是日志记录sql,接着是一个方法内部类,这里又用到实现了回调接口StatementCallback,它的主要方法是doInstatement,调用jdbc中的的executeUpdate方法执行完返回一个int类型的值,最后返回这个值,然后再次调用execute方法,把方法内部的实现类传进去,和刚才的一样,做了必要的关闭Statement和connection操作,还有一些异常的抛出操作和警告等的处理
2.7:jdbcTemplate的批量更新方法源码


批量更新batchupdate可以看到它传入的是一个sql的数组,那么我们在调用的时候需要传入肯定是一个sql的数组集合。然后和上面的一样断言、日志开启,再在方法内部新建一个类实现Statement回调接口,再其中先判断是否支持批量查询,再进行循环遍历,通过appendSql方法拼接sql语句,再用statement批量更新,最后返回影响的行数。
上面的源码是进行一场判断的处理,抛出具体哪个sql异常,else的部分是指不支持批量查询的情况,它就会抛出异常Invaild batch sql Statement+具体的sql语句,最后再返回影响的行数
三:jdbcTemplate的使用方法
3.1:首先在application.xml中配置jdbcTemplate Bean:

这里可以看到配置的过程中引用了DataSource数据源,这里的主要原因是我在2.3中讲到的它有DataSource这个成员变量,然后我们来看一下DataSource配置了哪些东西

这里的DateSource主要是数据库的配置,数据库也就是我们数据的来源,至于其中的${jdbc.property}指的是读取一个配置文件的值,一个.properties文件结束,关于如何读取文件,我的博客之前也有讲过:点我到达
不过使用这种方式的话,记得要在Spring的配置文件中写上,location是资源文件的路径:

3.2:如果其他配置好的话,接下来我们看看如何具体使用它:

jdbcTemplate肯定是在Dao层了,我们用@AutoWired注入这个对象,然后可以调用它的方法执行sql就行了。其他的比如关闭操作、异常处理、回滚等操作压根不用操心,这个类已经帮我们处理好了一切,怎么样,够简单吧。
ps:其他的使用方法同理。只需要用这个jdbctemplate调用方法就可以了。
四:总结
本篇博客主要讲解了原始jdbc的不足和jdbctemplate采用模板设计模式对其进行了高度封装的过程,并且分析它的源码,和使用过程,源码只要理解了就行, 用的时候可以直接调用,其中它本身大量使用了回调机制,在方法内部声明一个类实现回调接口,调用接口中的方法进行处理。主要的重要部分是源码的剖析,理解它的回调接口,然后理解模板设计方法带给我们的好处,在项目中多多使用它,毕竟封装可是面向对象的三大特性之一。
JDBCTemplate与模板设计方法(二)的更多相关文章
- IP地址的规划和设计方法(二)
五,IP地址规划方法 (1)IP地址规划的基本步骤 网络地址规划须要按下面6步进行: a)推断用户对网络与主机数的需求: ...
- Axure中移动端原型设计方法(附IPhoneX和IPhone8最新模板)
Axure中移动端原型设计方法(附IPhoneX和IPhone8最新模板) 2018年4月16日luodonggan Axure中基于设备模板的移动端原型设计方法(附IPhoneX和IPhone8最新 ...
- thinkphp二维数组模板输出方法
thinkphp二维数组模板输出方法 先写个记录,有空再整理发上来
- Robot Framework测试框架用例脚本设计方法
Robot Framework介绍 Robot Framework是一个通用的关键字驱动自动化测试框架.测试用例以HTML,纯文本或TSV(制表符分隔的一系列值)文件存储.通过测试库中实现的关键字驱动 ...
- paip.自适应网页设计 跟 响应式 设计方法与工具补充(2)o54
paip.自适应网页设计 跟 响应式 设计方法与工具补充(2)o54 #-----响应式 设计框架 Bootstrap比较热门. Foundation 号称是世界上最先进的响应式前端框架. #---绝 ...
- 黑盒测试用例设计方法&理论联系实际-> 功能图法
一. 概念 功能图由状态迁移图和布尔函数组成.状态迁移图用状态和迁移来描述.一个状态指出数据输入的位置(或时间),而迁移则指明状态的改变.同时要依靠判定表或因果图表示的逻辑功能.例,一个简化的自动出纳 ...
- [liu yanling]黑盒测试用例设计方法
1. 概述 黑盒测试用例设计方法包括等价类划分法.边界值分析法.错误推测法.因果图法.判定表驱动法.正交试验设计法.功能图法等. 2. 等价类划分法 2.1. 概念 等价类划分法是把 ...
- 在verilog中关于inout口的设计方法
在学习IIC的时候我们知道这么设计inout inout scl : reg scl_reg , scl_en ; scl = scl_en ? scl_reg : 1'dz ; 当 ...
- IP地址规划和设计方法
IP地址规划和设计方法 无类域间路由技术需要在提高 IP 地址利用率和减少主干路由器负荷两个方面取得平衡 网络地址转换 NAT 最主要的应用是专用网,虚拟专用网,以及 ISP 为拨号用户 提供的服务 ...
随机推荐
- UWP: 通过命令行启动 UWP 应用
最近在开发应用的过程中,我遇到了如标题所述的需求,其实主要是为了能够快捷启动应用,正像我们可以在"运行"对话框中可以输入一些可执行程序的名称后,就能够直接启动它:这样做,可以增加 ...
- dede的pagelist标签的listsize数字属性详解
转载▼http://blog.sina.com.cn/s/blog_a4f3bd4e01012c8n.html dede的pagelist标签的listsize数字属性详解.见远seo经常用织梦搭建各 ...
- php对数组进行分页
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ...
- [ios 开发笔记]:一句话笔记
1.NSString转int int a=[@"123" intValue]; 同样适用于NSDictionary将NSNumber转为int 2.switch(stateme ...
- Linux文件
Linux文件类型 对于内核而言,所有打开的文件都是通过文件描述符引用(FD),文件描述符是一个非负整数,当打开现有问价或创建一个新文件时,内核向进程返回一个文件描述符. 按照惯例,shell把文件描 ...
- [转]另一种遍历Map的方式: Map.Entry 和 Map.entrySet()
转自: http://blog.csdn.net/mageshuai/article/details/3523116 今天看Think in java 的GUI这一章的时候,里面的TextArea这个 ...
- 前端css常用class命名id命名
1.常用id的命名: (1)页面结构 容器: container 页头:header 内容:content/container 页面主体:main 页尾:footer 导航:nav 侧栏:sideba ...
- POI--HSSFSheet类
用POI来作成一个Sheet,可以用「HSSFSheet」类,该类构造器如下: 新建Sheet 从构造器可以看出,虽然它有两个构建器,但都是protected的,所以要新建Sheet,只能通过Work ...
- SQL Server——存储过程
我想从下面几个方面大概的讲述下存储过程,可能有些知识点是你没有注意的,也可能有些知识点我不知道,欢迎大家指点指点.如有不足,欢迎指教! 存储过程概念 存储过程优点 存储过程的接口 存储过程的解析.编译 ...
- javascript-声明对象及其属性和方法
/* 方法一 */ var p = new Object(); //声明对象 //为对象添加属性 p.width=300; p.height=400; p.num=4; p.autotime=3; / ...