Spring和TestNG @DataJpaTest不会运行@Convert注释属性的AttributeConverters。

huangapple go评论103阅读模式
英文:

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&#39;ll first apply a simple hash, but instead of un-hashing I&#39;ll just throw an `UnsupportedOperationException`.

```java
public class EncryptorConverter implements AttributeConverter&lt;String, byte[]&gt; {
    @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&lt;Account, String&gt; {
}

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(&quot;1&quot;, &quot;sensitive info&quot;);
    accountRepository.save(expectedAccount);

    // I was expecting an exceptioon with UnsupportedOperationException as root cause here
    Optional&lt;Account&gt; actualAccount = accountRepository.findById(&quot;1&quot;);

    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 = {&quot;...&quot;})
@EnableTransactionManagement
public class TestDatabaseConfig {

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
        LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
        lef.setDataSource(dataSource);
        lef.setJpaVendorAdapter(jpaVendorAdapter);
        lef.setPackagesToScan(&quot;...&quot;);
        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后执行flushclear。这将向数据库发出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(&quot;1&quot;, &quot;sensitive info&quot;);
    accountRepository.save(expectedAccount);
    
    // Simulate commit/tx end
    tem.flush();
    tem.clear();

    Optional&lt;Account&gt; actualAccount = accountRepository.findById(&quot;1&quot;);

    assertTrue(actualAccount.isPresent());
    assertEquals(actualAccount.get(), expectedAccount);
  }
}

huangapple
  • 本文由 发表于 2023年5月24日 17:15:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76321906.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定