视频:
第四章 访问数据库
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.*作为配置项。类似下面的代码:
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配置项:
- 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
data.sql
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)
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
step3: 创建实体类 ArticlePO
Lomok注解给类的属性生成set,get方法。 默认和所有参数构造方法
step4: 单元测试,注入JdbcTemplate对象
测试聚合函数
测试“?”占位符
测试自定义RowMapper
测试List集合
测试更新记录
4.2.3 NamedParameterJdbcTemplate
NamedParameterJdbcTemplate能够接受命名的参数,通过具名的参数提供代码的可读性,JdbcTemplate使用的是参数索引的方式。
在使用模板的位置注入NamedParameterJdbcTemplate对象,编写SQL语句,在SQL中WHERE部分“:命名参数”。调用NamedParameterJdbcTemplate的诸如query,queryForObject, execute,update等时,将参数封装到Map中。
step1:注入模板对象
step2: 使用命名参数
4.2.4 多表查询
多表查询关注是查询结果如何映射为Java Object。常用两种方案:一种是将查询结果转为Map。列名是key,列值是value,这种方式比较通用,适合查询任何表。第二种是根据查询结果中包含的列,创建相对的实体类。属性和查询结果的列对应。将查询结果自定义RowMapper、ResultSetExtractor映射为实体类对象。
现在创建新的表article_detail,存储文章内容,与article表是一对一关系。
article_detail表
需求:查询某个文章的全部属性,包括文章内容
step1:创建新的实体类ArticleMainPO, 将ArticlePO作为成员变量
step2: 查询一对一文章
总结:
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:创建实体类
step3: 创建Mapper接口
@Results部分为结果映射(XML中的<ResultMap>), 或者用MyBatis的驼峰命名也能实现默认的映射关系。
application.properties
#驼峰,下划线命名
mybatis.configuration.map-underscore-to-camel-case=true
step4: 启动类加入扫描注解
@MapperScan是扫描注解,参数是Mapper接口所在的包名。参数是数组,可以指定多个包位置。
step5: 配置数据源
application.properties或yml都可以
step6:单元测试
4.3.2 ResultMap
查询操作得到包含多个列的集合,将列值转为对象属性使用结果映射的功能,注解@Results,@ResultMap能够帮助我们完成此功能。
@Results用于定义结果映射,每个列和Java对象属性的一一对应。
@ResultMap 指定使用哪个结果映射,两种方式可以使用@Results,另一种XML文件。
需求:执行多个select语句,使用结果映射转换数据库记录为Java Object。
step1:创建新的Mapper对象。
@Results的id定义当前结果映射的唯一名称, 后面内容是列和属性的一一映射说明。
其他的查询方法@ResultMap引用@Results的id。使用BaseMapper的映射规则处理查询结果。
step2: 单元测试
另一种方法在xml中定义<resultMap>标签,在@ResultMap注解引用。 这种方式首先创建xml。在resources目录下创建自定义的mapper目录。 新建ArticleMapper.xml 。
ArticleMapper.xml 代码清单:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.orm.repository.ArticleDao">
<resultMap id="ArticleMapper" type="com.bjpowernode.orm.po.ArticlePO">
<id 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" />
</resultMap>
</mapper>
step2:修改application.properties配置mapper文件的路径
mybatis.mapper-locations:自定义mapper xml 文件保存路径。
step3:修改ArticleDao的查询方法上面的@ResultMap。
在重复执行单元测试代码。
4.3.3 SQL提供者
我们能在方法上面直接编写SQL语句。使用Text Block编写长的语句。方法上编写SQL显的不够简洁。MyBatis提供了SQL提供者的功能。将SQL以方法的形式定义在单独的类中。 Mapper接口通过引用SQL提供者中的方法名称,表示要执行的SQL。
SQL提供者有四类@SelectProvider,@InsertProvider,@UpdateProvider,@DeleteProvider。
SQL提供者首先创建提供者类,自定义的。类中声明静态方法,方法体是SQL语句并返回SQL。例如:
其次Mapper接口的方法上面,应用@SelectProvider(type = 提供者类.class, method = "方法名称")
step1: 创建SQL提供者
step2: 创建mapper接口
其他注解@InsertProvider,@DeleteProvider类似的使用方式
step3:单元测试
我们可以分别创建 Insert的提供者, Update提供者,Delete提供者,Select查询者。 每个查询者只提供一种操作。Select提供者的方法只提供Select语句。
4.3.4 @One一对一查询
MyBatis支持一对一,一对多,多对多查询。 XML文件和注解都能实现关系的操作。我们使用注解表示article和article_detail的一对一关系。 MyBatis维护这个关系, 开发人员自己也可以维护这种关系。
@One: 一对一
@Many: 一对多
关系表一个article有一个article_detail 文章内容。
step1: 创建两个表的实体
Article 声明了ArticleDetail 对象。表示文章内容。
step2:创建Mapper查询接口
step3:单元测试
4.3.5 @Many一对多查询
一对多查询使用@Many注解,步骤与一对一基本相同。
准备环境,新建comment评论表。article与comment存在一对多关系。一篇文章多个评论。
step1:创建CommentPO实体
step2:创建新的文章聚合实体
step3:新建Mapper接口
step4:单元测试
4.3.6 常用配置参数
MyBatis的项设置,在application文件中“mybatis”开头进行设置。
全部设置参考:
常用设置:
上述设置内容比较多时,可以将设置放到MyBatis主配置文件,mybatis.config-location 加载主配置文件。
sql-config.xml
4.3.7 MybatisAutoConfiguration
MyBatis框架的在Spring Boot的自动配置类MybatisAutoConfiguration.class
imports文件中定义了org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 自动配置类
关注:
MybatisProperties.class
DataSourceAutoConfiguration.class , DataSourceProperties.class
SqlSessionFactory.class
SqlSessionTemplate.class
SqlSessionTemplate是线程安全的,MyBatis为了与Spring继承。 提供的由Spring管理的Bean。这个SqlSesionTemplate实现了SqlSession接口, 能够由Spring事务管理器使用。提供Spring的事务处理。同时管理SqlSession的创建,销毁。
4.4 适合的连接池
HikariCP连接池
连接池配置:
MySQL连接池配置建议
application.yml
4.5 声明式事务
事务分为全局事务与本地事务,本地事务是特定于资源的,例如与JDBC连接关联的事务。本地事务可能更容易使用,但有一个显著的缺点:它们不能跨多个事务资源工作。比如在方法中处理连接多个数据库的事务,本地事务是无效的。
Spring解决了全局和本地事务的缺点。它允许应用程序开发人员在任何环境中使用一致的编程模型。只需编写一次代码,就可以从不同环境中的不同事务管理策略中获益。Spring框架同时提供声明式和编程式事务管理。推荐声明式事务管理。
Spring事务抽象的关键是事务策略的概念,org.springframework.transaction.PlatformTransactionManager接口定义了事务的策略。
事务控制的属性:
- Propagation : 传播行为。代码可以继续在现有事务中运行(常见情况),也可以暂停现有事务并创建新事务
- Isolation: 隔离级别。此事务与其他事务的工作隔离的程度。例如,这个事务能看到其他事务未提交的写吗?
- Timeout超时时间:该事务在超时和被底层事务基础结构自动回滚之前运行的时间。
- Read-only只读状态:当代码读取但不修改数据时,可以使用只读事务。在某些情况下,例如使用Hibernate时,只读事务可能是一种有用的优化。
AOP:
Spring Framework的声明式事务管理是通过Spring面向方面编程(AOP)实现的。事务方面的代码以样板的方式使用,及时不了解AOP概念,仍然可以有效地使用这些代码。事务使用AOP的环绕通知(TransactionInterceptor)。
声明式事务的方式:
- XML配置文件:全局配置
- @Transactional注解驱动 :和代码一起提供,比较直观。和代码的耦合比较高。【Spring团队建议您只使用@Transactional注释具体类(以及具体类的方法),而不是注释接口。当然,可以将@Transactional注解放在接口(或接口方法)上,但这只有在使用基于接口的代理时才能正常工作】
方法的可见性:
公共(public)方法应用@Transactional主机。如果使用@Transactional注释了受保护的、私有的或包可见的方法,则不会引发错误,但注释的方法不会显示配置的事务设置,事务不生效。如果需要受保护的、私有的方法具有事务考虑使用AspectJ。而不是基于代理的机制。
4.5.1.1 准备事务演示环境
在新的Spring Boot项目演示事务处理。新项目Lession011-Trans 。 添加MyBatis, MySQL, Lombok依赖。使用之前blog库的article , article_detail 表。
需求:某个作者发布了新的文章,article,article_detail两个表同时添加记录。需要事务控制两个表的insert操作。 step1: 创建实体类
step2:创建Mapper接口,创建两个方法,添加文章属性,文章内容
step3:创建Service接口,声明发布文章的方法
step4:启动类
step5:编写配置文件
step6:单元测试
现在业务方法正常执行,添加数据到两个表,但是事务没有Spring参与。 postNewArticle()方法没有事务管理。
4.5.1.2 添加事务注解
step1:修改postNewArticle()方法添加@Transactional
@Transactional可在类上,接口,方法上声明。 表示方法需要事务管理。 Spring对public方法添加事务处理。
step2:启动类
step3:单元测试
添加数据失败, 在事务中抛出运行时异常。Spring默认回滚事务。
4.5.1.3 无效事务1
Spring事务处理是AOP的环绕通知,只有通过代理对象调用具有事务的方法才能生效。类中有A方法,调用带有事务的B方法。 调用A方法事务无效。当然protected, private方法默认是没有事务功能的。
step1: 接口中增加方法managerArticles
step2:单元测试,readCount为0
测试发现,事务不起作用。aritcleService是代理对象,managerArticle方法不是事务方法。事务无效。
4.5.1.4 无效事务2
方法在线程中运行的,在同一线程中方法具有事务功能, 新的线程中的代码事务无效。
step1:修改接口方法的实现
step2: 单元测试
4.5.1.5 事务回滚规则
- RuntimeException的实例或子类时回滚事务
- Error会导致回滚
- 已检查异常不会回滚。默认提交事务
@Transactional注解的属性控制回滚
- rollbackFor
- noRollbackFor
- rollbackForClassName
- noRollbackForClassName