Default Sort on a Spring Data JPA Repository Method with Custom Query and Pageable Parameter - spring-data-jpa

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

Related

Duplicate key value violates unique constraint "x_pkey"

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)

How to query a many to many entity list via Specification API in Spring Data JPA

I have 2 entities with Many-To-Many relationships
public class Enterprise{
#Id
#Column(name = "id", nullable = false, length = 50)
#GeneratedValue(generator = "jpa-uuid")
private String id;
fields...
#ManyToMany
#JoinTable(name = "enterprise_to_tag",
joinColumns = #JoinColumn(name = "enterprise"),
inverseJoinColumns = #JoinColumn(name = "tag"))
private Set<EnterpriseTag> tags;
}
and
public class EnterpriseTag{
#Id
#Column(name = "id", nullable = false, length = 50)
#GeneratedValue(generator = "jpa-uuid")
private String id;
fields...
#ManyToMany(mappedBy = "tags")
private Set<Enterprise> enterprises;
}
I want to query enterprise list by some tags' ID then pack them to Page
private Page<Enterprise> searchEnterprise(int number, int size, String keyword, String tags, String county)
throws BusinessException {
validPageNumberAndPageSize(number, size);
Pageable pageable = PageRequest.of(number, size);
Specification<Enterprise> specification = (Specification<Enterprise>) (root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNoneBlank(keyword)) {
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + keyword + "%");
Predicate predicateSerialNumber = criteriaBuilder.like(root.get("serialNumber"), "%" + keyword + "%");
predicates.add(criteriaBuilder.and(criteriaBuilder.or(predicateName, predicateSerialNumber, predicateOrganizationCode)));
}
return criteriaQuery.where(predicates.toArray(new Predicate[0])).getRestriction();
};
//filter by tags here
if (StringUtils.isNoneBlank(tags)) {
List<String> tagIds = Arrays.asList(StringUtils.split(tags, ','));
List<Enterprise> enterprises = enterpriseRepository.findAll(specification).stream().filter(enterprise ->
enterprise.getTags().stream().map(EnterpriseTag::getId).collect(Collectors.toList()).containsAll(tagIds))
.collect(Collectors.toList());
return new PageImpl<>(enterprises, pageable, enterprises.size());
} else {
return enterpriseRepository.findAll(specification, pageable);
}
}
I don't know how to write this query. I have to handle it base on a database query result. But it's risky. If too much data is queried from the database, it will take up a lot of memory. Please help me to write this query by Specification API. Thanks.

Hibernate Filter being ignored

My application uses Hibernate 5.02 and Wildfly 10 with a PostgreSQL 9.5 database. I'm trying to enable a filter on a #OneToMany collection held within an entity that is constructed via a NamedQuery. Unfortunately, it seems as if the filter is just ignored. Here are the different components, redacted for ease of reading.
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "analystprocess_id IN (:processIds) AND id NOT IN (SELECT msg_id FROM analysis.analyzedmsg WHERE analyst_id IN (:analystIds) AND analystprocess_id IN (:processIds)) ORDER BY process_msg_id")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
private void buildAnalystProcess() {
LOG.info("Building AnalystProcessEntity");
analystUser.getJdbcSession().enableFilter(AnalystProcessEntity.MSG_FILTER)
.setParameter(AnalystProcessEntity.MSG_FILTER_PROC_ID_PARAM, analystProcessId)
.setParameter(AnalystProcessEntity.MSG_FILTER_ANALYST_ID_PARAM, analystUser.getId());
Query query = analystUser.getJdbcSession().getNamedQuery(AnalystProcessEntity.GET_PROCESS)
.setParameter("processId", analystProcessId);
// Query query = analystUser.getJdbcSession().createNativeQuery("SELECT * FROM analysis.analystprocess WHERE id = :processId")
// .setParameter("processId", analystProcessId)
// .addEntity(AnalystProcessEntity.class);
analystProcess = (AnalystProcessEntity) query.getSingleResult();
CREATE TABLE analysis.analystprocess (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_date TIMESTAMP NOT NULL DEFAULT now(),
...
);
CREATE TABLE analysis.msg (
id SERIAL PRIMARY KEY,
analystprocess_id INTEGER NOT NULL REFERENCES analysis.analystprocess(id) ON DELETE CASCADE ON UPDATE CASCADE,
process_msg_id INTEGER NOT NULL,
constraint tbl_statusid_analystprocessid unique(status_id, analystprocess_id)
);
As seen above, I have also tried the filter on constructing the AnalystProcessEntity class via createNativeQuery instead of getNamedQuery and no luck.
I also added a defaultCondition with hardcoded values into the #FilterDef just to see if it would execute the default condition and it still didn't.
I've tried the #Filter above the entity definition as well as above the class definition. I even came across a blog post which made it sound like the condition references entity fields (variable names) and not table fields (column names). Trying to stick to Java naming conventions in the Entity and Postgres naming conventions in the table, so I tried switching the references in the condition and to no avail.
I have sql logging turned on in Hibernate and the condition doesn't show up anywhere, as if it's just simply being ignored.
Any help would be greatly appreciated!
So, the problem was that I had the #FilterDef applied to the wrong class. It was my presumption that because I was constructing the AnalystProcessEntity which holds the MsgEntity collection (which I am trying to filter), that the #FilterDef would be applied to the AnalystProcessEntity class. Instead, it needs to be applied to the entity that it's actually filtering (hindsight being 20/20, that's pretty obvious).
Also, the actual condition needed to be modified to use complete references within the sub-select query.
I hope this helps someone at some point...
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "id NOT IN (SELECT amsg.msg_id FROM analysis.analyzedmsg amsg WHERE amsg.analyst_id IN (:analystIds) AND amsg.analystprocess_id IN (:processIds))")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
Additionally, I ran into another problem with null's appearing in the collection, despite the fact that I am using an #OrderColumn, which I thought fixed that issue. It seems that with the use of the #Filter, null's are inserted in place of what ended up being filtered OUT (excluded).

