Spring Boot 2.4.0
,数据库为mysql 8
.
每隔15秒用rest从远程获取数据,并用rest将其存储到mysql数据库 saveAll()
.
为所有给定实体调用save()方法。
所有数据都已设置id。
我希望如果db没有这样的id,它会被插入。
如果这样的id已经出现在db-它将被更新。
这里是从控制台剪下来的:
Hibernate:
insert
into
iot_entity
(controller_ref, description, device_id, device_ref, entity_type_ref, hw_address, hw_serial, image_ref, inventory_nr, ip6address1, ip6address2, ip_address1, ip_address2, latlng, location, mac_address, name, params, status, tenant, type, id)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
...
2020-12-05 23:18:28.269 ERROR 15752 --- [ restartedMain] o.h.e.jdbc.batch.internal.BatchingBatch : HHH000315: Exception executing batch [java.sql.BatchUpdateException: Duplicate entry '1' for key 'iot_entity.PRIMARY'], SQL: insert into iot_entity (controller_ref, description, device_id, device_ref, entity_type_ref, hw_address, hw_serial, image_ref, inventory_nr, ip6address1, ip6address2, ip_address1, ip_address2, latlng, location, mac_address, name, params, status, tenant, type, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2020-12-05 23:18:28.269 WARN 15752 --- [ restartedMain] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1062, SQLState: 23000
2020-12-05 23:18:28.269 ERROR 15752 --- [ restartedMain] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '1' for key 'iot_entity.PRIMARY'
2020-12-05 23:18:28.269 DEBUG 15752 --- [ restartedMain] o.s.orm.jpa.JpaTransactionManager : Initiating transaction rollback after commit exception
org.springframework.dao.DataIntegrityViolationException: could not execute batch; SQL [insert into iot_entity (controller_ref, description, device_id, device_ref, entity_type_ref, hw_address, hw_serial, image_ref, inventory_nr, ip6address1, ip6address2, ip_address1, ip_address2, latlng, location, mac_address, name, params, status, tenant, type, id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]; constraint [iot_entity.PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute batch
下面是获取和保存的方法:
@Override
@SneakyThrows
@Scheduled(fixedDelay = 15_000)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void fetchAndStoreData() {
IotEntity[] entities = restTemplate.getForObject(properties.getIotEntitiesUrl(), IotEntity[].class);
log.debug("ENTITIES:\n{}", mapper.writerWithDefaultPrettyPrinter().writeValueAsString(entities));
if (entities != null && entities.length > 0) {
entityRepository.saveAll(List.of(entities));
} else {
log.warn("NO entities data FETCHED !!!");
}
}
此方法每15秒运行一次。
实体:
@Data
@Entity
@NoArgsConstructor
@EqualsAndHashCode(of = {"id"})
@ToString(of = {"id", "deviceId", "entityTypeRef", "ipAddress1"})
public class IotEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Integer id;
// other fields
和存储库:
public interface EntityRepository extends JpaRepository<IotEntity, Integer> {
}
以下是json格式的iot实体截图:
2020-12-05 23:18:44.261 DEBUG 15752 --- [pool-3-thread-1] EntityService : ENTITIES:
[ {
"id" : 1,
"controllerRef" : null,
"name" : "Local Controller Unterföhring",
"description" : "",
"deviceId" : "",
...
所以身份证已经确定了。
此外,还为项目启用了批处理。它不应该对储蓄有任何影响。
我不明白为什么它试图插入一个新的实体而不是更新现有的实体?
为什么它不能区分新旧实体的区别?
更新:
为实体实现持久化:
@Data
@Entity
@NoArgsConstructor
@EqualsAndHashCode(of = {"id"})
@ToString(of = {"id", "deviceId", "entityTypeRef", "ipAddress1"})
public class IotEntity implements Serializable, Persistable<Integer> {
private static final long serialVersionUID = 1L;
@Id
private Integer id;
@Override
public boolean isNew() {
return false;
}
@Override
public Integer getId() {
return this.id;
}
但是,它失败了,只有一个例外- Duplicate entry '1' for key 'iot_entity.PRIMARY'
如果我再加上 @GeneratedValue
如下所示:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
不会失败的。但是,它将自己更新id值。
例如,它用 id = 15
:
[ {
"id" : 15,
"carParkRef" : 15,
"name" : "UF Haus 1/2",
应按以下方式保存:
事实上它有 id = 2
取而代之的是:
这是不正确的。
试图添加到存储服务:
private final EntityManager entityManager;
...
List.of(carParks).forEach(entityManager::merge);
失败时出现相同的异常(有或没有实现persistable)。它试图插入值- insert into ... Duplicate entry '15' for key '... .PRIMARY'
来自的代码段 application.yml
:
spring:
# ===============================
# = DATA SOURCE
# ===============================
datasource:
url: jdbc:mysql://localhost:3306/demo_db
username: root
password: root
initialization-mode: always
# ===============================
# = JPA / HIBERNATE
# ===============================
jpa:
show-sql: true
generate-ddl: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
generate_statistics: true
在这里您可以看到pom文件内容。
如何解决这个问题?
3条答案
按热度按时间vxqlmq5t1#
spring数据jpa使用@version@id字段的组合来决定是合并还是插入。
null@id和null@version意味着插入新记录
如果存在@id,@version字段用于决定是合并还是插入。
更新仅在以下情况下调用(更新…)。。。。其中id=和version=0)
因为您缺少@id和@version,它正在尝试插入,因为underlysing系统决定这是新记录,并且在运行sql时得到错误。
bq8i3lrv2#
问题很可能是,既然
@Id
未标记为@GeneratedValue
,spring数据假定所有分离的(暂时的)实体都传递给save()/saveAll()
应该有EntityManager.persist()
在他们身上调用。尝试制作
IotEntity
实施Persistable
又回来了false
从isNew()
. 这将告诉spring数据始终使用EntityManager.merge()
相反,它应该具有预期的效果(即插入不存在的实体并更新现有的实体)。qrjkbowd3#
看来我找到了这种行为的根源。
主应用程序启动程序看起来像:
这三个步骤都有严格的执行顺序。如果需要,第一种方法必须重复,以便在本地更新数据。另外两个服务器只处理存储的数据。
第一种方法如下所示:
此外,还安排了此执行。
当应用程序启动时,它尝试执行此获取两次:
两个线程在同一个catch和store上并行运行-尝试
insert
数据(每次开始时都会重新创建表)。所有稍后的回迁都很好,它们只由
@Sceduled
线程。if注解
@Sceduled
-它将毫无例外地正常工作。解决方案:
向服务类添加了其他布尔属性:
并在应用程序启动后控制值: