我在用 Spring Boot
, Hibernate
以及 JavaMail
要创建的库 Email Client
项目(类似于 Thunderbird
)为了我的学校项目。我很难坚持下去 Folder
实体。
以下是我拥有的大多数实体:
帐户
@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
s。这个想法是从一个单独的线程获取文件夹结构,我不希望我的前端等待。我获取文件夹结构的代码工作正常,我获取所有文件夹层次结构,但仅从主线程获取。在我创建之后 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
:
在设置new runnable并启动new thread之前,我设法获取整个层次结构,但是对于new thread,我得到以下异常:
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和并发,所以任何帮助都是非常受欢迎的。抱歉,我的帖子太长了。
暂无答案!
目前还没有任何答案,快来回答吧!