咨询@SpringBootTest与@DataJpaTest下assertSame的行为差异原因
问题场景
测试中出现三种不同表现:
- 使用
@DataJpaTest时,assertSame(emp, semp)结果为true - 使用
@SpringBootTest时,该断言结果为false - 给
@SpringBootTest的测试方法加上@Transactional后,断言又变回true
涉及代码如下:
实体类
import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import lombok.Data; @Entity @Data public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long eno; }
Repository接口
import org.springframework.data.jpa.repository.JpaRepository; import com.doding.board.domain.Employee; public interface EmployeeRepository extends JpaRepository<Employee, Long> {}
测试类
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import com.doding.board.domain.Employee; import com.doding.board.repo.EmployeeRepository; @SpringBootTest //@DataJpaTest public class IdentityTest { @Autowired EmployeeRepository repo; @Test //@Transactional void identityTest() { Employee emp = new Employee(); repo.save(emp); Employee semp = repo.findById(emp.getEno()).get(); assertEquals(emp, semp); // 始终为true assertSame(emp, semp); // 结果随注解变化 } }
原因解析
核心在于**JPA的一级缓存(EntityManager缓存)**和不同测试注解下的事务/EntityManager生命周期:
@DataJpaTest的默认行为:
这个注解会自动为测试方法添加@Transactional,整个测试过程共享同一个EntityManager。调用repo.save(emp)后,实体实例会存入一级缓存;后续findById直接从缓存取,不会创建新实例,所以assertSame返回true。测试结束后事务会自动回滚,避免污染测试数据库。@SpringBootTest无事务的情况:@SpringBootTest默认不给测试方法绑定事务。repo.save(emp)执行时,Spring Data JPA会临时开启一个EntityManager完成保存,之后立即关闭,此时emp变成游离态(脱离EntityManager管理)。调用repo.findById时,会新建一个EntityManager从数据库查询,生成全新的实体实例返回,两个实例内存地址不同,所以assertSame为false。但@Data生成的equals方法基于实体属性(这里是eno)比较,所以assertEquals始终成立。@SpringBootTest加@Transactional的情况:
加上事务后,整个测试方法处于同一个事务上下文,EntityManager会存活到测试结束。save和findById共用同一个EntityManager,findById直接从一级缓存获取之前的实例,所以assertSame返回true。
内容的提问来源于stack exchange,提问作者andy

