此方法以事务开头
@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;
}
这是一个映射器类,我在其中创建DB帐户实体并映射地址和联系人。
@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);
}
}
异常堆栈跟踪
null
好吧,答案会有点复杂,但请耐心听我说。
首先,根本原因是手动生成的ID
。
在SetAccountContact(...)
内部,您有:DbAccountContact.SetAccount(dbAccount)
,您在其中分配以前在外部映射(...)
方法中创建的帐户
。让我们将此帐户对象称为A
。
稍后,在saveAccount(...)
中,您可以调用AccountRepository.save(dbAccount)
。此调用返回一个account
对象,我们将其称为b
。
现在,Spring Data JPA存储库save
方法决定在save
内部调用EntityManager.merge()
还是调用EntityManager.persist()
,这取决于它是否认为实体是新的。默认情况下,它假定任何已经有id的实体都不能是新实体(因此选择merge()
)。
将非托管/分离的实体传递给EntityManager.merge()
时,总是返回输入实体的副本。这意味着A!=B
。
这正是具有相同标识符值的不同对象已经与session
错误相关联的地方。在事务提交时,具有相同ID
的accountContact
的不同副本(分配给DBAccountContact
的A
和刚从save
返回的B
)。这样的状态在JPA中是被禁止的,因为JPA不知道对象的哪个版本‘获胜’。请注意,如果save
调用了EntityManager.persist()
,则不会创建任何副本,原始的帐户
将变成托管帐户,因此问题不会存在。
解决此问题的方法:
您使用的是dbservice.genarateAccountId(accountCode)
,所以我假设涉及一些自定义逻辑,而简单的@generatedvalue(strategy=SEQUENCEAUTOTABLE)
是不行的。然而,这些并不是唯一的选择。请参阅这篇关于如何推出自定义id生成器的文章。
(注意:本文建议从SequenceStyleGenerator
继承,它允许您将DB序列与额外内容连接起来。但是,如果您实现更通用的IdentifierGenerator
,您将获得对SessionImplementor
的访问权,您可以使用它在DB上调用任意SQL,而不一定访问序列)。
我个人强烈推荐这个选项。但是,如果这对你不起作用,那么:
您可以实现persistable
和实现isnew()
来告诉spring数据‘嘿,不要被id骗了,这个对象实际上是一个新对象!’。正如您已经注意到的,这样做解决了问题。当然,需要注意的是account
的任何实例,无论是新的还是旧的,现在都将被jparepository.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
的当前状态不会触发约束冲突。或者,在save
account
之前考虑调用query方法)。此外,在此场景中,不必担心将persist
操作级联。在事务提交时仍应发生级联。
当然,这引入了一个泄漏的抽象,因此您可能不应该认真考虑这个解决方案。我只是说会管用的。
我有以下问题: 有人对此有解释吗?我考虑从我的业务服务中的hibernate会话中重新加载对象,并从Struts HTTP会话中复制实体对象中的数据。 Hibernate映射
null 对于我的数据,我有时会遇到这样的问题:A有一组不同的B对象,而这些B对象引用同一个C对象。 当我调用时,我会得到一个hibernate错误:。我知道hibernate不能在同一个会话中插入/更新/删除同一个对象两次,但是有什么方法可以解决这个问题吗?这似乎并不是一种罕见的情况。 在我研究这个问题的过程中,我看到有人建议使用,但当我这样做时,任何“冲突”对象都会作为所有值都设置为null的
我们试图通过Keycloak验证现有用户,因此实现了自定义SPI,并将自定义SPI添加为用户联合 原因:javax.persistence.EntityExistsException:具有相同标识符值的不同对象已与会话关联:[org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity#org.keycloak.storage.jpa
问题内容: 在使用Spring和Hibernate的应用程序中,我解析CSV文件,并在每次从CSV文件中读取记录时通过调用来填充db 。 我的域模型: “家庭”有很多“子家庭” “子家庭”有很多“位置” “ Locus”属于“ Species” 都是双向映射。 码: 使用以下方法将物种分配给场所,该方法仅访问DAO层: Hibernate给出以下错误: 有小费吗? 问题答案: 使用。该异常表示当前
问题内容: 我有两个用户对象,而在尝试使用以下方法保存对象时 我收到以下错误: 我正在使用创建会话 我还尝试过在保存之前进行操作,但仍然没有运气。 这是我第一次在用户请求到来时获取会话对象,因此我要为什么要说该对象存在于会话中。 有什么建议么? 问题答案: 我已经多次发生此错误,很难追踪… 基本上,hibernate是指您有两个具有相同标识符(相同主键)但不是相同对象的对象。 我建议您分解代码,即
问题内容: 我基本上在此配置中有一些对象(实际数据模型要复杂一些): A与B有多对多关系。(B具有) B与C具有多对一关系(我已设置为) C是一种类型/类别表。 另外,我可能应该提到主键是在保存时由数据库生成的。 使用我的数据,有时我会遇到一个问题,其中A具有一组不同的B对象,而这些B对象引用了相同的C对象。 打电话时,我收到了一个hibernate错误消息:。我知道hibernate无法在同一会