JPA - merge() duplicate record issue - java

I have three tables Account, AccountStatus and AccountStatusCodes. AccountStatusCodes is a master table at it has fixed account status codes. Account table will have different account listed. AccountStatus table is a kind of history table, whenever user performs some action on account, the old acount status is updated with flag N and new account status will be inserted. So on each operation on Account the Account status will mantain history with timestamp, user id who updated the status and Y/N flag.
Relationship - Many to many relationship from Account table to AccountStatusCodes code is broken like this
1. One Account can have multiple AccountStatus - Account --> one to many --> AccountStatus
2. One AccountStatusCodes can have multiple AccountStatus - AccountStatusCodes --> one to many --> AccountStatus
JPA Entity code - The actual code is not allowed to share, hence sharing the modifiable code to explain the scenario.
Class: AccountEntity
#DynamicUpdate(value = true)
public class AccountEntity{
private Long accountKey;
//Skipped other variables and getter setters
}
Class: AccountStatusEntity
public class AccountStatusEntity{
#Id
#SequenceGenerator(name = "ACCOUNT_STATUSES_DOCUMENT_STATUSKEY_GENERATOR" , sequenceName = "AS_SEQ")
#GeneratedValue(generator = "ACCOUNT_STATUSES_DOCUMENT_STATUSKEY_GENERATOR")
#Column(name = "ACCOUNT_STATUS_KEY" , unique = true , nullable = false , precision = 10)
private Integer accountStatusKey;
#ManyToOne
#JoinColumn(name = "ACCOUNT_STATUS_CODE_KEY" , nullable = false)
private AccountStatusCodeEntity accountStatusCodeEntity;
#ManyToOne
#JoinColumn(name = "ACCOUNT_KEY" , nullable = false)
private AccountEntity accountEntity;
#Column(name = "CURRENT_STATUS_IND" , nullable = false , length = 1)
private String currentStatusInd;
//skipped other variables and getter setters
}
Class: AccountStatusCodeEntity
public class AccountStatusCodeEntity{
#Id
#Column(name = "ACCOUNT_STATUS_CODE_KEY")
#GeneratedValue(generator = "ASC_SEQ")
#SequenceGenerator(name = "ASC_SEQ", sequenceName = "ASC_SEQ")
private Integer accountStatusCodeKey;
#OneToMany(mappedBy = "accountStatusEntity")
private List<AccountStatusEntity> accountStatuseEntitiess;
}
In the application each user performs some operation on the account and each time the account status gets incremented to next AccountStatusCode and it maintains the history in AccountStatusEntity table by modifying existing status to flag N and inserting new status with timestamp, user id and flag Y.
So there will be two DB operation performed with the #Transaction first one is to update old status to N and insert new status with Y.
The method which does this has following code.
private void moveAccountStatus(final Long accountKey, final String loggedInUserID, final Integer currentStatus,
final Integer nextStatus) {
//Search existing account status with accountKey
// here I have skipped the code which will pull latest status entity from the history table based on date
final AccountStatusEntity accountStatusEntity =
accountDAO.findAccountStatusByAccountKey(accountKey);
AccountEntity accountEntity;
AccountStatusEntity newAccountStatusEntity;
if (accountStatusEntity != null) {
accountEntity = accountDAO.findAccountByAccountKey(accountKey);
accountStatusEntity.setCurrentStatusInd(Constants.NO);
accountStatusEntity.setModifiedBy(loggedInUserID);
accountStatusEntity.setModifiedTs(new Date());
accountStatusEntity.setAccountEntity(accountEntity);
//The update method here is calling the JPA merge() to update the records in the table.
accountDAO.update(accountStatusEntity);
//Create new object of AccountStatusEntity to insert new row with the flag Y
newAccountStatusEntity = new AccountStatusEntity();
//Set the next status
newAccountStatusEntity.setAccountStatusCodeEntity(
(AccountStatusCodeEntity) accountDAO.getById(AccountStatusCodeEntity.class, nextStatus));
newAccountStatusEntity.setCurrentStatusInd(Constants.YES);
newAccountStatusEntity.setCreatedBy(loggedInUserID);
newAccountStatusEntity.setCreatedTs(new Date());
newAccountStatusEntity.setAccountEntity(accountEntity);
//The create() method is also calling the JPA merge() method. The Id is null hence it will consider a insert statement and will insert a new record.
accountDAO.create(newAccountStatusEntity);
}
}
This process works 99% fine in the Production but some times this method is createing duplicate record in the table AccountStatusEntity with the same timestamp, userid and flag Y. The method did not update record with flag N or due to some issue the old record is also getting updated with flag Y.
Table: AccountStatus
___________________________________________________________________________________________________________________
accountStatusKey | accountKey | accountStatusCodeKey | currentStatusInd | Created_TS | Created_BY
___________________________________________________________________________________________________________________
| | | | |
1 | 5 | 3 | Y | A | 4/9/2018
2 | 5 | 3 | Y | A | 4/9/2018
___________________________________________________________________________________________________________________
The above table shows the records which is getting created after the method execution. The accountStatusKey 1 should have the accountStatusCodeKey 2 (the old status code is 2 and next status code is 3) and currentStatusInd N. But somehow the merge method is inserting two records here.
One solution I can make to create unique constraint on the column so that it will avoid this situation but I just wanted to know why the merge method of JPA is creating this issue.
Another solution which I have not tried is to use JPA's persist() method at the time of insert instead of merge().
This issue is hard to reproduce in Development environment as 99% time it works, Also users reports this issue very late so that could not trace the log files.
As per the logs on dev environment, when the JPA transaction gets executed the insert statement is getting logged first and then the update query is getting logged in the log file. I am not sure how the statement order follows in the Transaction case.
I know the question is too long but I just wanted to give the exact background for understanding this issue. TIA.

