我们使用JdbcTemplate时,调用的query方法为:

public <T> List<T> query(String sql, @Nullable Object[] args, RowMapper<T> rowMapper)

把我们需要映射的自定义java类型传入到T中,也就是T的实际值为我们的类型,假设为User.我们传递的是RowMapper<User>,返回List<User>

该方法调用到

public <T> T query(String sql, @Nullable Object[] args, ResultSetExtractor<T> rse)

这里的T实际值为传递给ResultSetExtractor<T>的T值,后面会看到,最终传入接口类ResultSetExtractor<T>的实际T值为List<User>

传入的第三个参数为:

(ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))

此时rowMapper仍为我们传递的RowMapper<User>.

再看RowMapperResultSetExtractor类:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.jdbc.core; import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.util.Assert; public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {
private final RowMapper<T> rowMapper;
private final int rowsExpected; public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
this(rowMapper, 0);
} public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
Assert.notNull(rowMapper, "RowMapper is required");
this.rowMapper = rowMapper;
this.rowsExpected = rowsExpected;
} public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = this.rowsExpected > 0 ? new ArrayList(this.rowsExpected) : new ArrayList();
int var3 = 0; while(rs.next()) {
results.add(this.rowMapper.mapRow(rs, var3++));
} return results;
}
}

构造方法为泛型方法:

    public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
this(rowMapper, 0);
}

传入RowMapper<User>,那么传给这个类的形参T的实际值为User(注意不要与其他类签名中的T弄混,不同类的泛型参数相互独立,没联系)

但该类的泛型签名中:

RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>>

最终将这里的List<T>传入其接口类的泛型参数中,这里就是容易混淆的地方,重点--接口类签名:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.jdbc.core; import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.dao.DataAccessException;
import org.springframework.lang.Nullable; @FunctionalInterface
public interface ResultSetExtractor<T> {
@Nullable
T extractData(ResultSet var1) throws SQLException, DataAccessException;
}

这里实现类的List<T>被整体传入接口类的T,即接口类的T实际值为List<User>了,这个T不要与实现类的T混淆,这是不同类相互独立的泛型参数。

注意这里实现类需要覆盖的方法返回值为T(接口类的T),即List<User>,即实现类中的List<T>(实现类的T)

那么看实现类该方法实现:

    public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = this.rowsExpected > 0 ? new ArrayList(this.rowsExpected) : new ArrayList();
int var3 = 0; while(rs.next()) {
results.add(this.rowMapper.mapRow(rs, var3++));
} return results;
}

确实是List<T>(实现类的T,经历了List<T>整体传入接口类再以List<T>整体返回的过程)

这里的rowMapper即我们传入的需要我们自己实现的RowMapper<User>,我们需要实现其mapRow方法。

这里的实现逻辑是:获取java原生结果集ResultSet,使用while(rs.next())迭代获取的数据库表的每一行(仍以rs代表),mapRow方法只需实现自定义处理每一行即可,我们一般将各个列取出,设置到我们的类User里面完成映射。

这里的RowMapper接口签名:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.jdbc.core; import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.lang.Nullable; @FunctionalInterface
public interface RowMapper<T> {
@Nullable
T mapRow(ResultSet var1, int var2) throws SQLException;
}

实现时我们将User传入给T,使mapRow返回User,

调用时,我们将User最终传递给RowMapperResultSetExtractor<T>的T,使用其

List<T> extractData(ResultSet rs)

实现方法,该方法使用循环,每次回调RowMapper的mapRow方法,将ResultSet的每一行数据以User返回,加入List<User>,达到返回List<User>的目的。

弄清楚不同类的泛型形参相互独立(不同的T),和User作为泛型实参(以不同的方式,比如可能直接传入,可能List<T>实参传递给T形参)传递到不同类的泛型形参中,最终不同类的泛型方法(不同的T)返回多少与实参相关(但T值不一定都是原始的User,可能是User,可能不是User)的类型,相互调用时,再对这些实际类型进行匹配编码调用,返回即可。

好的设计模式是分模块的。

这里的query负责查询结果集模块,RowMapperResultSetExtractor的相关方法负责处理结果集模块;

这些模块都有其接口类型,达到统一接口,扩展性实现;

模块内部,主要方法(包括构造函数)有多个重载,和重载方法间调用,比如这里的query方法,达到内部进一步分工细化,细模块开发的结构;

模块间调用采用回调方式,又充分实现了面向接口、模块化调用、模块间解耦、可扩展:

子模块负责实现子模块接口的回调方法,比如这里的结果集处理实现类RowMapperResultSetExtractor<T>实现了ResultSetExtractor<T>接口的extractData方法,我们自定义并将实例传入到query方法的匿名内部类又实现了RowMapper<T>接口的mapRow方法,

