配置属性与数据
获取配置文件属性
在前面我们有学习过,在配置文件添加属性,使用 @ConfigurationProperties
获取配置文件属性的值,并且通过实体类来封装这些数据,实体类需要有 set 方法来进行自动注入。
使用实体类封装配置属性
使用实体类来封装配置文件的属性需要以下三个步骤:
定义属性
创建实体类封装属性
在实体类绑定属性并生成 get、set 方法
定义实体类为 Bean 对象 (这样实体类才可以被 IOC 容器管理注入信息)
定义属性
比如我们在配置文件添加如下几个属性:
1 2 3 student: name: 小码同学 major: 计算机科学与技术
封装属性
然后再使用一个实体类来封装这些信息
1 2 3 4 5 6 7 8 9 import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;@Data @ConfigurationProperties(prefix = "student") public class Student { private String name; private String major; }
这里的 @Data
用到了 lombok 依赖。
1 2 3 4 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency >
打印属性内容
我们在 SpringBoot 入口直接打印出来,就不写测试类了,你用测试类打印也是没有问题的。
1 2 3 4 5 6 7 8 9 10 11 12 13 @EnableConfigurationProperties(Student.class) @SpringBootApplication public class Springboot08ConfigurationApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(Springboot08ConfigurationApplication.class, args); System.out.println("-------封装实体类----------" ); Student student = run.getBean(Student.class); System.out.println(student.getName()); System.out.println(student.getMajor()); System.out.println("-----------------" ); } }
当我们在程序入口输出 student
的属性时,我们还要在当前的类使用 @EnableConfigurationProperties(Student.class)
注解指定 Student 实体类为 Bean 对象。
或者你也可以不使用使用 @EnableConfigurationProperties(Student.class)
注解,你直接在 Student 实体类上使用 @Component
注解,标注当前实体类时一个 bean 对象也是可以的,但推荐使用 @EnableConfigurationProperties
注解,因为这样可以更加清晰的知道你在哪一个类或方法使用了这个属性。
出现这个提示后只需要添加一个坐标此提醒就消失了
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > </dependency >
属性自动注入第三方 Bean
我们前面说的都是使用自定义的 bean 来封装属性的,如果我们使用第三方 bean 能不能加载属性呢?能不能自动装配到第三方的的属性里面?为什么会提出这个疑问?原因就在于当前 @ConfigurationProperties
注解是写在类定义的上方,而第三方开发的 bean 源代码不是你自己书写的,你也不可能到源代码中去添加 @ConfigurationProperties 注解,这种问题该怎么解决呢?
定义属性
我们继续在 SpringBoot 配置文件定义一下属性:
1 2 3 4 source: username: root password: root driverClassName: com.mysql.jdbc.Driver
创建第三方 Bean 进行属性绑定
引入依赖
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.20</version > </dependency >
我们在 SpringBoot 程序入口编写代码:
1 2 3 4 5 6 @Bean @ConfigurationProperties(prefix = "source") public DruidDataSource dataSource () { DruidDataSource source = new DruidDataSource (); return source; }
定义 DruidDataSource
是一个 Bean 对象,同时,还要使用 @ConfigurationProperties
注解来绑定属性,这样的话,当你在配置文件中定义属性,IOC 容器就会自动将属性注入到 DruidDataSource 中
打印属性内容
我们仍然在程序入口打印内容,这样就可以实现第三方获取属性的内容了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @SpringBootApplication public class Springboot08ConfigurationApplication { @Bean @ConfigurationProperties(prefix = "source") public DruidDataSource dataSource () { DruidDataSource source = new DruidDataSource (); return source; } public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(Springboot08ConfigurationApplication.class, args); DruidDataSource bean = run.getBean(DruidDataSource.class); System.out.println("--------第三方Bean---------" ); System.out.println(bean.getDriverClassName()); System.out.println(bean.getUsername()); System.out.println(bean.getPassword()); System.out.println("-----------------" ); } }
宽松绑定 / 松散绑定
定义属性
在进行属性绑定时,可能会遇到如下情况,为了进行标准命名,开发者会将属性名严格按照驼峰命名法书写,在 yml 配置文件中将 datasource 修改为 dataSource,如下:
1 2 dataSource: driverClassName: com.mysql.jdbc.Driver
定义第三方 Bean
此时程序可以正常运行,然后又将代码中的前缀 datasource 修改为 dataSource,如下:
1 2 3 4 5 6 @Bean @ConfigurationProperties(prefix = "dataSource") public DruidDataSource datasource () { DruidDataSource ds = new DruidDataSource (); return ds; }
运行错误
此时就发生了编译错误,而且并不是 idea 工具导致的,运行后依然会出现问题,配置属性名 dataSource 是无效的
1 2 3 4 5 6 7 8 Configuration property name 'dataSource' is not valid: Invalid characters: 'S' Bean: datasource Reason : Canonical names should be kebab -case ('-' separated ), lowercase alpha -numeric characters and must start with a letter Action :Modify 'dataSource ' so that it conforms to the canonical names requirements .
为什么会出现这种问题,这就要来说一说 springboot 进行属性绑定时的一个重要知识点了,有关属性名称的宽松绑定,也可以称为宽松绑定。
什么是宽松绑定?实际上是 springboot 进行编程时人性化设计的一种体现,即配置文件中的命名格式与变量名的命名格式可以进行格式上的最大化兼容。兼容到什么程度呢?几乎主流的命名格式都支持,例如:
在 ServerConfig 中的 ipAddress 属性名
1 2 3 4 5 6 @Component @Data @ConfigurationProperties(prefix = "servers") public class ServerConfig { private String ipAddress; }
可以与下面的配置属性名规则全兼容
1 2 3 4 5 servers: ipAddress: 192.168 .0 .2 ip_address: 192.168 .0 .2 ip-address: 192.168 .0 .2 IP_ADDRESS: 192.168 .0 .2
也可以说,以上 4 种模式最终都可以匹配到 ipAddress 这个属性名。为什么这样呢?原因就是在进行匹配时,配置中的名称要去掉中划线和下划线后,忽略大小写的情况下去与 java 代码中的属性名进行忽略大小写的等值匹配,以上 4 种命名去掉下划线中划线忽略大小写后都是一个词 ipaddress,java 代码中的属性名忽略大小写后也是 ipaddress,这样就可以进行等值匹配了,这就是为什么这 4 种格式都能匹配成功的原因。不过 springboot 官方推荐使用烤肉串模式,也就是中划线模式。
到这里我们掌握了一个知识点,就是命名的规范问题。再来看开始出现的编程错误信息
1 2 3 4 5 6 7 8 Configuration property name 'dataSource' is not valid: Invalid characters: 'S' Bean: datasource Reason : Canonical names should be kebab -case ('-' separated ), lowercase alpha -numeric characters and must start with a letter Action :Modify 'dataSource ' so that it conforms to the canonical names requirements .
其中 Reason 描述了报错的原因,规范的名称应该是烤肉串 (kebab) 模式 (case),即使用 - 分隔,使用小写字母数字作为标准字符,且必须以字母开头
。
常用计量单位绑定
我们在配置文件书写了如下配置值,其中第三项超时时间 timeout 描述了服务器操作超时时间,当前值是 - 1 表示永不超时。
1 2 3 4 servers: ip-address: 192.168 .0 .1 port: 2345 timeout: -1
但是每个人都这个值的理解会产生不同,比如线上服务器完成一次主从备份,配置超时时间 240,这个 240 如果单位是秒就是超时时间 4 分钟,如果单位是分钟就是超时时间 4 小时。面对一次线上服务器的主从备份,设置 4 分钟,简直是开玩笑,别说拷贝过程,备份之前的压缩过程 4 分钟也搞不定,这个时候问题就来了,怎么解决这个误会?
解决
除了加强约定之外,springboot 充分利用了 JDK8 中提供的全新的用来表示计量单位的新数据类型,从根本上解决这个问题。以下模型类中添加了两个 JDK8 中新增的类,分别是 Duration 和 DataSize
1 2 3 4 5 6 7 8 9 10 @Component @Data @ConfigurationProperties(prefix = "servers") public class ServerConfig { @DurationUnit(ChronoUnit.HOURS) private Duration serverTimeOut; @DataSizeUnit(DataUnit.MEGABYTES) private DataSize dataSize; }
常用单位
Duration :表示时间间隔,可以通过 @DurationUnit 注解描述时间单位,例如上例中描述的单位为小时(ChronoUnit.HOURS)
DataSize :表示存储空间,可以通过 @DataSizeUnit 注解描述存储空间单位,例如上例中描述的单位为 MB(DataUnit.MEGABYTES)
使用上述两个单位就可以有效避免因沟通不同步或文档不健全导致的信息不对称问题,从根本上解决了问题,避免产生误读。
Druation 常用单位如下:
DataSize 常用单位如下:
校验
目前我们在进行属性绑定时可以通过松散绑定规则在书写时放飞自我了,但是在书写时由于无法感知模型类中的数据类型,就会出现类型不匹配的问题,比如代码中需要 int 类型,配置中给了非法的数值,例如写一个 “a",这种数据肯定无法有效的绑定,还会引发错误。 SpringBoot 给出了强大的数据校验功能,可以有效的避免此类问题的发生。在 JAVAEE 的 JSR303 规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架,此处使用 Hibernate 提供的校验框架来作为实现进行数据校验。
导入依赖
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > javax.validation</groupId > <artifactId > validation-api</artifactId > </dependency > <dependency > <groupId > org.hibernate.validator</groupId > <artifactId > hibernate-validator</artifactId > </dependency >
添加属性
在配置文件下添加如下属性:
1 2 3 4 student: name: 小码同学 major: 计算机科学与技术 age: 20
开启校验
这里和上面一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;import org.springframework.validation.annotation.Validated;import javax.validation.constraints.Max;import javax.validation.constraints.Min;@Data @ConfigurationProperties(prefix = "student") @Validated public class Student { private String name; private String major; @Max(value = 120,message = "最大值不能超过120") @Min(value = 0,message = "最小值不能小于0") private Integer age; }
通过设置数据格式校验,就可以有效避免非法数据加载,其实使用起来还是挺轻松的,基本上就是一个格式。
补充:数据类型注意
问题来源
连接数据库出错,提示用户名和密码不匹配。
1 java.sql.SQLException: Access denied for user 'root' @'localhost' (using password: YES)
我们在配置数据库的信息如下:
1 2 3 4 5 6 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: 0127
我们密码使用了 0127
,这个问题就处在这里了,因为 `0127 在开发者眼中是一个字符串 “0127”,但是在 springboot 看来,这就是一个数字,而且是一个八进制的数字。当后台使用 String 类型接收数据时,如果配置文件中配置了一个整数值,他是先安装整数进行处理,读取后再转换成字符串。巧了,0127 撞上了八进制的格式,所以最终以十进制数字 87 的结果存在了。
注意:
第一,字符串标准书写加上引号包裹,养成习惯
第二,遇到 0 开头的数据多注意
测试
加载测试专用属性
我们在测试中,有时候想对某个模块进行测试,需要加一些参数,但又不想修改配置文件的参数,我们应该做呢?如果可以在测试类里面自定义添加就好了。
在创建的时候 SpringBoot 帮我们创建好了测试类
1 2 3 4 5 import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest(classes = Application.class) class ApplicationTests { }
并且 SpringBoot 集成了 Junit5,看这个测试类,只有一个 @SpringBootTest
注解,估计集成的东西都在里面了,我们点点进去看看有什么东西。
在 @SpringBootTest
注解里面,我们可以看到有一个 properties
和 args
的参数,我们使用过 classes
属性,用于指定 Springboot 的程序入口类。
springboot 在创建项目的时候,默认的配位文件类型就是 properties 类型的,这里也有一个 properties 参数名,我们猜测就是设置参数的,我们可以测试一下。
添加配置属性
我们在 springboot 的配置文件添加如下信息:
1 hikki.username ="zhangsan"
测试
在测试类中,使用 @Value
注入了配置的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest(classes = Application.class) class ApplicationTests { @Value("${hikki.username}") private String username; @Test public void test () { System.out.println(username); } }
运行测试方法,在控制到可以看到输出以下:
添加 properties 属性
我们继续在 @SpringBootTest
注解上添加 properties 属性,设置和配置文件一样的属性,并设置值,看看会不会发生冲突,还是有优先级。
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest(classes = Application.class,properties = "hikki.username='lisi'") class ApplicationTests { @Value("${hikki.username}") private String username; @Test public void test () { System.out.println(username); } }
结果
再次运行测试方法,我们可以看到输出了'lisi'
,表示没有发生冲突,但有优先级,在 @SpringBootTest
上的 properties 属性设置的值优先级比配置文件的优先级要高,其实也可以理解,因为要是在注解上的优先级要低的话,这不是白忙活了嘛。
使用 args 设置属性‘
我们之前有在 SpringBoot 的入口程序设置过参数,就是下面这个东西,创建项目就帮我们创建好了程序入口,我们可以在 args
这个数组添加参数,这里添加参数的优先级最高。
1 2 3 4 5 6 @SpringBootApplication() public class Application { public static void main (String[] args) { SpringApplication.run(Application.class, args); } }
上面是在程序入口添加参数,我们试试在测试类来添加参数。
我们在 @SpringBootTest
注解上添加 args
的参数,并设置值 --hikki.username='wangwu'
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest(classes = Application.class,properties = "hikki.username='lisi'",args = "--hikki.username='wangwu'") class ApplicationTests { @Value("${hikki.username}") private String username; @Test public void test () { System.out.println(username); } }
运行测试方法
运行测试方法后,可以在控制台看到,输出了'wangwu'
,说明属性 args
优先级最高。
在命令行添加属性
1 2 3 java -jar XXX-SNAPSHOT.jar 属性=值 java -jar .\lab-0.0.1-SNAPSHOT.jar spring.profiles.active=dev
加载测试专用配置
上面我们试了一下,我们可以在测试里面添加运行参数,那能不能也添加一些只有测试能用的配置呢?
答案是可以的,我们之前在学习 Spring 也有使用过这相关的知识,在主配置类使用 @Import()
导入其他的配置类,下面我们来演示一下
创建配置类
这个配置类,只是模拟一下,真实开发不会这样使用,这个配置类,不作任何配置,只是当做一个 Bean,并且返回一个字符串就好。
1 2 3 4 5 6 7 8 9 10 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MsgConfig { @Bean public String msg(){ return "bean msg ........."; } }
编辑测试类
在测试类使用 @Import
注解,导入 MsgConfig
类,并且自动注入配置类的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.context.annotation.Import;import site.hikki.config.MsgConfig;@SpringBootTest(classes = Application.class) @Import({MsgConfig.class}) class ApplicationTests { @Autowired private String msg; @Test void testConfiguration () { System.out.println(msg); } }
运行测试方法
运行后可以在控制台看到如下信息:
当我们需要在测试类引入其他的配置类,或者是需要一些只有测试才用到的配置,我们就可以使用上面这种方式,创建一个配置类,配置相关信息,然后导入相关信息就可以了。
Web 环境模拟测试
前面使用的测试都是测试服务层或者数据层的,但都没有对表现层进行测试,在开发 Web 端,我们每次测试都要使用 Postman 或者 Swagger 这类 api 第三方测试工具来测试。
如果我们要在测试中对表现层测试,那测试中必须具有能够对 web 请求的能力,不然无法实现 web 功能的测试。所以在测试用例中测试表现层接口这项工作就转换成了两件事,一,如何在测试类中启动 web 测试,二,如何在测试类中发送 web 请求。下面一件事一件事进行,先说第一个。
配置测试 Web 环境
每一个 springboot 的测试类上方都会标准 @SpringBootTest 注解,我们点开这个注解里面看看。
从注解里面可以看到有个 webEnvironment
属性,并且还有四个枚举值,下面举例用法:
1 2 3 4 @SpringBootTest(classes = Application.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ApplicationTests { }
MOCK
:根据当前设置确认是否启动 web 环境,例如使用了 Servlet 的 API 就启动 web 环境,属于适配性的配置
DEFINED_PORT
:使用自定义的端口作为 web 服务器端口
RANDOM_PORT
:使用随机端口作为 web 服务器端口
NONE
:不启动 web 环境
通过上述配置,现在启动测试程序时就可以正常启用 web 环境了,建议大家测试时使用 RANDOM_PORT,避免代码中因为写死设定引发线上功能打包测试时由于端口冲突导致意外现象的出现。就是说你程序中写了用 8080 端口,结果线上环境 8080 端口被占用了,结果你代码中所有写的东西都要改,这就是写死代码的代价。现在你用随机端口就可以测试出来你有没有这种问题的隐患了。
开启 Web 虚拟调用功能
Java 自带的 API 发送请求不太好用,SpringBoot 为了方便开发,对其功能进行了包装,简化开发步骤。
我们新建一个 WebTest 测试类,然后添加 @SpringBootTest
注解。
定义发起虚拟调用的对象 MockMVC,通过自动装配的形式初始化对象
1 2 3 4 5 6 7 8 9 10 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.web.servlet.MockMvc;@AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebTest {}
定义发起虚拟调用的对象 MockMVC
通过自动装配的形式初始化对象
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.web.servlet.MockMvc;@AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebTest { @Test public void testWeb (@Autowired MockMvc mvc) { } }
创建一个虚拟请求对象
封装请求的路径,并使用 MockMVC 对象发送对应请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.web.servlet.MockMvc;import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;@AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebTest { @Test void testWeb (@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/hello" ); mvc.perform(builder); } }
创建完请求对象,就可以运行测试方法了。
注意:访问路径不用写主机和端口,因为主机和端口使用的就是当前虚拟 web 环境,无需再次指定,只描述请求路径就可以了。
对比请求结果
上面已经模拟出 web 环境了,并且成功发送了 web 请求了。但请求发送出去了,得校验结果才有用啊,不然只是发送请求,不知道结果没用啊,下面就来讲一下怎么比对结果。
响应状态匹配
一般常用的有三种,分别是响应状态匹配、响应体匹配、响应头匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void testBody (@Autowired MockMvc mvc) throws Exception{ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/hello" ); ResultActions actions = mvc.perform(builder); ContentResultMatchers content = MockMvcResultMatchers.content(); ResultMatcher resultMatcher = content.string("hello run ......" ); actions.andExpect(resultMatcher); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test void testStatus (@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/hello" ); ResultActions action = mvc.perform(builder); StatusResultMatchers status = MockMvcResultMatchers.status(); ResultMatcher ok = status.isOk(); action.andExpect(ok); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void testContentType (@Autowired MockMvc mvc) throws Exception{ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/hello" ); ResultActions actions = mvc.perform(builder); HeaderResultMatchers header = MockMvcResultMatchers.header(); ResultMatcher contentType = header.string("Content-Type" , "application/json" ); actions.andExpect(contentType); }
基本上齐了,头信息,正文信息,状态信息都有了,就可以组合出一个完美的响应结果比对结果了。以下范例就是三种信息同时进行匹配校验,也是一个完整的信息匹配过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test void testGetById (@Autowired MockMvc mvc) throws Exception { MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/hello" ); ResultActions action = mvc.perform(builder); StatusResultMatchers status = MockMvcResultMatchers.status(); ResultMatcher ok = status.isOk(); action.andExpect(ok); HeaderResultMatchers header = MockMvcResultMatchers.header(); ResultMatcher contentType = header.string("Content-Type" , "application/json" ); action.andExpect(contentType); ContentResultMatchers content = MockMvcResultMatchers.content(); ResultMatcher result = content.json("{}" ); action.andExpect(result); }
设置了比对的结果后,可以对 web 请求进行简单的测试了,对于一些简单的请求还是非常方便的。
数据层测试回滚
需求
当前我们的测试程序可以完美的进行表现层、业务层、数据层接口对应的功能测试了,但是测试用例开发完成后,在打包的阶段由于 test 生命周期属于必须被运行的生命周期,如果跳过会给系统带来极高的安全隐患,所以测试用例必须执行。但是新的问题就呈现了,测试用例如果测试时产生了事务提交就会在测试过程中对数据库数据产生影响,进而产生垃圾数据。这个过程不是我们希望发生的,作为开发者测试用例该运行运行,但是过程中产生的数据不要在我的系统中留痕,这样该如何处理呢?
解决办法
SpringBoot 早就为开发者想到了这个问题,并且针对此问题给出了最简解决方案,在原始测试用例中添加注解 @Transactional
即可实现当前测试用例的事务不提交。当程序运行后,只要注解 @Transactional
出现的位置存在注解 @SpringBootTest,SpringBoot 就会认为这是一个测试程序,无需提交事务,所以也就可以避免事务的提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Transactional @Rollback(true) @SpringBootTest public class RollbackTest { @Autowired private BookDao bookDao; @Test public void testDelBook () { int i = bookDao.delBookById(8 ); System.out.println(i); } @Test @Rollback(value = false) public void testAddBook () { Book book = new Book (); book.setName("标准日本语" ); book.setDescription("这是一本日语初学者常用的课本" ); book.setType("外语" ); int i = bookDao.addBook(book); System.out.println(i); } }
如果开发者想提交事务,在运行的方法上面添加一个 @RollBack 的注解,设置回滚状态为 false 即可正常提交事务
数据层 - 数据源技术
我们之前整合 Druid+Mybatis+MySQL,这三个技术分别对应数据层操作的三个层面:
数据源技术:Druid
持久化技术:Mybatis
数据库技术:MySQL
我们下面看看这些有什么区别。
前言
我们在开发的时候,使用最多的是 Druid 数据源,但我们不使用 Druid 数据源时,也是可以正常使用了,因为 SpringBoot 有内置的数据源技术,默认是是 HikariCP
数据源技术,不使用数据源运行项目时可以在控制台看到如下信息:
1 2 INFO 31820 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... INFO 31820 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
SpringBoot 内置了三种数据源技术,分别是:
HikariCP
Tomcat 提供 DataSource
Commons DBCP
HikariCP
SpringBoot 内置的数据源,并且是默认使用的数据源,我们不做任何数据源配置就默认使用它了。
Tomcat 提供 DataSource
Tomcat 也有内置的数据源,如果我们不想用 HikariCP 数据源,我们还有可以选择 tomcat 内置的数据源,使用 Tomcat 的数据源主要是,我们在导入 web 的启动依赖时,这个依赖有默认使用了内嵌的 tomcat,而 tomcat 有内置的数据源,所以 SpringBoot 也有多一个数据源选择了。
我们要怎么使用 tomcat 内置的数据源呢?我们只需要把 HikartCP 技术的坐标排除掉就 OK 了。
DBCP
这个数据源就是在上面两个数据源都不使用的情况才会使用这个数据源,使用要求有点严苛,既不使用 HikartCP 也不使用 tomcat 的 DataSource 时,默认给你用这个。
用法
不配置数据源
不配置数据源默认使用 hikari
数据源,
1 2 3 4 5 6 spring: datasource: url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root
配置 hikari 数据源
1 2 3 4 5 6 7 spring: datasource: url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC hikari: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root
配置 druid 数据源:
1 2 3 4 5 6 7 spring: datasource: druid: url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root
数据层 - 持久化技术
我们开发用的最多的是 mybatis,但 SpringBoot 也有提供一个持久化技术给我们,叫 JdbcTemplate,这个技术其实是 Spring 提供的,不是 SpringBoot 的技术,但还是可以使用的,SpringBoot 是对 Spring 的简化开发。
在进行持久化之前,你首先需要配置数据库信息,也即是上面所说的数据源技术。
JdbcTemplate
导入坐标
在 pom.xml 导入 JdbcTemplate 的相关坐标,JdbcTemplate 有 SpringBoot 的启动依赖,导入启动依赖。
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency >
自动装配
在 Dao 层创建一个 BookDaoTemplate 类,进行自动装配。
1 2 3 4 5 6 @Repository public class BookDaoTemplate { @Autowired JdbcTemplate jdbcTemplate; }
使用 JdbcTemplate 进行增删改查
使用 JdbcTemplate 进行增删改查仍然是在 BookDaoTemplate
类中,JdbcTemplate 对 Jdbc 的封装,让我们在 SpringBoot 更方便的使用,提高了使用效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import org.springframework.stereotype.Repository;import site.hikki.pojo.Book;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;import java.util.Map;@Repository public class BookDaoTemplate { @Autowired JdbcTemplate jdbcTemplate; public List<Map<String,Object>> findBook () { String sql = "select * from tb_book" ; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } public List<Book> find () { String sql = "select * from tb_book" ; RowMapper<Book> rm = new RowMapper <Book>() { @Override public Book mapRow (ResultSet rs, int rowNum) throws SQLException { Book temp = new Book (); temp.setId(rs.getInt("id" )); temp.setName(rs.getString("name" )); temp.setType(rs.getString("type" )); temp.setDescription(rs.getString("description" )); return temp; } }; List<Book> list = jdbcTemplate.query(sql, rm); return list; } }
测试
编写完实现类,我们可以使用 Junit 进行单元测试了,首先自动注入 BookDaoTemplate 的类,再进行测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import site.hikki.dao.BookDaoTemplate;import site.hikki.pojo.Book;import java.util.List;import java.util.Map;@SpringBootTest(classes = Application.class) public class JdbcTemplateTest { @Autowired private BookDaoTemplate bookDaoTemplate; @Test void testJdbcTemplate () { List<Map<String, Object>> book = bookDaoTemplate.findBook(); System.out.println(book); } @Test void findJdbcTemplate () { List<Book> books = bookDaoTemplate.find(); System.out.println(books); } }
Spring Data JPA
前言
介绍
Spring Data JPA 是更大的 Spring Data 家族的一部分,可以轻松实现基于 JPA 的存储库。本模块处理对基于 JPA 的数据访问层的增强支持。它使构建使用数据访问技术的 Spring 驱动的应用程序变得更加容易。
提供了增删改查 等常用功能,使开发者可以用较少的代码实现数据操作,同时还易于扩展。
定义了独特的 JPQLJava Persistence Query Language) ,一种和 SQL 非常类似的 中间性 和对象化查询语言 ,最终被编译成针对不同底层数据库 的 SQL 查询,从而屏蔽不同数据库的差异。JPQL 语句可以是 select 语句、update 语句或 delete 语句,它们都通过 Query 接口封装执行。
特点
基于 Spring 和 JPA 构建存储库的成熟支持
支持查询 谓词,从而实现类型安全的 JPA 查询
域类的透明审计
分页支持、动态查询执行、集成自定义数据访问代码的能力
验证 @Query
引导时带注释的查询
支持基于 XML 的实体映射
基于 JavaConfig 的仓库配置 @EnableJpaRepositories
。
截止到目前为止,Spring Data JPA 最新版本为 v3.1.0
功能
支持 XML 和 注解 两种元数据的形式,元数据用来描述对象和表之间的映射关系 ,框架据此将实体对象持久化到数据库表中
通过面向对象 而非面向数据库 的查询语言查询数据,避免程序的 SQL 语句紧密耦合
用来操作实体对象 ,执行 CRUD 操作,框架在后台替我们完成所有的事情,开发者从繁琐的 JDBC 和 SQL 代码中解脱出来
接口
使用 Spring Data JPA 自定义 Repository
接口,必须继承 XXRepository<T, ID>
接口,其中 T
表示实体类,ID 表示数据表的主键的类型,一般是使用 Integer 较多。
我们进入 JpaRepository
的类可以看到集成了很多方法,由名字我们大概可以猜到这些就是对数据库操作封装好的方法,比如查询数据库总条数,增删改查操作。
话不多说,直接上手操作一下看看
环境准备
导入依赖
其中主要是 Spring Data JPA
的依赖,其他依赖可根据自己的需求添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <version > 8.0.33</version > <scope > runtime</scope > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.2.16</version > </dependency >
数据库配置
1 2 3 4 5 6 7 spring: datasource: druid: username: root driver-class-name: com.mysql.cj.jdbc.Driver password: root url: jdbc:mysql://localhost:3306/spring_db?useUnicode=true&characterEncoding=utf-8
创建 ORM 实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;@Entity(name = "tb_student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer num; private Integer age; private String name; private String major; }
这里出现了几个注解
注解
参数
描述
@Entity
name
设置实体类对应的数据库表名
@Id
设置数据表中的 ID
@GeneratedValue
strategy
设置 GenerationType.IDENTITY 表示每次添加数据时自动自增 ID
实现 JPA 接口
1 2 3 4 5 6 import org.springframework.data.jpa.repository.JpaRepository;import site.hikki.domain.Student;public interface StudentRepositoty extends JpaRepository <Student,Integer> {}
参数解释:
Student:查询数据表对应的实体类
Integer:Student 实体类的主键 ID 的类型
测试
我们只需要实现接口,就可以开始测试了,因为 JPA 已经帮我们封装了一部分的方法。
创建一个测试类:
注入 StudentRepositoty
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import site.hikki.domain.Student; import site.hikki.repository.StudentRepositoty; import java.util.List; @SpringBootTest(classes = Application.class) class ApplicationTests { @Autowired private StudentRepositoty studentRepositoty; }
实现注入后,我们可以尝试实现 studentRepositoty
方法看看,如下图,我们我们可以看到已经实现了很多方法,我们可以直接使用。
上面只是一个简单的实现方法,下面开始对 JPA 进行详细的介绍。
JPA 进阶
分析封装方法
从继承的接口来看,我们最终实现的是 JpaRepository
,实现了上面一堆的方法,我们下面看看应该怎么用。
Repository 接口
Repository 提供了根据方法名查询方式。
方法的名称要遵循 findBy+ 属性名 (首字母大写) + 查询条件 (首字母大写 Is Equals) 的格式,如下:
1 2 findByNameLike(String name) findByName(String name)
其实就是规范的驼峰命名,注意一下就好了。
CrudRepository 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @NoRepositoryBean public interface CrudRepository <T, ID> extends Repository <T, ID> { <S extends T > S save (S entity) ; <S extends T > Iterable<S> save (Iterable<S> entities) ; T findOne (ID id) ; Iterable<T> findAll () ; Iterable<T> findAll (Iterable<ID> ids) ; boolean exists (ID id) ; long count () ; void delete (ID id) ; void delete (T entity) ; void delete (Iterable<? extendsT> entities) ; void deleteAll () ; }
PagingAndSortingRepository 接口
1 2 3 4 5 @NoRepositoryBean public interface PagingAndSortingRepository <T, ID> extends CrudRepository <T, ID> { Iterable<T> findAll (Sort sort) ; Page<T>findAll(Pageable pageable); }
JpaRepository 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @NoRepositoryBean public interface JpaRepository <T,ID extendsSerializable> extends PagingAndSortingRepository <T,ID>,QueryByExampleExecutor<T> { List<T>findAll(); List<T>findAll(Sort sort); List<T>findAll(Iterable<ID> ids); void flush () ; <S extends T > List<S> save (Iterable<S> entities) ; List<S extends T > S saveAndFlush (S entity) ; void deleteInBatch (Iterable<T> entities) ; void deleteAllInBatch () ; T getOne (ID id) ; <S extends T > List<S> findAll (Example<S> example) ; <S extends T > List<S> findAll (Example<S> example, Sort sort) ; }
上面给出了大部分的封装方法的作用,你可以根据需要选择合适的方法,减少自己编写的额 SQL 语句,造成代码冗余。
自定义方法
虽然 JPA 给我们封装了不少的方法,但需求是多变的,我们总会用到一些需要自定义的方法,下面带你实现一下:
查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import org.springframework.data.domain.Pageable;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Modifying;import org.springframework.data.jpa.repository.Query;import org.springframework.transaction.annotation.Transactional;import site.hikki.domain.Student;import java.util.List;public interface StudentRepositoty extends JpaRepository <Student,Integer> { @Query("select s from tb_student s where s.num=:num") List<Student> getStudentByNum (Integer num) ; @Query("select s from tb_student s") List<Student> getStudentPage (Pageable pageable) ; @Query(value = "select * from tb_student",nativeQuery = true) List<Student> getStudentPage2 (Pageable pageable) ; }
在上面自定义查询语句中,我们只使用了 @Query
注解,该注解表示执行 SQL 语句,之后不管你使用 insert、update、delete
等的 SQL 语句,都可以使用 @Query
注解来执行。
修改
1 2 3 4 5 6 7 8 9 @Transactional @Modifying @Query(value = "update tb_student set name = ?2 where num = ?1",nativeQuery = true) int updateStudentByNum (Integer num,String name) ;
上面使用到了两个新的注解,@Transactional 和 @Modifying
注解
描述
@Transactional
开启事务处理
@Modifying
数据变更操作
事务处理
针对数据的变更操作(修改、删除),无论是否使用了 @Query 注解,都必须在方法上方添加 @Transactional 注解进行事务管理,否则程序执行就会出现 InvalidDataAccessApiUsageException 异常。
如果在调用 Repository 接口方法的业务层 Service 类上已经添加了 @Transactional 注解进行事务管理,那么 Repository 接口文件中就可以省略 @Transactional 注解。
数据变更
在自定义的 Repository
接口中,使用 @Query
注解方式执行数据变更操作(修改、删除),除了要使用 @Query
注解,还必须添加 @Modifying
注解表示数据变更。
删除
1 2 3 4 5 6 7 8 @Transactional @Modifying @Query(value = "delete from tb_student where num = ?1",nativeQuery = true) int delStudentByNum (Integer num) ;
综合测试
SQL 语句写完了,自然是要开始单元测试了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.domain.PageRequest;import site.hikki.domain.Student;import site.hikki.repository.StudentRepositoty;import java.util.List;@SpringBootTest(classes = Application.class) class ApplicationTests { @Autowired private StudentRepositoty studentRepositoty; @Test public void find () { List<Student> all = studentRepositoty.findAll(); System.out.println(all); } @Test public void getStudentByNum () { List<Student> page = studentRepositoty.getStudentByNum(3 ); System.out.println(page); } @Test public void getStudentPage () { PageRequest request = PageRequest.of(1 , 2 ); List<Student> studentPage = studentRepositoty.getStudentPage(request); for (Student s :studentPage) { System.out.println(s); } } @Test public void getStudentPage2 () { PageRequest request = PageRequest.of(1 , 2 ); List<Student> studentPage = studentRepositoty.getStudentPage2(request); for (Student s :studentPage) { System.out.println(s); } } @Test public void updateStudentByNum () { int i = studentRepositoty.updateStudentByNum(12 , "小黑子" ); System.out.println(i); } @Test public void delStudentByNum () { int i = studentRepositoty.delStudentByNum(12 ); System.out.println(i); } }
关联查询
下面演示一个一对多
关联查询。
tb_dept 表:
字段
类型
描述
dept_id
int
主键 ID
dept_name
varchar
系名
tb_major 表:
字段
类型
描述
major_id
int
主键 ID
major_name
varchar
专业名
tuition
varchar
学费
dept_id
int
外键系 ID
需求
需求一:根据系编号 (dept_id) 查询该系的全部专业
需求二:根据专业名称 (major_name,模糊查询) 查询;
创建实体类
创建实体类非常重要,因为你需要在实体类描述表和表之间的关系,如果描述不清楚,查询不会成功,所以,这个描述符非常重要
Dept 实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import javax.persistence.*;import java.util.List;@Entity(name = "tb_dept") public class Dept { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer deptId; private String deptName; @OneToMany( mappedBy = "dept", //定义在关系的被维护端,指向关系的维护端;@ManyToOne不存在该属性,只有@OneToOne,@OneToMany,@ManyToMany上才有该属性。 targetEntity = Major.class, // 关联类的类型,默认是该成员属性对应的类类型 fetch = FetchType.EAGER //急加载,加载一个实体时,会立即从数据库中加载 ) private List<Major> majors; @Override public String toString () { return "Dept{" + "deptId=" + deptId + ", deptName='" + deptName + '\'' + ", majorList=" + majors + '}' ; } public Integer getDeptId () { return deptId; } public void setDeptId (Integer deptId) { this .deptId = deptId; } public String getDeptName () { return deptName; } public void setDeptName (String deptName) { this .deptName = deptName; } public List<Major> getMajorList () { return majors; } public void setMajorList (List<Major> majorList) { this .majors = majorList; } }
Major 实体类
在 Major 实体类中,重写 toString 方法,不能添加 dept
属性,不然会报错,因为当你查询系别下全部专业时,专业中又有系别,但系别下又有专业,这样会形成无线套娃,会爆内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import com.fasterxml.jackson.annotation.JsonIgnore;import javax.persistence.*;import java.io.Serializable;@Entity(name = "tb_major") public class Major implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer majorId; private String majorName; private Integer tuition; @ManyToOne( fetch = FetchType.EAGER, //急加载,加载一个实体时,会立即从数据库中加载 targetEntity = Dept.class // 关联类的类型,默认是该成员属性对应的类类型 ) @JoinColumn( name = "deptId", referencedColumnName = "deptId" ) @JsonIgnore private Dept dept; @Override public String toString () { return "Major{" + "majorId=" + majorId + ", majorName='" + majorName + '\'' + ", tuition=" + tuition + '}' ; } public void setDept (Dept dept) { this .dept = dept; } public Integer getMajorId () { return majorId; } public void setMajorId (Integer majorId) { this .majorId = majorId; } public String getMajorName () { return majorName; } public void setMajorName (String majorName) { this .majorName = majorName; } public Integer getTuition () { return tuition; } public void setTuition (Integer tuition) { this .tuition = tuition; } public Dept getDeptId () { return dept; } public void setDeptId (Dept deptId) { this .dept = deptId; } }
继承 JpaRepository 类
MajorRepository 接口
根据 JPA 的查询命名规范,我们需要模糊查询专业名称 MajorName,可以得出使用 findByMajorNameContains
方法名。
1 2 3 4 5 6 7 8 import org.springframework.data.jpa.repository.JpaRepository;import site.hikki.domain.Major;import java.util.List;public interface MajorRepository extends JpaRepository <Major,Integer> { List<Major> findByMajorNameContains (String majorName) ; }
DeptRepository 接口
需求是根据系别 ID 查询所有专业。
1 2 3 4 5 6 import org.springframework.data.jpa.repository.JpaRepository;import site.hikki.domain.Dept;public interface DeptRepository extends JpaRepository <Dept,Integer> { Dept findByDeptId (Integer id) ; }
Service 层
DeptAndMajorService 接口
1 2 3 4 5 6 7 8 import site.hikki.domain.Dept;import site.hikki.domain.Major;import java.util.List;public interface DeptAndMajorService { Dept findDeptWithMajor (Integer id) ; List<Major> findByMajorNameContains (String majorName) ; }
实现 DeptAndMajorService 接口
记得给该类添加 @Service
注解,受 Ioc 容器管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import site.hikki.domain.Dept;import site.hikki.domain.Major;import site.hikki.repository.DeptRepository;import site.hikki.repository.MajorRepository;import site.hikki.service.DeptAndMajorService;import java.util.List;@Service public class DeptAndMajorServiceImpl implements DeptAndMajorService { @Autowired private DeptRepository deptRepository; @Autowired private MajorRepository majorRepository; @Override public Dept findDeptWithMajor (Integer id) { return deptRepository.findByDeptId(id); } @Override public List<Major> findByMajorNameContains (String majorName) { return majorRepository.findByMajorNameContains(majorName); } }
测试结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package site.hikki;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import site.hikki.domain.*;import site.hikki.service.DeptAndMajorService;import java.util.List;@SpringBootTest(classes = Application.class) public class OneToManyTest { @Autowired DeptAndMajorService deptAndMajorService; @Test public void MajorByMajorName () { List<Major> contains = deptAndMajorService.findByMajorNameContains("工程" ); for (Major m :contains) { System.out.println(m); } } @Test public void findDeptWithMajor () { Dept major = deptAndMajorService.findDeptWithMajor(407 ); System.out.println(major); } }
数据层 - 数据库技术
SpringBoot 提供了内置数据源、持久化技术的解决方案,自然数据库也有提供。
SpringBoot 提供了 3 款内置的数据库,分别是
以上三款数据库除了可以独立安装之外,还可以像是 tomcat 服务器一样,采用内嵌的形式运行在 spirngboot 容器中。内嵌在容器中运行,那必须是 java 对象啊,这三款数据库底层都是使用 java 语言开发的。
任何技术的出现都是有需求,我们在做测试的时候,我们希望测试的数据不要留在磁盘中,最好是每次测试完,关闭测试后,测试过程中的数据都消失不见,这样测试就不会对真实数据库产生影响,这样可以极大的方便测试工作。
导入依赖
1 2 3 4 5 6 7 8 <dependency > <groupId > com.h2database</groupId > <artifactId > h2</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency >
如果你已经导入过 web 工程依赖,就不需要添加了了,如果还没添加 web 依赖,就需要添加一下。
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
配置 H2 数据库
1 2 3 4 5 6 7 8 9 10 11 spring: h2: console: enabled: true path: /h2 datasource: url: jdbc:h2:~/spring_db hikari: driver-class-name: org.h2.Driver username: sa password: 123456
打开数据库
打开 H2 数据库:http://127.0.0.1:8080/h2
打开后可以看到这样的页面,我们在数据库配置的初始用户名为 sa,密码为 123456,数据库名需要改一下,我们设置的 spring_db,图片中的数据库名是 test,我们需要修改为 spring_db。
熟悉 H2 数据库
我们可以在终端数据库输入 sql 命令,我们尝试创建一个数据表。
创建数据表后,我们可以在左侧看到数据表,我们添加一条数据。
添加数据时候需要注意,字段使用 '' 单引号,不能使用双引号
上方白色的终端输入,下方是查询结果展示区。
总结
使用 H2 进行测试可以减少对磁盘的数据进行操作,不对真实数据库进行改动,对测试非常友好。
本文章来源于我的博客:https://blog.hikki.site