Spring学习笔记:Spring JDBC(jdbc Template)的深入学习和使用
Spring JDBC是对传统JDBC访问的简单封装,使用JDBC可以提高使用,省去一些底层操作,像注册驱动,获得连接,执行查询等,Spring JDBC是一个简单的封装持久化框架。相对来说比较简单(对齐其他的mybatis,hebernate等)。功能比较简单原始,算不上真正的ORM框架,要想使用更好的话,可以在这个基础上封装。
1 Spring JDBC的能力和架构
Spring能完成和不能完成的,不能完成需要你
其实Spring能完成的很多了,像这种指定定义链接,SQL语句这些肯定需要你自己来完成,毕竟这些需要自己配置,大家不能用一个数据库一个链接一个语句。
Spring Framwork的JDBC框架4个不同的包组成:
1 core
org.springframework.jdbc.core包包含JdbcTemplate类及其各种回调接口,以及各种相关类。simple子包含SimpleJdbcInsert和SimpleJdbcCall类。namedparam子包中包含NamedParameterJdbcTemplate类和相关的支持
2 dataSource
org.springframework.jdbc.datasource包包含一个实用程序类,用于轻松访问DataSource和各种简单DataSource实现,比如SimpleDriverDataSource、DriverManagerDataSource等,可用于在Java EE容器之外测试和运行未修改的JDBC代码。embedded子包支持使用 Java 数据库引擎(如 HSQL、H2 和 Derby)创建嵌入式数据库。
3 object
org.springframework.jdbc.object包包含将RDBMS查询、更新和存储过程表示为线程安全的可重用的对象的类,比如SqlQuery、SqlUpdate等。这种更高级别的 JDBC 抽象取决于 org.springframe.jdbc.core 包中的较低级别的抽象。
4 support
org.springframework.jdbc.support包提供SQLException异常转换功能和一些实用程序类。JDBC处理期间引发的异常将转换为org.springframework.dao包中定义的异常。这意味着使用Spring JDBC抽象层的代码不需要实现JDBC或RDBMS特定的错误处理。所有转换的异常都是非受检异常,因此可以选择捕获异常获取传播到调用方。
2 Spring JDBC模版
Spring JDBC采用一些模版类来操作数据库,简化数据库操作。最著名的模版类就是JDBC Template,jdbcTemplate是spring-jdbc包中的核心类,属于Spring Framework核心组件。
JdbcTempalte是对原始JDBC API对象的封装,改造后的操作模版类。主要是用于数据库的交互,以及对表的crud等所有操作。
在Spring中带有xxxTemplate字样的类,就是对原生API的二次封装,比如RedisTemplate,HebernateTemplate等。jdbcTemplete是对JDBC的封装,做一个简化JDBC的使用,轻松配置使用Spring声明式事务。Spring官网就说过JdbcTemplate的功能:“The Spring Framework takes care of all the low-level details that can make JDBC such a tedious API.”,即Spring框架负责处理所有低级的乏味的JDBC API。
Spring JDBC提供访问数据库的模版类:
(1) jdbcTemplate:最经典且流行的Spring JDBC访问模版,提供访问Spring访问数据的功能。
(2)NamedParameterJdbcTemplate:Spring新增的一个对JdbcTemplate对象并进行扩展,提供了“named parameters(命名参数)”这个新特性,不再需要使用传统的JDBC 的“?”占位符代表sql参数。
(3)SimpleJdbcInsert:Spring2.5新增的一个多线程的,可重用的对象,为数据库提供方便的插入功能。它提供元数据处理,以简化构造基本插入语句所需的编码,只需要提供表的名称和包含列名和列值的映射。实际插入仍是使用JdbcTemplate。
(4)SimpleJdbcCall:也是Spring2.5新增,一个多线程,可重用的对象。表示对存储过程(procedure)和函数(function)的调用。它提供元数据处理来简化访问基本存储过程/函数所需的编码,只需要提供存储过程/函数的名称和在执行调用时包含参数的Map。
(5)RDBMS对象:Spring JDBC提供了数据库RDBMS操作的Java对象,包括MappingSqlQuery、SqlUpdate、StoredProcedure,分别代表可重用的sql查询操作抽象(还支持结果映射)对象、可重用的sql更新操作对象、可重用的存储过程/函数调用对象。
3 JdbcTemplate模版
3.1 JdbcTemplate概述
JdbcTemplate是Spring JDBC的核心类,jdbcTemplate主要功能:
(1)数据库CRUD操作以及存储过程/函数执行
(2)对结果集ResultSet执行迭代并提取,封装为指定类型的结果
(3)捕获JDBC异常并将其转换为org.springframe.dao 包中定义的通用、信息更丰富的异常层次结构。
(4)JdbcTemplate的参数化sql方法使用PreparedStatement,可以防止sql注入。
jdbcTemplate的方法按照名字和作用可以分为几类
(1)update:执行表的新增,修改,删除等语句,batchUpdate则是支持批处理语句
(2)query:执行表数据查询语句
(3)call:执行存储过程,参数等高级语句
(4)execute:自定义执行任何的sql语句,包括对数据库、表、字段本身的DDL操作,包括表数据的增删改查的操作,包括存储过程、函数等高级语句的调用操作。
JdbcTemplate必须和DataSource数据源一起使用,常见的数据源:
(1)DBCP:老数据源,Apache Commons
(2)c3p0:来数据源,不如DBCP流行
(3)Tomcat-jdbc:来自tomcat, Apache Commons DBCP 连接池的一种替换或备选方案,提供了更多功能比DBCP更多的功能,现在维护的还挺正常的。
(4)Proxool:不在维护
(5)BoneCP:作者不再维护,推荐Hikari
(6)Druid:alibaba,在国内,目前的新项目应该是使用的最多的数据源吧。速度非常快,并且除了管理数据库连接这个老本行之外,还提供了监控功能,比如日志监控、sql性能监控等,人家自己介绍的就是“为监控而生的数据库连接池”,维护的很不错。
(7)Hikari:一个非常推荐的,Spring Boot2.0默认数据源是Hikari,其他的框架中,HikariCP使用的也比Druid多,国外更多的是使用HikariCP,但是在国内,Druid用的更多。
3.2 配置JdbcTemplate
3.2.1 配置依赖
以使用Druid的数据源的情况下,采用注解开发,maven依赖其中spring-context提供Spring核心机制的支持,spring-jdbc提供Spring JDBC的操作支持。
<properties><spring-framework.version>5.2.8.RELEASE</spring-framework.version><mysql-connector-java>8.0.16</mysql-connector-java><druid>1.2.3</druid><lombok>1.18.12</lombok><junit>4.12</junit>
</properties>
<dependencies><!--spring 核心组件所需依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring-framework.version}</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-test --><!--Spring 测试--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring-framework.version}</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --><!--spring-jdbc--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring-framework.version}</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><!--mysql数据库驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connector-java}</version></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/druid --><!--druid数据源--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid}</version></dependency><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>${junit}</version></dependency>
</dependencies>
3.2.2配置JdbcTemplate
JdbcTemplate是线程安全的,内部状态是dataSource,不影响会话状态,可以将其交给IoC容器管理。在DAO中直接注入单例bean实例,应用程序需要多个数据库,配置多个数据源。
XML配置
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="url"value="jdbc:mysql://xxx"/><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><property name="username" value="root"/><property name="password" value="123456"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="druidDataSource"/>
</bean>
采用注解,Java Config方式配置
@ComponentScan
@Configuration
public class JdbcStart {/*** 配置JdbcTemplate*/@Beanpublic JdbcTemplate jdbcTemplate() {return new JdbcTemplate(druidDataSource());}/*** 配置Druid数据源*/@Beanpublic DruidDataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();//为了方便,直接硬编码了,如果使用Spring boot就更简单了//简单的配置数据库连接信息,其他连接池信息采用默认配置druidDataSource.setUrl("jdbc:mysql://xxx");druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");druidDataSource.setUsername("root");druidDataSource.setPassword("123456");return druidDataSource;}
}
3.2.3 数据库表
3.3 query操作
JdbcTemplate提供一系列query方法查询数据。
3.3.1 query通用查询
常用的方法就是query(String sql, RowMapper< T > rowMapper, @Nullable Object… args)。该方法查询给定的 SQL语句,使用PreparedStatement和要绑定到Statement的args参数列表,并且通过 RowMapper 将每一行查询到的数据映射到结果对象。将返回映射之后的结果对象list集合,没有查询到数据将返回一个空集合。
sql:执行的SQL查询,对于参数使用“?”占位符表示
rowMapper:将每一个查询结果映射到指定对象的会调函数
args:要绑定到查询SQL的参数数组
使用预定义的RowMapper
跟传统JDBC编程,第二个rowMapper参数仍需要很多代码将查询结果映射为Java实例对象。
实际上spring JDBC提供了一个默认映射规则rowMapper实现BeanPropertyMapper,我们只需要传递一个BeanPropertyRowMapper实现,并且执行映射对象的class即可实现自动映射。
BeanPropertyRowMapper使用了默认的映射规则,将查询结果与实例对应的属性一一映射,它的映射规则如下:
(1)数据库字段名和实体的属性名相等,或者仅仅存在大小写的区别,可以映射
(2)如果数据库字段名和实体的属性名不相等,数据库字段名有下划线_,那可以映射,这要求数据库字段名在对应下划线位置后的第一个字符为大写,其他字符必须为小写,驼峰映射。如果数据库字段名开头和结尾有下滑线_,这要求实体的属性名的开头或者结尾同样必须有下划线。
(这个不相等的情况下,如果下划线在中间就是驼峰映射实体名,但是数据库的字段下划线在开头和尾部,对应的就要也要跟数据库实体一样开头结尾是下划线。)
(3)一个数据库字段映射多个实体的属性,选择最匹配一个填充
如果查询结果不符合映射规则,那么仍然需要实现RowMapper手动指定映射规则,或者在sql语句中指定as别名!Spring JDBC还提供了其他的rowMapper实现,比如:
(1)ColumnMapRowMapper:它将每一行数据转换为一个Map,key为字段名,value为字段值
(2)SingleColumnMapper:对每一行数据提取值并返回,仅会对单个列的结果进行处理
3.3.2 queryForObject查询单行结果
要求SQL语句只返回一行结果,比如一条数据,统计结果等,但可能含多个列。
3.3.2.1 查询一行多列
查询结果为一行多列!可以使用queryForObject(String sql, RowMapper< T > rowMapper, @Nullable Object… args) 方法
3.3.2.2 查询一行一列
可以使用queryForObject(String sql, RowMapper< T > rowMapper, @Nullable Object… args) 方法,更简单
3.3.3 特性化查询
其他特性化查询操作:
1 queryForList:每一行的结果映射为一个Map,key为查询返回的字段名,value为字段值,随后将所有map置于list中返回。没有查询到结果将会报错。
2 queryForMap:只针对单行查询结果,将单行结果映射为一个Map,key为查询返回的字段名,value为字段值随后返回。没有查询到结果或者不符合要求将会报错。
3.4 update操作
jdbcTemplate提供的一系列update方法来插入,修改,删除操作。
3.4.1 插入操作
插入一行数据,采用update(String sql, @Nullable Object… args) 通用方法即可。
3.4.1.1 自增主键返回
上面那个update方法返回的结果是影响行数,我想要update方法返回主键,需要一个新的update方法。
传递一个PreparedStatementCreator和keyHolder,PreparedStatementCreator用于创建PreparedStatement,此时需要设置返回主键,而keyHolder而用于在插入完毕之后获取返回的主键。
可以看到,jdbcTemplate返回自增主键还是比较麻烦的,如果不是Java 8的lambda表达式,那么代码更加复杂。
@Test
public void insertReturn() {//插入的sqlString sql = "insert into jt_study (name,age) values (?,?)";//主键持有者KeyHolder keyHolder = new GeneratedKeyHolder();//调用另一个update方法,传递PreparedStatementCreator和KeyHolderjdbcTemplate.update(con -> {//设置返回自增主键PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);//或者如下样式//PreparedStatement ps = con.prepareStatement(sql, new String[] { "id" });//传统JDBC操作ps.setString(1, "insertReturn");ps.setInt(2, 15);return ps;}, keyHolder);//现在可以从keyHolder中获取主键long id = Objects.requireNonNull(keyHolder.getKey()).longValue();System.out.println("id: " + id);
}
主要是KeyHolder
3.4.2 修改操作
3.4.3 删除操作
3.5 batchUpdate批处理操作
JdbcTemplate提供的一系列batchUpdate方法用来批量插入、修改、删除数据。
批处理可以通过一个链接执行多个sql语句,执行效率高,资源利用率好
3.5.1 基本批处理
int[ ] batchUpdate(String sql, final BatchPreparedStatementSetter pss)
sql:固定格式的sql语句。
BatchPreparedStatementSetter:用于设置批处理的次数以及每一次批处理的参数。
int[ ]:返回一个int数组,里面的值表示对应位置的sql语句影响的数据条数。如果计数不可用,JDBC 驱动程序将返回值 -2。
3.5.2 便捷批处理
上面的批次操作,自己要设置参数比较麻烦,可以弄一个简单的版本。int[] batchUpdate(String sql, List<Object[ ]> batchArgs)
sql:固定格式的sql语句。
batchArgs:一个Object[]数组的list集合,每一个Object[]数组表示一条sql语句的参数,list集合中有多少个Object[]数组就表示本批次会执行多少个sql语句。
int[ ]:返回一个int数组,里面的值表示对应位置的sql语句影响的数据条数。如果计数不可用,JDBC 驱动程序将返回值 -2。
3.5.3 批处理分批
批处理数量批次过大,那么可能导致长期链接,吞吐量降低会发生一些问题。我们可以把这些数据量大的批次数据在分批次处理。
int[ ][ ] batchUpdate(String sql, final Collection< T > batchArgs, final int batchSize, final ParameterizedPreparedStatementSetter< T > pss)
sql:固定格式的sql语句。
batchArgs:一个list集合,每一个集合元素包含一条sql语句的参数,list集合中有多少个元素就表示会执行多少个sql语句。
batchSize:每一批sql语句的最大执行数量
ParameterizedPreparedStatementSetter:用于为每一个sql语句设置参数。
int[ ][ ]:返回一个int的二维数组,顶级二维数组的长度表示运行的批处理数,二级数组的长度指示该批处理中的更新数,每个批处理中的更新数应该是所有批处理提供的批处理大小(除了最后一个可能较少的批处理),具体取决于提供的更新对象总数。二级数组的每个元素值表示对应每个更新语句的更新计数,返回的是 JDBC 驱动程序报告的更新计数,如果计数不可用,JDBC 驱动程序将返回值 -2。
多了个批次数量的设置
3.6 其他JdbcTemplate操作
可以使用execute操作()方法运行任何的SQL,方法通常用于DDL语句。
4 NamedParameterJdbcTemplate模版
NamedParameterJdbcTemplate类使用命名参数增加了针对JDBC语句编程的支持。不仅使用经典的“?”参数对JDBC语句进行编程。参数的顺序可以不一致,但是参数名要一致。
4.1 配置NamedParameterJdbcTemplate
NamedParameterJdbcTemplate类是模版线程安全的模版类,因此我们可以直接在JdbcStart配置类中加入一个NamedParameterJdbcTemplate的@Bean方法,并且传递以前配置的jdbcTemplate对象。
/*** 配置NamedParameterJdbcTemplate*/
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {return new NamedParameterJdbcTemplate(jdbcTemplate());
}
4.2 基本使用
命名参数的使用方法很简单,在分配给sql变量的值中使用命名参数使用表示法,即使用“: xxx”作为占位符,来表示引用名为xxx的变量,替代“?”占位符,并且在命名参数变量(MapSqlParameterSource)中的设置的相应值,随后传递给query方法即可。
4.3 编译批处理
NamedParameterJdbcTemplate的批处理和JdbcTempelate差不多,没有了批处理分批的功能,参数不是传递List<Object[]>,而是传递一个Map<String, ?>[]数组,即数组元素为Map,Map里面包含了当前执行的sql的命名参数及其对象的值的映射,数组长度就是批处理数量。
5 SimpleJdbc简化操作类
Spring JDBC提供一系列的SimpleJdbc类,用于进一步简化JDBC操作。
SimpleJdbcInsert和SimpleJdbcCall类利用可以通过JDBC驱动程序检索数据库元数据来提供简化的配置。
5.1 SimpleJdbcInsert简化插入
SimpleJdbcInsert用于简化插入操作!它的操作都是execute方法完成的,都是insert操作
5.1.1 初始化SimpleJdbcInsert
SimpleJdbcInsert对象需要使用dataSource或者JdbcTemplate初始化,因此我们需要dataSourcce或者JdbcTempalte实例。
一个SimpleJdbcInsert对象还需要和一个数据库表绑定,通过withTableName方法指定一个数据库表的名字,随后就可以操作这个数据库表。因此,我们通常需要为一个DAO创建一个SimpleJdbcInsert对象。
5.1.2 基本插入数据
基本插入数据采用execute方法就可以,传递一个Map,key为对应的表的字段名,value为修改的值。
5.1.3 默认值和主键返回
插入数据库id增加,但是create_time相关字段没有变更,设置的是create_time为CURRENT_TIMESTAMP,即插入时自动填充当前时间,但是并没有填充。
设置的是create_time为CURRENT_TIMESTAMP,即插入时自动填充当前时间,但是并没有填充。
使用 UsingColumns 方法指定列名称列表来限制插入的列,此时,其他的列将同样会使用默认值。我们加入如下配置,并且没有配置create_time。
5.1.4 SqlParameterSource参数源
使用Map提供参数值,这是没问题的可以正常工作,但却不是最方便的方法。类似于NamedParameterJdbcTemplate,这里的SimpleJdbcInsert同样可以使用SqlParameterSource来代理Map提供参数。
另一个选择是类似于 Map 的 MapSqlParameterSource参数源实现,它提供了一种可以链式编程的更方便的 addValue 方法。
5.2 SimpleJdbcCall简化存储过程/函数
可以以类似于声明 SimpleJdbcInsert 的方式声明 SimpleJdbcCall。SimpleJdbcCall 类提供了对存储过程/函数的简化调用方式,它使用数据库中的元数据查找输入(in)和退出(out)参数的名称,因此不必显式声明它们。
5.2.1 初始化SimpleJdbcCall
SimpleJdbcCall对象需要使用dataSource或者JdbcTemplate初始化,因此我们要引入dataSoure或者JdbcTemplate的实例。
一个SimpleJdbcCall对象还需要一个存储过程/函数绑定,可以通过withProcedureName方法指定一个存储过程或者通过withFunctionName指定一个函数的名字,注意一个SimpleJdbcCall对象只能绑定其中一个。
private SimpleJdbcCall simpleJdbcCall;@PostConstruct
public void simpleJdbcCall() {//初始化SimpleJdbcInsert,通过withProcedureName绑定一个存储过程//或者通过withFunctionName绑定一个函数名simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate).withProcedureName("myProcedure");
}
5.2.2 执行存储过程
创建一个存储过程,以下是一个存储过程,输入一个ageNum,如果数据库对应的表jt_study有就返回1,否则返回0
(这个存储过程的变量设置有点像go,类型在后面)
CREATE PROCEDURE `myProcedure`( IN ageNum INT, OUT flag INT )
BEGINDECLAREnum INT;SET flag = 1;SELECTcount( * ) INTO num FROMjt_study WHEREage = ageNum;IFnum != 1 THENSET flag = 0;END IF;END
使用这个存储过程,使用execte方法
@Test
public void executeProcedure() {//IN 参数源MapSqlParameterSource msps = new MapSqlParameterSource("ageNum", 0);//执行存储过程,获取返回值Map<String, Object> result = simpleJdbcCall.execute(msps);System.out.println(result);
}
5.2.3 执行函数
创建一个函数
CREATE FUNCTION `myFun`( ia INT, ib INT ) RETURNS intDETERMINISTIC
BEGINRETURN ia + ib;END
使用函数创建一个JdbcCall
private SimpleJdbcCall simpleJdbcCall2;@PostConstruct
public void simpleJdbcCall2() {//初始化simpleJdbcCall2,通过withFunctionName绑定一个函数名simpleJdbcCall2 = new SimpleJdbcCall(jdbcTemplate).withFunctionName("myFun");
}
通过这个SimpleJdbcCall在通过这个execute调用这个函数
@Test
public void executeFun() {//IN 参数源MapSqlParameterSource msps = new MapSqlParameterSource();msps.addValue("ia", 1).addValue("ib", 4);//通过execute执行存储过程,获取返回值Map<String, Object> result = simpleJdbcCall2.execute(msps);System.out.println(result);//更简单的,通过executeFunction方法还行函数,返回对应类型的返回值Integer integer = simpleJdbcCall2.executeFunction(Integer.class, msps);System.out.println(integer);
}
5.2.4 封装结果集
对于返回结果集的存储过程或者函数,可以使用返回ResultSet方法,指定返回结果集的名称。并声明要用于特定参数的 RowMapper 实现(指定对结果集的封装操作)。
6 Spring JDBC的总结
Spring JDBC是Spring提供了简单的框架,对传统JDBC进行简单的包装,核心类是JdbcTemplate。JdbcTempalte也能够解决SQL注入。
相比于MyBatis、Hibernate这种重量级框架,JdbcTemplate作为轻量级框架,它的功能并不是很完善,相比于Hibernate它仍然需要写sql语句,相比于MyBatis它不支持动态sql,另外可能需要大量的rowmapper对象,对于查询操作也很容易抛出各种异常(比如没查询到数据或者返回的数据格式不满足),对于一些大型项目如果需要使用JdbcTemplate,那么可能需要我们自己封装一些工具类来增强。不过由于这个框架非常的底层,仅仅是对JDBC操作做了简单的封装,因此sql执行效率更高,对于复杂sql的效果更加明显。
通常,如果不想引入外部数据框架,那么可以使用Spring Data JPA + JdbcTemplate,简单的操作使用JPA,不需要写sql语句,可以真正的实现面对对象,这正是领域驱动设计(DDD:Domain-Driven Design)的追求,而复杂操作可以使用JdbcTemplate直接写sql语句,如果一个系统的需要大量的复杂sql语句,不同领域模型的组合,或许是数据库设计的问题(比如为了节省某个字段造成多表联查)!或者,也可以直接使用简单的Spring Data JDBC来代替二者。