数据库场景的自动配置分析与整合测试
-
①:导入 JDBC 场景
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
-
②:接着导入数据库驱动包(MySQL为例)。
<!--默认版本:--> <mysql.version>8.0.22</mysql.version> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!--<version>5.1.49</version>--> </dependency> <!-- 想要修改版本 1、直接依赖引入具体版本(maven的就近依赖原则) 2、重新声明版本(maven的属性的就近优先原则) --> <properties> <java.version>1.8</java.version> <mysql.version>5.1.49</mysql.version> </properties>
-
③:修改配置项
spring: datasource: url: jdbc:mysql://localhost:3306/db_account username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver
-
④:单元测试数据源
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; @SpringBootTest class Boot05WebAdminApplicationTests { @Autowired JdbcTemplate jdbcTemplate; @Test//用@org.junit.Test会报空指针异常,可能跟JUnit新版本有关 void contextLoads() { // jdbcTemplate.queryForObject("select * from account_tbl") // jdbcTemplate.queryForList("select * from account_tbl",) Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class); log.info("记录总数:{}",aLong); } }
更多配置项
- DataSourceAutoConfiguration : 数据源的自动配置。
- 修改数据源相关的配置:spring.datasource。
- 数据库连接池的配置,是自己容器中没有DataSource才自动配置的。
- 底层配置好的连接池是:HikariDataSource。
- DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置。
- JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行CRUD。
- 可以修改前缀为spring.jdbc的配置项来修改JdbcTemplate。
- @Bean @Primary JdbcTemplate:Spring容器中有这个JdbcTemplate组件,使用@Autowired。
- JndiDataSourceAutoConfiguration: JNDI的自动配置。
- XADataSourceAutoConfiguration: 分布式事务相关的。
自定义方式整合druid数据源
druid 官网
druid是数据库连接池,它能够提供强大的监控和扩展功能。
Spring Boot整合第三方技术的两种方式:
- 自定义
- 找starter场景
自定义方式
-
添加依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.17</version> </dependency>
-
配置Druid数据源:
@Configuration public class MyConfig { @Bean @ConfigurationProperties("spring.datasource")//复用配置文件的数据源配置 public DataSource dataSource() throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); // druidDataSource.setUrl(); // druidDataSource.setUsername(); // druidDataSource.setPassword(); return druidDataSource; } }
-
更多选项
druid数据源starter整合方式
引入依赖即可:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
分析自动配置:
- 扩展配置项 spring.datasource.druid
- 自动配置类DruidDataSourceAutoConfigure
- DruidSpringAopConfiguration.class, 监控SpringBean的; 配置项:spring.datasource.druid.aop-patterns
- DruidStatViewServletConfiguration.class, 监控页的配置。- spring.datasource.druid.stat-view-servlet默认开启。
- DruidWebStatFilterConfiguration.class,web监控配置。- - spring.datasource.druid.web-stat-filter默认开启。
- DruidFilterConfiguration.class所有Druid的filter的配置:
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
配置示例:
-
代码如下:
spring: datasource: url: jdbc:mysql://localhost:3306/db_account username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver druid: aop-patterns: com.atguigu.admin.* #监控SpringBean filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙) stat-view-servlet: # 配置监控页功能 enabled: true login-username: admin login-password: admin resetEnable: false web-stat-filter: # 监控web enabled: true urlPattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' filter: stat: # 对上面filters里面的stat的详细配置 slow-sql-millis: 1000 logSlowSql: true enabled: true wall: enabled: true config: drop-table-allow: false
Mybatis 整合版
1、配置模式:
大致步骤如下:
-
导入MyBatis官方Starter。
starter 中的导入的依赖有下面几个:
-
编写Mapper接口,需
@Mapper
注解。 -
编写SQL映射文件并绑定Mapper接口。
-
在
application.yaml
中指定Mapper配置文件的所处位置,以及指定全局配置文件的信息 (建议:配置在mybatis.configuration)。
starter的命名方式:
- SpringBoot官方的Starter:
spring-boot-starter-*
- 第三方的:
*-spring-boot-starter
具体代码
-
引入starter:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>
-
在application.yaml中指定Mapper配置文件的所处位置,以及指定全局配置文件的信息
spring: datasource: username: root password: 1234 url: jdbc:mysql://localhost:3306/my driver-class-name: com.mysql.jdbc.Driver # 配置mybatis规则 mybatis: config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置 mapper-locations: classpath:mybatis/*.xml #sql映射文件位置
-
创建 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 由于Spring Boot自动配置缘故,此处不必配置,只用来做做样。--> </configuration>
-
创建 Mapper接口:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lun.boot.mapper.UserMapper"> <select id="getUser" resultType="com.lun.boot.bean.User"> select * from user where id=#{id} </select> </mapper>
import com.lun.boot.bean.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper { public User getUser(Integer id); }
-
POJO
public class User { private Integer id; private String name; //getters and setters... }
-
DB:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-
Controller and Service:
@Controller public class UserController { @Autowired private UserService userService; @ResponseBody @GetMapping("/user/{id}") public User getUser(@PathVariable("id") Integer id){ return userService.getUser(id); } }
@Service public class UserService { @Autowired private UserMapper userMapper;//IDEA下标红线,可忽视这红线 public User getUser(Integer id){ return userMapper.getUser(id); } }
2、注解模式
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getById(Long id);
public void insert(City city);
}
3、混合模式
最佳实战:
- 引入mybatis-starter
- 配置application.yaml中,指定mapper-location位置即可
- 编写Mapper接口并标注@Mapper注解
- 简单方法直接注解方式
- 复杂方法编写mapper.xml进行绑定映射
- @MapperScan(“com.atguigu.admin.mapper”) 简化,其他的接口就可以不用标注@Mapper注解
实例:
-
简单的方法用注解
@Mapper public interface UserMapper { public User getUser(Integer id); @Select("select * from user where id=#{id}") public User getUser2(Integer id); public void saveUser(User user); @Insert("insert into user(`name`) values(#{name})") @Options(useGeneratedKeys = true, keyProperty = "id") public void saveUser2(User user); }
-
复杂的方法用配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lun.boot.mapper.UserMapper"> <select id="getUser" resultType="com.lun.boot.bean.User"> select * from user where id=#{id} </select> <insert id="saveUser" useGeneratedKeys="true" keyProperty="id"> insert into user(`name`) values(#{name}) </insert> </mapper>
-
使用
@MapperScan("com.lun.boot.mapper")
简化,Mapper接口就可以不用标注@Mapper注解。@MapperScan("com.lun.boot.mapper") @SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } }
单元测试
JUnit5简介
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
JUnit 5官方文档
作为最新版本的JUnit框架,JUnit5与之前版本的JUnit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform 是在 JVM 上启动测试框架的基础,不仅支持 Junit 自制的测试引擎,其他测试引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了 JUnit5 的新的编程模型,是 JUnit5 新特性的核心。内部包含了一个测试引擎,用于在 Junit Platform 上运行。
- JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,JUnit3.x的测试引擎。
注意
-
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容JUnit4需要自行引入(即默认情况下不能使用JUnit4的功能 @Test)
-
JUnit 5’s Vintage已经从spring-boot-starter-test移除。如果需要继续兼容Junit4需要自行引入Vintage依赖如下:
<dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency>
-
使用添加
JUnit 5
,添加对应的 starter :<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
-
Junit5测试模板:
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test;//注意不是org.junit.Test(这是JUnit4版本的) import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class SpringBootApplicationTests { @Autowired private Component component; @Test //@Transactional 标注后连接数据库有回滚功能 public void contextLoads() { Assertions.assertEquals(5, component.getFive()); } }
常用测试注解
官方文档
-
@Test: 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
-
@ParameterizedTest:表示方法是参数化测试。
-
@RepeatedTest:表示方法可重复执行。
-
@DisplayName:为测试类或者测试方法设置展示名称。
-
@BeforeEach:表示在每个单元测试之前执行。
-
@AfterEach:表示在每个单元测试之后执行。
-
@BeforeAll:表示在所有单元测试之前执行。
-
@AfterAll:表示在所有单元测试之后执行。
-
@Tag:表示单元测试类别,类似于JUnit4中的@Categories。
-
@Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore。
-
@Timeout:表示测试方法运行如果超过了指定时间将会返回错误。
-
@ExtendWith:为测试类或测试方法提供扩展类引用。
-
代码如下:
import org.junit.jupiter.api.*; @DisplayName("junit5功能测试类") public class Junit5Test { @DisplayName("测试displayname注解") @Test void testDisplayName() { System.out.println(1); System.out.println(jdbcTemplate); } @ParameterizedTest @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" }) void palindromes(String candidate) { assertTrue(StringUtils.isPalindrome(candidate)); } @Disabled @DisplayName("测试方法2") @Test void test2() { System.out.println(2); } @RepeatedTest(5) @Test void test3() { System.out.println(5); } /** * 规定方法超时时间。超出时间测试出异常 * * @throws InterruptedException */ @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) @Test void testTimeout() throws InterruptedException { Thread.sleep(600); } @BeforeEach void testBeforeEach() { System.out.println("测试就要开始了..."); } @AfterEach void testAfterEach() { System.out.println("测试结束了..."); } @BeforeAll static void testBeforeAll() { System.out.println("所有测试就要开始了..."); } @AfterAll static void testAfterAll() { System.out.println("所有测试以及结束了..."); } }
断言机制
断言 Assertion 是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions
的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。
简单断言
用来对单个值进行简单的验证。如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
测试代码如下:
@Test
@DisplayName("simple assertion")
public void simple() {
assertEquals(3, 1 + 2, "simple math");
assertNotEquals(3, 1 + 1);
assertNotSame(new Object(), new Object());
Object obj = new Object();
assertSame(obj, obj);
assertFalse(1 > 2);
assertTrue(1 < 2);
assertNull(null);
assertNotNull(new Object());
}
数组断言
通过 assertArrayEquals
方法来判断两个对象或原始类型的数组是否相等。
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
组合断言
assertAll()
方法接受多个org.junit.jupiter.api.Executable
函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule
注解的 ExpectedException
变量还是比较麻烦的。而 JUnit5 提供了一种新的断言方式Assertions.assertThrows()
,配合函数式编程就可以进行使用。
@Test
@DisplayName("异常测试")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
超时断言
JUnit5还提供了Assertions.assertTimeout()
为测试方法设置了超时时间。
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
快速失败
通过 fail 方法直接使得测试失败。
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
前置假设 assumptions
Unit5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言assertions会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。
- 前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
assumeTrue 和 assumFalse
确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat
的参数是表示条件的布尔值和对应的Executable
接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
嵌套测试
官方文档
- JUnit 5 可以通过 Java 中的 内部类和@Nested 注解 实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。
- 在内部类中可以使用 @BeforeEach 和@AfterEach注解,而且嵌套的层次没有限制。
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
//嵌套模式,内部类的Beforeeach 会调用,所以此时stack=null
assertNull(stack);
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach//表示在每个执行单元之前执行
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
//由于此时栈里面元素为空,所以会抛出异常
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach//表示在每个执行单元之前,执行。内层可以使用外层的元素
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
参数化测试
参数化测试文档
参数化测试是 JUnit5 很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource
等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
- @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource: 表示为参数化测试提供一个null的入参
- @EnumSource: 表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。
- 如:
CSV,YML,JSON 文件
甚至方法的返回值也可以作为入参。 - 只需要去实现 ArgumentsProvider 接口,任何外部文件都可以作为它的入参。
@ParameterizedTest @ValueSource(strings = {"one", "two", "three"}) @DisplayName("参数化测试1") public void parameterizedTest1(String string) { System.out.println(string); Assertions.assertTrue(StringUtils.isNotBlank(string)); } @ParameterizedTest @MethodSource("method") //指定方法名 @DisplayName("方法来源参数") public void testWithExplicitLocalMethodSource(String name) { System.out.println(name); Assertions.assertNotNull(name); } static Stream<String> method() { return Stream.of("apple", "banana"); }
迁移化指南
官方文档 - Migrating from JUnit 4
- 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
- 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
- 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
- 把@Ignore 替换成@Disabled。
- 把@Category 替换成@Tag。
- 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。