Отделение объекта в рекурсивной функции в новом потоке?

я использую Spring Boot, Hibernate и JavaMail библиотека для создания Email Client проект (что-то вроде Thunderbird) для моего школьного проекта. У меня проблемы с сохранением сущности.

Вот большинство сущностей, которые у меня есть:

Счет

      @Getter
@Setter
@RequiredArgsConstructor
@SuperBuilder
@NoArgsConstructor
@Entity
@Table(name = "account")
public class Account {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "account_id", unique = true, nullable = false)
    private int id;

    @NonNull
    @Column(nullable = false)
    private String username;

    @NonNull
    @Column(nullable = false)
    private String password;

    @NonNull
    @Builder.Default
    @OneToMany(mappedBy = "account", orphanRemoval = true, cascade = CascadeType.ALL)
    private Set<Folder> folders = new HashSet<>();
  
    //There are other properties such as: smtpPort, inServerType(pop3 or imap), inServerPort...all primitives
    
}

Папка

      @Getter
@Setter
@SuperBuilder
@NoArgsConstructor
@Entity
public class Folder {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "folder_id", unique = true, nullable = false)
    private int id;
    
    @Column(name = "full_folder_name", nullable = true)
    private String fullFolderName; //this is name of Folder on email server
    
    @Column(name = "folder_name", nullable = true)
    private String folderName;
    
    @Column(name = "folder_url", nullable = true)
    private String folderUrl;

    @Builder.Default
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval = true)
    private Set<Folder> children = new HashSet<Folder>();

    @Builder.Default
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "folder", orphanRemoval = true)
    @Column(nullable = false)
    private Set<Message> messages = new HashSet<>();


    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "parent_id", referencedColumnName = "folder_id", nullable = true)
    private Folder parent;
    
    @ManyToOne
    private Account account;
}

Я хочу иметь возможность получать исходную структуру папок с любого почтового сервера (например, gmail, yahoo и т. Д.) И сохранять эту структуру в моей базе данных (следующим шагом будет получение сообщений для каждого из них Folderс. Идея состоит в том, чтобы получить структуру папок из отдельного потока, я не хочу, чтобы мой интерфейс ждал. Мой код для получения структуры папок работает правильно, я получаю всю иерархию папок, но только из основного потока. После того, как я создам Account сущность и сохраняю ее, только тогда я передаю эту новую ссылку (которая имеет Id) к следующему вспомогательному методу.

      public void fetchInitialFolderStructure(@NonNull Account account)  {    
        Runnable thread = () -> {
            //Folder rootFolder = null;
            try {
                Store store = getStore(account);
                
                if (store.isConnected()) {
                    System.out.println("ok");
                    javax.mail.Folder root = store.getDefaultFolder();
                    final Folder rootFolder = folderService.save(Folder.builder()
                            .fullFolderName(root.getFullName())
                            .folderName(root.getName())
                            .folderUrl(root.getURLName().toString())
                            .children(new HashSet<Folder>())
                            .messages(new HashSet<Message>())
                            .account(account)
                            .build());
                    
                    Arrays.stream(root.list("%")).forEach(jmailFolder -> {
                        
                        try {
                            
                            Folder levelFolder = Folder.builder()
                                    .fullFolderName(jmailFolder.getFullName())
                                    .folderName(jmailFolder.getName())
                                    .folderUrl(jmailFolder.getURLName().toString())
                                    .children(new HashSet<Folder>())
                                    .messages(new HashSet<Message>())
                                    .parent(rootFolder)
                                    .account(account)
                                    .build();
                            levelFolder = folderService.save(levelFolder);
                            dumpFolder(jmailFolder, levelFolder, account);
                        } catch (MessagingException e) {
                            e.printStackTrace();
                        } 

                    });
                    
                    
                } else {
                    throw new IllegalArgumentException("Cannot connect to Store!");
                }

                store.close();
            } catch (NoSuchProviderException e) {
                e.printStackTrace();
            } catch (MessagingException e) {
                e.printStackTrace();
            } 
        };
        new Thread(thread).start();
        
    }

А вот рекурсивный метод:

      @Transactional
private void dumpFolder(javax.mail.Folder jmailFolder, Folder folder, Account account) {
    try {
        if ((jmailFolder.getType() & javax.mail.Folder.HOLDS_FOLDERS) != 0) {
            
                Arrays.stream(jmailFolder.list()).forEach(f -> {
                    Folder childFolder = null;
                
                    try {
                        childFolder = Folder.builder()
                                        .fullFolderName(jmailFolder.getFullName())
                                        .folderName(jmailFolder.getName())
                                        .folderUrl(jmailFolder.getURLName().toString())
                                        .children(new HashSet<Folder>())
                                        .messages(new HashSet<Message>())
                                        .parent(folder)
                                        .account(account)
                                        .build();
                        } catch (MessagingException e) {

                                e.printStackTrace();
                        }
                            
                    folderService.save(childFolder);
                    dumpFolder(f, childFolder, account);

                });
        }
    } catch (MessagingException e) {
        e.printStackTrace();
    }
    
    
}

Интересно посмотреть, возможно, кто-то получит представление о том, как сделать рекурсию по-другому, - это документация по list("%") метод, который вызывается root:

Прежде чем я установил новый Runnable и запустил новый поток, мне удалось получить всю иерархию, но с новым потоком я получаю это исключение:

          Exception in thread "Thread-19" org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.uns.ac.rs.emailclient.model.Folder; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.uns.ac.rs.emailclient.model.Folder
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:297)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
    at com.sun.proxy.$Proxy135.save(Unknown Source)
    at com.uns.ac.rs.emailclient.service.impl.FolderServiceImpl.save(FolderServiceImpl.java:18)
    at com.uns.ac.rs.emailclient.service.helper.JavaxMailHelper.lambda$2(JavaxMailHelper.java:101)
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
    at com.uns.ac.rs.emailclient.service.helper.JavaxMailHelper.dumpFolder(JavaxMailHelper.java:83)
    at com.uns.ac.rs.emailclient.service.helper.JavaxMailHelper.lambda$1(JavaxMailHelper.java:61)
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
    at com.uns.ac.rs.emailclient.service.helper.JavaxMailHelper.lambda$0(JavaxMailHelper.java:47)
    at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.uns.ac.rs.emailclient.model.Folder
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:120)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:113)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:744)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:712)
    at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:492)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:416)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:151)
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:427)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:264)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:193)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:123)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:720)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:706)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311)
    at com.sun.proxy.$Proxy132.persist(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:557)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:524)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:531)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:156)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:131)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
    ... 17 more

Итак, вопрос в том, как запустить рекурсивную функцию из отдельного потока, чтобы Hibernate не сходил с ума (говоря языком непрофессионала)? Я не очень хорошо разбираюсь в Hibernate и concurrency, поэтому любая помощь более чем приветствуется. Извините за более длинный пост.

0 ответов

Другие вопросы по тегам