org.springframework.dao.DataIntegrityViolationException:具有相同标识符值的不同 object 已与 session 相关联
org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session
此方法以事务开头
@Transactional(propagation = Propagation.REQUIRED)
public Account generateDirectCustomerAccountEntity(AccountReq source, Map<String, String> headers, String workflowId) {
Account dbAccount = dCustomerModelToEntityMapper.map(source, headers);
dbAccount = saveAccount(source, dbAccount, headers, workflowId);
return dbAccount;
}
这是一个映射器 class,我在其中创建数据库帐户实体并映射地址和联系人。
@Override
@LogExecutionTime(log=true)
public Account map(AccountReq accountReq, Map<String, String> headers) {
logger.info("AccountModelToEntityMapper.mapDirectCustomer*****START");
Account dbAccount = new Account();
AccountCode accountCode = dbService.getAccountCodeForDirectCustomer(accountReq);
String accountId = dbService.genarateAccountId(accountCode);
logger.info("genarated AccountID for Direct Customer is={}", accountId);
dbAccount.setAccountId(accountId);
setAccountContact(dbAccount, accountReq, headers);
setAddress(dbAccount, accountReq);
dbAccount.setAccountType(accountCode.getCodeId());
return dbAccount;
}
当我不在地图中调用以下方法时,一切正常。但是当我调用它时,我得到了标题中描述的错误。
private void setAccountContact(Account dbAccount, AccountReq accountReq, Map<String, String> headers) {
try {
if (null != accountReq.getContacts() && !accountReq.getContacts().isEmpty()) {
logger.info("setAccountContact accountReq.getContacts().size()={}", accountReq.getContacts().size());
List<String> emailList=new ArrayList<>();
for (ContactReq contact : accountReq.getContacts()) {
String email = contact.getEmail();
logger.info("setAccountContact:"+email);
if(emailList.contains(email)) {
logger.error("ERROR={}", "same emailId sent in multiple contacts");
throw new AccountException(AccountConstant.INVALID_FIELDS, AccountConstant.INVALID_FIELDS_CODE);
}
emailList.add(email);
Contact dbcontact = contactRepository.findByEmail(email);
if (null == dbcontact) {
dbcontact = new Contact();
dbcontact.setCreatedAt(dbAccount.getCreatedAt());
dbcontact.setCreatedBy(dbAccount.getCreatedBy());
dbcontact.setEmail(contact.getEmail());
dbcontact.setFirstName((contact.getFirstName()));
dbcontact.setLastName((contact.getLastName()));
dbcontact.setPhone(contact.getPhoneNumber());
dbcontact.setStatus(AccountConstant.STATUS_ACTIVE);
logger.info("contactRepository={} {}", contactRepository,contact.getEmail());
try {
dbcontact = contactRepository.save(dbcontact);
} catch (Exception e) {
logger.error("ERROR in updating contact table={}", e);
throw new InternalServerException(AccountConstant.INTERNAL_SERVER_ERROR,
AccountConstant.INTERNAL_SERVER_ERROR_CODE);
}
}
AccountContact dbAccountContact = new AccountContact();
dbAccountContact.setCreatedAt(dbAccount.getCreatedAt());
dbAccountContact.setCreatedBy(dbAccount.getCreatedBy());
dbAccountContact.setStatus(AccountConstant.STATUS_ACTIVE);
ContactRole contactRole = getContactRole(contact.getType());
if (null == contactRole) {
logger.error("ERROR={}", "contact type is invalid");
throw new AccountException(AccountConstant.INVALID_FIELDS, AccountConstant.INVALID_FIELDS_CODE);
}
dbAccountContact.setContactRole(contactRole);
dbAccountContact.setAccount(dbAccount);
dbAccountContact.setContact(dbcontact);
if (null != dbcontact.getAccountContact() && !dbcontact.getAccountContact().isEmpty()) {
logger.error("dbcontact.getAccountContact() is not null, dbcontact.getAccountContact().size()={}",
dbcontact.getAccountContact().size());
List<AccountContact> accountContactList = dbcontact.getAccountContact();
accountContactList.add(dbAccountContact);
} else {
logger.error("getAccountStatusHistory is null");
List<AccountContact> accountContactList = new ArrayList<>();
accountContactList.add(dbAccountContact);
dbcontact.setAccountContact(accountContactList);
}
if (null != contact && AccountConstant.ADMIN_CONTACT_ROLE.equalsIgnoreCase(contact.getType())) {
if (null != contact.getAdminId()) {
dbService.saveExternalID(String.valueOf(dbcontact.getContactId()), contact.getAdminId(), headers, AccountConstant.STATUS_ACTIVE,
CosConstants.ID_TYPE);
}
}
}
}
} catch (Exception e) {
logger.error("ERROR in setAccountContact to dbAccount contacts={},{}", accountReq.getContacts(), e);
throw e;
}
}
@LogExecutionTime(log = true)
public Account saveDirectCustomerAccount(AccountReq source, Account dbAccount, Map<String, String> headers, String workflowId) {
try {
String statusCode=AccountConstant.INITIAL_STATUS_CODE;
dbAccount = updateAccountStatusHistory(dbAccount, statusCode);
} catch (Exception e) {
logger.error("ERROR in updateAccountStatusHistory={}", e);
throw new InternalServerException(AccountConstant.INTERNAL_SERVER_ERROR,
AccountConstant.INTERNAL_SERVER_ERROR_CODE);
}
return dbAccount;
}
@LogExecutionTime(log = true)
private Account updateAccountStatusHistory(Account dbAccount, String statusCode) {
logger.info("updateAccountStatusHistory *****START");
AccountStatusHistory accountStatusHistory = new AccountStatusHistory();
accountStatusHistory.setAccount(dbAccount);
accountStatusHistory.setCreatedAt(dbAccount.getCreatedAt());
accountStatusHistory.setCreatedBy(dbAccount.getCreatedBy());
try {
updateAccountStatus(dbAccount, statusCode, accountStatusHistory);
} catch (Exception e) {
logger.error("ERROR updateAccountStatus e={}", e);
}
if (null != dbAccount.getAccountStatusHistory()) {
logger.error("getAccountStatusHistory not null");
dbAccount.getAccountStatusHistory().add(accountStatusHistory);
} else {
logger.error("getAccountStatusHistory is null");
List<AccountStatusHistory> accountStatusHistoryList = new ArrayList<>();
accountStatusHistoryList.add(accountStatusHistory);
dbAccount.setAccountStatusHistory(accountStatusHistoryList);
}
Account createdAccount = saveAccount(dbAccount);
logger.debug("createdAccount.getAccountId()={}", createdAccount.getAccountId());
return createdAccount;
}
最后的方法是
public Account saveAccount(Account dbAccount) {
try {
Account createdAccount = accountRepository.save(dbAccount);
logger.debug("createdAccount.getAccountId()={}", createdAccount.getAccountId());
return createdAccount;
} catch (Exception e) {
logger.error("ERROR in account creation={}", e);
throw new InternalServerException(AccountConstant.INTERNAL_SERVER_ERROR,
AccountConstant.INTERNAL_SERVER_ERROR_CODE);
}
}
异常堆栈跟踪
ERROR Throwable={}","stack_trace":"org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session : [com.adobe.costheta.account.model.db.mysql.Account#1000000080]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.adobe.costheta.account.model.db.mysql.Account#1000000080]\n\tat org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:400)\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:256)\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:537)\n\tat org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)\n\tat org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:534)\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:305)\n\tat org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\n\tat com.adobe.costheta.service.DbService$$EnhancerBySpringCGLIB$d6d52b1.generateDirectCustomerAccountEntity()\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl.createDirectCustomerAccount(DirectCustomerAccountServiceImpl.java:158)\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl$$FastClassBySpringCGLIB$f4a473.invoke()\n\tat org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl$$EnhancerBySpringCGLIB$af2ff1.createDirectCustomerAccount()\n\tat com.adobe.costheta.resource.AccountResource.createAccount(AccountResource.java:93)\n\tat com.adobe.costheta.resource.AccountResource$$FastClassBySpringCGLIB$$dd5cfc46.invoke()\n\tat org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)\n\tat org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)\n\tat com.adobe.asr.logging.aspect.ExecutionTimeAspect.logExecutionTime(ExecutionTimeAspect.java:83)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\n\tat org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)\n\tat org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)\n\tat org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\n\tat com.adobe.costheta.resource.AccountResource$$EnhancerBySpringCGLIB$$d988334a.createAccount()\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:798)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:665)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:750)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:97)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:88)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.filter.AsrRequestResponseFilter.doFilterInternal(AsrRequestResponseFilter.java:88)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.logging.http.servlet.AsrLoggingFilter.doFilter(AsrLoggingFilter.java:69)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.filter.AsrRequestIdFilter.doFilterInternal(AsrRequestIdFilter.java:99)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.costheta.filters.ApiLoggingFilter.doFilter(ApiLoggingFilter.java:52)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.exception.AsrExceptionFilter.doFilterInternal(AsrExceptionFilter.java:82)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.costheta.filters.MDCFilter.doFilter(MDCFilter.java:44)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114)\n\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:834)\nCaused by: javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.adobe.costheta.account.model.db.mysql.Account#1000000080]\n\tat org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:123)\n\tat org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)\n\tat org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)\n\tat org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1478)\n\tat org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:512)\n\tat org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3310)\n\tat org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2506)\n\tat org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447)\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178)\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access0(JdbcResourceLocalTransactionCoordinatorImpl.java:39)\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271)\n\tat org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:533)\n\t... 126 common frames omitted\n","requestId":"f5d17eccdfa90271","Content-Type":"application/json"}
好的,答案会有点复杂,但请耐心等待。
首先根本原因是手动生成的id
.
在 setAccountContact(...)
内,您有: dbAccountContact.setAccount(dbAccount)
分配先前在外部 map(...)
方法中创建的 Account
。我们将此帐户对象命名为 A
.
稍后,在 saveAccount(...)
中,您调用 accountRepository.save(dbAccount)
。这调用 returns 一个 Account
对象,我们称之为 B
.
现在,Spring Data JPA 存储库 save
方法根据是否考虑实体来决定是在 save
内部调用 EntityManager.merge()
还是 EntityManager.persist()
是不是新的。默认情况下,它假设任何已经有 id 的东西都不能是新实体(因此选择 merge()
)。
当您将 unmanaged/detached 实体传递给 EntityManager.merge()
时,始终会返回输入实体的 副本 。这意味着 A != B
.
这正是 different object with the same identifier value was already associated with the session
错误的来源。在事务提交时,您有 Account
的不同副本(分配给 dbAccountContact
的 A
和刚刚从 save
返回的 B
)具有相同的 id
.这种状态在JPA中是被禁止的,因为JPA不知道对象是哪个版本'wins'。请注意,如果 save
调用了 EntityManager.persist()
,则不会创建任何副本,原始 Account
将简单地变为托管状态,因此问题不会存在。
解决这个问题的方法:
- 以Hibernate的序列生成器的形式封装底层数据库的id生成能力
您正在使用 dbService.genarateAccountId(accountCode)
,所以我假设其中涉及一些自定义逻辑,而简单的 @GeneratedValue(strategy = SEQUENCE|AUTO|TABLE)
是行不通的。但是,这些 不是 唯一的选择。请参阅 this article 了解如何推出自定义 ID 生成器。
(注意:文章建议从 SequenceStyleGenerator
继承,这允许您将 DB 序列与额外的内容连接起来。但是,如果您实现更通用的IdentifierGenerator
,您将可以访问 SessionImplementor
,您可以使用它在您的数据库上调用 arbitrary SQL,不一定访问序列) .
就个人而言,我强烈推荐此选项。但是,如果这对您不起作用,那么:
- 使用
Persistable
你可以实现 Persistable
和实现 isNew()
来告诉 Spring 数据 '嘿,不要被 id 骗了,这个对象实际上是一个新对象! .正如您已经注意到的那样,这样做可以解决问题。当然,需要注意的是 Account
的 任何 个实例,新的或旧的,现在都将被 JpaRepository.save()
视为新的。如果您 在我们的应用程序的其他地方合并分离的 Account
实体,这将产生问题(否则,您应该完全没问题)。
- 将您的代码重新排列为
save
Account
早
另一个解决方案是在 map
:
中创建新帐户后, 立即调用 accountRepository.save()
Account dbAccount = new Account(); //Account A created
dbService.genarateAccountId(accountCode);
dbAccount = accountRepository.save(dbAccount); //Account A passed to `save`, `save` returns Account B, Account A is not referenced anywhere anymore, problem solved
(您也可以稍后调用 save
,但必须在 之前 有问题的 dbAccountContact.setAccount(dbAccount)
行)。
这避免了在持久性上下文中存在两个 Account
副本的问题。 但是,您可能 运行 在执行查询时遇到问题,例如在这一行中:
Contact dbcontact = contactRepository.findByEmail(email);
在到达这一行之前,您需要确保 Account
的所有必需关联都已正确填充,因为此时持久性上下文将被刷新。如果您不能满足此要求,替代方法是使用 FlushMode.COMMIT
和查询方法本身:
@QueryHints(value = { @QueryHint(name = org.hibernate.annotations.QueryHints.FLUSH_MODE, value = "COMMIT") }) // this prevents a flush before querying
Contact findByEmail(String email);
(不确定上述问题是否适用于您,但如果适用,只需重新安排您的代码,以便保存 Account
的当前状态不会触发约束违规 before 执行任何查询。或者,考虑在 之前 你甚至 save
Account
) 调用查询方法。此外,不必担心在这种情况下级联 persist
操作。级联仍应在事务提交时发生。
- 只需将
EntityManager
注入您的服务并自己调用 EntityManager.persist()
当然,这会引入泄漏抽象,因此您可能不应该认真考虑此解决方案。我只是说它会起作用。
这将解决 DataIntegrityViolationException
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
其实我找到了另一个解决方案。
我在更新实体时遇到了这个问题。
首先,我通过 id 查询实体。
其次,我隐式更新了那个实体。
解决方案是使用显式调用来保存实体。
来自
Entity entityFromDb = repository.findById(id)
mapper.merge(newEntity, entityFromDb)
到
Entity entityFromDb = repository.findById(id)
mapper.merge(newEntity, entityFromDb)
repository.save(entityFromDb) // so here is the solution
此方法以事务开头
@Transactional(propagation = Propagation.REQUIRED)
public Account generateDirectCustomerAccountEntity(AccountReq source, Map<String, String> headers, String workflowId) {
Account dbAccount = dCustomerModelToEntityMapper.map(source, headers);
dbAccount = saveAccount(source, dbAccount, headers, workflowId);
return dbAccount;
}
这是一个映射器 class,我在其中创建数据库帐户实体并映射地址和联系人。
@Override
@LogExecutionTime(log=true)
public Account map(AccountReq accountReq, Map<String, String> headers) {
logger.info("AccountModelToEntityMapper.mapDirectCustomer*****START");
Account dbAccount = new Account();
AccountCode accountCode = dbService.getAccountCodeForDirectCustomer(accountReq);
String accountId = dbService.genarateAccountId(accountCode);
logger.info("genarated AccountID for Direct Customer is={}", accountId);
dbAccount.setAccountId(accountId);
setAccountContact(dbAccount, accountReq, headers);
setAddress(dbAccount, accountReq);
dbAccount.setAccountType(accountCode.getCodeId());
return dbAccount;
}
当我不在地图中调用以下方法时,一切正常。但是当我调用它时,我得到了标题中描述的错误。
private void setAccountContact(Account dbAccount, AccountReq accountReq, Map<String, String> headers) {
try {
if (null != accountReq.getContacts() && !accountReq.getContacts().isEmpty()) {
logger.info("setAccountContact accountReq.getContacts().size()={}", accountReq.getContacts().size());
List<String> emailList=new ArrayList<>();
for (ContactReq contact : accountReq.getContacts()) {
String email = contact.getEmail();
logger.info("setAccountContact:"+email);
if(emailList.contains(email)) {
logger.error("ERROR={}", "same emailId sent in multiple contacts");
throw new AccountException(AccountConstant.INVALID_FIELDS, AccountConstant.INVALID_FIELDS_CODE);
}
emailList.add(email);
Contact dbcontact = contactRepository.findByEmail(email);
if (null == dbcontact) {
dbcontact = new Contact();
dbcontact.setCreatedAt(dbAccount.getCreatedAt());
dbcontact.setCreatedBy(dbAccount.getCreatedBy());
dbcontact.setEmail(contact.getEmail());
dbcontact.setFirstName((contact.getFirstName()));
dbcontact.setLastName((contact.getLastName()));
dbcontact.setPhone(contact.getPhoneNumber());
dbcontact.setStatus(AccountConstant.STATUS_ACTIVE);
logger.info("contactRepository={} {}", contactRepository,contact.getEmail());
try {
dbcontact = contactRepository.save(dbcontact);
} catch (Exception e) {
logger.error("ERROR in updating contact table={}", e);
throw new InternalServerException(AccountConstant.INTERNAL_SERVER_ERROR,
AccountConstant.INTERNAL_SERVER_ERROR_CODE);
}
}
AccountContact dbAccountContact = new AccountContact();
dbAccountContact.setCreatedAt(dbAccount.getCreatedAt());
dbAccountContact.setCreatedBy(dbAccount.getCreatedBy());
dbAccountContact.setStatus(AccountConstant.STATUS_ACTIVE);
ContactRole contactRole = getContactRole(contact.getType());
if (null == contactRole) {
logger.error("ERROR={}", "contact type is invalid");
throw new AccountException(AccountConstant.INVALID_FIELDS, AccountConstant.INVALID_FIELDS_CODE);
}
dbAccountContact.setContactRole(contactRole);
dbAccountContact.setAccount(dbAccount);
dbAccountContact.setContact(dbcontact);
if (null != dbcontact.getAccountContact() && !dbcontact.getAccountContact().isEmpty()) {
logger.error("dbcontact.getAccountContact() is not null, dbcontact.getAccountContact().size()={}",
dbcontact.getAccountContact().size());
List<AccountContact> accountContactList = dbcontact.getAccountContact();
accountContactList.add(dbAccountContact);
} else {
logger.error("getAccountStatusHistory is null");
List<AccountContact> accountContactList = new ArrayList<>();
accountContactList.add(dbAccountContact);
dbcontact.setAccountContact(accountContactList);
}
if (null != contact && AccountConstant.ADMIN_CONTACT_ROLE.equalsIgnoreCase(contact.getType())) {
if (null != contact.getAdminId()) {
dbService.saveExternalID(String.valueOf(dbcontact.getContactId()), contact.getAdminId(), headers, AccountConstant.STATUS_ACTIVE,
CosConstants.ID_TYPE);
}
}
}
}
} catch (Exception e) {
logger.error("ERROR in setAccountContact to dbAccount contacts={},{}", accountReq.getContacts(), e);
throw e;
}
}
@LogExecutionTime(log = true)
public Account saveDirectCustomerAccount(AccountReq source, Account dbAccount, Map<String, String> headers, String workflowId) {
try {
String statusCode=AccountConstant.INITIAL_STATUS_CODE;
dbAccount = updateAccountStatusHistory(dbAccount, statusCode);
} catch (Exception e) {
logger.error("ERROR in updateAccountStatusHistory={}", e);
throw new InternalServerException(AccountConstant.INTERNAL_SERVER_ERROR,
AccountConstant.INTERNAL_SERVER_ERROR_CODE);
}
return dbAccount;
}
@LogExecutionTime(log = true)
private Account updateAccountStatusHistory(Account dbAccount, String statusCode) {
logger.info("updateAccountStatusHistory *****START");
AccountStatusHistory accountStatusHistory = new AccountStatusHistory();
accountStatusHistory.setAccount(dbAccount);
accountStatusHistory.setCreatedAt(dbAccount.getCreatedAt());
accountStatusHistory.setCreatedBy(dbAccount.getCreatedBy());
try {
updateAccountStatus(dbAccount, statusCode, accountStatusHistory);
} catch (Exception e) {
logger.error("ERROR updateAccountStatus e={}", e);
}
if (null != dbAccount.getAccountStatusHistory()) {
logger.error("getAccountStatusHistory not null");
dbAccount.getAccountStatusHistory().add(accountStatusHistory);
} else {
logger.error("getAccountStatusHistory is null");
List<AccountStatusHistory> accountStatusHistoryList = new ArrayList<>();
accountStatusHistoryList.add(accountStatusHistory);
dbAccount.setAccountStatusHistory(accountStatusHistoryList);
}
Account createdAccount = saveAccount(dbAccount);
logger.debug("createdAccount.getAccountId()={}", createdAccount.getAccountId());
return createdAccount;
}
最后的方法是
public Account saveAccount(Account dbAccount) {
try {
Account createdAccount = accountRepository.save(dbAccount);
logger.debug("createdAccount.getAccountId()={}", createdAccount.getAccountId());
return createdAccount;
} catch (Exception e) {
logger.error("ERROR in account creation={}", e);
throw new InternalServerException(AccountConstant.INTERNAL_SERVER_ERROR,
AccountConstant.INTERNAL_SERVER_ERROR_CODE);
}
}
异常堆栈跟踪
ERROR Throwable={}","stack_trace":"org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session : [com.adobe.costheta.account.model.db.mysql.Account#1000000080]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.adobe.costheta.account.model.db.mysql.Account#1000000080]\n\tat org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:400)\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:256)\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:537)\n\tat org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)\n\tat org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:534)\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:305)\n\tat org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\n\tat com.adobe.costheta.service.DbService$$EnhancerBySpringCGLIB$d6d52b1.generateDirectCustomerAccountEntity()\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl.createDirectCustomerAccount(DirectCustomerAccountServiceImpl.java:158)\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl$$FastClassBySpringCGLIB$f4a473.invoke()\n\tat org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl$$EnhancerBySpringCGLIB$af2ff1.createDirectCustomerAccount()\n\tat com.adobe.costheta.resource.AccountResource.createAccount(AccountResource.java:93)\n\tat com.adobe.costheta.resource.AccountResource$$FastClassBySpringCGLIB$$dd5cfc46.invoke()\n\tat org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)\n\tat org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)\n\tat com.adobe.asr.logging.aspect.ExecutionTimeAspect.logExecutionTime(ExecutionTimeAspect.java:83)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\n\tat org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)\n\tat org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)\n\tat org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)\n\tat com.adobe.costheta.resource.AccountResource$$EnhancerBySpringCGLIB$$d988334a.createAccount()\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:566)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:798)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:665)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:750)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:97)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:88)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.filter.AsrRequestResponseFilter.doFilterInternal(AsrRequestResponseFilter.java:88)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.logging.http.servlet.AsrLoggingFilter.doFilter(AsrLoggingFilter.java:69)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.filter.AsrRequestIdFilter.doFilterInternal(AsrRequestIdFilter.java:99)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.costheta.filters.ApiLoggingFilter.doFilter(ApiLoggingFilter.java:52)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.asr.exception.AsrExceptionFilter.doFilterInternal(AsrExceptionFilter.java:82)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.adobe.costheta.filters.MDCFilter.doFilter(MDCFilter.java:44)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114)\n\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:834)\nCaused by: javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.adobe.costheta.account.model.db.mysql.Account#1000000080]\n\tat org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:123)\n\tat org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)\n\tat org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)\n\tat org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1478)\n\tat org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:512)\n\tat org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3310)\n\tat org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2506)\n\tat org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447)\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178)\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access0(JdbcResourceLocalTransactionCoordinatorImpl.java:39)\n\tat org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271)\n\tat org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)\n\tat org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:533)\n\t... 126 common frames omitted\n","requestId":"f5d17eccdfa90271","Content-Type":"application/json"}
好的,答案会有点复杂,但请耐心等待。
首先根本原因是手动生成的id
.
在 setAccountContact(...)
内,您有: dbAccountContact.setAccount(dbAccount)
分配先前在外部 map(...)
方法中创建的 Account
。我们将此帐户对象命名为 A
.
稍后,在 saveAccount(...)
中,您调用 accountRepository.save(dbAccount)
。这调用 returns 一个 Account
对象,我们称之为 B
.
现在,Spring Data JPA 存储库 save
方法根据是否考虑实体来决定是在 save
内部调用 EntityManager.merge()
还是 EntityManager.persist()
是不是新的。默认情况下,它假设任何已经有 id 的东西都不能是新实体(因此选择 merge()
)。
当您将 unmanaged/detached 实体传递给 EntityManager.merge()
时,始终会返回输入实体的 副本 。这意味着 A != B
.
这正是 different object with the same identifier value was already associated with the session
错误的来源。在事务提交时,您有 Account
的不同副本(分配给 dbAccountContact
的 A
和刚刚从 save
返回的 B
)具有相同的 id
.这种状态在JPA中是被禁止的,因为JPA不知道对象是哪个版本'wins'。请注意,如果 save
调用了 EntityManager.persist()
,则不会创建任何副本,原始 Account
将简单地变为托管状态,因此问题不会存在。
解决这个问题的方法:
- 以Hibernate的序列生成器的形式封装底层数据库的id生成能力
您正在使用 dbService.genarateAccountId(accountCode)
,所以我假设其中涉及一些自定义逻辑,而简单的 @GeneratedValue(strategy = SEQUENCE|AUTO|TABLE)
是行不通的。但是,这些 不是 唯一的选择。请参阅 this article 了解如何推出自定义 ID 生成器。
(注意:文章建议从 SequenceStyleGenerator
继承,这允许您将 DB 序列与额外的内容连接起来。但是,如果您实现更通用的IdentifierGenerator
,您将可以访问 SessionImplementor
,您可以使用它在您的数据库上调用 arbitrary SQL,不一定访问序列) .
就个人而言,我强烈推荐此选项。但是,如果这对您不起作用,那么:
- 使用
Persistable
你可以实现 Persistable
和实现 isNew()
来告诉 Spring 数据 '嘿,不要被 id 骗了,这个对象实际上是一个新对象! .正如您已经注意到的那样,这样做可以解决问题。当然,需要注意的是 Account
的 任何 个实例,新的或旧的,现在都将被 JpaRepository.save()
视为新的。如果您 在我们的应用程序的其他地方合并分离的 Account
实体,这将产生问题(否则,您应该完全没问题)。
- 将您的代码重新排列为
save
Account
早
另一个解决方案是在 map
:
accountRepository.save()
Account dbAccount = new Account(); //Account A created
dbService.genarateAccountId(accountCode);
dbAccount = accountRepository.save(dbAccount); //Account A passed to `save`, `save` returns Account B, Account A is not referenced anywhere anymore, problem solved
(您也可以稍后调用 save
,但必须在 之前 有问题的 dbAccountContact.setAccount(dbAccount)
行)。
这避免了在持久性上下文中存在两个 Account
副本的问题。 但是,您可能 运行 在执行查询时遇到问题,例如在这一行中:
Contact dbcontact = contactRepository.findByEmail(email);
在到达这一行之前,您需要确保 Account
的所有必需关联都已正确填充,因为此时持久性上下文将被刷新。如果您不能满足此要求,替代方法是使用 FlushMode.COMMIT
和查询方法本身:
@QueryHints(value = { @QueryHint(name = org.hibernate.annotations.QueryHints.FLUSH_MODE, value = "COMMIT") }) // this prevents a flush before querying
Contact findByEmail(String email);
(不确定上述问题是否适用于您,但如果适用,只需重新安排您的代码,以便保存 Account
的当前状态不会触发约束违规 before 执行任何查询。或者,考虑在 之前 你甚至 save
Account
) 调用查询方法。此外,不必担心在这种情况下级联 persist
操作。级联仍应在事务提交时发生。
- 只需将
EntityManager
注入您的服务并自己调用EntityManager.persist()
当然,这会引入泄漏抽象,因此您可能不应该认真考虑此解决方案。我只是说它会起作用。
这将解决 DataIntegrityViolationException
@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
其实我找到了另一个解决方案。
我在更新实体时遇到了这个问题。 首先,我通过 id 查询实体。 其次,我隐式更新了那个实体。
解决方案是使用显式调用来保存实体。
来自
Entity entityFromDb = repository.findById(id)
mapper.merge(newEntity, entityFromDb)
到
Entity entityFromDb = repository.findById(id)
mapper.merge(newEntity, entityFromDb)
repository.save(entityFromDb) // so here is the solution