父模块调用子模块时,引用子模块的接口作为形参,子模块实例整体传递给父模块,父模块中调用的是统一的子模块接口方法,利用多态(动态绑定,运行时绑定),清晰实现了模块间解耦和模块组件的可扩展性,比如这里query调用ResultSetExtractor<T>接口的extractData方法,其实现类RowMapperResultSetExtractor<T>模块在实现其extractData方法时,又调用了RowMapper<T>接口的mapRow方法。

总结起来:模块向上实现接口,向下调用接口,接口的实现是模块。这样一层一层的关系,层次分明,功能独立、细化、可复用,清晰解耦,组合简单,实现清晰。即使相互调用,或多个模块复杂组合,模块本身都是相对独立的(也基本相当于功能独立,可复用),模块间调用关系也面向接口,清晰可扩展,轻耦合。

这种面向接口、模块化、层次化、轻耦合开发,清晰的源码架构,与Spring的增强功能IOC,DI,AOP有异曲同工之妙,可以说是一种最原始的、根本的解耦方法,再与这些功能进一步配合,分别从框架开发层面和用户应用级开发(直接使用Spring的人)层面简化了程序开发。

这才是我们该从Spring源码中窥伺和学到的。

SpringJDBC源码分析记录的更多相关文章

  1. seajs3.0.0源码分析记录

    自己边读变加了一些注释,理解了一下seajs3.0.0工作的流程.正则没有一个个去理解,插件模块也没看, 以后有时间了可以补充完整~ 事件系统中事件队列的获取&定义方法 var list = ...

  2. cordova 源码分析记录

    1.模块定义 (function () { var modules = {}; // Stack of moduleIds currently being built. var requireStac ...

  3. [Abp 源码分析]零、文章目录

    0.系列文章目录 一.Abp 框架启动流程分析 二.模块系统 三.依赖注入 四.模块配置 五.系统设置 六.工作单元的实现 七.仓储与 Entity Framework Core 八.缓存管理 九.事 ...

  4. 分析jQuery源码时记录的一点感悟

    分析jQuery源码时记录的一点感悟      1.  链式写法      这是jQuery语法上的最大特色,也许该改改POJO里的set方法,和其他的非get方法什么的,可以把多行代码合并,减去每次 ...

  5. 【RabbitMQ学习记录】- 消息队列存储机制源码分析

    本文来自 网易云社区 . RabbitMQ在金融系统,OpenStack内部组件通信和通信领域应用广泛,它部署简单,管理界面内容丰富使用十分方便.笔者最近在研究RabbitMQ部署运维和代码架构,本篇 ...

  6. spring transaction源码分析--事务架构

    1. 引言  事务特性 事务是并发控制的单元,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性.事 ...

  7. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  8. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  9. ABP源码分析八:Logger集成

    ABP使用Castle日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方 ...

随机推荐

  1. PyQt5标准对话框

    很全的Qt的标准对话框,包含QInputDialog.QColorDialog.QFontDialog.QMessageBox.QOpenFileDialog... 全部是由官网的C++版本,转换成P ...

  2. oracle(三) SQL语句

    1.聚集函数遇到空值时,除count(*)外,都会跳过空值. 2.group by 细化聚集函数的作用对象 3.group by有个原则,就是select后面出面的列,除聚集函数外必须出现在group ...

  3. redhat7下对用户账户的管理

    redhat7对用户帐号的管理主要集中在新建,删除和修改三个动作. 1.新建用户 通过useradd --help,我们得到useradd的详细参数. -d 目录 指定用户主目录,如果此目录不存在,则 ...

  4. POJ2983 Is the Information Reliable?

    http://acm.sdut.edu.cn:8080/vjudge/contest/view.action?cid=267#problem/B                        B -  ...

  5. notification 是同步的

    所有notification的观察者执行之后,post notification的函数才会往下执行.

  6. 解决nginx下不能require根目录以外的文件

    我们常规的做法是将统一入口文件.css.js这些放在网站根木,其他php文件放到根目录外部,这个时候nginx访问是require不到的,需要设定一下 1.vi  /usr/local/nginx/c ...

  7. 混合使用ForkJoin+Actor+Future实现一千万个不重复整数的排序(Scala示例)

    目标       实现一千万个不重复整数的排序,可以一次性加载到 2G 的内存里. 本文适合于想要了解新语言 Scala 并发异步编程框架 Akka, Future 的筒鞋. 读完本文后,将了解如何综 ...

  8. Python tricks(4) -- with statement

    简介 with是从2.5版本引入的一个语法. 这个语法本身是为了解决try..finally繁琐的释放各类资源(文件句柄, Lock等)的问题. 如果想在旧版本中使用这个功能, 直接引入future模 ...

  9. python-正则表达式练习题

    1.匹配一行文字中的所有开头的字母内容 #coding=utf-8 import re s="i love you not because of who you are, but becau ...

  10. Linux基础命令---sum,cksum

    cksum 检查文件的crc是否正确,统计文件的字节数. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.openSUSE.Fedora. 1.语法       cks ...