Отделение объекта в рекурсивной функции в новом потоке?
я использую
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, поэтому любая помощь более чем приветствуется. Извините за более длинный пост.