前言
此篇博客主要针对的是《Spring揭秘》的下半部分,对应了spring的数据访问部分(包括事务)和Spring MVC的部分。
第十三章
程序无法脱离数据存在。我们的程序中或多或少都会和数据库、文件或者别的系统进行交互。为了屏蔽这些不同系统之间的差异,我们抽象出了一层叫DAO(data access object)的类(接口),我们只需要调用这些类中的方法,就可以进行数据的访问和存储,而不需要在乎实现细节。
理想很美好,但是现实却没那么简单。想一想,如果你的数据库访问出现了问题,需要处理这个异常,那么应该由谁来处理?DAO吗?那客户端就无从得知它的操作有没有成功了呀。那抛给客户端?那客户端就需要根据不同的底层系统来处理不同的异常,这样岂不是又回到了最初的起点?
我们其实需要的就只是一套异常层次的体系,有了这套体系,客户端可以愉快的处理它了。好在spring已经为我们抽象出了这么一套体系:以DataAccessException这个抽象类为统领的一组类。
第十四章
Spring为我们提供了两种JDBC的最佳实践,虽然现在都是用的ORM框架如MyBatis等,但是我觉得理解下还是有必要的。
JDBC是成功的吗?当然,现在几乎只要你通过java访问数据库,必然需要通过JDBC。但是我作为一个使用者,用起来爽吗?不不不,就算一个最最简单的select,我也需要写一大堆的代码,就算有snippet功能,还是觉得烦。其次是所有的异常都是SQLException,而且我们还需要自己去分析这个exception。
在这种情况下,JdbcTemplate诞生了。spring就是以JdbcTemplate作为基石来构建的。在这里首先先需要对设计模式中的模板方法进行一波简单的介绍。其实就是在一个类中用一个final修饰一个方法,然后这个方法是所有子类都需要的,子类只需要把不同的方法改一下就好了。
下面是我抽象的一个模板类:
1 | public abstract class Template { |
可以看到doSomething被声明成了final,就是不想子类修改,因为这是所有子类必须遵守的逻辑部分。但是又特别把step02设置成了抽象方法,这样每个子类可以根据自己的需要修改这一个步骤:
1 | public class Impl extends Template{ |
这个设计模式对于JDBC访问数据库真的是特别适合,因为访问数据库就那么几步,而且就是照着规矩来的。
但是似乎还有那么一丝丝不方便,就是模板的类是抽象的,我们必须要实现一个子类去实现它,稍微有点麻烦(当然比起没有模板的时候方便了太多了)。所以spring在实现的时候,还加入了Callback接口。现在,如果我们希望访问数据库,可以这样访问了:
1 | JdbcTemplate jdbcTemplate = new JdbcTemplate(); |
当然由于没有配置DataSource,所以显然是无法访问数据库的。下面是JdbcTemplate的详细的继承层次,还算挺简单的:

继承了JdbcAccessor,并且实现了JdbcOperations。
- JdbcAccessor里面就有一个DataSource的对象,spring对数据库访问全部建立在DataSource之上;还有一个SQLExceptionTranslator的对象,这个就是用来解决之前说的SQLException设计不良,导致乱象丛生的问题的。
- JdbcOperations则规定了所有的操作。
同时,在JdbcTemplate里面,还有一些面向不同API来分成了几组方法,分别是面向Connectioni,Statement,PreparedStatement和CallableStatement。我们看一个面向Statement的方法好了:
1 |
|
可以看到奥秘就在第11行,通过传入的action来做操作,并且返回这个结果。同时如果你仔细观看,会发现第4行也有点奇怪,为什么不是直接从dataSource直接获取连接,而是需要大费周章从DataSourceUtils这个类来获取呢?这个主要是为了将conn绑定到当前线程,然后在事务管理的时候发挥作用。
之前还聊到了我们需要把复杂的SQLException转译成DataAccessException,这个是由SQLExceptionTranslator来做的,具体的实现类有三个,逻辑首先是通过查询自己有没有sql-error-codes.xml文件,如果有就根据该文件来提取,如果当前的类路径下也有这个文件,那么会覆盖掉spring包下面的。所以我们可以在类路径下放置这个文件,来告知如何根据错误代码来转换成DataAccessException。当然实际中我是没有遇到过这种需求。
关于JdbcTemplate对数据进行查询、更新(包括插入和删除),我个人认为不是重点,就直接跳过了。
DataSource
其实DataSource更为通用的名字应该叫ConnectionFactory,因为我们的conn就是根据它来获取的。Spring中的DataSource主要有两种,一种是DriverManagerDataSource,另外一种是SingleConnectionDataSource。听名字也知道了,每次向single去请求conn的时候,返回的都是同一个对象。但是由于这两者都没有缓冲池,所以生产环境中请不要使用。
至于带有缓冲池功能的datasource嘛,现在一堆开源的都是。
基于对象的操作
这边先直接跳过了。我个人觉得现在都是用的ORM框架来进行操作,所以JDBC的最佳实践我了解一种JdbcTemplate就足够了。
第十五章
和之前的问题一样,每个ORM都有它们自己的异常,为了屏蔽这些,spring需要提供一个异常转译机制。同理,spring还需要提供统一的资源管理方式和事务管理等。
iBatis
虽然现在已经是MyBatis了,但是作为前身,我觉得理解一下也是好的。基本上所有的ORM框架都至少需要两个配置文件,一个是全局配置文件,指定好数据源等相关配置;另外一个(多个)用来指定表和对象之间的映射关系。
第十六章
不管是Spring,还是其他的ORM框架,它们都不约而同使用了模板的方式来处理,相关的好处相信你已经了解了。在学了JdbcTemplate之后,相信你也可以根据日常中重复的逻辑,来生成属于自己的template了。
第十七章
事务中的成员
- Resource Manager:负责存储并管理系统数据资源。如MySQL数据库服务器就是典型的RM。
- Transaction Processing Moniotr:在分布式事务中协调多个RM进行事务处理。
- Transaction Manager:是上面的TPM的核心模块,提供多种功能模块。
- Application:就是应用程序呗。
全局事务(也叫做分布式事务)就是有多个Application和多个RM,然后它们之间通过一个TPM(TM)来协调。TM通过两阶段提交来保证事务的ACID。
局部事务就是一个Application和一个RM,由于RM本身就自带了事务支持,所以其实直接和RM打交道就行了。
第十八章
局部事务支持
编写过JDBC代码的同学一定非常熟悉,只需要设置conn.setAutoCommit(false);就可以自己手动提交事务,然后进行相应的回滚处理了。
分布式事务支持
书中介绍的分布式事务的支持都太老了,这里直接略过。
第十九章
spring通过分析之前的缺点,并对事务管理进行了抽象,得到了Spring事务框架,核心原则就是:事务管理和数据访问相互分离。
我们一般会将事务的管理放在service层,而将数据访问放在dao层。所以可能会出现这种情况:两个dao需要在同一个service中,而且它们的conn必须是同一个,那么我们只需要传递这个conn就可以了,也不是很麻烦。
具体实现的话,可以把conn绑定到当前的线程,这样大家都可以获取这个conn了。而TransactionResourceManager就是封装了这个逻辑,我们实际中只需要向它“索要”conn就行了。
TRM的代码见下:
1 | public interface PlatformTransactionManager extends TransactionManager { |
可以看到相关的还有TransactionDefinition和TransactionStatus这两个接口。TD根据名字可以判断出就是用来定义事务的相关属性的,包括隔离级别、传播行为等。而TS则是用来表示事务的状态的。
TD
这个接口主要包括了:
- 事务的隔离级别:
1 | int ISOLATION_DEFAULT = -1; // 使用数据库默认的 |
- 事务的传播行为:
1 | int PROPAGATION_REQUIRED = 0; |
- 事务的超时时间:
int TIMEOUT_DEFAULT = -1;,-1代表使用系统默认的超时时间。 - 事务是否是只读的:
default boolean isReadOnly() {return false;}
上面可能会对事务的传播行为有点陌生。事务的传播行为指的是,整个事务处理过程中,跨越的业务对象,会以什么样的行为来参与事务。
- REQUIRED:当前存在事务则加入,没有事务则创建一个事务。这个是默认的。
- SUPPORTS:当前存在事务则加入,没有事务则直接执行。对于一些查询的方法,这个是比较推荐的。
- MANDATORY:必须要有一个事务,否则就抛出异常。
- REQUIRES_NEW:不管是否存在事务,都会创建事务。如果之前的事务是存在的,则挂起它。比如我向数据库更新一些不重要的信息,就算这些信息更新失败了,原来的事务还是要不受影响。
- NOT_SUPPORTED:必须没有事务才能执行。
- NEVER:永远都不能有事务,有的话就抛出异常。
- NESTED:如果存在事务,那么就在事务中的嵌套事务中执行;否则创建事务执行。
TS
- 查询相关的事务状态
- 标记事务以便进行回滚
- 创建内部嵌套事务
第二十章
编程式事务管理
我们可以直接使用PlatformTransactionManager来进行代码的编写。只要自己实现一个TD和一个TS,就可以完成整个事务处理流程了。而且这套流程是比较固定的,所以我们可以使用模板的设计模式来优化它,Spring则是抽象出了TransactionTemplate来进行。
1 | TransactionTemplate tx = new TransactionTemplate(); |
声明式事务管理
spring aop可以在这里很好用上。具体实现可以有xml的和注解的方式。使用xml的话可以有以下四种:
- ProxyFactory+TransactionInterceptor
- 直接使用TransactionProxyFactoryBean
- 使用BeanNameAutoProxyCreator
- 使用spring2.x的声明事务配置
而目前一般都是直接使用注解来进行开发的。
第二十一章
之前提到过用threadlocal来保存conn信息,这样可以在一个线程的各个方法之间进行有效的传递。
从本质上说,threadlocal和synchronized是一点关系也没有,但是它们都能够有效解决线程安全问题,一个是通过变量的私有化,另外一个是通过同步来实现的。
第二十二章
一些MVC框架的发展史,有兴趣的可以看看。
第二十三章
Spring MVC是一个请求驱动的Web框架,其中有一个DispatcherServlet作为Font Controller,它负责接收所有的web请求,然后根据处理逻辑来分发给下一级控制器,也就是我们常说的Controller(更加具体的应该说的是Page Controller)。
我们其实分析一下日常写的一些业务逻辑,不难发现一些共同的特点:
- 获取请求的参数
- 根据请求的参数来进行相应的处理
- 处理完成之后,把数据交给jsp视图
其实spring也算是源于业务了,看看它是怎么做的。首先容器中必然是有一个DispatcherServlet的,这个毋庸置疑。然后又有一个HandlerMapping,来处理URL的匹配。在查找到了之后,会首先发回给DispatcherServlet,然后由它来发送给下一级的controller。controller处理完之后,就会返回一个ModelAndView对象,里面封装了对应的数据和视图的逻辑名称。DispatcherServlet接下来就会依赖ViewResolver来查找对应的View实现类,然后把找到的实现类交给DispatcherServlet。最后DispatcherServlet会把数据封装到View里面,并且发还给用户。