深入了解Spring (第三天)

本文介绍了Spring框架下的测试方法,包括单元测试和集成测试,并详细讲解了如何使用JUnit5进行测试。此外,还深入探讨了Spring提供的JdbcTemplate类如何简化JDBC编程,以及Spring事务管理的具体实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试Spring应用程序

JUnit 5

使用JUnit 5编写测试

在实验中均使用JUnit 5进行测试

  • 支持Junit 5 是Spring 5的一个主要特征
  • JUnit 5是Spring Boot 2.2版本开始的默认JUnit版本
  • 需要Java 8 + 运行环境
  • 利用Lambda

组件

JUnit 平台  在JVM上启动测试框架的基础 

JUnit Jupiter  在JUnit 5中编写测试和扩展模型

JUnit Vintage  用于在平台上运行JUnit 3和4测试的TestEngine 

JUnit 5:新编程模型(JUnit 5忽略了所有JUnit 4注解)

替换JUnit 4注解 

介绍新注解

 单元测试(没有Spring的单元测试)

  • 测试功能的一个单元
  • 保持最少的依赖项
  • 与环境(包括Spring)隔离
  • 使用简化的依赖项替代方案  Stubs 与/或 Mocks

集成测试(使用Spring的集成测试)

集成测试

测试多个单元共同工作的互动  首先所有单元都能单独运行(单元测试显示了这一点)

在其周围的基础架构的上下文中测试应用程序类 

  • 容器外的测试,不需要运行整个应用程序服务器
  • 基础架构可能“缩小”  使用ActiveMQ代替商业消息服务器
  • 测试容器  使用轻量级的,可抛弃的普通数据库实例,或其它任何可以在Docker容器中运行的东西

集成测试示例

 

 Spring对测试的支持

Spring有丰富的测试支持

  • 基于TestContext框架  为你的测试定义一个ApplicationContext
  • @ContextConfiguration  定义需要使用的Spring配置

作为一个单独的模块打包

spring-test.jar 

JUnit 5中的@ExtendWith

JUnit 5有通过@ExtendWith的可扩展框架

  • 取代JUnit 4的@RunWith
  • JUnit 5支持多种扩展-因此名称进行升级 

Spring的扩展点是SpringExtension类  一个感知Spring的测试运行者 

 

 @SpringJUnitConfig

@SpringJUnitConfig是一个“合成”注解,他整合了

  • 来自JUnit 5的@ExtendWith(SpringExtension.class)
  • 来自Spring的@ContextConfiguration

 在测试中自动布线的替代方案

 将配置作为一个内部类

 多个测试方法

 脏上下文

在测试方法结束时强制关闭上下文  允许测试@PreDestroy行为

下一个测试将获取一个新的应用程序上下文  缓存的上下文已经被销毁,取而代之,新的上下文会被缓存

 测试属性源

自定义仅用于测试的properties 

  • 定义1个或多个properties  具有更高的优先级
  • 定义需要加载的1个或多个properties的位置  默认情况下将查找[classname].properties

 使用Spring进行测试的好处

无需部署到外部容器中测试应用程序功能

  • 在你的IDEA内快速运行一切
  • 支持持续集成测试

 允许在测试环境和生产环境之间重复使用你的配置

应用程序配置逻辑通常被重复使用

基础架构配置与环境相关  数据源  JMS序列

激活测试使用的Profiles

测试类中的@ActiveProfiles

  • 定义1个或多个Profiles
  • 与该Profile关联的Bean会被实例化
  • 还有不关联到任何Profile的Bean

示例:激活2个Profiles - jdbc和dev

 

 使用Java配置的Profiles激活

在@Configuration类或任何它的@Bean方法上使用@Profile

 使用注解的Profiles激活

在组件类上使用@Profile

使用数据库测试

也许,你在执行数据测试时遇到过这些问题

  • 数据库的“用户名”字段设置为了“唯一(unique)”约束,第1次测试插入数据成功了,第2次就会因为“唯一”约束而失败
  • 想要测试更新或删除数据,但表中没有数据,或不知道数据的id
  • 想要测试查询某些数据,但表中没有适合用于测试的条件的数据  例如测试分页查询,但表中的数据量太大

 使用数据库测试

  • 针对SQl数据库的测试很常见
  • 对于这类测试,基于内存的数据很实用 无需提前安装
  • 常见需求:在测试之前填充DB  使用@Sql注解

 @Sql示例

 @Sql选项

SQL何时/如何运行?

  • executionPhase:在测试方法之前(默认)或之后
  • config:控制SQL脚本的选项  如果脚本执行失败了怎么办?FAIL_ON_ERROR, CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS,DEFAULT*    SQL语法控制:注释,语句分隔符

 总结

测试是任何开发中的重要组成部分

单元测试是对一个类进行隔离测试

  • 外部依赖项应该最小化
  • 考虑创建Stubs或Mocks进行单元测试
  • 你不需要Spring进行单元测试

