Hibernate SaveOrUpdate using GeneratedValue for primary key - java

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.

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.

JPA - merge() duplicate record issue

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

How to use custom #Query for joining #ElementCollection column in Hibernate?

I have a stupid problem and thinking about it for 3 hours. I have #Entity class, for example:
private long id;
private Instant startTime;
private Instant lastTime;
private String name;
#ElementCollection
#Builder.Default
private Set<Integer> codes = new HashSet<>();
So, Hibernate creates new table with my main class id and codes. It is nice, works correct.
I have a problem with custom query.. I would like select given codes between given start and endTime.
It is not difficult to prepare query on my oracle db:
select id, startTime, lastTime, name from main_table mt left outer join created_table_codes cd on mt.id = cd.main_table_id
where cd.codes = 12345 and mt.startTime=‘date’ between ‘endDate’ ;
Unfortunately, in #Query it is doesnt work. Hibernate cant see table created from my #ElementCollection :(
Do you know how can I get data by #Query - using join and ma #ElementCollection table?

When the #OneToMany relation is used, SELECT returns multiple objects even if there is only one object in the database - Java, Hibernate, PostgreSQL

I have two tables in my database:
1. Warehouse
2. WarehouseItem
Relation between them are like listed below:
#Entity
#Table(name = "warehouse")
public class WarehouseModel {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="warehouse")
private List<WarehouseItemModel> _items;
and
#Entity
#Table(name = "warehouseItem")
public class WarehouseItemModel {
#ManyToOne
public WarehouseModel warehouse;
and now I want to SELECT all the objects of the entity WarehouseModel:
public List getObjects(Class pClass)
{
startTime();
connect();
Session session = getSession();
Transaction lTransaction = session.beginTransaction();
List lRet = session.createCriteria(pClass).list();
lTransaction.commit();
endTime("getObjects: " + lRet.size() + " objects");
Collections.reverse(lRet);
return lRet;
}
In my database I have:
1x object in the table: Warehouse (WarehouseModel.java)
5x objects in the table: WarehouseItem (WarehouseItemModel.java)
When I want to retrive all the Warehouses including related WarehouseItems:
databaseConnector.eDocumentConnector.getObjects(WarehouseModel.class)
the result is:
- 5x the same object of WarehouseModel
It seems that there is dependancy that I always get as much entities of the same WarehouseModel as there is WarehouseItemModels inside field WarehouseModel._items
How to fix it and why it happens? (I have more relations like this one in my project and if it happends here, maybe it happends also in the other places)
Project details:
- Java 1.8
- Hibernate 5.0.7
- database: PostgreSQL 9.5.2
It is solved by using DISTINCT in hibernate query:
session.createCriteria(pClass).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
More importantly, you should understand how a 1-to-many relationship works in SQL.
The raw select statement is going to be something like
SELECT
.
.
<what columns you want>
.
FROM
warehouse w
JOIN warehouseItem wi ON (w.id = wi.warehouse_id)
WHERE
<whatever you need to filter on>
When you join, you're always going to get the number of rows that are related to the primary table's ID. Since there are 5 rows in the child table, 5 rows are returned, where the warehouse columns are duplicated each time but the warehouseItem columns are unique to each row.

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