春季-在@EventListener上分离但在@Service类中附加的相同实体

时间:2020-01-25 21:38:54

标签: spring hibernate jpa

我的Spring应用程序中有以下类:var btn1 = document.getElementById('1'); var btn2 = document.getElementById('2'); var btn3 = document.getElementById('3'); var display1 = btn1.getAttribute('display') var display2 = btn2.getAttribute('display') var display3 = btn3.getAttribute('display') function open() { if (display1 === ('none')) { btn1.setAttribute('display', 'block'); } else { btn1.setAttribute('display', 'none'); } } <img id="1" src="forge.PNG" style="height:320px; display:none; padding:10px"> <img id="2" src="lizard.jpg" style="height:320px; display:none; padding:10px"> <img id="3" src="walkway.jpg" style="height:320px; display:none; padding:10px"> <button onclick="open()">1</button> <button onclick="document.getElementById('2').style.display='block'">2</button> <button onclick="document.getElementById('3').style.display='block'">3</button>

应用程序启动时TaskService会运行,但是DevStartup被视为DevStartup上的分离实体,这会在启动过程中引发分离实体异常。

Tag("Home")

与此同时,我在tasksRepository.save(task)类中拥有准确的代码,并使用来自REST控制器的相同参数来调用它。

@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实体已已附加

addTask("Task title", "Task description", Stream.of("Home").collect(toSet());

两者之间有何区别?为什么实体在一种情况下是分离的,而在另一种情况下是附着的?

我正在将Spring Boot 2.1.9与Hibernate 5和JPA(Spring Data JPA项目)一起使用。

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; } // ... }

TagsService.findByName()

更新

以下是TagsRepository.findByNameContainingIgnoreCase的跟踪日志。我可以发现从TagService提取Tag之后会话立即关闭,然后再次打开以保存Task(这就是为什么我得到分离的实体异常)。

public interface TagsRepository extends JpaRepository<Tag, Long> {

    Optional<Tag> findByNameContainingIgnoreCase(String name);

}

以下是从DevStartup运行代码时的日志。在获取标签和保存任务之间不会关闭会话。

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

1 个答案:

答案 0 :(得分:2)

很可能您已启用Open Session In View

如果您正在运行Web应用程序,则默认情况下,Spring Boot将注册OpenEntityManagerInViewInterceptor以应用“在视图中打开EntityManager”模式,以允许在Web视图中进行延迟加载。如果您不希望出现这种情况,则应在application.properties中将spring.jpa.open-in-view设置为false。

这将在请求开始时打开会话,并在请求处理完成后关闭会话。拦截器是针对Web请求运行的,但在function timeSince(date) { var seconds = Math.floor((new Date() - date) / 1000); var interval = Math.floor(seconds / 31536000); if (interval > 1) { return interval + " years"; } interval = Math.floor(seconds / 2592000); if (interval > 1) { return interval + " months"; } interval = Math.floor(seconds / 86400); if (interval > 1) { return interval + " days"; } interval = Math.floor(seconds / 3600); if (interval > 1) { return interval + " hours"; } interval = Math.floor(seconds / 60); if (interval > 1) { return interval + " minutes"; } return Math.floor(seconds) + " seconds"; } var aDay = 24*60*60*1000 console.log(timeSince(new Date(Date.now()-aDay))); console.log(timeSince(new Date(Date.now()-aDay*2))); 事件监听器的情况下不会运行。

当您不依赖于打开的会话时,您可能想使用ApplicationReadyEvent来延长会话的生存期。参见https://stackoverflow.com/a/24713402/1570854

在春季,@ Transactional划分的业务交易与休眠会话之间存在一一对应的关系。

也就是说,当通过调用@Transactional方法开始业务交易时,将创建休眠会话(TransactionManager可能将实际创建延迟到首次使用该会话之前)。该方法完成后,将提交或回滚业务事务,这将关闭休眠会话。

许多人认为开放会话是一种反模式:https://vladmihalcea.com/the-open-session-in-view-anti-pattern/