集成测试则测试多个单元共同工作的互动

  • Spring提供了良好的集成测试支持
  • 不同的测试与部署配置使用不同的Profiles
  • 内置支持数据库测试 

 JUnit 4与JUnit 5的区别

  • JUnit 5增强了JUnit框架  在JUnit 4的基础上增加了新的功能
  • JUnit 5支持多种扩展  解决了JUnit 4的“单一运行者限制”

 JUnit 5断言(断言:本次测试结果必定如此)

 使用断言  --  添加单元测试依赖

 使用断言  --  静态导入Assertions类所有静态成员

 Assertions类中可使用的断言方法(均重载多次)

 新的断言

新的断言介绍

  • assertThrows(<exception.class>,<Lambda表达式>)
  • assertTimeout(<duration>,<Lambda表达式>)
  • assertAll(<description>,<multiple-assertions>)

 改进设想

使用Lambda表达式  要测试的代码

 assertThrows(..)与assertTimeout(..)

 假设

断言与假设区别

  • 当测试不通过时,断言将失败(failed),而假设将终止(aborted)
  • 当失败时,会显示预期的信息(例如断言的期望值/期望效果、实际值/实际效果),当终止,则结束
  • 当你明确测试结果,可以使用断言,当你并不能肯定测试结果是哪一种,可以使用假设

Spring JDBC简介

模板设计模式

广泛使用且实用的模式: http://en.wikipedia.org/wiki/Template_method_pattern

定义算法的轮廓或骨架

  •  具体细节留待以后具体的实现
  •  隐藏大量的模板代码

Spring提供了许多模板类

  •   JdbcTemplate,JmsTemplate
  •   RestTemplate,WebServiceTemplate....
  •   大部分隐藏了低级资源管理 

Spring的JdbcTemplate

大大简化了JDBC API的使用

  • 消除重复的模板代码
  • 减轻引起错误的常见原因
  • 正确处理SQLExceptions

在不牺牲性能的情况下 提供对标准JDBC结构的完全访问

JdbcTemplate简介

 

 JdbcTemplate内部的回调图

 创建一个JdbcTemplate

需要一个 DataSource: JdbcTemplate template = new JdbcTemplate(dataSource);

创建一个模板并重复使用它

  • 不要为每个线程都创建
  • 构建之后线程安全

使用

  • 任何需要JDBC的时候
  • 在实用或测试代码中
  • 清理混乱的遗留代码 

JdbcTemplate的使用

实现一个基于JDBC的存储库

 使用JdbcTemplate查询

JdbcTemplate能查询:

  • 简单类型(int,long,String,Date,...)
  • Generic Maps
  • Domain Objects

查询简单的Java类型

无绑定变量的查询 

 绑定变量的查询

能使用绑定变量进行查询  注意变量参数列表的使用

 处理结果集

一般查询

JdbcTemplate能将ResultSet中的每一行作为一个Map返回

  • 当查询的预期结果只有1行时  使用queryForMap(..)
  • 当查询的预期结果有多行时  使用queryForList(..)

实用与ad hoc数据、测试报告  获取到的数据不需要映射到Java对象

 查询Generic Maps(1)

查询1个结果

 返回

 查询Generix Maps(2)

查询多行数据

 返回

 查询Domain Object

通常情况下,将关联数据映射到domain objects非常实用  例如将ResultSet 映射到 Account

Spring的JdbcTemplate使用回调方法进行支持

你可能使用OPM

  • 需要在JdbcTemplate查询和JPA(或类似的)映射之间做出决定
  • 有些表可能难以使用JPA映射

 RowMapper用于映射

Spring提供了一个RowMapper接口,用于将一个ResultSet的某1行映射到1个对象上

  • 可用于单行和多行查询
  • 参数化定义其返回类型

 查询Domain Objects

使用JdbcTemplate查询1行数据

 查询Domain Object(2)

查询多行数据

 ResultSetExtractor

Spring提供了一个ResultSetExtractor接口,用于一次性处理整个ResultSet

  • 你负责迭代ResultSet
  • 示例:将整个ResultSet映射到一个对象上

 使用ResultSetExtractor

 回调接口总结

  • RowMapper  当ResultSet的每一行都映射到Domain Object时的最佳选择
  • ResultSetExtractor  当一个ResultSet的多行映射到单个对象时的最佳选择
  • RowCallbackHandler  另一个写到其他目的地的处理程序

 异常处理与Spring

  • Checked Exceptions  强制开发者处理错误 但是如果你不能处理,则声明它。  缺点:中间方法必须声明下面所有方法的异常 (紧耦合的一种形式)
  • Unchecked Exceptions  可以抛出调用层次结构的最佳处理位置。  优点:中间方法并不知道它的存在,在企业级程序中,使用它更好一些。    Spring抛出的一直是Runtime(unchecked)Exceptions

