We are facing Unique constraint violation issue when concurrent clients are trying to insert data into child table.
Suppose We have tables below
1.Users
user_id, first_name, last_name.
2.Projects
project_id project_name and project_description.
And both are having many-to-many relation ship.
When two clients are trying to create a new users. Suppose client1 created user1(id=aa1),child record project(id=1).Client2 also created user2(id=aa2), child record is project(id=1). When two clients concurrently saving record user1, and child also inserting and while second client is trying as project(id-1) already available, it is throwing unique key constraint violation.
We are checking findby(projectx) before associating child record.
We are facing this issue rarely. We have implemented retry operation when we have an exception. Even with retry operation issue is not resolving consistently. Please guide me on this.
Exception is:
HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement.
ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (ForkJoinPool.commonPool-worker-1) ERROR: duplicate key value violates unique constraint "x_key"
We are using this sample code.
#Entity
#Table(name = "tbl_users", schema = "abcd")
public class user implements Serializable {
private static final long serialVersionUID = -8153443692098431986L;
#Id
#Column(name = "userId", nullable = false)
private String userid;
#Column(name = "last_name", nullable = false)
private String lastName;
#Column(name = "first_name", nullable = false)
private String firstName;
#ManyToMany(mappedBy = "projectsSet", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Project> projectsSet;
}
#Entity
#Table(name = "tbl_projects", schema = "abcd")
public class Project implements Serializable {
private static final long serialVersionUID = -8153443692098431986L;
#Id
#Column(name = "project_id", nullable = false)
private String projectId;
#Column(name = "project_Name", nullable = false)
private String projectName;
#Column(name = "projectDescription", nullable = false)
private String projectDescription;
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.LAZY)
#JoinTable(name = "users_projects", schema = "abcd", joinColumns = {#JoinColumn(name = "_projectId")},
inverseJoinColumns = {#JoinColumn(name = "_userId")})
private Set<User> usersSet;
}
#Transactional
public void saveProject(ProjectDTO projectDTO, Set<UserDTO> userDTOSet) {
Optional<Project> optionalProject = getProjectFromDB(projectDTO.getProjectId());
List<User> existUsers = null;
if (!optionalProject.isPresent()) {
Map<String, UserDTO> userDTOMap = userDTOSet.stream().collect(Collectors.toMap(UserDTO::getUserId, userDTO -> userDTO));
if (MapUtils.isNotEmpty(userDTOMap)) {
existingUsers = getUsersFromDB(userDTOMap.keySet());
}
Project project = new Project();
try {
BeanUtils.copyProperties(project, projectDTO)
updateExistingUsers(project, userDTOMap, existingUsers);
addNewUsers(project, userDTOMap);
} catch (IllegalAccessException e) {
throw new BeanCopyException("Exception raised while copying the data", e);
} catch (InvocationTargetException e) {
throw new BeanCopyException("Exception raised while copying the data", e);
}
try {
ProjectRepository.save(user);
} catch (Exception e) {
throw new CustomException("Exception raised while saving the new Project", e);
}
}
private void updateExistingUsers (Project
newProject, Map < String, UserDTO > newUsers, List < User > existingUsers){
if (CollectionUtils.isNotEmpty(existingUsers)) {
existingUsers.stream().forEach(existinguser -> {
newProject.addNewUser(existinguser);
newUsers.remove(existinguser.getUserId());
});
}
}
private void addNewUsers(Project newProject, Map < String, UserDTO > userDTOMap){
userDTOMap.values().stream().forEach(userDTO -> {
User user = convertToModel(userDTO);
newProject.addNewUser(newUser);
});
}
You could try to use #SQLInsert to use a custom "upsert" statement for User as I have answered a few times already. See here Hibernate Transactions and Concurrency Using attachDirty (saveOrUpdate)
Related
Here below is a simple model for a pet shop...
Pet Class
#Entity
#Table(name = "pet")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public abstract class Pet {
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "birth_date", nullable = false)
private LocalDate birthDate;
#Column(name = "death_date")
private LocalDate deathDate;
#ManyToOne
#JoinColumn(name = "pet_shop_id", nullable = false, referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private PetShop petShop;
public void setPetShop(PetShop petShop) {
setPetShop(petShop, true);
}
public void setPetShop(PetShop petShop, boolean add) {
this.petShop= petShop;
if (petShop!= null && add) {
petShop.addPet(this, false);
}
}
PetShop Class
#Entity
#Table(name = "pet_shop")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class PetShop {
#Column(name = "id", nullable = false)
private Long id;
...
#OneToMany(
mappedBy = "petShop",
fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
private List<Pet> pets= new ArrayList<>();
public void addPet(final Pet pet) {
addPet(pet, true);
}
public void addPet(final Pet pet, boolean set) {
if (pet!= null) {
if (pets.contains(pet)) {
pets.set(pets.indexOf(pet), pet);
} else {
pets.add(pet);
}
if (set) {
pet.setPetShop(this, false);
}
}
}
}
PetShopRepository Interface
public interface PetShopRepository
extends JpaRepository<PetShop, Long> {
#Query(
"SELECT DISTINCT ps FROM PetShop ps"
+ " JOIN ps.pets p"
+ " WHERE ps.id = :id AND p.deathDate IS NULL")
#Override
Optional<PetShop> findById(#NonNull Long id);
}
... and here is how to create a PetShop with 2 Pet instances (one alive and another one dead):
final Pet alive = new Pet();
alive.setName("cat");
alive.setCall("meow");
alive.setBirthDate(LocalDate.now());
final Pet dead = new Pet();
dead.setName("cat");
dead.setCall("meow");
dead.setBirthDate(LocalDate.now().minusYears(15L));
dead.setDeathDate(LocalDate.now());
final PetShop petShop = new PetShop();
petShop.getPets().add(alive);
petShop.getPets().add(dead);
petShopRepositiry.save(petShop);
Now I want to retrieve the PetShop and I'd assume it contains only pets that are alive:
final PetShop petShop = petShopRepository.findById(shopId)
.orElseThrow(() -> new ShopNotFoundException(shopId));
final int petCount = petShop.getPets().size(); // expected 1, but is 2
According to my custom query in PetShopRepository I'd expect petShop.getPets() returns a list with 1 element, but it actually returns a list with 2 elements (it includes also the dead pet).
Am I missing something? Any hint would be really appreciated :-)
This is because Jpa maintains the coherence of the relations despite your query.
I.e. : your query returns the shops having at least one pet alive. But, Jpa will return the shop with the complete set of pets. And you can probably see extra sql queries sent by Jpa (if you set show_sql=true) to refill pets collection on the returned shop.
Fundamently, it's not because you wanted to get the shops with living pets that these shops loose their dead pets.
To get it right you would have to design the pets collection so that it would filter the dead pets. Hibernate provides such annotations (#Filter and #FilterDef), but apparently JPA does not.
I don't think that filtering at #Postload would be a good idea, because you would have to put back the filtered dead pets in the collection before any flush in the database. That looks risky to me.
Owner:
#Entity
public class Strategy implements Serializable {
#Id
#GeneratedValue
private Long id;
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST})
#JoinTable(name = "StrategyHost", joinColumns = {#JoinColumn(name = "strategyId")}, inverseJoinColumns = {#JoinColumn(name = "hostId")})
private Set<Host> hostName;
}
Related entity:
#Entity
public class Host {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String name;
#ManyToMany(mappedBy = "hostName")
private List<Strategy> strategies;
public Host(String name) {
this.name = name;
}
}
Test:
#Test
#Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testStrategyWithHosts() {
Strategy s = new Strategy();
Set<Host> hosts= new HashSet<>();
hosts.add(Host.builder().name("aaa").build());
hosts.add(Host.builder().name("bbb").build());
s.setHostName(hosts);
Strategy saved= strategyDao.save(s);
Set<Host> hostName = saved.getHostName();
}
debug shows the persisted saved object having Host:
Where are name values? However, if I add merge in cascade type array, name are valued. Why insert (not update managed entities) operation for related entities must have merge cascade type? Although log shows nothing suspicious:
insert into strategy...
insert into host...
insert into host...
update strategy ...
insert into strategy_host ...
insert into strategy_host ...
I have the following repository method that works exactly the way I need it to iff the user provides a sort column in the page parameter:
public interface IdentityRepository extends JpaRepository<Identity, String> {
#Query("select distinct ident from Identity ident left outer join ident.authorities authority "
+ "where ("
+ "(:src is null or ident.source = :src) and "
+ "(:org is null or ident.organization = :org) and "
+ "(:auth is null or authority.authority = :auth) and "
+ "(:authSrc is null or authority.authoritySource = :authSrc))")
#RestResource(path="filter")
public Page<Identity> findWithFilter(
#Param("src") String source,
#Param("org") String org,
#Param("auth") Authority auth,
#Param("authSrc") AuthoritySource authSrc,
Pageable page);
...
}
If the caller provides a page count, but not a sort column, they will get back the correct number of results when retrieving all the pages. However, many of the entities will be duplicated, so even though the result count is correct, many expected entities are missing and others are duplicated (or triplicated).
What I'm wondering is if there is a way to provide a default sort column and direction if the user does not specify one. I've learned that #EnableSpringDataWebSupport can help here, but we're not using Spring MVC, so I don't have any controllers to attach the #SortDefaults to. We are using Spring Data Rest though. Also, I've tried changing the method name to findWithFilterOrderByIdAsc, but that did not seem to help. Ran across this issue in the Spring JIRA, which I believe is exactly what I need, but until it's resolved, does anyone know of a work around?
Here's my entity...
#Entity
#Table(name = "identity", indexes = { #Index(columnList = "user_id", unique = true) })
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Audited
public class Identity implements Serializable, Identifiable<String> {
/**
* The unique identifier for this identity within the IDD application.
*/
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "IDDUidGenerator")
#GenericGenerator(name = "IDDUidGenerator")
private String id;
/**
* The name of the identity provider wherein this identity is originally defined.
*/
#Column(name = "source")
private String source = INTERNAL_SOURCE;
/**
* The unique identifier for this identity within the customer's identity provider.
*/
#NotNull
#Column(name = "user_id", nullable = false, unique = true)
private String userId;
/**
* The roles this identity is authorized to perform.
*/
#OneToMany(fetch = FetchType.EAGER, mappedBy = "identity", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<IdentityAuthority> authorities = new HashSet<>();
...
}
And its sub-entity...
#Entity
#Table(name = "identity_authority")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Audited
public class IdentityAuthority implements Serializable, Identifiable<Long> {
private static final long serialVersionUID = -5315412946768343445L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#XmlTransient
#JsonIgnore
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "identity_id", nullable = false)
#XmlTransient
#JsonIgnore
private Identity identity;
#Enumerated(EnumType.STRING)
#Column(name = "authority", length = 20, nullable = false)
private Authority authority;
#Enumerated(EnumType.STRING)
#Column(name = "authority_source", length = 30, nullable = false)
private AuthoritySource authoritySource;
...
}
Here's the test case I ran to demonstrate the problem...
#Test
public void testPagedRequestsReturnAllResults() {
// Create identities
String source = "One Hundred Identities Generator";
int numIdentities = 100;
int pageSize = 5;
List<Identity> input = new ArrayList<>();
for (int i=0; i<numIdentities; i++) {
Identity identity = new Identity();
identity.setUserId(UUID.randomUUID().toString());
identity.setSource(source);
input.add(identity);
}
// Save identities
List<Identity> output = repository.saveBulk(input);
Set<String> savedIds = collectIds(output, null);
assertThat(savedIds.size()).isEqualTo(numIdentities);
// Test Sorted Find Filter with Paging (THIS PASSES)
Pageable pageRequest = new PageRequest(0, pageSize, new Sort(Direction.ASC, "id"));
Set<String> foundPagedIds = new HashSet<>();
do {
Page<Identity> page = repository.findOrderByIdAsc(source, null, null, null, pageRequest);
List<Identity> foundIdentities = page.getContent();
foundPagedIds = collectIds(foundIdentities, foundPagedIds);
pageRequest = page.nextPageable();
} while (pageRequest != null);
assertThat(foundPagedIds.size()).isEqualTo(numIdentities);
assertThat(foundPagedIds).isEqualTo(savedIds);
// Test Unsorted Find Filter with Paging (THIS FAILS)
pageRequest = new PageRequest(0, pageSize);
foundPagedIds = new HashSet<>();
do {
Page<Identity> page = repository.findOrderByIdAsc(source, null, null, null, pageRequest);
List<Identity> foundIdentities = page.getContent();
foundPagedIds = collectIds(foundIdentities, foundPagedIds);
pageRequest = page.nextPageable();
} while (pageRequest != null);
assertThat(foundPagedIds.size()).isEqualTo(numIdentities);
assertThat(foundPagedIds).isEqualTo(savedIds);
}
I need help to understand how ORM works. Here is the scenario that is very common. I have two main tables Organization and RelatedParty which have to be in many to many relation. But there is also relation_type attribute that defines what kind of relation exists between Organization and Relatedparty.
Here are my entity classes:
Organization:
#Entity
#Table(name = "organization", catalog = "...", schema = "")
#XmlRootElement
public class Organization implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "organization_id", nullable = false)
private Integer organizationId;
#Column(name = "organization_name", nullable = false)
private String organizationName;
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "organization")
private List<Organdrelatedparty> organdrelatedpartyList;
...
//getter setter methods
Organdrelatedparty: which uses composite primary key OrgandrelatedpartyPK
#Entity
#Table(name = "organdrelatedparty", catalog = "...", schema = "")
#XmlRootElement
public class Organdrelatedparty implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected OrgandrelatedpartyPK organdrelatedpartyPK;
#JoinColumn(name = "relatedParty_id", referencedColumnName = "relatedParty_id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false, cascade= {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private Relatedparty relatedparty;
#JoinColumn(name = "orgRelation_id", referencedColumnName = "orgRelation_id", nullable = false)
#ManyToOne(optional = false)
private ParOrgrelationtype orgRelationid;
#JoinColumn(name = "organization_id", referencedColumnName = "organization_id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false)
private Organization organization;
...
//getter setter methods
OrgandrelatedpartyPK
#Embeddable
public class OrgandrelatedpartyPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "relatedParty_id", nullable = false)
private int relatedPartyid;
#Basic(optional = false)
#NotNull
#Column(name = "organization_id", nullable = false)
private int organizationId;
...
//getter setter methods
RelatedParty: which is in unidirectional oneToMany relationship with organdRelatedParty class. In other word that relatedParty entity has no knowledge about organdRelatedParty entity that is on the other side.
#Entity
#Table(name = "relatedparty", catalog = "...", schema = "")
#XmlRootElement
public class Relatedparty implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "relatedParty_id", nullable = false)
private Integer relatedPartyid;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "firstName", nullable = false, length = 100)
private String firstName;
#Size(max = 100)
#Column(name = "lastName", length = 100)
private String lastName;
#Basic(optional = false)
#NotNull
#Column(name = "isForeign", nullable = false)
private boolean isForeign;
...
//getter setter methods
For insertion, if I persist new Organization Entity, it cascades persist activity to new OrgandrelatedParty which also cascades persist activity to new RelatedParty. So all the related entities are persisted and it works fine.
For updating, User is expected to change existing organization and relatedParty entities and also add new relatedParty to organization. So we prefer to delete all OrgandrelatedParties first and add new relatedParties and edited relatedParties again after that.
This is our method that handles updating: We pass new organization and also all new and old relatedParties as a list to method:
firs we delete all old OrgAndRelatedParties then we create again all relatedParties in list as new OrgandrelatedParties. This is main method to update organization.
public void updateOrganization(Organization newOrganization, List<Relatedparty> newShareList) throws ControlException {
try{
tx.begin();
this.updateOrgAndRelatedShares(newOrganization, newShareList);
customerController.updateOrganization(newOrganization);
tx.commit();
}catch(ControlException ex){
...
customerController's updateOrganization method does first find old Organization by find method of entity manager then copies all attributes of new organization to old then merges old organization and flush:
public void updateOrganization(Organization newOrganization)
{
Organization preOrganization = em.find(Organization.class, newOrganization.getOrganizationId);
preOrganization.setOrganizationId(newOrganization.getOrganizationId);
preOrganization.setOrganizationName(newOrganization.getOrganizationName);
em.merge(preOrganization);
em.flush();
}
here are other methods:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void updateOrgAndRelatedShares(Organization org, List<Relatedparty> shareList) throws ControlException
{
for(Iterator<Organdrelatedparty> it = org.getOrgandrelatedpartyList().iterator(); it.hasNext();)
{
Organdrelatedparty op = it.next();
it.remove();
op.setOrganization(null);
op.setRelatedparty(null);
deleteOrgRelated(op);
}
org.getOrgandrelatedpartyList().clear();
for(Relatedparty relatedParty: shareList){
int parOrgRelationTypeId = relatedParty.getIsPerson() ? 1:2;
createOrgAndRelatedParty(org, relatedParty, parOrgRelationTypeId);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteOrgRelated(Organdrelatedparty org) throws ControlException{
try{
org = em.find(Organdrelatedparty.class, org.getOrgandrelatedpartyPK());
em.remove(org);
em.flush();
}
catch(Exception ex){
Logger.getLogger(RelatedpartyController.class.getName()).log(Level.SEVERE, null, ex);
throw new ControlException("Couln't delete org relation", ex);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void createOrgAndRelatedParty(Organization org, Relatedparty relatedParty, int parOrgRelationTypeId) throws ControlException{
if(findRelatedPartyByRegNum(relatedParty.getRegisterNumber()) == null || relatedParty.getRelatedPartyid() == null){
createRelated(relatedParty);
}else{
relatedParty = updateRelatedParty(relatedParty);
}
Organdrelatedparty preOrp = new Organdrelatedparty(relatedParty.getRelatedPartyid(),
preOrp.setOrganization(org);
preOrp.setRelatedparty(relatedParty);
preOrp.setOrgRelationid(prepareOrgandRelatedPartyType(parOrgRelationTypeId));
org.getOrgandrelatedpartyList().add(preOrp);
}
And my question is when I merge organization entity with new List organdrelatedpartyList
it throws exception like this:
SEVERE: java.lang.IllegalArgumentException: Cannot merge an entity that has been removed: mn.bsoft.crasmonclient.model.Organdrelatedparty[ organdrelatedpartyPK=mn.bsoft.crasmonclient.model.OrgandrelatedpartyPK[ relatedPartyid=71, organizationId=19 ] ]
I found out that eclipseLink does persist operation first then remove operations. So I think that it tries to insert organdrelatedparty entity that has same composite id with entity which was not deleted previously from database. I flushes every time I remove old organdrelatedparties. But it doesn't help. What is the solution? Any idea guys.
I'm using jpa 2.0; eclipseLink as provider and glassfish 3.1.2
You seem to be making these a lot more complicated than they need to be.
Why don't you just remove the Organdrelatedparty that have been removed, instead of deleting all of them, then reincarnating some of them? Reincarnating objects, especially in the same transaction is normally a bad idea.
The error that is occurring is on merge() according to the code you included you are only call merge in updateOrgAndRelatedShares(), so I don't see how this object is removed at this point? Or is your code different than you show, please include the exception stack.
You updateOrganization() method is bad, it updates the objects Id, which you should never do. Also it calls merge for no reason, it already changed the object.
Also I would normally recommend using an IdClass instead of an EmbeddedId, and recommend using TABLE or SEQUENCE id generation instead if IDENTITY.
It seems like that when I update my existing entity using jpa merge, it does insert entity with same id instead of updating existing one expected. Because after insertion, database row order lost. But I still have same entities with same ids, does Jpa use insertion to update? I mean does it delete existing entity and insert again with updated value to do its update job. Main chaos is database order is lost then.
Here is my listener method: userService Is EJB class within I use JPA.
public void onEditUserOrganization(RowEditEvent event){
UserOrganization uorg =(UserOrganization) event.getObject();
try {
userService.updateUserOrganization(uorg);
} catch (UserException ex) {
ex.printStackTrace();
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("error)"));
}finally{
parentlist = getUserOrganizationParents();
branchlist = getUserOrganizationBranches();
}
}
Here is my main update method
#Override
public void updateUserOrganization(UserOrganization org) throws UserException{
if(org != null && !em.contains(org)){
try{
UserOrganization existing = em.find(UserOrganization.class, org.getUorgId());
existing.setOrgcode(org.getOrgcode());
existing.setOrgname(org.getOrgname());
existing.setParent(org.getParent());
}catch(Exception e){
throw new UserException("Couldn't update user org with id " + org.getUorgId());
}
}
}
Here is my entity class:
public class UserOrganization implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "uorg_id", nullable = false)
private Integer uorgId;
#Basic(optional = false)
#NotNull
#Column(name = "parent", nullable = false)
private short parent;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 10)
#Column(name = "orgcode", nullable = false, length = 10)
private String orgcode;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 150)
#Column(name = "orgname", nullable = false, length = 150)
private String orgname;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "uorgId")
private List<refMain> refList;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "uorgId")
private List<User1> user1List;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "userOrganization")
private List<Bankrelation> relationList;
public UserOrganization() {
}
//getter setters..
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof UserOrganization)) {
return false;
}
UserOrganization other = (UserOrganization) object;
if ((this.uorgId == null && other.uorgId != null) || (this.uorgId != null && !this.uorgId.equals(other.uorgId))) {
return false;
}
return true;
}
#Override
public String toString() {
return "mn.bs.crasmon.model.UserOrganization[ uorgId=" + uorgId + " ]";
}
What is the best way to do update In JPA