目录标题
前言
本章主要是对Spring支持持久化技术的理论知识讲解,不涉及到源码解析。对源码解析感兴趣的小伙伴可以阅读另一篇文章《Spring源码深度分析》第8章 数据库连接JDBC。该篇文章主要以’JdbcTemplate‘作为示例进行源码解读。
一、深入理解‘DAO理念’
在日常开发中,我们习惯性的定义类似这样的目录结构:controller、service、dao。
我们大多数时候给DAO层的定义是:用于DB的交互。其实,抛开DB,DAO层也是可以与其他存储介质(OSS、File…)进行交互的。所以说,请不要把DAO仅仅限制为DB交互。
1.DAO的作用
DAO (Data Access Object)是用于访问数据的对象,DAO的作用有两处:
1、DAO屏蔽了数据存储的最终介质
的不同,
2、DAO屏蔽了具体的实现技术
的不同。
2.DAO存储介质
虽然在大多数情况下将数据保存在数据库中,但这并不是唯一的选择。数据存储的位置选择:
- 数据库
- 文件
- LDAP
- …
二、数据库持久化技术
DAO层支持的存储介质有多种,现在我们以‘数据库’为主,进行讲解。
1.常见的持久化技术
下面列举常见的持久化技术:
- 传统JDBC;–仅关系型数据库
- Hibernate(底层是JDBC);–仅关系型数据库
- MyBatis(底层是JDBC);–仅关系型数据库
- JPA(底层是JDBC);
- JDO ;–可以在任何数据底层上运行
DAO层可以整合以上持久化技术,对数据库进行CRUD操作。
三、模板类
Spring使用模板类,提供了对持久化技术的支持。个人认为模板类的主要作用有两处:
- 简化数据库操作;
- 并发安全性:多个dao实例使用模板类是安全的;
1.使用模板类的好处
1.简化数据库操作
单独使用持久化技术的繁琐性
以上五种持久化技术,如果离开spring单独实现数据库操作
,需要进行一系列繁琐的代码编写。以JDBC技术为例:
如上述代码所示,JDBC 数据访问操作按以下流程进行:
(1)准备资源。
(2) 启动事务。
(3)在事务中执行具体的数据访问操作。
(4)提交/回滚事务。
(5)关闭资源,处理异常
Spring提供模板类快速开发
按照传统的方式,在编写任何带事务的数据访问程序时,都需要重复编写上面的代码,而其中只有粗体部分所示的代码是业务相关的,其刚行公事,因而导致大量八股文式的代码充斥着整个程序。
Spring 为不同的持久化技术所提供的模板类。关于模板类的具体使用,后续博客会介绍。
注:spring并没有提供对mybatis持久化技术的模板类。
下面以JdbcTemplate为例:
2.并发安全性
数据库操作的并发安全性,本质是’事务并发安全性‘。并发事务会给我们带来超出预期的错误:脏写、脏读、不可重复读、幻读。具体模板类是如何解决并发事务的,会在第11章讲解。
2.DaoSupport
如果直接使用模板类,则一般需要在 DAO 中定义一个模板对象并提供数据资源。Spring 为每种持久化技术都提供了支持类,支持类中己经完成了这样的功能。这样,只需扩展这些支持类,就可以直接编写实际的数据访问逻辑,因此更加方便。
不同持久化技术的支持类如表所示。
这些支持类都继承于 dao.support.DaoSupport 类,DaoSupport 类实现了 InitializingBean接口,在 afterPropertiesSet接口方法中检查模板对象和数据源是否被正确设置,否则将抛出异常。
DaoSupport如何使用,请参考链接:
https://blog.csdn.net/llussize/article/details/79444104
四、统一的异常体系
统一的异常体系是整合不同的持久化技术的关键。(无论是spring提供的模板类(jdbctemplate…),或者说spring整合的持久化技术(mybatis…),都是使用spring统一异常体系)
1、Spring 提供了一套和实现技术无关的、面向 DAO 层语义的异常体系,
2、并通过转换器将不同持久化技术的异常转换成Spring 的异常
。
1.为什么Spring必须提供自己的DAO异常体系
1、在很多正统 API 或框架中,检查型异常被过多地使用
,以至在使用 API 时,代码里充斥着大量 try/catch 样板式的代码
。在很多情况下,除在 try/catch 中记录异常信息外,并没有做多少实质性的工作。引发异常的问题往往是不可恢复的,如数据连接失败、SQL语句存在语法错误等。而强制捕捉的检查型异常除限制开发人员的自由外,并没有提供什么有价值的东西。
2、因此,Spring 的异常体系都是建立在运行期
异常的基础上的,开发者可以根据需要捕捉感兴趣的异常。
JDK 的很多 API之所以难用,一个很大的原因就是检查型异常的泛滥,如 JavaMail、EJB、 JDBC 等。使用这些 API,一堆堆异常处理的代码喧宾夺主地侵入到业务代码中,破坏了代码的整洁和优雅。
2.Spring-DataAccessException
Spring 在tx模块‘spring-tx’
中的 org.springfiramework.dao 包中提供了一套完备优雅的 DAO 异常体系,这些异常都继承于 DataAccessException
, 而 DataAecessException 本身又继承于 NestedRuntimeException
。
NestedRuntimeException 异常以嵌套的方式封装了源异常。因此,虽然不同持久化技术的特定异常被转换到 Spring 的 DAO 异常体系中,但
原始的异常信息并不会丢失
;只要用户愿意,就可以方便地通过 getCause()方法获取原始的异常信息。【Java异常】Java异常监控重要手段 --异常链
3.异常转换器
1.传统JDBC 的异常转换器
传统的 JDBC API 在发生几乎所有的数据操作问题时都会抛出相同的 SQLException, 它将异常的细节性信息封装在异常属性中。所以,如果希望了解异常的具体原因,则必须分析异常对象的信息。
SQLException 拥有两个代表异常具体原因的属性:
- 错误码:是数据库相关的,可通过 getErrorCode()方法返回,其值的类型是 int;
- SQL 状态码:是一个标准的错误代码,可通过 getSQLState()方法返回,是一个 String 类型的值,由5个字符组成。
Spring 根据错误码和 SQL 状态码信息将 SQLException 译成 Spring DAO 的异常体系所对应的异常。在 org.springfiramework jdbc.support 包中定义了 SQLExceptionTranslator
接口,该接口的两个实现类
SQLErrorCodeSQLExceptionTranslator(jdbc模块)
:处理SQLException 中错误码的翻译工作;SQLStateSQLExceptionTranslator(jdbc模块)
:处理SQLException中的SQL状态码的翻译工作。
Spring 也支持 MyBatis ORM持久化技术,由于 MyBatis 抛出的异常是和传统JDBC 相同的 SQLException 异常,所以直接采用和 JDBC 相同的异常转换器。
2.其他持久化技术的异常转换器
由于各种框架级的持久化技术都拥有一个语义明确的异常体系,所以将这些异常转换为 Spring DAO 的体系相对轻松一些。下面将学习不同持久化技术的异常转换器。
五、数据源
不管采用何种持久化技术,都必须拥有数据连接。在 Spring 中,数据连接是通过数据源
获得的。
1、在以往的应用中,数据源一般是由 Web 应用服务器提供的。
2、在 Spring 中,不但可以通过JNDI 获取应用服务器的数据源,也可以直接在 Spring 容器中配置数据源;
3、此外,还可以通过代码的方式创建一个数据源,以便进行无容器依赖的单元测试。
1.Spring提供的两种数据源
Spring 在第三方依赖包中包含了两个数据源的实现类包:其一是 Apache 的 DBCP
;其二是 C3P0
。可以在 Spring 配置文件中利用二者中的任何一个配置数据源。且都提供了数据库连接池技术。
除此之外,还有阿里提供的druid
数据源,以及其他公司提供的数据源。
2.获取Connection连接的方式
获取Connection连接总共有两种方式:
- 直连方式(不会用到连接池)。
底层是用java.sql.Driver类获取一个Connection
, Connection使用完后被close,断开与数据库的连接,我们称这总方式是直连数据库, 因为每次都需要重新建立与数据库之间的连接,而并没有把之前的Connection保留供下次使用。
a:典型的有java.sql.DriverManager、org.springframework.jdbc.datasource.DriverManagerDataSource等。 - 池化方式(会用到连接池)。
使用javax.sql.DataSource,且DataSource内部也封装了一个连接池
。当你获取DataSource的时候,它已经敲敲的与数据库建立了多个Connection, 并将这些Connection放入了连接池,此时调用DataSource.getConnection()它从连接池里取一个Connection返回,Connection使用完后被close, 但这个close并不是真正的与数据库断开连接,而是告诉连接池"我"已经被使用完,"你"可以把我分配给其它"人"使用了. 就这样连接池里的Connection被循环利用,避免了每次获取Connection时重新去连接数据库。
a:典型的有C3P0、DBCP、Druid等。
DataSource与连接池的关系是: DataSource利用连接池缓存Connection,以达到系统效率的提升,资源的重复利用.
1.直连方式:DriverManager-java.sql
来自JDK中rt.jar包,位于java.sql包下的
DriverManager
类,提供了最原始的获取数据库连接的方式。
通过 DriverManager 类创建数据库连接对象 Connection。DriverManager 类作用于 Java 程序和 JDBC 驱动程序之间,用于检查所加载的驱动程序是否可以建立连接,然后通过它的 getConnection 方法根据数据库的 URL、用户名和密码,创建一个JDBC Connection 对象,例如:Connection connection = DriverManager.geiConnection(“连接数据库的 URL”,“用户名”,“密码”)。其中,URL-协议名+1P 地址(域名)+端口+数据库名称;用户名和密码是指登录数据库时所使用的用户名和密码。具体示例创建 MySQL 的数据库连接代码如下:
2.池化方式:DataSource-javax.sql
来自JDK中rt.jar包,位于javax.sql包下的
DataSource
类,使用DataSource对象是连接到数据源的首选方法。其底层都是调用
java.sql.Driver
的方式获取数据库连接。
在DriverManager类的注释头中,找到这么一句描述:
The basic service for managing a set of JDBC drivers. NOTE: The javax.sql.DataSource interface, new in the JDBC 2.0 API, provides another way to connect to a data source. The use of a DataSource object is the preferred means of connecting to a data source.
翻译如下:
用于管理一组JDBC驱动程序的基本服务。注意:javax.sql.DataSource接口是JDBC 2.0 API中的新接口,它提供了另一种连接数据源的方法。使用DataSource对象是连接到数据源的首选方法。
注意,实现了javax.sql.DataSource接口,只是说具备了池化的条件,要想真正的实现池化,需要该实现类自己内部实现池化。
(1)DriverManagerDataSource-Spring的数据源实现类(非池化
)
Spring 本身也提供了一个简单的数据源实现类
DriverManagerDataSource
,它位于org.springframework-jdbe.datasource 包中。
这个类实现了 javax.sql.DataSource 接口,但它并没有提供池化连接的机制
;每次调用 getConnection()方法获取新连接时,只是简单地创建一个新的连接。因此,这个数据源类比较适合在单元测试或简单的独立应用中使用,因为它不需要额外的依赖类。
下面来看一下 DriverManagerDataSource 类的简单使用,如下:
(2)BasicDataSource-dbcp的数据源实现类(池化
)
位于org.apache.commons.dbcp.BasicDataSource。
Connection con = BasicDataSource.getConnection();
2.获取Connection连接的方式
1.Java的方式
(1)DriverManager(不提供池化)
使用Java标准库中的DriverManager类来获取数据库连接。此方法适用于所有支持JDBC的数据库,并且不需要引入第三方库。获取连接的代码示例如下:
Connection connection = DriverManager.getConnection(url, username, password);
(2)DataSource(提供池化)
使用DataSource来获取数据库连接。DataSource是一个数据库连接池,它可以管理多个数据库连接,以便于高效地处理多个并发请求。DataSource还提供了连接池管理、连接池监控等功能,可以使应用程序更加稳定和高效。获取连接的代码示例如下:
DataSource dataSource = new MyDataSource();
Connection connection = dataSource.getConnection();
(3)JNDI(提供池化)
使用Java命名和目录接口(Java Naming and Directory Interface,JNDI)来获取数据库连接。JNDI是一种标准的Java API,用于管理各种命名和目录服务,包括LDAP、DNS、NIS等。通过JNDI获取数据库连接可以使应用程序更加灵活和可配置。获取连接的代码示例如下:
InitialContext ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup("jdbc/mydatasource");
Connection connection = dataSource.getConnection();
这三种方式的区别主要在于连接池管理、连接池监控、应用程序的可配置性等方面。DriverManager可以快速地获取数据库连接,但不能管理连接池。DataSource是一种更加高级的获取数据库连接的方式,它可以管理连接池并提供连接池监控功能。JNDI可以让应用程序更加灵活和可配置,但是需要额外的配置工作。
对于较小的应用程序,使用DriverManager获取数据库连接可能是一个更加简单和有效的方法。对于需要处理大量并发请求的大型应用程序,使用DataSource或JNDI获取数据库连接可能是更好的选择,因为它们可以管理连接池并提供更高效的资源管理和监控。
2.Spring的方式
(1)DriverManagerDataSource(不提供池化)
使用Spring提供的DriverManagerDataSource来获取数据库连接。这是一种非常简单和基础的获取数据库连接的方法,它使用DriverManager来获取数据库连接,不需要引入其他依赖库。它是基于JDBC DriverManager获取数据库连接,使用简单,但不能实现连接池的功能。获取连接的代码示例如下:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/testdb");
dataSource.setUsername("root");
dataSource.setPassword("root");
Connection connection = dataSource.getConnection();
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
(2)ComboPooledDataSource(提供池化)
使用Spring提供的ComboPooledDataSource来获取数据库连接。这是一种比较常用的获取数据库连接的方法,它可以管理连接池,并提供一些连接池相关的配置参数。获取连接的代码示例如下:
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");
dataSource.setUser("root");
dataSource.setPassword("root");
Connection connection = dataSource.getConnection();
(3)JndiObjectFactoryBean(提供池化)
使用Spring提供的JndiObjectFactoryBean来获取数据库连接。这是一种通过JNDI获取数据库连接的方法,可以使应用程序更加灵活和可配置。获取连接的代码示例如下:
JndiObjectFactoryBean factory = new JndiObjectFactoryBean();
factory.setJndiName("java:comp/env/jdbc/mydatasource");
factory.setResourceRef(true);
factory.afterPropertiesSet();
DataSource dataSource = (DataSource) factory.getObject();
Connection connection = dataSource.getConnection();
(4)HikariCP(提供池化)
HikariCP 是一个高性能的连接池,Spring框架提供了对其的支持。
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
dataSource.setConnectionTimeout(30000);
dataSource.setIdleTimeout(600000);
dataSource.setMaxLifetime(1800000);
dataSource.setMinimumIdle(2);
dataSource.setMaximumPoolSize(10);
return dataSource;
}
在实际应用中,应根据具体需求和情况选择合适的方法。对于小型应用程序,可以使用DriverManagerDataSource来获取数据库连接;对于大型应用程序,可以使用ComboPooledDataSource或JndiObjectFactoryBean来获取数据库连接,以获得更好的性能和可扩展性。
3.第三方工具
使用第三方数据源:Spring还支持集成第三方数据源,如Apache Commons DBCP、C3P0等。通过配置第三方数据源相关属性,然后使用Spring的JdbcTemplate或NamedParameterJdbcTemplate类来获取数据库连接。
总结
以JdbcTemplate类作为入口,快速梳理本章节的知识点。