数据访问Exceptions

SQLException 

  • 过于笼统——每一个数据库错误都有一个异常
  • 调用类“知道”你正在使用JDBC
  • 紧耦合

 Spring提供了DataAccessException体系

  • 隐藏你是否在使用JPA、Hibernate、JDBC...
  • 实际上是一个异常的体系  对于各种情况而言不只是一种异常
  • 在所有支持的数据访问技术中保持一致
  • Unchecked

Spring的数据访问Exceptions

 

 总结

  • JDBC很实用  但是直接使用JDBC API既繁琐又容易出错
  • JdbcTemplate简化了            了一致性: DRY原则隐藏了大部分的JDBC   读取数据的多种选择
  • SQLExceptions通常不能在抛出的位置被处理  : 不应该是checked Exceptions   Spring提供了DataAccessException来取代它

 使用Spring进行事务管理

为什么使用事务

什么是事务

作为单一、不可分割的行动而进行的任务集

  • Atomic(原子性):每个工作单元都是一个全部或全无的操作
  • Consistent(一致性):永不违背数据库完整性约束
  • Lsolated(隔离性):彼此之间隔离事务
  • Durable(持久性):提交的数据改变是永久性的

 Java事务管理

局部和全局的事务管理

 Java事务API——不同资源的不同API(难以使用)

 

 Java事务管理的问题

  • 不同本地资源的多个API
  • 程序化事务的划分  通常在业务层中进行,但我们不希望数据访问代码在业务层中(关注点分离) 通常重复(横切关注)
  • 正交关注  事务划分应独立于事务实现

Spring事务管理

  • Spring将事务划分与事务实现分离  通过AOP声明式表达的划分(也可以用程序化方法)  PlatformTransactionManager提取隐藏了实现细节(有多种可用的实现)
  • Spring对全局或本地使用相同的API  从本地到全局的变化不大(只改变了事务管理器) 

有两个步骤 

  •  声明一个PlatformTransactionManager类型的Bean
  • 声明事务的方法  使用注解,程序化  能混合并且匹配

PlatformTransactionManager的实现

 Spring的PlatformTransactionManager是提取的基础接口

有多种可用的实现

  • DataSourceTransactionManager
  • JmsTransactionManager
  • JpaTransactionManager
  • JtaTransactionManages
  • WebLogicJtaTransactionManages
  • WebSphereUowTransactionManages
  • ....

部署事务管理器

创建所需的实现  就像其它的Spring bean一样(适当配置它)

例:DataSource的管理器

 

 访问JTA事务管理器

对容器管理的DataSource使用JNDI查找

@Transactional配置

声明式事务管理

目标业务被包裹在代理中  使用“around”类型的Advice

代理引用注入调用器

 @Transaction:中的实现

代理实现了一下行为

  • 在进入该方法之前,事务已经开始
  • 在方法结束时提交
  • 如果方法抛出RuntimeException则回滚  (a.默认行为 b.可以被覆盖 c.Checked exceptions不会导致回滚)

Transaction绑定当前线程

Transaction上下文绑定了当前线程  持有底层的JDBC连接  Hibernate会话,JTA(Java EE)工作方式相似

在@Transactional方法中使用JdbcTemplate  自动使用那个连接

手动访问方式: 

 @Transaction  在类级别

适用于接口声明的所有方法

 @Transaction  在类和方法级别

结合类和方法级别

Java的@Transaction

Java也有一个注解:javax.transaction.Transactional

Spring也支持:Spring的@Transactional

事务的传播

 例:

如果ClientServiceImpl调用AccountServiceImpl会发生什么

 事务传播分析图

事务的传播级别与它们的行为

 例:REQUIRED

默认值

在当前事务中执行,如果当前没有事务,则创建一个新事务

REQUIRES_NEW

创建一个新的事务,如果当前已存在事务,则暂停当前事务

传播规则由代理强制执行

下列,传播规则并没有被应用,因为没有通过代理来调用

 

事务的隔离级别

4种隔离级别

事务的隔离级别是保证事务隔离性的解决方案

由低到高:

Read uncommitted、Read committed、Repeatable read、Serializable

Read uncommitted  读未提交

一个事务可以读取到另一个事务未提交的操作的结果。该级别不保证任何的隔离性。该隔离级别可能出现脏读、不可重复度、幻读问题。

  • 脏读:一个事务使用了另一个事务未提交数据
  • 不可重复度:指在一个事务内,多次读同一数据,得到的结果不同
  • 幻读:一个事务读取全表数据时,读取到另一个事务向表中新增、删除操作提交的结果 

 Read committed 读已提交

一个事务仅能读取到另一个事务已提交的数据 ,可以保证部分隔离性,可以防止脏读问题,但是具有不可重复读和幻读问题。

Repeatable read 可重复读

该级别下,一个事务无法读取另一个后开启的事务已提交的操作结果 

修改隔离级别

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值