英文:
Spring and TestNG @DataJpaTest does not run AttributeConverters for @Convert annotated attributes
问题
以下是您要翻译的内容:
我有一个非常基本的`AttributeConverter`实现,我想要测试它。因此,我首先将应用一个简单的哈希函数,但不会进行反哈希操作,而是会抛出`UnsupportedOperationException`异常。
```java
public class EncryptorConverter implements AttributeConverter<String, byte[]> {
@Override
public byte[] convertToDatabaseColumn(String attribute) {
// 应用哈希函数
}
@Override
public String convertToEntityAttribute(byte[] dbData) {
throw new UnsupportedOperationException();
}
}
还有一个实体类
@Entity
public class Account {
@Id
private String id;
@Column
@Convert(converter = EncryptorConverter.class)
private String sensitiveInfo;
}
假设我有一个非常基本的Account
存储库:
@Repository
public interface AccountRepository extends JpaRepository<Account, String> {
}
我想确保在@DataJpaTest
中调用了我的EncryptorConverter
,就像这样:
@DataJpaTest(excludeAutoConfiguration = {LiquibaseAutoConfiguration.class})
public class AccountRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
private AccountRepository accountRepository;
public void shouldSaveEncryptedData() throws Exception {
Account expectedAccount = new Account("1", "sensitive info");
accountRepository.save(expectedAccount);
// 我期望在这里出现一个以UnsupportedOperationException为根本原因的异常
Optional<Account> actualAccount = accountRepository.findById("1");
assertTrue(actualAccount.isPresent());
assertEquals(actualAccount.get(), expectedAccount);
}
}
我原本期望上述测试会失败,但实际上它通过了。我进行的所有调试都表明,实体已在数据库中存储,但没有进行任何加密。我已经阅读了这篇文章,它似乎更适合我面临的问题,但没有太多帮助。
有人知道在这里该怎么做吗?
额外信息:这是我配置测试数据库的方式:
@EnableJpaRepositories(basePackages = {"..."})
@EnableTransactionManagement
public class TestDatabaseConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource);
lef.setJpaVendorAdapter(jpaVendorAdapter);
lef.setPackagesToScan("...");
return lef;
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
<details>
<summary>英文:</summary>
I have a very basic `AttributeConverter` implementation that I want to test. As such, I'll first apply a simple hash, but instead of un-hashing I'll just throw an `UnsupportedOperationException`.
```java
public class EncryptorConverter implements AttributeConverter<String, byte[]> {
@Override
public byte[] convertToDatabaseColumn(String attribute) {
// Apply hash
}
@Override
public String convertToEntityAttribute(byte[] dbData) {
throw new UnsupportedOperationException();
}
}
And an entity
@Entity
public class Account {
@Id
private String id;
@Column
@Convert(converter = EncryptorConverter.class)
private String sensitiveInfo;
}
And let's say I have a very fundamental repository for Account
:
@Repository
public interface AccountRepository extends JpaRepository<Account, String> {
}
I want to make sure that my EncryptorConverter
is being called in a @DataJpaTest
, like this:
@DataJpaTest(excludeAutoConfiguration = {LiquibaseAutoConfiguration.class})
public class AccountRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
private AccountRepository accountRepository;
public void shouldSaveEncryptedData() throws Exception {
Account expectedAccount = new Account("1", "sensitive info");
accountRepository.save(expectedAccount);
// I was expecting an exceptioon with UnsupportedOperationException as root cause here
Optional<Account> actualAccount = accountRepository.findById("1");
assertTrue(actualAccount.isPresent());
assertEquals(actualAccount.get(), expectedAccount);
}
}
I expected the aforementioned test to fail, but instead it passes. All debug I made suggested that the entity was persisted without any encryption in the DB. I already read this article, which seemed more promising for the problem I'm facing, but it didn't help much.
Does someone know what to do here?
Extra: this is how I'm configuring my test DB:
@EnableJpaRepositories(basePackages = {"..."})
@EnableTransactionManagement
public class TestDatabaseConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource);
lef.setJpaVendorAdapter(jpaVendorAdapter);
lef.setPackagesToScan("...");
return lef;
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
答案1
得分: 2
问题不在于转换器没有被识别,问题在于你的测试以及对Hibernate工作原理的理解不足。
你正在保存实体,这将把它放入第一级缓存,接下来你执行了一个findById
,这将导致EntityManager.find
,它将首先查找第一级缓存,由于实体已找到,不会发生查询或转换。
要解决这个问题,模拟一个事务。为此,在你的类中注入TestEntityManager
,并在save
后执行flush
和clear
。这将向数据库发出SQL并清除第一级缓存。
@DataJpaTest(excludeAutoConfiguration = {LiquibaseAutoConfiguration.class})
public class AccountRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
private TestEntityManager tem;
@Autowired
private AccountRepository accountRepository;
@Test
public void shouldSaveEncryptedData() throws Exception {
Account expectedAccount = new Account("1", "sensitive info");
accountRepository.save(expectedAccount);
// 模拟提交/事务结束
tem.flush();
tem.clear();
Optional<Account> actualAccount = accountRepository.findById("1");
assertTrue(actualAccount.isPresent());
assertEquals(actualAccount.get(), expectedAccount);
}
}
英文:
The problem isn't the fact that the converter isn't being picked up, the problem is your test and lack of understanding of how Hibernate works.
You are saving the entity, which puts it in the first level cache, next you do a findById
which will result in an EntityManager.find
which will first look into the 1st level cache and as the entity is found no query nor conversion will take place.
To fix, simulate a transaction. For this inject the TestEntityManager
into your class and flush
and clear
after the save
. This will issue the SQL to the database and clear the 1st level cache.
@DataJpaTest(excludeAutoConfiguration = {LiquibaseAutoConfiguration.class})
public class AccountRepositoryTest extends AbstractTransactionalTestNGSpringContextTests {
@Autowired
private TestEntityManager tem;
@Autowired
private AccountRepository accountRepository;
@Test
public void shouldSaveEncryptedData() throws Exception {
Account expectedAccount = new Account("1", "sensitive info");
accountRepository.save(expectedAccount);
// Simulate commit/tx end
tem.flush();
tem.clear();
Optional<Account> actualAccount = accountRepository.findById("1");
assertTrue(actualAccount.isPresent());
assertEquals(actualAccount.get(), expectedAccount);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论