目录
4.2.3 NamedParameterJdbcTemplate
4.3.7 MybatisAutoConfiguration
第四章 访问数据库
Spring Boot框架为SQL数据库提供了广泛的支持,既有用JdbcTemplate直接访问JDBC,同时支持“object relational mapping”技术(如Hibernate,MyBatis)。Spring Data独立的项目提供对多种关系型和非关系型数据库的访问支持。比如 MySQL, Oracle , MongoDB , Redis, R2DBC,Apache Solr,Elasticsearch...
Spring Boot也支持嵌入式数据库比如H2, HSQL, and Derby。这些数据库只需要提供jar包就能在内存中维护数据。我们这章访问关系型数据库。
4.1 DataSource
通常项目中使用MySQL,Oracle,PostgreSQL等大型关系数据库。Java中的jdbc技术支持了多种关系型数据库的访问。在代码中访问数据库,我们需要知道数据库程序所在的ip,端口,访问数据库的用户名和密码以及数据库的类型信息。以上信息用来初始化数据源,数据源也就是DataSource。数据源表示数据的来源,从某个ip上的数据库能够获取数据。javax.sql.DataSource接口表示数据源,提供了标准的方法获取与数据库绑定的连接对象(Connection)。
javax.sql.Connection是连接对象,在Connection上能够从程序代码发送查询命令,更新数据的语句给数据库;同时从Connection获取命令的执行结果。Connection很重要,像一个电话线把应用程序和数据库连接起来。
DataSource在application配置文件中以spring.datasource.*作为配置项。类似下面的代码:
spring.datasource.url=jdbc:mysql://localhost/mydb
spring.datasource.username=dbuser
spring.datasource.password=dbpass
DataSourceProperties.java是数据源的配置类,更多配置参考这个类的属性。
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
}
Spring Boot能够从spring.datasource.url推断所使用的数据驱动类,如果需要特殊指定请设置spring.datasource.driver-class-name为驱动类的全限定名称。
Spring Boot支持多种数据库连接池,优先使用 HikariCP,其次是Tomcat pooling,再次是 Commons DBCP2,如果以上都没有,最后会使用Oracle UCP连接池。当项目中starter依赖了spring-boot-starter-jdbc 或者spring-boot-starter-data-jpa默认添加HikariCP连接池依赖,也就是默认使用HikariCP连接池。
4.2 轻量的JdbcTemplate
使用JdbcTemplate我们提供自定义SQL, Spring执行这些SQL得到记录结果集。JdbcTemplate和NamedParameterJdbcTemplate类是自动配置的,您可以@Autowire注入到自己的Bean中。开箱即用。
JdbcTemplate执行完整的SQL语句,我们将SQL语句拼接好,交给JdbcTemplate执行,JdbcTemplate底层就是使用JDBC执行SQL语句。是JDBC的封装类而已。
NamedParameterJdbcTemplate可以在SQL语句部分使用“:命名参数”作为占位符, 对参数命名,可读性更好。NamedParameterJdbcTemplate包装了JdbcTemplate对象,“:命名参数”解析后,交给JdbcTemplate执行SQL语句。
JdbcTemplateAutoConfiguration自动配置了JdbcTemplate对象,交给JdbcTemplateConfiguration创建了JdbcTemplate对象。并对JdbcTemplate做了简单的初始设置(QueryTimeout,maxRows等)。
4.2.1 准备环境
访问数据库先准备数据库的script。SpringBoot能够自动执行DDL,DML脚本。两个脚本文件名称默认是schema.sql和data.sql。脚本文件在类路径中自动加载。
自动执行脚本还涉及到spring.sql.init.mode配置项:
- always:总是执行数据库初始化脚本
- never:禁用数据库初始化
更进一步
Spring Boot处理特定的数据库类型,为特定的数据库定制script文件。首先设置spring.sql.init.platform=hsqldb、h2、oracle、mysql、postgresql等等,其次准备 schema-${platform}. sql 、 data-${platform}. sql 脚本文件。
4.2.1.1 准备数据库和表脚本
首先创建数据库,安装MySQL8.5。有可用的MySQL数据库就可以,最好是5以上版本。
数据库名称Blog , 表目前使用一个 article(文章表),初始两条数据。
schema.sql
CREATE TABLE `article` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` int(11) NOT NULL COMMENT '作者ID',
`title` varchar(100) NOT NULL COMMENT '文章标题',
`summary` varchar(200) DEFAULT NULL COMMENT '文章概要',
`read_count` int(11) unsigned zerofill NOT NULL COMMENT '阅读读数',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '最后修改时间',
PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
data.sql
INSERT INTO `article` VALUES ('1','2101','SpringBoot核心注解',
'核心注解的主要作用','00000008976','2023-01-16 12:11:12','2023-01-16 12:11:19');
INSERT INTO `article` VALUES ('2','356752','JVM调优',
'HotSpot虚拟机详解','00000000026','2023-01-16 12:15:27','2023-01-16 12:15:30');
4.2.1.2 创建Spring Boot工程
新建Spring Boot工程Lession09-JdbcTemplate
构建工具:Maven
包名:com.bjpowernode.jdbc
JDK:19
Starter依赖:Lombok,MySQL Driver, JDBC API
Maven依赖(pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
IDEA Maven Tool查看依赖列表
依赖包含了连接池com.zaxxer:HikariCP:5.0.1 , spring-jdbc 6.0.3 , mysql驱动mysql-connector-j 8.0.31。
4.2.2 JdbcTemplate访问MySQL
项目中依赖了spring-jdbc 6.0.3,JdbcTemplate对象会自动创建好。把JdbcTemplate对象注入给你的Bean,再调用JdbcTemplate的方法执行查询,更新,删除的SQL。
JdbcTemplate上手快,功能非常强大。提供了丰富、实用的方法,归纳起来主要有以下几种类型的方法:
- execute方法:可以用于执行任何SQL语句,常用来执行DDL语句。
- update、batchUpdate方法:用于执行新增、修改与删除等语句。
- query和queryForXXX方法:用于执行查询相关的语句。
- call方法:用于执行数据库存储过程和函数相关的语句。
我们在2.2.1.2已经创建了Spring Boot工程,在工程上继续添加代码,完成对Blog库,article表的CRUD。
step1:将schema.sql , data.sql拷贝到resources目录
step2:修改application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#总是执行数据库脚本,以后设置为never
spring.sql.init.mode=always
step3: 创建实体类 ArticlePO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ArticlePO {
private Integer id;
private Integer userId;
private String title;
private String summary;
private Integer readCount;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
Lomok注解给类的属性生成set,get方法。 默认和所有参数构造方法
step4: 单元测试,注入JdbcTemplate对象
@SpringBootTest
public class TestJdbcTemplate {
@Resource
private JdbcTemplate jdbcTemplate;
}
测试聚合函数
@Test
void testCount() {
String sql="select count(*) as ct from article";
Long count = jdbcTemplate.queryForObject(sql, Long.class);
System.out.println("文章总数 = " + count);
}
测试“?”占位符
@Test
void testQuery() {
// ?作为占位符
String sql = "select * from article where id= ? ";
//BeanPropertyRowMapper 将查询结果集,列名与属性名称匹配, 名称完全匹配或驼峰
ArticlePO article = jdbcTemplate.queryForObject(sql,
new BeanPropertyRowMapper<>(ArticlePO.class), 1 );
System.out.println("查询到的文章 = " + article);
}
测试自定义RowMapper
@Test
void testQueryRowMapper() {
//只能查询出一个记录,查询不出记录抛出异常
String sql = "select * from article where id= " + 1;
ArticlePO article = jdbcTemplate.queryForObject(sql, (rs, rownum) -> {
var id = rs.getInt("id");
var userId = rs.getInt("user_id");
var title = rs.getString("title");
var summary = rs.getString("summary");
var readCount = rs.getInt("read_count");
var createTime = new Timestamp(rs.getTimestamp("create_time").getTime())
.toLocalDateTime();
var updateTime = new Timestamp(rs.getTimestamp("update_time").getTime())
.toLocalDateTime();
return new ArticlePO(id, userId, title, summary, readCount,
createTime, updateTime);
});
System.out.println("查询的文章 = " + article);
}
测试List集合
@Test
void testList() {
String sql="select * from article order by id ";
List<Map<String, Object>> listMap = jdbcTemplate.queryForList(sql);
listMap.forEach( el->{
el.forEach( (field,value)->{
System.out.println("字段名称:"+field+",列值:"+value);
});
System.out.println("===================================");
});
}
测试更新记录
@Test
void testUpdate() {
String sql="update article set title = ? where id= ? ";
//参数是从左往右 第一个,第二个...
int updated = jdbcTemplate.update(sql, "Java核心技术思想", 2);
System.out.println("更新记录:"+updated);
}
4.2.3 NamedParameterJdbcTemplate
NamedParameterJdbcTemplate能够接受命名的参数,通过具名的参数提供代码的可读性,JdbcTemplate使用的是参数索引的方式。
在使用模板的位置注入NamedParameterJdbcT