我正在创建一个新服务,目的是以幂等方式使用Kafka事件并将数据存储到新的PostgreSQL数据库中。
该事件将提供将在复合键中使用的数据:
@Embeddable
public class MyCompositeKey implements Serializable {
@Column(name="field1", nullable = false)
private UUID field1;
@Column(name="field2", nullable = false)
private UUID field2;
@Column(name="field3", nullable = false)
private UUID field3;
... boilerplate Constructors/getters ...
实体将通过@EmbeddedId
引用它:
@Entity
@Table
public class MyEntity implements Serializable {
@EmbeddedId private MyCompositeKey myCompositeKey;
... Columns/Constructors/getters ...
当一个事件被消耗时,我想让spring-data-jpa
足够聪明,知道我们是从现有的MyEntity替换数据,还是创建一个新行。
在研究该方法中逻辑的期望之前,逻辑被认为足够安全以使用CrudRepository#save
方法:
@Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
我已经达到了交易似乎已完成的程度,但没有记录持久存在于表中。
我已通过调试确认调用#save
正在分支到上面引用的return this.em.merge(entity)
逻辑。
我只发现了一个可能有用的博客文章[1]用于类似的场景,并且在它似乎无法解决问题之后,我将失去下一步的位置。
我可以预见的唯一其他选择是手动执行潜在的三查询执行:
findById
delete
save
[1] https://jivimberg.io/blog/2018/11/05/using-uuid-on-spring-data-jpa-entities/
好吧,我发现了这个问题。所有这些设计都运行良好,缺少配置。
对于某些上下文 - Spring Boot似乎配置默认的javax.sql.DataSource
,默认的javax.persistence.EntityManagerFactory
和默认的org.springframework.transaction.PlatformTransactionManager
bean。
我的上下文配置了javax.sql.DataSource
bean,以便使用org.springframework.boot.context.properties.ConfigurationProperties
指定配置前缀的区别。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
@Bean
@ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
我的上下文没有为依赖的javax.persistence.EntityManagerFactory
和org.springframework.transaction.PlatformTransactionManager
bean添加替代品。
修复是添加所有配置。来自docs:
您必须直接创建LocalContainerEntityManagerFactoryBean而不是EntityManagerFactory,因为除了创建EntityManagerFactory之外,前者还参与异常转换机制。
结果配置:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
@Bean
@ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.myservice");
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}