How to use JPA criteriaBuilder to search on attributes in a collection of sub-attributes

I have an Entity that maps to a table defined this way:
#Entity
#Table(name = "cmmn_calendar_evnt")
public class CommonCalendarEvent implements java.io.Serializable
{
private Integer cevId;
private Set<CommonCalendarEventPart> commonCalendarEventParts = new HashSet<CommonCalendarEventPart>(0)
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEV_ID", unique = true, nullable = false)
public Integer getCevId()
{
return this.cevId;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "commonCalendarEvent")
public Set<CommonCalendarEventPart> getCommonCalendarEventParts()
{
return this.commonCalendarEventParts;
}
}
and CommonCalendarEventPart is defined like this:
#Entity
#Table(name = "cmmn_calendar_evnt_part")
public class CommonCalendarEventPart implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer ceeId;
private CommonCalendarEvent commonCalendarEvent;
private PartParticipant partParticipant;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEE_ID", unique = true, nullable = false)
public Integer getCeeId()
{
return this.ceeId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_CEV_ID", nullable = false)
public CommonCalendarEvent getCommonCalendarEvent()
{
return this.commonCalendarEvent;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_PPT_ID", nullable = false)
public PartParticipant getPartParticipant()
{
return this.partParticipant;
}
}
and finally:
#Entity
#Table(name = "part_participant")
public class PartParticipant implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer pptId;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "PPT_ID", unique = true, nullable = false)
public Integer getPptId()
{
return this.pptId;
}
}
I want to use the CriteriaBuilder to generate a query finding all CommonCalendarEvent for a specific Participant ID.
In Hql it would look something like this: (although I have not confirmed that this Hql is correct either)
"from commonCalendarEvent cce where :pptId in (cce.commonCalendarEventParts.partParticipant.pptId)"
I've tried some approaches of what I thought were intuitive attempts at writing a criteriaBuilder approach, but my attempts have resulted in errors ranging from:
“unexpected end of subtree” to just implementation errors.
.....
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> criteria = builder.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> root = criteria.from(CommonCalendarEvent.class);
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf = root.fetch(CommonCalendarEvent_.commonCalendarEventParts, JoinType.LEFT);
Join<CommonCalendarEvent, CommonCalendarEventPart> evj = (Join<CommonCalendarEvent, CommonCalendarEventPart>) evf;
Join<CommonCalendarEventPart, PartParticipant> evpj = evj.join(CommonCalendarEventPart_.partParticipant);
List<Predicate> pred = new ArrayList<Predicate>();
pred.add(builder.equal(evpj.get(PartParticipant_.pptId), pptId));
criteria.where(builder.and(pred.toArray(new Predicate[] {})));
return getEntityManager().createQuery(criteria).getResultList();
.............
above yields an "unexpected end of subtree" error.
Any Help is appreciated.
+1 for using Lazy initialization. The JPA model is Object, or Entity oriented, so you need to get used to thinking in those terms. A PartParticipant is not identified by its id in JPA, but by the object itself. Assuming you have a list of participants:
PartParticipant pp = em.find(PartParticipant.class, 2);
List<PartParticipant> pps = new ArrayList<PartParticipant>();
pps.add(pp);
Then you pass that list to the queries. In JPQL:
TypedQuery<CommonCalendarEvent> cev = em.createQuery("select cev from CommonCalendarEvent cev join fetch cev.commonCalendarEventParts cce where cce.partParticipant in :pps", CommonCalendarEvent.class);
List<CommonCalendarEvent> cevs = cev.setParameter("pps", pps).getResultList();
Notice the fetch is needed to prevent LazyInitializationExceptions.
Knowing the JPQL, the CriteriaQuery should follow pretty much the same:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> q = cb.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> r = q.from(CommonCalendarEvent.class);
Join<CommonCalendarEvent, CommonCalendarEventPart> j = r.join("commonCalendarEventParts");
r.fetch("commonCalendarEventParts");
q.select(r).where(j.get("partParticipant").in(pps));
List<CommonCalendarEvent> rs = em.createQuery(q).getResultList();
You don't need to do anything special with the fetch other than execute it. As you can see, the query uses the PartParticipant Id.
select
commoncale0_.CEV_ID as CEV_ID1_0_0_,
commoncale1_.CEE_ID as CEE_ID1_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_1_1_,
commoncale1_.CEE_PPT_ID as CEE_PPT_3_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_0_0__,
commoncale1_.CEE_ID as CEE_ID1_1_0__
from cmmn_calendar_evnt commoncale0_
inner join cmmn_calendar_evnt_part commoncale1_ on commoncale0_.CEV_ID=commoncale1_.CEE_CEV_ID
where commoncale1_.CEE_PPT_ID in (?)
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf is not necessary, and the first join statement should be corrected:
Join<CommonCalendarEvent, CommonCalendarEventPart> evj =
root.join(CommonCalendarEvent_.commonCalendarEventParts);
The rest of the query seems correct.

Updating multiple entities with jpa 2

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.