基于最新Spring 5.x,详细介绍了Spring JDBC(JdbcTemplate)框架的使用!html
Spring JDBC是对传统JDBC访问的简单封装,使用Spring JDBC以后,能够省去一部分之前须要开发人员编写的访问数据的底层操做,好比注册驱动、得到链接、执行查询等等。Spring JDBC至关于一个简单封装的持久层框架,原始功能比较简单,使用起来也比较简单,若是开发一些小型项目,那仍是能够直接使用的,若是是一些大型项目,因为它并非真正的orm框架,所以须要本身封装一些工具,若是有能力封装的话,那么Spring JDBC用起来是很是舒服的,性能也很强,不比mybatis差!
java
文章目录
1 Spring JDBC的能力和架构
下表展现了Spring JDBC能为咱们作了哪些事,咱们本身又须要作哪些事。这些事在之前都是须要开发人员作的。mysql
Action | Spring | You |
Define connection parameters(定义链接参数) | Y | |
Open the connection.(打开链接) | Y | |
Specify the SQL statement.(指定sql语句-statement) | Y | |
Declare parameters and provide parameter values.(声明参数并提供参数值) | Y | |
Prepare and run the statement.(准备并运行sql语句-statement) | Y | |
Set up the loop to iterate through the results (if any).(若是有须要,那么循环遍历结果集-resultset) | Y | |
Do the work for each iteration.(执行每一个迭代的工做) | Y | |
Process any exception.(处理任何异常。) | Y | |
Handle transactions.(处理事务。) | Y | |
Close the connection, the statement, and the resultset.(关闭链接- connection、语句- statement和结果集- resultset。) | Y |
Spring Framework 的 JDBC框架由四个不一样的包组成:git
- core:org.springframework.jdbc.core包包含JdbcTemplate类及其各类回调接口,以及各类相关类。simple子包含SimpleJdbcInsert和SimpleJdbcCall类。namedparam子包中包含NamedParameterJdbcTemplate类和相关的支持类。
- datasource:org.springframework.jdbc.datasource包包含一个实用程序类,用于轻松访问DataSource和各类简单DataSource实现,好比SimpleDriverDataSource、DriverManagerDataSource等,可用于在Java EE容器以外测试和运行未修改的JDBC代码。embedded子包支持使用 Java 数据库引擎(如 HSQL、H2 和 Derby)建立嵌入式数据库。
- object:org.springframework.jdbc.object包包含将RDBMS查询、更新和存储过程表示为线程安全的可重用的对象的类,好比SqlQuery、SqlUpdate等。这种更高级别的 JDBC 抽象取决于 org.springframe.jdbc.core 包中的较低级别的抽象。
- support:org.springframework.jdbc.support包提供SQLException异常转换功能和一些实用程序类。JDBC处理期间引起的异常将转换为org.springframework.dao包中定义的异常。这意味着使用Spring JDBC抽象层的代码不须要实现JDBC或RDBMS特定的错误处理。全部转换的异常都是非受检异常,所以能够选择捕获异常获取传播到调用方。
2 Spring JDBC模版
Spring JDBC采用一些模板类来操做数据库,用以简化数据库操做,其中最著名的就是JdbcTemplate,JdbcTemplate是spring-jdbc包中的核心类,属于Spring Framework核心组件!
JdbcTemplate是对原始JDBC API对象的封装、改造以后的操做模板类,它就是用于和数据库交互的,实现对表的CRUD等全部操做。Spring中凡是带有xxxTemplate字样的类,基本上都是对于原生API的二次封装,常见模版好比HibernateTemplate、RedisTemplate、JmsTemplate、RestTemplate等等。
JdbcTemplate简化了原始JDBC的使用步骤,而使用JdbcTemplate以后,咱们只须要提供sql语句和获取结果集就好了,同时能够很轻松的配置使用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还提供了更多的能够访问数据库的模板类:github
- JdbcTemplate:最经典且最流行的Spring JDBC访问模版,提供了基本的Spring JDBC访问数据的功能!
- NamedParameterJdbcTemplate:Spring 2.0新增的模版,内部包装了一个JdbcTemplate对象并进行功能扩展,提供了“named parameters(命名参数)”这个新特性,再也不须要使用传统的JDBC 的“?”占位符表明sql参数。
- SimpleJdbcInsert:Spring 2.5新增的模版,一个多线程的、可重用的对象,为数据库表提供了方便的插入功能。它提供元数据处理,以简化构造基本插入语句所需的编码,只须要提供表的名称和包含列名和列值的映射。实际插入还是使用JdbcTemplate。
- SimpleJdbcCall:Spring 2.5新增的模版,一个多线程的、可重用的对象,表示对存储过程(procedure)和函数(function)的调用。它提供元数据处理来简化访问基本存储过程/函数所需的编码,只须要提供存储过程/函数的名称和在执行调用时包含参数的Map。提供的参数的名称将与建立存储过程时声明的输入(in)和输出(out)参数匹配。
- RDBMS对象:Spring JDBC提供了数据库RDBMS操做的Java抽象,包括MappingSqlQuery、SqlUpdate、StoredProcedure,分别表明可重用的sql查询操做抽象(还支持结果映射)对象、可重用的sql更新操做对象、可重用的存储过程/函数调用对象。它们的特色是能够安全的重复使用!
咱们主要学习JdbcTemplate的使用!web
3 JdbcTemplate模版
3.1 JdbcTemplate的概述
JdbcTemplate 是 Spring JDBC 的核心类,咱们学习Spring JDBC的使用,主要就是学习这个JdbcTemplate核心类的使用!
JdbcTemplate主要功能以下:spring
- 数据库CRUD操做以及存储过程/函数执行。
- 对结果集ResultSet 执行迭代并提取、封装为指定类型的结果。
- 捕获 JDBC 异常并将其转换为 org.springframe.dao 包中定义的通用、信息更丰富的异常层次结构。
- JdbcTemplate的参数化sql方法使用PreparedStatement,能够防止sql注入。
JdbcTemplate的方法按照名字和做用能够为分几大类:sql
- update:执行表数据的新增、修改、删除等语句,batchUpdate则是支持批处理语句;
- query:执行表数据查询语句。
- call:执行存储过程、函数等高级语句。
- execute:自定义执行任何的sql语句,包括对数据库、表、字段自己的DDL操做,包括表数据的增删改查的操做,包括存储过程、函数等高级语句的调用操做。
JdbcTemplate必须和DataSource数据源一块儿使用。常见的Java数据源有:数据库
- DBCP:老数据源了,来自Apache Commons,如今维护的还挺正常的。https://github.com/apache/commons-dbcp。
- c3p0:老数据源了,也还在用,可是维护的没有DBCP多。https://github.com/swaldman/c3p0。
- Tomcat-jdbc:来自tomcat, Apache Commons DBCP 链接池的一种替换或备选方案,提供了更多功能比DBCP更多的功能,如今维护的还挺正常的。
- Proxool:老古董了,做者在2016年声明已放弃维护,声明说他本人甚至在2006年的时候就不使用Java语言了……。https://github.com/proxool/proxool。
- BoneCP:老古董了,性能击败了DBCP、c3p0,但做者在2015年声明已放弃维护,还推荐使用HikariCP……。https://github.com/wwadge/bonecp。
- Druid:后起之秀。alibaba的,大品牌,值得信赖。在国内,目前的新项目应该是使用的最多的数据源吧。速度很是快,而且除了管理数据库链接这个老本行以外,还提供了监控功能,好比日志监控、sql性能监控等,人家本身介绍的就是“为监控而生的数据库链接池”,维护的很不错。项目地址:https://github.com/alibaba/druid。官方数据:https://github.com/alibaba/druid/wiki/Druid%E8%BF%9E%E6%8E%A5%E6%B1%A0%E4%BB%8B%E7%BB%8D。
- HikariCP:后起之秀。这里的Hikari来源日语,翻译过来就是“光”,就像“光”同样快?看来做者是个老二次元了。人家本身介绍的就是“Fast, simple, reliable”,就是一个存粹的很是很是快的链接池,老牛逼了,维护的也很好。https://github.com/brettwooldridge/HikariCP
- Spring Boot 2.0开始,默认数据源就是HikariCP,其余的框架中,HikariCP使用的也比Druid多,国外更多的是使用HikariCP,可是在国内,Druid用的更多。关于Druid和HikariCP的“官方撕逼”1:https://github.com/brettwooldridge/HikariCP/issues/232。“官方撕逼”2:https://www.zhihu.com/question/53892749/answer/518733788。因此说,如今的新项目应该用什么数据源链接池,不用我说了吧?
另外,某些开发者使用Spring提供的DriverManagerDataSource或者SimpleDriverDataSource数据源,然而该类只不过实现相同的标准接口,它们确实是一个Spring框架自带的数据源,可是不支持链接池管理,每次链接数据库都是建立新的链接对象,这个类本身去看看源码甚至注释就知道了,这些数据源仅仅用于测试!apache
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实例便可。若是应用程序访问多个数据库,这须要多个数据源,随后须要多个配置不一样数据源的 JdbcTemplate 实例。
可使用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方式配置,这种方式目前更流行,咱们下面的案例都是使用Java Config方式配置,舍弃了XML文件!
@ComponentScan
@Configuration
public class JdbcStart {
/**
* 配置JdbcTemplate
*/
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(druidDataSource());
}
/**
* 配置Druid数据源
*/
@Bean
public 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 数据库表
本人数据库是MySql 8版本。数据库表:
CREATE TABLE `jt_study` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`name` VARCHAR ( 200 ) DEFAULT NULL COMMENT '姓名',
`age` INT ( 11 ) DEFAULT NULL COMMENT '年龄',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '建立时间',
PRIMARY KEY ( `id` )
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
插入一些数据:
INSERT INTO `jt_study`
VALUES
( NULL, 'Google', 12, '2019-04-21 15:55:15' ),
( NULL, '淘宝', 11, CURRENT_TIMESTAMP() ),
( NULL, '百度', 1, '2018-04-21 15:55:15' ),
( NULL, '微博', 5, CURRENT_TIMESTAMP() ),
( NULL, 'Facebook', 5, '2020-04-21 15:55:15' );
实体:
public class JtStudy {
private Date createTime;
private Integer id;
private String name;
private Integer age;
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "JtStudy{" +
"createTime=" + createTime +
", id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public JtStudy() {
}
public JtStudy(String name, Integer age) {
this.name = name;
this.age = age;
}
}
3.2.4 测试类
咱们使用spring-test进行测试!测试类以下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JdbcStart.class)
public class JdbcTest {
@Resource
private JdbcTemplate jdbcTemplate;
}
咱们首先看看配置成功没有:
@Test
public void testJdbcTemplate() {
System.out.println(jdbcTemplate);
System.out.println(Objects.requireNonNull(jdbcTemplate.getDataSource()).getClass());
}
结果以下:
org.springframework.jdbc.core.JdbcTemplate@5e5d171f
class com.alibaba.druid.pool.DruidDataSource
能够看到数据源确实是使用的DruidDataSource数据源,这说明配置成功,下面正式使用。
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:将每个查询结果映射到指定对象的回调函数,咱们能够本身指定,也可使用预约义映射函数!这实际上就是此前传统JDBC编程中对ResultSet的处理!
args:要绑定到查询sql的参数数组。
查询jt_study表中age为5的数据!
@Test
public void query() {
String sql = "select * from jt_study where age = ?";
List<JtStudy> jtStudies = jdbcTemplate.query(sql, (rs, rowNum) -> {
/*
* mapRow映射函数
* @param rs 要映射的ResultSet结果集
* @param rowNum 当前行的编号
* @return 当前行的结果对象
*/
JtStudy jtStudy = new JtStudy();
jtStudy.setId(rs.getInt("id"));
jtStudy.setName(rs.getString("name"));
jtStudy.setAge(rs.getInt("age"));
jtStudy.setCreateTime(rs.getDate("create_time"));
return jtStudy;
}, 5);
System.out.println(jtStudies);
}
结果以下:
[JtStudy{
id=4, name='微博', age=5, createTime=2020-11-29}, JtStudy{
id=5, name='Facebook', age=5, createTime=2020-04-21}]
能够看到,咱们只须要一个方法query方法,第一个参数是query语句,而后传递一个rowMapper映射实现将查询结果封装为JtStudy对象(我这里传递的是lambda对象),第三个参数传递sql的参数,便可获取结果。
在DAO开发中,咱们能够为每一个实例单独实现一个RowMapper并单独提取出来,这样能够被其余sql复用,代码更加优化。
private final RowMapper<JtStudy> jtStudyRowMapper = (rs, rowNum) -> {
JtStudy jtStudy = new JtStudy();
jtStudy.setId(rs.getInt("id"));
jtStudy.setName(rs.getString("name"));
jtStudy.setAge(rs.getInt("age"));
jtStudy.setCreateTime(rs.getDate("create_time"));
return jtStudy;
};
public List<JtStudy> findAllJdbcTests() {
return jdbcTemplate.query("select * from jt_study ", jtStudyRowMapper);
}
public List<JtStudy> findJdbcTestsByAge(int age) {
return jdbcTemplate.query("select * from jt_study where age = ?", jtStudyRowMapper, age);
}
@Test
public void daoQuery() {
//service调用
List<JtStudy> jtStudies = findJdbcTestsByAge(5);
System.out.println(jtStudies);
//service调用
List<JtStudy> allJtStudies = findAllJdbcTests();
}
3.3.1.1 使用预约义的RowMapper
相比于传统JDBC编程,咱们看到上面的代码确实更加方便了,可是第二个rowMapper参数仍然须要不少的代码去将查询结果映射为Java实例对象,比较麻烦。实际上spring JDBC提供了一个默认映射规则的rowMapper实现BeanPropertyRowMapper,咱们只须要传递一个BeanPropertyRowMapper实现,而且执行映射对象的class便可实现自动映射:
@Test
public void beanPropertyRowMapper() {
String sql = "select * from jt_study where age = ?";
List<JtStudy> jtStudies = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(JtStudy.class), 5);
//也可使用BeanPropertyRowMapper.newInstance静态方法免去咱们手动new对象
//List<JtStudy> jtStudies = jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(JtStudy.class), 5);
System.out.println(jtStudies);
}
是否是发现代码忽然清爽了好多^_^,没错BeanPropertyRowMapper使用了默认的映射规则,将查询结果与实例对应的属性一一映射,它的映射规则以下:
- 数据库字段名和实体的属性名相等,或者仅仅存在大小写的区别,那么能够映射;
- 若是数据库字段名和实体的属性名不相等,数据库字段名有下滑线_,实体的属性名下划线_,那么能够映射,这要求数据库字段名在对应下划线位置后的第一个字符为大写,其余字符必须为小写(开头第一个字符除外),这就是驼峰映射!若是数据库字段名开头和结尾有下滑线_,这要求实体的属性名的开头或者结尾一样必须有下划线!
- 若是一个数据库字段映射了多个实体的属性,那么选择最匹配的那一个填充。
因此,若是查询结果不符合映射规则,那么仍然须要实现RowMapper手动指定映射规则,或者在sql语句中指定as别名!Spring JDBC还提供了其余的rowMapper实现,好比:
- ColumnMapRowMapper:它将每一行数据转换为一个Map,key为字段名,value为字段值。
- SingleColumnRowMapper:对每一行数据提取值并返回,仅会对单个列的结果进行处理。
3.3.2 queryForObject查询单行结果
要求sql语句只会返回一行结果,好比一条数据、统计结果等,但可能包含多个列。
3.3.2.1 查询一行多列
查询jt_study表中age为11的数据,查询结果为一行多列!可使用queryForObject(String sql, RowMapper< T > rowMapper, @Nullable Object… args) 方法:
@Test
public void queryForObject() {
String sql = "select * from jt_study where age = ?";
JtStudy jtStudy = jdbcTemplate.queryForObject(sql, BeanPropertyRowMapper.newInstance(JtStudy.class), 11);
System.out.println(jtStudy);
}
若是没有查找到数据或者不是单行数据,那么将抛出异常。
3.3.2.2 查询一行一列
查询jt_study表中的数据行数,很明显查询结果为一行一列!可使用queryForObject(String sql, RowMapper< T > rowMapper, @Nullable Object… args) 方法,更简单!
@Test
public void queryCount() {
String sql = "select count(*) from jt_study ";
Long count=jdbcTemplate.queryForObject(sql, Long.class, 11);
System.out.println(count);
}
若是没有查找到数据或者不是一行一列的数据,那么将抛出异常。
3.3.3 特性化查询
其余特性化查询操做,好比:
- queryForList:每一行的结果映射为一个Map,key为查询返回的字段名,value为字段值,随后将全部map置于list中返回。没有查询到结果将会报错。
- queryForMap:只针对单行查询结果,将单行的结果映射为一个Map,key为查询返回的字段名,value为字段值,随后返回。没有查询到结果或者不符合要求将会报错。
@Test
public void queryOther() {
List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from jt_study where age = 5 ");
System.out.println(maps);
Map<String, Object> stringObjectMap = jdbcTemplate.queryForMap("select * from jt_study where age = 11");
System.out.println(stringObjectMap);
}
3.4 update操做
JdbcTemplate 提供的一系列update方法用来插入、修改、删除数据!
3.4.1 插入操做
插入一行数据!采用update(String sql, @Nullable Object… args) 通用方法便可!
@Test
public void insert() {
//插入的sql
String sql = "insert into jt_study (id,name,age) values (?,?,?)";
//调用update,返回受影响的行数
int insert = jdbcTemplate.update(sql, null, "insert1", 14);
System.out.println(insert);
//查询该插入数据
String querySql = "select * from jt_study where age = ?";
JtStudy jtStudy = jdbcTemplate.queryForObject(querySql, BeanPropertyRowMapper.newInstance(JtStudy.class), 14);
System.out.println(jtStudy);
}
3.4.1.1 自增主键返回
插入后自增主键返回!这里须要采用另外一个update方法,传递一个PreparedStatementCreator和keyHolder,PreparedStatementCreator用于建立PreparedStatement,此时须要设置返回主键,而keyHolder而用于在插入完毕以后获取返回的主键!
能够看到,jdbcTemplate返回自增主键仍是比较麻烦的,若是不是Java 8的lambda表达式,那么代码更加复杂!
@Test
public void insertReturn() {
//插入的sql
String sql = "insert into jt_study (name,age) values (?,?)";
//主键持有者
KeyHolder keyHolder = new GeneratedKeyHolder();
//调用另外一个update方法,传递PreparedStatementCreator和KeyHolder
jdbcTemplate.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);
}
3.4.2 修改操做
修改操做是一样的逻辑!
@Test
public void update() {
//修改的sql
String updateSql = "update jt_study set name = ? where age = ?";
//调用update,返回受影响的行数
jdbcTemplate.update(updateSql, "update", 14);
//查询该修改的数据
String querySql = "select * from jt_study where age = ?";
JtStudy jtStudy = jdbcTemplate.queryForObject(querySql, BeanPropertyRowMapper.newInstance(JtStudy.class), 14);
System.out.println(jtStudy);
}
3.4.3 删除操做
删除操做是一样的逻辑!
@Test
public void delete() {
//修改的sql
String deleteSql = "delete from jt_study where age = ?";
//调用update,返回受影响的行数
jdbcTemplate.update(deleteSql, 14);
//查询剩下的数据
String querySql = "select * from jt_study ";
List<JtStudy> query = jdbcTemplate.query(querySql, BeanPropertyRowMapper.newInstance(JtStudy.class));
System.out.println(query);
}
3.5 batchUpdate批处理操做
JdbcTemplate 提供的一系列batchUpdate方法用来批量插入、修改、删除数据!批处理能够经过一个链接执行多个sql语句,执行效率高,资源利用率好!
3.5.1 基本批处理
int[ ] batchUpdate(String sql, final BatchPreparedStatementSetter pss)
sql:固定格式的sql语句。
BatchPreparedStatementSetter:用于设置批处理的次数以及每一次批处理的参数。
int[ ]:返回一个int数组,里面的值表示对应位置的sql语句影响的数据条数。若是计数不可用,JDBC 驱动程序将返回值 -2。
以下案例,批量插入一批数据,更新和删除的语法同样:
@Test
public void basicBatch() {
//参数数组集合,这表示将会批量插入三条数据,在web项目中,这个集合能够经过service传递给dao
List<JtStudy> batchArgs = new ArrayList<>();
batchArgs.add(new JtStudy("basicBatch1", 0));
batchArgs.add(new JtStudy("basicBatch2", 0));
batchArgs.add(new JtStudy("basicBatch3", 0));
//service调用dao的方法,返回每条sql影响的数据条数数组
int[] ints = basicBatchDao(batchArgs);
//查询所有的数据
String querySql = "select * from jt_study";
List<JtStudy> query = jdbcTemplate.query(querySql, BeanPropertyRowMapper.newInstance(JtStudy.class));
System.out.println(query);
}
private int[] basicBatchDao(List<JtStudy> batchArgs) {
//插入的sql
String insertSql = "insert into jt_study (name,age) values (?,?)";
//调用batchUpdate
return jdbcTemplate.batchUpdate(insertSql, new BatchPreparedStatementSetter() {
/**
* 设置sql参数值
* @param ps 当前sql预处理对象
* @param i 当前处理sql的位置
*/
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, batchArgs.get(i).getName());
ps.setInt(2, batchArgs.get(i).getAge());
}
/**
* @return 返回批处理的大小(执行次数)
*/
@Override
public int getBatchSize() {
return batchArgs.size();
}
});
}
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。
@Test
public void batchInsert() {
//插入的sql
String insertSql = "insert into jt_study (name,age) values (?,?)";
//参数数组集合,这表示将会批量插入三条数据
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{
"batchInsert1", -1});
batchArgs.add(new Object[]{
"batchInsert2", -2});
batchArgs.add(new Object[]{
"batchInsert3", -3});
//调用batchUpdate
jdbcTemplate.batchUpdate(insertSql, batchArgs);
//查询所有的数据
String querySql = "select * from jt_study";
List<JtStudy> query = jdbcTemplate.query(querySql, BeanPropertyRowMapper.newInstance(JtStudy.class));
System.out.println(query);
}
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。
@Test
public void batchBatch() {
//参数数组集合,这表示将会批量插入五条数据,在web项目中,这个集合能够经过service传递给dao
List<JtStudy> batchArgs = new ArrayList<>();
batchArgs.add(new JtStudy("batchBatch1", -4));
batchArgs.add(new JtStudy("batchBatch2", -4));
batchArgs.add(new JtStudy("batchBatch3", -4));
batchArgs.add(new JtStudy("batchBatch4", -4));
batchArgs.add(new JtStudy("batchBatch5", -4));
//service调用dao的方法
//返回一个int[][]二维数组,第一个数值表示第几批,第二个数值表示某一批处理中的某条sql影响的数据条数
int[][] ints = batchBatchDao(batchArgs);
System.out.println(Arrays.deepToString(ints));
}
private int[][] batchBatchDao(List<JtStudy> batchArgs) {
//插入的sql
String insertSql = "insert into jt_study (name,age) values (?,?)";
//调用batchUpdate,设置每一批最多处理2个sql,所以五条数据将分为3批
//返回一个int[][]二维数组,第一个数值表示第几批,第二个数值表示某一批处理中的某条sql影响的数据条数
return jdbcTemplate.batchUpdate(insertSql,
batchArgs,
2,
(ps, argument) -> {
ps.setString(1, argument.getName());
ps.setInt(2, argument.getAge());
});
}
3.6 其余 JdbcTemplate 操做
咱们可使用 execute ()方法运行任何任意 SQL。所以,该方法一般用于 DDL 语句。
下面的示例建立一个表:
@Test
public void execute() {
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
}
你甚至可使用update调用一个存储过程,可是没有返回值,只能本身查找!
建立一个存过:
CREATE PROCEDURE demo_in_parameter ( IN ageNum INT, OUT flag INT ) BEGIN
DECLARE
num INT;
SET flag = 1;
SELECT
count( * ) INTO num
FROM
jt_study
WHERE
age = ageNum;
IF
num != 1 THEN
SET flag = 0;
END IF;
END;
调用:
@Test
public void procedure() {
jdbcTemplate.update("call test.demo_in_parameter(?,@flag)", 12);
System.out.println(jdbcTemplate.queryForObject("select @flag", Integer.class));
jdbcTemplate.update("call test.demo_in_parameter(?,@flag)", 5);
System.out.println(jdbcTemplate.queryForObject("select @flag", Integer.class));
}
4 NamedParameterJdbcTemplate模版
NamedParameterJdbcTemplate 类使用命名参数增长了对 JDBC 语句编程的支持,而不是仅使用经典占位符 "?"参数对 JDBC 语句进行编程。这样的好处就是不须要参数顺序一致,只须要保证命名参数惟一便可!
NamedParameterJdbcTemplate类包装了一个JdbcTemplate,并委托到包装的 JdbcTemplate上完成大部分工做,可使用getJdbcOperations()方法获取内部包装的 JdbcTemplate。
这里仅介绍NamedParameterJdbcTemplate类中与 JdbcTemplate 自己不一样的地方,即便用命名参数对 JDBC 语句进行编程。
4.1 配置NamedParameterJdbcTemplate
NamedParameterJdbcTemplate是线程安全的模板类,所以咱们能够直接在JdbcStart配置类中加入一个NamedParameterJdbcTemplate的@Bean方法,而且传递之前配置的jdbcTemplate对象!
/**
* 配置NamedParameterJdbcTemplate
*/
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(jdbcTemplate());
}
随后在测试类JdbcTest中引入NamedParameterJdbcTemplate便可使用:
@Resource
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
4.2 基本使用
命名参数的使用方法很简单,在分配给 sql 变量的值中使用命名参数表示法,即便用“:xxx”做为占位符,来表示引用名为xxx的变量,替代“?”占位符,而且在命名参数变量(MapSqlParameterSource)中的设置的相应值,随后传递给query方法便可!
好比查询jt_study表中age为5的数据!
@Test
public void namedParameterQuery() {
String sql = "select * from jt_study where age = :age";
//创建一个SqlParameterSource,只有一个参数
SqlParameterSource namedParameters = new MapSqlParameterSource("age", 5);
//调用查询
List<JtStudy> query = namedParameterJdbcTemplate.query(sql, namedParameters, BeanPropertyRowMapper.newInstance(JtStudy.class));
System.out.println(query);
//若是有多个参数,那么能够传递一个map
//HashMap<String, Object> objectObjectHashMap = new HashMap<>();
//objectObjectHashMap.put("age",5);
//MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource(objectObjectHashMap);
}
若是以为使用MapSqlParameterSource画蛇添足,那么支持更简单的Map参数,NamedParameterJdbcTemplate会自动建立一个MapSqlParameterSource。
@Test
public void namedParameterMapQuery() {
String sql = "select * from jt_study where age = :age";
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("age",5);
//调用查询,直接传递一个map
List<JtStudy> query = namedParameterJdbcTemplate.query(sql, stringObjectHashMap, BeanPropertyRowMapper.newInstance(JtStudy.class));
System.out.println(query);
}
SqlParameterSource表示sql命名参数值的来源抽象,上面咱们就见过了它的一个实现MapSqlParameterSource,MapSqlParameterSource简单的包装了一个Map做为参数来源,另外提供的一个实现就是BeanPropertySqlParameterSource,此类包装任意 JavaBean(即符合 JavaBean 约定的类的实例:https://www.oracle.com/java/technologies/javase/javabeans-spec.html),并使用包装的 JavaBean 的属性做为命名参数值的来源。
有了BeanPropertySqlParameterSource,咱们就再也不封装map,而是直接传递JavaBean实例便可,NamedParameterJdbcTemplate会自动查找与sql命名参数变量一致的JavaBean的属性名,将属性值做为该sql参数值。
web开发中常见的一种BeanPropertySqlParameterSource使用形式以下:
@Test
public void beanPropertySqlParameterSource() {
//service中的对象
JtStudy jtStudy = new JtStudy();
jtStudy.setAge(5);
//调用dao的方法,传递一个对象便可
List<JtStudy> jtStudies = beanPropertySqlParameterSourceDao(jtStudy);
System.out.println(jtStudies);
}
private List<JtStudy> beanPropertySqlParameterSourceDao(JtStudy jtStudy) {
String sql = "select * from jt_study where age = :age";
BeanPropertySqlParameterSource bpsps = new BeanPropertySqlParameterSource(jtStudy);
return namedParameterJdbcTemplate.query(sql, bpsps, BeanPropertyRowMapper.newInstance(JtStudy.class));
}
4.3 便捷批处理
NamedParameterJdbcTemplate的批处理和JdbcTemplate差很少,可是没有了批处理分批的功能。另外,参数不是传递List<Object[]>,而是传递一个Map<String, ?>[]数组,即数组元素为Map,Map里面包含了当前执行的sql的命名参数及其对象的值的映射,数组长度就是批处理数量。
这里的传递的Map数组将会被SqlParameterSourceUtils经过createBatch转换为SqlParameterSource[ ]数组!
@Test
public void namedParameterBatch() {
String sql = "insert into jt_study (name,age) values (:name , :age )";
HashMap<String, Object> stringObjectHashMap1 = new HashMap<>();
stringObjectHashMap1.put("age", 20);
stringObjectHashMap1.put("name", "namedParameterBatch1");
HashMap<String, Object> stringObjectHashMap2 = new HashMap<>();
stringObjectHashMap2.put("name", "namedParameterBatch2");
stringObjectHashMap2.put("age", 20);
//参数数组
Map<String, Object>[] maps = new Map[]{
stringObjectHashMap1, stringObjectHashMap2};
int[] ints = namedParameterJdbcTemplate.batchUpdate(sql, maps);
System.out.println(Arrays.toString(ints));
}
很明显上面的方式太麻烦了,对此咱们能够直接使用SqlParameterSourceUtils.createBatch传递一个对象集合,来快速建立SqlParameterSource[ ] 数组:
@Test
public void sqlParameterSourceUtilsBatch() {
String sql = "insert into jt_study (name,age) values ( :name , :age )";
List<JtStudy> batchArgs = new ArrayList<>();
batchArgs.add(new JtStudy("SqlParameterSource1", 21));
batchArgs.add(new JtStudy("SqlParameterSource2", 21));
batchArgs.add(new JtStudy("SqlParameterSource3", 21));
int[] ints = namedParameterJdbcTemplate.batchUpdate(sql, SqlParameterSourceUtils.createBatch(batchArgs));
System.out.println(Arrays.toString(ints));
}
5 SimpleJdbc简化操做类
Spring JDBC提供了一系列的SimpleJdbc类,用于进一步简化 JDBC 操做。
SimpleJdbcInsert 和 SimpleJdbcCall 类利用能够经过 JDBC 驱动程序检索的数据库元数据来提供简化的配置,这意味着咱们能够更少地进行前期配置。
5.1 SimpleJdbcInsert简化插入
SimpleJdbcInsert用于简化插入操做!它的操做都是execute方法完成的,都是insert操做!
5.1.1 初始化SimpleJdbcInsert
SimpleJdbcInsert对象须要使用dataSource或者jdbcTemplate初始化,所以咱们须要引入dataSource或者jdbcTemplate实例。
一个SimpleJdbcInsert对象还须要和一个数据库表绑定,经过withTableName方法指定一个数据库表的名字,随后就能够操做这个数据库表。所以,咱们一般须要为一个DAO建立一个SimpleJdbcInsert对象。
在web项目的DAO中常见初始化策略为:
@Resource
private JdbcTemplate jdbcTemplate;
private SimpleJdbcInsert simpleJdbcInsert;
@PostConstruct
public void postConstruct() {
//初始化SimpleJdbcInsert,绑定一个数据库表
simpleJdbcInsert = new SimpleJdbcInsert(jdbcTemplate).withTableName("jt_study");
}
随后咱们就可使用simpleJdbcInsert了!
5.1.2 基本插入数据
基本的插入数据方法采用execute方法就能够了,咱们只须要传递一个Map,key为对性的表的字段名,value就是插入的该字段的值,不须要其余sql语句,便可直接插入数据,是否是很神奇!
以下案例,插入一条数据到jt_study表中!
@Test
public void simpleJdbcInsert() {
//插入一条数据到jt_study中
HashMap<String, Object> paramHashMap = new HashMap<>();
paramHashMap.put("name", "simpleJdbcInsert");
paramHashMap.put("age", 100);
simpleJdbcInsert.execute(paramHashMap);
System.out.println(jdbcTemplate.queryForObject("select count(*) from jt_study where age = ?", Integer.class,
100));
}
5.1.3 默认值与主键返回
在上面插入数据以后,咱们会发现数据库的数据虽然id自增了,可是create_time倒是null,咱们设置的是create_time为CURRENT_TIMESTAMP,即插入时自动填充当前时间,可是并无填充。
对于这种取默认值的字段,咱们能够在初始化simpleJdbcInsert的时候,经过调用usingGeneratedKeyColumns设置自动生成的字段名数组,此后对于自动生成的字段,若是不在map中传递该字段,那么就会采用自动生成的值!
咱们添加以下设置,添加两个字段,id和create_time:
@PostConstruct
public void postConstruct() {
//初始化SimpleJdbcInsert,绑定一个数据库表
simpleJdbcInsert = new SimpleJdbcInsert(jdbcTemplate).withTableName("jt_study");
simpleJdbcInsert.usingGeneratedKeyColumns("id","create_time");
}
再次尝试插入,该设置还能够配合executeAndReturnKey方法返回生成的主键,该方法将会返回一个Number类型的队形爱过,能够从里面获取返回的主键:
@Test
public void simpleJdbcInsertReturn() {
HashMap<String, Object> paramHashMap = new HashMap<>();
paramHashMap.put("name", "simpleJdbcInsertReturn");
paramHashMap.put("age", 102);
//插入一条数据到jt_study中,返回自增的主键
Number number = simpleJdbcInsert.executeAndReturnKey(paramHashMap);
System.out.println("id: " + number.longValue());
System.out.println(jdbcTemplate.queryForObject("select count(*) from jt_study where age = ?", Integer.class,
102));
}
若是有多个自增的主键字段或者不是Number类型的主键,那么可使用executeAndReturnKeyHolder方法,该方法返回一个KeyHolder,内部持有返回的key集合,使用keyHolder.getKeyList()便可获取!
public void executeAndReturnKeyHolder() {
HashMap<String, Object> paramHashMap = new HashMap<>();
paramHashMap.put("name", "executeAndReturnKeyHolder");
paramHashMap.put("age", 104);
//插入一条数据到jt_study中
KeyHolder keyHolder = simpleJdbcInsert.executeAndReturnKeyHolder(paramHashMap);
System.out.println(keyHolder.getKeyList());
}
使用 UsingColumns 方法指定列名称列表来限制插入的列,此时,其余的列将一样会使用默认值。咱们加入以下配置,而且没有配置create_time:
@PostConstruct
public void postConstruct() {
//初始化SimpleJdbcInsert,绑定一个数据库表
simpleJdbcInsert = new SimpleJdbcInsert(jdbcTemplate).withTableName("jt_study");
simpleJdbcInsert.usingColumns("name","age");
}
尝试插入数据,咱们会发现create_time使用了默认值:
@Test
public void usingColumns() {
HashMap<String, Object> paramHashMap = new HashMap<>();
paramHashMap.put("name", "usingColumns");
paramHashMap.put("age", 105);
//插入一条数据到jt_study中
simpleJdbcInsert.execute(paramHashMap);
}
5.1.4 SqlParameterSource参数源
前面咱们都是使用Map提供参数值,这是没问题的能够正常工做,但却不是最方便的方法。相似于NamedParameterJdbcTemplate,这里的SimpleJdbcInsert一样可使用SqlParameterSource来代理Map提供参数。
咱们可使用SqlParameterSource的默认实现之一BeanPropertySqlParameterSource来做为参数源,该实现包装一个JavaBean对象来提供参数值,以下案例:
@Test
public void sqlParameterSource() {
//参数对象
JtStudy jtStudy = new JtStudy("sqlParameterSource", 106);
//参数源
BeanPropertySqlParameterSource bpsps = new BeanPropertySqlParameterSource(jtStudy);
//插入一条数据到jt_study中,传递一个参数源
simpleJdbcInsert.execute(bpsps);
}
另外一个选择是相似于 Map 的 MapSqlParameterSource参数源实现,它提供了一种能够链式编程的更方便的 addValue 方法。
@Test
public void mapSqlParameterSource() {
//参数源
MapSqlParameterSource msps = new MapSqlParameterSource();
//链式的添加参数
msps.addValue("name", "mapSqlParameterSource").addValue( "age", 107);
//插入一条数据到jt_study中,传递一个参数源
simpleJdbcInsert.execute(msps);
}
5.2 SimpleJdbcCall简化存储过程/函数
咱们能够以相似于声明 SimpleJdbcInsert 的方式声明 SimpleJdbcCall。SimpleJdbcCall 类提供了对存储过程/函数的简化调用方式,它使用数据库中的元数据查找输入(in)和退出(out)参数的名称,所以没必要显式声明它们。
5.2.1 初始化SimpleJdbcCall
SimpleJdbcCall对象须要使用dataSource或者jdbcTemplate初始化,所以咱们须要引入dataSource或者jdbcTemplate实例。
一个SimpleJdbcCall对象还须要和一个存储过程/函数绑定,能够经过withProcedureName方法指定一个存储过程或者经过withFunctionName指定一个函数的名字,注意一个SimpleJdbcCall对象只能绑定其中一个。
在web项目的DAO中常见初始化策略为:
private SimpleJdbcCall simpleJdbcCall;
@PostConstruct
public void simpleJdbcCall() {
//初始化SimpleJdbcInsert,经过withProcedureName绑定一个存储过程
//或者经过withFunctionName绑定一个函数名
simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("myProcedure");
}
5.2.2 执行存储过程
建立一个存储过程(mysql 8),该存储过程,输入一个ageNum的age值,返回一个int类型的falg变量。判断给定的ageNum在数据库jt_study表中是否有且只有一条数据,若是是那么返回1,不然返回0。
CREATE PROCEDURE `myProcedure`( IN ageNum INT, OUT flag INT )
BEGIN
DECLARE
num INT;
SET flag = 1;
SELECT
count( * ) INTO num
FROM
jt_study
WHERE
age = ageNum;
IF
num != 1 THEN
SET flag = 0;
END IF;
END
参数名就是存储过程的的IN参数名,使用execut方法便可执行,返回一个map,里面就是执行的返回结果,key就是OUT参数名:
@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. 执行函数
另外初始化一个simpleJdbcCall,经过withFunctionName绑定一个函数名:
private SimpleJdbcCall simpleJdbcCall2;
@PostConstruct
public void simpleJdbcCall2() {
//初始化simpleJdbcCall2,经过withFunctionName绑定一个函数名
simpleJdbcCall2 = new SimpleJdbcCall(jdbcTemplate).withFunctionName("myFun");
}
建立一个函数(mysql 8),计算两个输入参数ia+ib的和并返回:
CREATE FUNCTION `myFun`( ia INT, ib INT ) RETURNS int
DETERMINISTIC
BEGIN
RETURN ia + ib;
END
参数名就是函数的参数名,使用execut方法便可执行函数,返回一个map,里面的key固定为“return”,value就是执行的返回结果。更简单的,可使用executeFunction方法,直接返回预期类型函数结果:
@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 实现(指定对结果集的封装操做)。
以下案例,定义一个存储过程(mysql 8),该存储过程首先对于接收到的ageNum参数加1的值赋给var,随后查找所有age等于var的jt_study表数据。
这种存储过程并无主动设置返回值,而是将随后的第一查询结果做为返回值:
CREATE PROCEDURE `select_jt_study`(IN ageNum int)
BEGIN
DECLARE var int;
set var = ageNum +1;
SELECT * FROM jt_study where age = var;
END
建立一个SimpleJdbcCall绑定该存储过程,指定返回的结果集名为"resultSet",因为存储过程当中的查询语句返回的是多行队列的结果,所以结果集的处理方式为BeanPropertyRowMapper(将每一行结果映射到JtStudy对象中)。
private SimpleJdbcCall simpleJdbcCall3;
@PostConstruct
public void simpleJdbcCall3() {
//初始化simpleJdbcCall3,经过withProcedureName绑定一个函数名
simpleJdbcCall3 = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("select_jt_study")
//指定返回的结果集名为"resultSet",处理方式为将每一条结果映射到JtStudy对象中
.returningResultSet("resultSet", BeanPropertyRowMapper.newInstance(JtStudy.class));
}
执行,便可得到封装后的结果值:
@Test
public void executeResultSet() {
//IN 参数源
MapSqlParameterSource msps = new MapSqlParameterSource("ageNum", 4);
//执行存储过程,获取返回值
Map<String, Object> result = simpleJdbcCall3.execute(msps);
System.out.println(result);
}
结果以下:
{
resultSet=[JtStudy{
createTime=2020-11-29 15:29:41.0, id=4, name='微博', age=5}, JtStudy{
createTime=2020-04-21 23:55:15.0, id=5, name='null', age=5}], #update-count-1=0}
6 Spring JDBC的总结
Spring JDBC官方文档。
Spring JDBC是Spring提供了的简单框架,它对传统JDBC访问进行了的简单封装,其核心类就是JdbcTemplate,在上面咱们已经简单的学习了它的基本使用和一些高级技巧,咱们可以明显的感觉到,它相比于传统JDBC编程有了很大的改进,使用起来也很简单,能够轻松入门。
JdbcTemplate也可以自动解决sql注入,但这须要咱们调用参数化sql的方法(前面的讲的全部方法),若是是咱们本身拼接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来代替两者。
后面有机会,咱们在慢慢介绍Spring Data下面的经常使用数据库框架。
相关文章:
https://spring.io/
Spring Framework 5.x 学习
Spring Framework 5.x 源码
若有须要交流,或者文章有误,请直接留言。另外但愿点赞、收藏、关注,我将不间断更新各类Java学习博客!