org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session
Asked Answered
G

3

5

This method begins with Transaction

@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;
}   

This is a mapper class where I create a DB Account entity and map addresses and contacts.

@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;
}

When I don't call the below method inside the map, everything works fine. But when I call it I get the error described in the title.

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;

}

And the final method is

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);
    }
}

Exception stacktrace

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$$8d6d52b1.generateDirectCustomerAccountEntity()\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl.createDirectCustomerAccount(DirectCustomerAccountServiceImpl.java:158)\n\tat com.adobe.costheta.service.DirectCustomerAccountServiceImpl$$FastClassBySpringCGLIB$$27f4a473.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$$13af2ff1.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.access$300(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"}

Gibbs answered 18/7, 2020 at 20:8 Comment(15)
We have a utility method to generate accountId which ensures that duplicate id is not generated. Important part is when I comment SetAccountContact method it works when I uncomment that error is thrown.Gibbs
Could you please post the entire exception stack trace?Stroup
Stack trace is updated.Gibbs
Are you sure dbService.genarateAccountId(accountCode) generates a unique ID? It might be that dbcontact.getAccountContact() (called inside the method you mention) loads some other Accounts into the session, one of which happens to have the same id as the newly generated oneStroup
I am sure that accountId is always unique.Gibbs
Could you try (1) moving dbAccount.setAccountId(accountId) to the end of map (2) calling accountRepository.save(dbAccount); right before that line? I think I can see the problem now, if the suggestion helps then that would confirm my hypothesisStroup
No luck, still the same error. Jus to re check, I have added dbAccount.setAccountId(accountId) to the end of map method. Secondly accountRepository.save(dbAccount) cant be added before setting accountId right ? Hope i understood u correctly. id is not autogenerated we have to set it before persist.Gibbs
No, that was the whole point. Set the id after persisting. I think you're confused about how the save method works. It only marks the entity as being tracked, the actual flushing to the db happens later, usually upon transaction commitStroup
As an alternative to the above test, you can try making Account implement org.springframework.data.domain.Persistable, returning true from isNewStroup
org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save(): com.adobe.costheta.account.model.db.mysql.Account; nested exception is org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.adobe.costheta.account.model.db.mysql.Account This error I am getting while setting accountId after persisting. The other solution implementing org.springframework.data.domain.Persistable, returning true from isNew cant be done since same entity is been referred in many business logic.Gibbs
What happens if you implement Persistable and assign the id before saving? I know its not the solution, I'm still trying to help you diagnose the symptomsStroup
No, I meant implementing Persistable and assigning the id before the save method. Spring Data JPA decides whether to call EntityManager.merge() or EntityManager.persist() inside save, depending on whether it considers the entity to be new or not. By default, it assumes anything that already has an id cannot be a new entity (and so merge() is selected). The point is to force the save method to call persist rather than merge instead.Stroup
Implementing Persistable and returning true from isNew works fine. But will that affect existing reference to Account object used in creation of different types of account.Gibbs
In that case we are calling save method in update logic too .. How hibernate gonna behaviour in those scenarios ? So in ma case it was merge which is being called instead of persist ? that is the reason why i was getting that error ?Gibbs
OK, so that would confirm my hypothesis. I'll post an answer to your question in a little while. I'll try to explain possible alternatives to Persistable as wellStroup
S
12

OK, the answer is going to be a little complicated, but bear with me.

First of all, the root cause is the manually generated id.

Inside setAccountContact(...), you have: dbAccountContact.setAccount(dbAccount) where you assign the Account previously created in the outer map(...) method. Let's called this account object A.

Later on, inside saveAccount(...), you call accountRepository.save(dbAccount). This call returns an Account object, which we'll call B.

Now, Spring Data JPA repository save method decides whether to call EntityManager.merge() or EntityManager.persist() inside save, depending on whether it considers the entity to be new or not. By default, it assumes anything that already has an id cannot be a new entity (and so merge() is selected).

When you pass an unmanaged/detached entity to EntityManager.merge(), a copy of the input entity is always returned. This means that A != B.

This is precisely where the different object with the same identifier value was already associated with the session error is coming from. At transaction commit, you have different copies (the A assigned to dbAccountContact and the B just returned from save) of Account with the same id. Such a state is forbidden in JPA, because JPA doesn't know which version of the object 'wins'. Note that if save called EntityManager.persist(), no copy would be created, the original Account would simply become managed, so the issue wouldn't exist.

Ways you can solve this problem:

  1. Encapsulate the id-generating capabilities of the underlying database in the form of a Hibernate's sequence generator

You're using dbService.genarateAccountId(accountCode), so I'm assuming there's some custom logic involved and a simple @GeneratedValue(strategy = SEQUENCE|AUTO|TABLE) won't do. However, those are not the only options. See this article on how to roll out a customized id generator.

(note: the article suggests to inherit from SequenceStyleGenerator, which allows you to concatenate a DB sequence with extra content. However, if you implement the more general IdentifierGenerator, you'll get access to SessionImplementor, which you can use to call arbitrary SQL on your DB, not necessarily accessing a sequence).

Personally, I strongly recommend this option. However, if that doesn't work for you, then:

  1. Use Persistable

You can implement Persistable and implement isNew() to tell Spring Data 'hey, don't be fooled by the id, this object is in fact a new object!'. As you already noticed, doing so solves the problem. Of course, the caveat is any instance of Account, new or old, will now be considered new by JpaRepository.save(). This will create problems if you ever merge detached Account entities elsewhere in our app (If not, you should be completely fine).

  1. Rearrange your code to save the Account early

Another solution is to call accountRepository.save() as soon as you create the new account inside map:

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

(you could also call save a bit later, but necessarily before the problematic dbAccountContact.setAccount(dbAccount) line).

This avoids the problem of two Account copies being present in the persistence context. However, you may run into problems when executing queries, e.g. in this line:

Contact dbcontact = contactRepository.findByEmail(email);

before reaching this line, you need to make sure all the required associations of Account are populated correctly because at this point, the persistence context will be flushed. If you cannot satisfy this requirement, the alternative is to use FlushMode.COMMIT with the query method itself:

@QueryHints(value = { @QueryHint(name = org.hibernate.annotations.QueryHints.FLUSH_MODE, value = "COMMIT") }) //  this prevents a flush before querying
Contact findByEmail(String email);

(not sure if the above issue applies to you, but in case it does, simply rearrange your code so that saving the current state of Account does not trigger constraint violations before executing any queries. Alternatively, consider calling the query method before you even save the Account). Also, don't worry about cascading the persist operation in this scenario. The cascade should still happen upon transaction commit.

  1. Simply inject EntityManager into your service and call EntityManager.persist() yourself

Of course, this introduces a leaking abstraction, so you should probably not consider this solution seriously. I'm just saying it would work.

Stroup answered 21/7, 2020 at 13:21 Comment(3)
First of all I would like to thank you for the detailed explanation. This was very helpful. I could not implement 1st solution since I ID generation in our system has complex logic and it used by many dependent services. 2) Since we were not sure about the detached objects anywhere in the application i did not give a try for that. 4) We are using JPA crud repository to perform crud operations and existing code standards would not allow me to implement that.Gibbs
The last option to rearrange the code helped me to solve the issue. the good part is I did not even get the exception while loading contact based on email which you warned me to take care of. Finally the rearrangement of code looks like this, Account dbAccount = dCustomerModelToEntityMapper.map(source,headers); dbAccount = saveDirectCustomerAccount(source, dbAccount, headers, workflowId); setAccountContact(dbAccount, source, headers); return dbAccount; } After persisting I set the account contact. Once again thank u so muchGibbs
I would like to propose another option: One can use Springs Version-Property inspection as well. It precedes the Id-Property check. Please take a look at docs.spring.io/spring-data/jpa/docs/current-SNAPSHOT/reference/… for details.Passer
I
0

this will work to resolve to DataIntegrityViolationException

@Id
@Column(name="id")
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
Interfuse answered 11/9, 2021 at 15:35 Comment(0)
R
0

Actually for me I found another solution.

I had this issue when updating an entity. Firstly I queried entity by id. Secondly I implicitly updated that entity.

And the solution was to use explicit call to save the entity.

From

Entity entityFromDb = repository.findById(id)
mapper.merge(newEntity, entityFromDb)

To

Entity entityFromDb = repository.findById(id)
mapper.merge(newEntity, entityFromDb)
repository.save(entityFromDb) // so here is the solution
Ranged answered 23/3, 2022 at 9:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.