Spring与TestNG中@DataJpaTest未触发@Convert的AttributeConverter
问题分析与解决方案
核心问题原因
- JPA一级缓存规避了数据库查询:执行
save后,实体被存入EntityManager的一级缓存,后续findById直接从缓存取数据,不会触发数据库读取操作,因此不会调用convertToEntityAttribute方法,自然不会抛出预期异常。 - 转换器未生效或逻辑缺失:要么Spring未扫描到转换器/实体导致注解失效,要么
convertToDatabaseColumn未实现实际加密逻辑,导致数据未被加密存储。
具体修复方案
1. 打破缓存,强制从数据库读取
修改测试用例,通过清除EntityManager缓存或拆分事务,迫使查询从数据库获取数据:
@DataJpaTest(excludeAutoConfiguration = {LiquibaseAutoConfiguration.class}) public class AccountRepositoryTest extends AbstractTransactionalTestNGSpringContextTests { @Autowired private AccountRepository accountRepository; @PersistenceContext private EntityManager entityManager; public void shouldThrowExceptionOnReadingSensitiveData() throws Exception { Account expectedAccount = new Account("1", "sensitive info"); accountRepository.save(expectedAccount); // 清除一级缓存,强制后续查询走数据库 entityManager.clear(); // 预期抛出UnsupportedOperationException Assertions.assertThrows(UnsupportedOperationException.class, () -> { accountRepository.findById("1"); }); } }
2. 完善转换器加密逻辑
给convertToDatabaseColumn添加实际哈希加密实现,确保数据存入数据库前被加密:
import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class EncryptorConverter implements AttributeConverter<String, byte[]> { private static final String HASH_ALGORITHM = "SHA-256"; @Override public byte[] convertToDatabaseColumn(String attribute) { if (attribute == null) { return null; } try { MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); return digest.digest(attribute.getBytes(StandardCharsets.UTF_8)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Hash algorithm not supported", e); } } @Override public String convertToEntityAttribute(byte[] dbData) { throw new UnsupportedOperationException("Hashed data cannot be converted back to plaintext"); } }
3. 确保配置与实体注解有效性
- 检查
TestDatabaseConfig的packagesToScan参数,确保包含实体类和转换器所在的包,让Spring能扫描到这些组件。 - 给实体字段指定匹配的数据库类型,确保byte[]类型能正确存储:
import jakarta.persistence.*; import java.util.Objects; @Entity public class Account { @Id private String id; // 指定数据库字段类型为VARBINARY,适配byte[]存储 @Column(columnDefinition = "VARBINARY(255)") @Convert(converter = EncryptorConverter.class) private String sensitiveInfo; public Account(String id, String sensitiveInfo) { this.id = id; this.sensitiveInfo = sensitiveInfo; } // 重写equals和hashCode,确保测试断言有效 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Account account = (Account) o; return Objects.equals(id, account.id) && Objects.equals(sensitiveInfo, account.sensitiveInfo); } @Override public int hashCode() { return Objects.hash(id, sensitiveInfo); } }
4. 修正测试预期逻辑
原测试用例的断言逻辑与预期矛盾,应该改为验证异常抛出,而非断言实体相等:
public void shouldThrowExceptionOnReadingSensitiveData() throws Exception { Account expectedAccount = new Account("1", "sensitive info"); accountRepository.save(expectedAccount); entityManager.clear(); // 验证查询时抛出指定异常 Assertions.assertThrows(UnsupportedOperationException.class, () -> { accountRepository.findById("1"); }); }
内容的提问来源于stack exchange,提问作者Rodrigo Novaes
相关产品推荐
相关产品推荐

