第四章访问数据库-Springboot3视频笔记

动力节点王鹤老师springboot3视频第四章

4 访问数据库

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是数据源的配置类,更多配置参考这个类的属性。

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配置项:

[if !supportLists]· [endif]always:总是执行数据库初始化脚本

[if !supportLists]· [endif]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)

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jdbc</artifactId>

<groupId>com.mysql</groupId>

<artifactId>mysql-connector-j</artifactId>

<scope>runtime</scope>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

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上手快,功能非常强大。提供了丰富、实用的方法,归纳起来主要有以下几种类型的方法:

(1)execute方法:可以用于执行任何SQL语句,常用来执行DDL语句。

(2)update、batchUpdate方法:用于执行新增、修改与删除等语句。

(3)query和queryForXXX方法:用于执行查询相关的语句。

(4)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

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

测试List集合

@Test

void testList() {

 String sql="select * from article order by id ";

 List> 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使用的是参数索引的方式。

在使用模板的位置注入NamedParameterJdbcTemplate对象,编写SQL语句,在SQL中WHERE部分“:命名参数”。调用NamedParameterJdbcTemplate的诸如query,queryForObject, execute,update等时,将参数封装到Map中。

step1:注入模板对象

@Resource

private JdbcTemplate jdbcTemplate;

step2: 使用命名参数

@Test

void testNameQuery() {

 // :参数名

 String sql="select count(*) as ct from article where user_id=:uid and read_count > :num";

//key是命名参数

 Map param = new HashMap<>();

 param.put("uid", 2101);

 param.put("num", 0);    

 Long count = nameJdbcTemplate.queryForObject(sql, param, Long.class);

 System.out.println("用户被阅读的文章数量 = " + count);

}

4.2.4 多表查询

多表查询关注是查询结果如何映射为Java Object。常用两种方案:一种是将查询结果转为Map。列名是key,列值是value,这种方式比较通用,适合查询任何表。第二种是根据查询结果中包含的列,创建相对的实体类。属性和查询结果的列对应。将查询结果自定义RowMapper、ResultSetExtractor映射为实体类对象。

现在创建新的表article_detail,存储文章内容,与article表是一对一关系。


article_detail表

CREATE TABLE `article_detail` (

 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '注解',

 `article_id` int(11) NOT NULL COMMENT '文章ID',

 `content` text NOT NULL COMMENT '文章内容',

 PRIMARY KEY (`id`)) 

ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

需求:查询某个文章的全部属性,包括文章内容

step1:创建新的实体类ArticleMainPO, 将ArticlePO作为成员变量

@Data

@NoArgsConstructor

@AllArgsConstructor

public class ArticleMainPO {

 private Integer id;

 private Integer userId;

 private String title;

 private String summary;

 private Integer readCount;

 private LocalDateTime createTime;

 private LocalDateTime updateTime;

 private ArticleDetailPO articleDetail;

}

step2: 查询一对一文章

@Test

void testArticleContent() {

 String sql= """

 select m.*,d.id as detail_id, d.article_id,d.content

 from article m join article_detail d

 on m.id = d.article_id

 where m.id=:id

 """;

 Map param = new HashMap<>();

 param.put("id", 1);

 List list = nameJdbcTemplate.query(sql, param, (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();

 //文章详情

 var detailId = rs.getInt("detail_id");

 var content = rs.getString("content");

 var articleId = rs.getInt("article_id");

 ArticleDetailPO detail = new ArticleDetailPO();

 detail.setId(detailId);

 detail.setArticleId(articleId);

 detail.setContent(content);

 return new ArticleMainPO(id, userId, title, summary, readCount,

 createTime, updateTime, detail);

 });

 list.forEach(m -> {

 System.out.println("m.getId() = " + m.getId());

 System.out.println("m.getArticleDetail() = " + m.getArticleDetail());

 });

}

总结:

JdbcTemplate的优点简单,灵活,上手快,访问多种数据库。对数据的处理控制能力比较强,RowMapper, ResultSetExtractor能够提供按需要灵活定制记录集与实体类的关系。

缺点:对SQL要求高,适合对SQL比较了解,自定义查询结果比较多,调优需求的。

JdbcTemplate对象的调整参数,比较少。可设置spring.jdbc.template.开头的配置项目,比如设置超时为10秒,spring.jdbc.template.query-timeout=10。

4.3 MyBatis

数据库访问MyBatis,MyBatis-Plus国内很常用,掌握了MyBatis,MyBatis-Plus就会了大部分了。MyBatis-Plus附加的功能需要单独学习。我们以MyBatis来自介绍Spring Boot集成ORM框架。

MyBatis使用最多的是mapper xml文件编写SQL语句。本章使用MyBatis的注解,JDK新特性文本块,以及Record完成java对象和表数据的处理。

4.3.1 单表CRUD

首先向blog数据库的article表添加新的文章,以及修改,查询文章。在新工程Lession10-MyBatis集成MyBatis框架。项目包名com.bjpowernode.orm。依赖需要mysql驱动、mybatis依赖,Lombok。


step1: Maven依赖

step2:创建实体类

//PO:Persistent Object@Datapublic class ArticlePO { private Integer id; private Integer userId; private String title; private String summary; private Integer readCount; private LocalDateTime createTime; private LocalDateTime updateTime;}

step3: 创建Mapper接口

public interfaceArticleMapper { String field_list="id,user_id,title,summary,read_count,create_time,update_time"; @Insert(""" insert into article(user_id,title,summary,read_count,create_time,update_time) \ values(#{userId},#{title},#{summary},#{readCount},#{createTime},#{updateTime}) """) int insertArticle(ArticlePO article); @Update(""" update article set read_count=#{num} where id=#{id} """) int updateReadCount(Integer id,Integer num); @Delete(""" delete from article where id=#{id} """) int deleteById(Integer id); @Select("select " + field_list + " from article where id=#{articleId}") @Results({ @Result(id = true,column = "id",property = "id"), @Result(column = "user_id",property = "userId"), @Result(column = "read_count",property = "readCount"), @Result(column = "create_time",property = "createTime"), @Result(column = "update_time",property = "updateTime"), }) ArticlePO selectById(@Param("articleId") Integer id);}

@Results部分为结果映射(XML中的<ResultMap>), 或者用MyBatis的驼峰命名也能实现默认的映射关系。

application.properties

#驼峰,下划线命名mybatis.configuration.map-underscore-to-camel-case=true

step4: 启动类加入扫描注解

@MapperScan({"com.bjpowernode.orm.repository"})@SpringBootApplicationpublic class Lession10MyBatisApplication { public static void main(String[] args) { SpringApplication.run(Lession10MyBatisApplication.class, args); }}

@MapperScan是扫描注解,参数是Mapper接口所在的包名。参数是数组,可以指定多个包位置。

step5: 配置数据源

application.properties或yml都可以

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=falsespring.datasource.username=rootspring.datasource.password=123456#驼峰,下划线命名mybatis.configuration.map-underscore-to-camel-case=true

step6:单元测试

@SpringBootTestclass Lession10MyBatisApplicationTests {

 @Autowired private ArticleMapper articleMapper;

 @Test void testInsert() { ArticlePO article = new ArticlePO(); article.setTitle("什么时候用微服务"); article.setSummary("微服务优缺点"); article.setUserId(219); article.setReadCount(560); article.setCreateTime(LocalDateTime.now()); article.setUpdateTime(LocalDateTime.now()); articleMapper.insertArticle(article); } @Test void testUpdate() { int rows = articleMapper.updateReadCount(1, 230); System.out.println("修改的记录数量:" + rows); } @Test void testDelete(){ int rows = articleMapper.deleteById(11); System.out.println("删除记录的数量 " + rows); } @Test void testSelect(){ ArticlePO articlePO = articleMapper.selectById(3); System.out.println("查询到的文章:" + articlePO); }}

4.3.2 ResultMap

查询操作得到包含多个列的集合,将列值转为对象属性使用结果映射的功能,注解@Results,@ResultMap能够帮助我们完成此功能。

@Results用于定义结果映射,每个列和Java对象属性的一一对应。

@ResultMap 指定使用哪个结果映射,两种方式可以使用@Results,另一种XML文件。

需求:执行多个select语句,使用结果映射转换数据库记录为Java Object。

step1:创建新的Mapper对象。

public interfaceArticleDao { //定义mapper, id表示唯一名称 @Select("") @Results(id = "BaseMapper", value = { @Result(id = true, column = "id", property = "id"), @Result(column = "user_id", property = "userId"), @Result(column = "read_count", property = "readCount"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime"), }) ArticlePO articleMapper(); @Select(""" select id,user_id,title,summary,read_count,create_time,update_time from article whereuser_id=${userId} """) @ResultMap("BaseMapper") List<ArticlePO> selectList(Integer userId); @Select(""" select id,user_id,title,summary,read_count,create_time,update_time from article whereid=#{articleId} """) @ResultMap("BaseMapper") ArticlePO selectById(@Param("articleId") Integer id);}

@Results的id定义当前结果映射的唯一名称, 后面内容是列和属性的一一映射说明。

其他的查询方法@ResultMap引用@Results的id。使用BaseMapper的映射规则处理查询结果。

step2: 单元测试

@SpringBootTestpublic class ArticleDaoTest { @Autowired private ArticleDao articleDao; @Test void testSelectList() { List<ArticlePO> poList = articleDao.selectList(219); poList.forEach(po -> { System.out.println("po = " + po); }); } @Test void testSelect(){ ArticlePO articlePO = articleDao.selectById(1); System.out.println("查询到的文章:" + articlePO); }}

另一种方法在xml中定义标签,在@ResultMap注解引用。 这种方式首先创建xml。在resources目录下创建自定义的mapper目录。 新建ArticleMapper.xml 。


ArticleMapper.xml 代码清单:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容