I have modified code and instead of updating child records directly I am creating and updating child entity and adding into the Parent entity list and asking JPA to Save or update. So based on the Id either it is a null or having valid id the Parent will decide whether to add or update child. I have move my code to Prod environment and from last 2-3 weeks I am not seeing any duplicate rows issue.
I will keep you posted if I see this issue again. Thanks!

This was the known issue in the older Hibernate versions. My application was using hibernate version 4.2.8.Final.
Reported issue link - https://hibernate.atlassian.net/browse/HHH-6776
This issue was fixed in 5.0.8.Final version. I have updated my maven dependencies and will keep testing.
Link: https://hibernate.atlassian.net/browse/HHH-5855
More information about this issue -
https://javacodinggeeks.blogspot.com/2015/05/hibernate-inserts-duplicate-child-on.html
Hibernate inserts duplicates into a #OneToMany collection

Related

Hibernate Envers wrong modified flag on list

I use Envers to audit my data and sometimes the value of the _MOD is incorrect. It stays at 0 instead of 1 when I am adding an element in my list. But it happens only in a specific case.
My entity:
#Entity
#Table(name = "PERSONNE")
#Audited(withModifiedFlag = true)
public class PersonEntity {
#Id
#Column(name = "ID_PERSONNE")
private Long id;
#Column(name = "NAME", length = 100)
private String name;
#Audited( withModifiedFlag = true, modifiedColumnName = "SERVICES_MOD")
private Set<PersonneServiceEntity> services = new HashSet<>(); // Entity with attributs, gettters, setters and envers annotations...
#Audited( withModifiedFlag = true, modifiedColumnName = "OPT_INS_MOD")
private Set<OptinEntity> optIns = new HashSet<>();// Entity with attributs, gettters, setters and envers annotations...
// more fields
// + getters, setteurs, equals, tostring
my service:
// personFromDB is retrieve via an Id
private void update(PersonEntity personFromRequest, PersonEntity personFromDB) {
personFromDB.setName(personFromRequest.getName());
updateServices(personFromRequest, personFromDB); // add new support to the list
updateOptins(personFromRequest, personFromDB); // add new services to the list
personDao.saveAndFlush(personFromDB);
}
This is were the magic happens: When I am updating name, services and optIns. Values in my database are all correct, my entity is correctly persisted, except one envers's column: OPT_INS_MOD ( OPT_INS_MOD == 0).
But if I am not updating the name ( line commented ) then everything is correctly persisted including all _MOD values ( OPT_INS_MOD == 1 and SERVICES_MOD ).
And finally if I am switching updateSupport(personFromRequest, personFromDB) and updateServices(personFromRequest, personFromDB), in this case OPT_INS_MOD is correct but not SERVICES_MOD.
My guess is that there is a problem when Envers is getting all modified fields. Because it does not make any sense to me.
Any ideas? I am using Envers version 4.3.11.Final
I'm not sure this will help you because it doesn't sound like the same problem but I've noticed a weirdness with modified flags and collections.
I get my entities back from the front end converted from JSON back to POJOs. In order to keep from having a transient object error from Hibernate, I need to reset the value in the #Id field (which was never sent to the FE). This works fine for 1-1 entities.
On collections, I found that if I create a new instance of the collection class and fill it with refreshed entities from the old collection and then assign that new collection to the old attribute, the modified flag is set to true.
However, if I fill a new collection with refreshed entities, clear() the old collection, then add all the items in the new collection, modified flag will be false unless there were actual changes to the collection.

Spring / JPA join on namespaced key

I am creating a Spring 4 / Spring Data application for an existing database. The database structure and data are defined by a closed source software.
One aspect of the existing system is that you can create a comment on any other item in the system. This means, that an article, a document, a media file (all entities in the system) can have any number of comments, and each comment is exactly for one entity in the system. All comments are in the same comment table.
The way this is implemented is that the table comment has a column comment_for that holds a concatenated/namespaced/prefixed reference to the actual entity it is a comment for. The current system seems to just builds the join query by prefixing the primary key with the table name:
+----+-------------------+----------------+
| id | comment_for | comment |
+----+-------------------+----------------+
| 1| article:12345 | This is nice...|
| 2| document:42 | Cool doc! |
+----+-------------------+----------------+
This sample shows two comments, one for an Article with an article.id of 12345 and one for a document with document.id of 42. I created #Entities matching the database tables and the corresponding Repository Interfaces with the query methods I need.
I would like to make use of Spring Data Repositories / Entities to populate the collections of my entities with the corresponding comments, like this (pseudocde) for Entity Article.
#OneToMany(mappedBy = "comment_for", prefix = "article:")
private List<Comment> comment = new ArrayList<>();
I only need it unidirectional. My entities (at the moment Article, Document and Mediafile) should hold a collection of their comments. I don't need comments to hold a reference back to the entity.
Is there a way to do this? The resulting SQL query should be something like
SELECT * FROM .... WHERE comment.comment_for = concat('<entityname>:', <entity>.id);
I looked at #JoinColumn but I can't modify the used value for the join, only the column name. The only solution I have at the moment are manual #Querys on the CommentRepository Interface, which gives me an ArrayList of all comments for a certain Entity / ID combination. But I would like to have the comments automatically joined as part of my Business Entity.
Update : It looks like I am able to split the namespace and id from comment_for into two new columns without interrupting the existing software. The two columns are now comment_for_id and comment_for_entityname
You could also break out comment_for to contain only the id like your entities. Adding an additional column like entity_type would allow you to avoid duplicate id values between different entities.
Also you could use #JoinColumn on the owner side of the relationship between Entity and Comments. It looks like in your case that would be the Comment entity/table, since there are many comments per each entity.
Example:
#Entity
#NamedQueries({ #NamedQuery(name = "Comments.findAll", query = "select o from Comments o") })
#IdClass(CommentsPK.class)
public class Comments implements Serializable {
private static final long serialVersionUID = 4787438687752132432L;
#Id
#Column(name = "COMMENT_TEXT", nullable = false, length = 30)
private String commentText;
#Id
#Column(name = "ENTITY_TYPE", nullable = false, length = 30)
private String entityType;
#ManyToOne
#Id
#JoinColumn(name = "COMMENT_FOR")
private EntityDemo entityDemo;
Note that I set the combination of all three fields as the primary key, I am not sure what criteria is used as the PK in your current set up.
Here is an example of an Entity. The attributes have been made up for the purpose of demonstration.
#Entity
#NamedQueries({ #NamedQuery(name = "EntityDemo.findAll", query = "select o from EntityDemo o") })
#Table(name = "ENTITY_DEMO")
public class EntityDemo implements Serializable {
private static final long serialVersionUID = -8709368847389356776L;
#Column(length = 1)
private String data;
#Id
#Column(nullable = false)
private BigDecimal id;
#OneToMany(mappedBy = "entityDemo", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private List<Comments> commentsList;

JPA/Hibernate: Update Parent column value before inserting child column value

This is my scenario. I have a Parent table Files_Info and a child table Files_Versions.
create table files_info(
id bigint primary key,
name varchar(255) not null,
description varchar(255) not null,
last_modified TIMESTAMP,
latest_version integer default 0 not null
);
create table files_versions(
id bigint primary key,
file_id bigint references files_info(id),
version integer not null,
location text not null,
created TIMESTAMP,
unique(file_id, version)
);
This is mainly to track a file and its various versions. When the user initiates a new file creation (not yet uploaded any version of the file), an entry is made to the files_info table with basic info like name, description. The latest_version will be 0 initially.
Then when the user uploads the first version, an entry is created in the files_versions table for that file_id and the version
value is set as parent's latest_version + 1. Parent's latest_version is now set to 1.
The user can also upload an initial version of the file when he/she initiates a new file creation. In that case, parent record
will be created with latest_version as 1 and also the corresponding version 1 child record.
I do not know how to design this using JPA / Hibernate.
I wrote my Entity and Repository classes and the save methods seem to work independently. But I do not know how to do the simultaneously latest_version updates.
Can this be done using JPA / Hibernate? Or should it be a database trigger?
A trigger is a valid option, but It can be done using JPA/Hibernate.
I'll suggest to use #PrePersist annotation on some method defined at the files_versions entity ... This method will be called by JPA when you execute: EntityManager.persist(FileVersion); and it can be use to update entity's derivative attributes ... In your case, will be the sum of the file last_version + 1 ... Example:
#Entity
#Table(name = "files_info")
public class FileInfo {
}
#Entity
#Table(name = files_versions)
public class FileVersion {
... //some attributes
#Column(name = "version")
private int version;
#ManyToOne
#JoinColumn(name = "file_id")
private FileInfo fileInfo;
... //some getters and setters
#PrePersist
private void setupVersion() {
// fileInfo should be set before of calling persist()!
// fileInfo should increase its lastest Version before of calling persist()!
this.version = this.fileInfo.getLastVersion();
}
}

Hibernate SaveOrUpdate using GeneratedValue for primary key

I am new to Hibernate and I am trying to learn the nuances of it .
I am trying to saveOrUpdate an entity using hibernate .
My pojo class :-
#Entity
#Table(name = "TestDB")
public class TestCaseData {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private intid;
#Column(columnDefinition="TEXT")
private String location;
private String name;
/*Getters and setters*/
Main Class :-
public TestDbDao{
public static void main(String args[]){
TestCaseData d1 = new TestCaseData();
TestCaseData d2 = new TestCaseData();
TestCaseData d3 = new TestCaseData();
d1.setHash("New York");
d1.setName("Panache");
saveOrUpdate(d1);
/** d2 and d3 **/
}
}
I saveOrUpdate this entity .The first time I run the main class ,3 rows are created in the table.
When I run the same program the second time , according to my knowledge when there is saveOrUpdate, it should either save if the value is not present in the table or update if the already existing value is updated in the table.But I find same 3 rows being added to the table with different ids.
ids 1,2,3,4,5,6 are same . Why doesnt saveOrUpdate doesnt work with generateValue id ?
Can anyone please explain ?
It is expected, because you are running the program 2 times.
Each time the program run, Hibernate only see you passing in 3 new entities (without ID), therefore you are telling Hibernate to create new records in DB.
To see the effect of saveAndUpdate(), you may pass a detached instance to Hibernate in 2nd time, e.g. a TestCaseData instance with ID corresponds to an existing record in DB, but with different name. You should be able to see Hibernate updating the corresponding record in DB.

JPA Mapping Multi-Rows with ElementCollection

I'm trying to follow the JPA tutorial and using ElementCollection to record employee phone numbers:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
Short version
What I need is a class like this:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#Embedded
List<Phone> phones;
}
that stores each person's phone numbers in a collection.
Long version
I follow the tutorial code:
#Entity
#Table(name="Phones")
public class PhoneId {
#Id
#Column(name="owner_id")
long owner_id;
#ElementCollection
#CollectionTable(
name="Phones",
joinColumns=#JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type")
String type = "";
#Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
with a slight difference that I only keep one table. I tried to use the following code to add records to this table:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
but it keeps throwing exceptions
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "type" violates not-null constraint Detail: Failing
row contains (0, null, null). Error Code: 0 Call: INSERT INTO Phones
(owner_id) VALUES (?) bind => [1 parameter bound] Query:
InsertObjectQuery(tutorial.Phone1#162e295)
What did I do wrong?
Sadly, i think the slight difference that you only keep one table is the problem here.
Look at the declaration of the PhoneId class (which i would suggest is better called PhoneOwner or something like that):
#Entity
#Table(name="Phones")
public class PhoneId {
When you declare that a class is an entity mapped to a certain table, you are making a set of assertions, of which two are particularly important here. Firstly, that there is one row in the table for each instance of the entity, and vice versa. Secondly, that there is one column in the table for each scalar field of the entity, and vice versa. Both of these are at the heart of the idea of object-relational mapping.
However, in your schema, neither of these assertions hold. In the data you gave:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
There are two rows corresponding to the entity with owner_id 1, violating the first assertion. There are columns TYPE and NUMBER which are not mapped to fields in the entity, violating the second assertion.
(To be clear, there is nothing wrong with your declaration of the Phone class or the phones field - just the PhoneId entity)
As a result, when your JPA provider tries to insert an instance of PhoneId into the database, it runs into trouble. Because there are no mappings for the TYPE and NUMBER columns in PhoneId, when it generates the SQL for the insert, it does not include values for them. This is why you get the error you see - the provider writes INSERT INTO Phones (owner_id) VALUES (?), which PostgreSQL treats as INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null), which is rejected.
Even if you did manage to insert a row into this table, you would then run into trouble on retrieving an object from it. Say you asked for the instance of PhoneId with owner_id 1. The provider would write SQL amounting to select * from Phones where owner_id = 1, and it would expect that to find exactly one row, which it can map to an object. But it will find two rows!
The solution, i'm afraid, is to use two tables, one for PhoneId, and one for Phone. The table for PhoneId will be trivially simple, but it is necessary for the correct operation of the JPA machinery.
Assuming you rename PhoneId to PhoneOwner, the tables need to look like:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(I've made (owner_id, number) the primary key for Phone, on the assumption that one owner might have more than one number of a given type, but will never have one number recorded under two types. You might prefer (owner_id, type) if that better reflects your domain.)
The entities are then:
#Entity
#Table(name="PhoneOwner")
public class PhoneOwner {
#Id
#Column(name="owner_id")
long id;
#ElementCollection
#CollectionTable(name = "Phone", joinColumns = #JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
#Embeddable
class Phone {
#Column(name="type", nullable = false)
String type;
#Column(name="number", nullable = false)
String number;
}
Now, if you really don't want to introduce a table for the PhoneOwner, then you might be able to get out of it using a view. Like this:
create view PhoneOwner as select distinct owner_id from Phone;
As far as the JPA provider can tell, this is a table, and it will support the queries it needs to do to read data.
However, it won't support inserts. If you ever needed to add a phone for an owner who is not currently in the database, you would need to go round the back and insert a row directly into Phone. Not very nice.

Categories