【发布时间】:2020-01-25 21:38:54
【问题描述】:
我的 Spring 应用程序中有以下类:TaskService 和 DevStartup。
当应用程序启动时DevStartup 运行,但Tag("Home") 被视为tasksRepository.save(task) 上的分离实体,在启动期间引发分离实体异常。
@Component
@AllArgsConstructor
@Slf4j
@Profile("dev")
public class DevStartup {
private final TagsService tagsService;
private final TagsRepository tagsRepository;
private final TasksRepository tasksRepository;
private final Clock clock;
private final EntityManager entityManager;
@EventListener(ApplicationReadyEvent.class)
public void initializeApplication() {
insertTags();
insertTasks();
}
private void insertTags() {
List<Tag> tags = Arrays.asList(
new Tag("Home")
);
tagsRepository.saveAll(tags);
}
private void insertTasks() {
Task task = new Task("Run a webinar", "Zoom.us", clock.time());
Set<Tag> tagsForTask = Stream.of("Home")
.map(tag -> tagsService.findByName(tag).orElseGet(() -> new Tag(tag)))
.collect(Collectors.toSet());
task.addTags(tagsForTask);
tasksRepository.save(task); // ERROR -> Tag("Home") Entity Detached!!!!!
}
}
同时,我在 TaskService 类中有确切的代码,我使用 REST 控制器中的相同参数调用它。
addTask("Task title", "Task description", Stream.of("Home").collect(toSet());
这一次,Tag("Home") 实体被附加
@Service
@RequiredArgsConstructor
public class TasksService {
private final StorageService storageService;
private final TasksRepository tasksRepository;
private final TagsService tagsService;
private final Clock clock;
private final EntityManager entityManager;
public Task addTask(String title, String description, Set<String> tags) {
Task task = new Task(
title,
description,
clock.time()
);
Set<Tag> tagsForTask = tags.stream()
.map(tag -> tagsService.findByName(tag).orElseGet(() -> new Tag(tag)))
.collect(Collectors.toSet());
task.addTags(tagsForTask);
tasksRepository.save(task); // OK -> Tag("Home") Entity Attached
return task;
}
// ...
}
这两者有什么区别,为什么实体在一种情况下是分离的,而在另一种情况下是附加的?
我正在使用带有 Hibernate 5 和 JPA(Spring Data JPA 项目)的 Spring Boot 2.1.9。
TagsService.findByName() 只是打电话给TagsRepository.findByNameContainingIgnoreCase:
public interface TagsRepository extends JpaRepository<Tag, Long> {
Optional<Tag> findByNameContainingIgnoreCase(String name);
}
更新
这是来自DevStartup 的跟踪日志。我可以发现会话在从 TagService 获取标记后立即关闭,然后再次打开以保存任务(这就是我得到分离实体异常的原因)。
2020-01-25 23:06:46.774 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : Automatically flushing session
2020-01-25 23:06:46.778 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : SessionImpl#afterTransactionCompletion(successful=true, delayed=false)
2020-01-25 23:06:46.779 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : Closing session [d8094673-13fd-4b7e-af38-4aa01afbcaf7]
2020-01-25 23:06:46.789 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : Opened Session [d2c0c551-6523-466f-ab07-a8c1455c62a4] at timestamp: 1579990006789
2020-01-25 23:06:46.848 DEBUG 33093 --- [ restartedMain] org.hibernate.SQL : select tag0_.id as id1_2_, tag0_.name as name2_2_ from tag tag0_ where upper(tag0_.name) like upper(?) escape ?
Hibernate: select tag0_.id as id1_2_, tag0_.name as name2_2_ from tag tag0_ where upper(tag0_.name) like upper(?) escape ?
2020-01-25 23:06:46.850 TRACE 33093 --- [ restartedMain] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [%Home%]
2020-01-25 23:06:46.850 TRACE 33093 --- [ restartedMain] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [CHAR] - [\]
2020-01-25 23:06:46.855 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : Closing session [d2c0c551-6523-466f-ab07-a8c1455c62a4]
2020-01-25 23:06:46.860 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : Opened Session [6caeb288-a608-4aa7-a5f9-091c69900815] at timestamp: 1579990006860
2020-01-25 23:06:46.867 DEBUG 33093 --- [ restartedMain] org.hibernate.SQL : insert into task (id, uuid, created_at, description, title) values (null, ?, ?, ?, ?)
Hibernate: insert into task (id, uuid, created_at, description, title) values (null, ?, ?, ?, ?)
2020-01-25 23:06:46.868 TRACE 33093 --- [ restartedMain] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [fc9c993b-eccf-45ff-b49a-12498b6e62eb]
2020-01-25 23:06:46.868 TRACE 33093 --- [ restartedMain] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [2020-01-25T23:06:46.785455]
2020-01-25 23:06:46.870 TRACE 33093 --- [ restartedMain] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [Zoom.us]
2020-01-25 23:06:46.872 TRACE 33093 --- [ restartedMain] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [Run a webinar]
2020-01-25 23:06:46.879 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : SessionImpl#afterTransactionCompletion(successful=false, delayed=false)
2020-01-25 23:06:46.880 TRACE 33093 --- [ restartedMain] org.hibernate.internal.SessionImpl : Closing session [6caeb288-a608-4aa7-a5f9-091c69900815]
2020-01-25 23:06:46.900 ERROR 33093 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
以下是从TaskService 运行代码时的日志。在获取标签和保存任务之间会话未关闭。
2020-01-25 23:10:24.966 TRACE 33194 --- [ restartedMain] org.hibernate.internal.SessionImpl : Closing session [f3ea3806-3c65-4269-81b4-ba8bc1abf2af]
2020-01-25 23:10:55.518 INFO 33194 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-01-25 23:10:55.519 INFO 33194 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-01-25 23:10:55.533 INFO 33194 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 14 ms
2020-01-25 23:10:55.553 TRACE 33194 --- [nio-8080-exec-1] org.hibernate.internal.SessionImpl : Opened Session [61d3d232-2d92-4002-8e4c-cedb0278a2e9] at timestamp: 1579990255552
2020-01-25 23:10:55.858 INFO 33194 --- [nio-8080-exec-1] p.s.t.tasks.boundary.TasksController : Storing new task: CreateTaskRequest(title=Dokończyć Moduł 8, description=Jpa i Hibernate cz. 2, attachmentComment=null, tags=[Home])
2020-01-25 23:10:55.914 DEBUG 33194 --- [nio-8080-exec-1] org.hibernate.SQL : select tag0_.id as id1_2_, tag0_.name as name2_2_ from tag tag0_ where upper(tag0_.name) like upper(?) escape ?
Hibernate: select tag0_.id as id1_2_, tag0_.name as name2_2_ from tag tag0_ where upper(tag0_.name) like upper(?) escape ?
2020-01-25 23:10:55.920 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [%Home%]
2020-01-25 23:10:55.920 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [CHAR] - [\]
2020-01-25 23:10:59.218 DEBUG 33194 --- [nio-8080-exec-1] org.hibernate.SQL : insert into task (id, uuid, created_at, description, title) values (null, ?, ?, ?, ?)
Hibernate: insert into task (id, uuid, created_at, description, title) values (null, ?, ?, ?, ?)
2020-01-25 23:10:59.218 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [bac8dacb-9424-4c6e-9a3a-860973ebc350]
2020-01-25 23:10:59.219 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [2020-01-25T23:10:55.878153]
2020-01-25 23:10:59.223 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [Jpa i Hibernate cz. 2]
2020-01-25 23:10:59.224 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [Dokończyć Moduł 8]
2020-01-25 23:10:59.244 TRACE 33194 --- [nio-8080-exec-1] org.hibernate.internal.SessionImpl : SessionImpl#beforeTransactionCompletion()
2020-01-25 23:10:59.244 TRACE 33194 --- [nio-8080-exec-1] org.hibernate.internal.SessionImpl : Automatically flushing session
2020-01-25 23:10:59.262 DEBUG 33194 --- [nio-8080-exec-1] org.hibernate.SQL : insert into tags_tasks (task_id, tag_id) values (?, ?)
Hibernate: insert into tags_tasks (task_id, tag_id) values (?, ?)
2020-01-25 23:10:59.263 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2020-01-25 23:10:59.264 TRACE 33194 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]
2020-01-25 23:10:59.273 TRACE 33194 --- [nio-8080-exec-1] org.hibernate.internal.SessionImpl : SessionImpl#afterTransactionCompletion(successful=true, delayed=false)
【问题讨论】: