Hibernate persist() vs save() method - java

The hibernate document says:
persist():
persist() makes a transient instance persistent. However, it does not guarantee that the identifier value will be assigned to the
persistent instance immediately, the assignment might happen at flush
time. persist() also guarantees that it will not execute an INSERT
statement if it is called outside of transaction boundaries. This is
useful in long-running conversations with an extended
Session/persistence context.
save():
save() does guarantee to return an identifier. If an INSERT has to be executed to get the identifier ( e.g. "identity" generator, not
"sequence"), this INSERT happens immediately, no matter if you are
inside or outside of a transaction. This is problematic in a
long-running conversation with an extended Session/persistence
context.
So I am trying with a small example on how this works. I created an entity called DomesticCat:
#Entity
public class DomesticCat {
#Id
#GeneratedValue
private long id;
private String name;
}
and a small program to test this, once using save() and another time with persist():
private static void saveData() {
Session session = getSession();
DomesticCat cat = new DomesticCat();
cat.setName("My Cat");
//session.save(cat);
session.persist(cat);
}
For this program, hibernate generated same queries for save & persist, in this case it is:
select hibernate_sequence.nextval from dual
Now I added an extra line to my code saying:
session.flush();
Now hibernate generated insert query for both cases i.e save & persist:
insert into CAT (name, id) values (?, ?)
Also when I do session.flush(), the id is getting assigned to my cat object when I use save() and also for persist()
Finally, when I use the transaction then the data is stored in the DB table.
So using this example I can see only single difference between persist vs save, that is save returns the identifier where as persist will not.
Then what exactly the document says, can someone please help me with some examples?
Update:
I am using Oracle as my database.
Now I modified my entity class Id generation strategy to increment as follows:
#Id
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy = "increment")
private long id;
But even then I can see that calling session.persist() is hitting the DB to get the Id value. Here is my program and its output:
private static void saveData() {
Session session = getSession();
DomesticCat cat = new DomesticCat();
cat.setName("My Cat");
System.out.println("before id="+cat.getId());
session.persist(cat);
System.out.println("after id="+cat.getId());
session.flush();
System.out.println("after flush id="+cat.getId());
}
Output:
before id=0
Hibernate: select max(id) from CAT
after id=1
Hibernate: insert into CAT (name, id) values(?, ?)
after flush id=1
As per the output hibernate is hitting the DB to get the ID before I call session.flush() and the case is same for session.save() also. So there is no difference in output if I use Id generation strategy to increment.

All the information is in the documentation. save() flushes the entity to the database when you make the call. persist() actually just marks the entity to be persisted in the upcoming flush. There is a difference and with persist you have more control on when the actual write to the database takes place.

It's a simple logic problem.
it does not guarantee that the identifier value will be assigned to the persistent instance immediately
is not the same thing as
it guarantees that the identifier value will not be assigned to the persistent instance immediately
The ID generation strategy used for your database is to use a sequence. In that case, Hibernate asks the sequence for the next ID when calling persist(). If the ID generation strategy for your database was to use an auto-increment column, the ID would only be assigned to the entity only when the entity is inserted to the database, at flush time.

Related

Hibernate : Lock a row while update, so user's don't retrieve a counter from it

I am working on a Spring-MVC project in which I am using Hibernate as the ORM, PostgreSQL as our DB and in one of our Objects(GroupCanvas), we have a number which is incremented everytime when user takes some action, and then the GroupCanvas object is updated in DB, and it should be unique.
THe problem we have currently is, if multiple users take action in front-end, some of them are getting duplicate numbers. We are working on fixing this now, so later we can implement a sequence and are assured that the numbers are unique.
How can I ensure that when I am updating the row, other users are waiting till the row is updated. I tried LockMode.Pessimistic_write, and a few others, none helped.
Code :
#Override
public void incrementNoteCounterForGroupCanvas(int canvasId) {
Session session = this.sessionFactory.getCurrentSession();
session.flush();
Query query = session.createQuery("update GroupCanvas as gc set gc.noteCount=gc.noteCount+1 where gc.mcanvasid=:canvasId");
query.setParameter("canvasId",canvasId);
query.executeUpdate();
session.flush();
}
#Override
public GroupCanvas getCanvasById(int mcanvasid) {
Session session = this.sessionFactory.getCurrentSession();
session.flush();
return (GroupCanvas) session.get(GroupCanvas.class, mcanvasid,LockMode.PESSIMISTIC_WRITE);
}
Both methods are in DAO, which has #Transactional annotation, and annotation present in service layer as well.
Thank you.
Looking at the method you have posted the usage if the 'LOCKING' technique is not quite correct. In order for a lock to end up with the result you are looking for the sequence of actions should be similar to the ones below (in the nutshell it is similar to the Double-Checked Locking but implemented using DB locks - https://en.wikipedia.org/wiki/Double-checked_locking).
Start the transaction (eg #Transactional annotation on your service method)
Retrieve entity from database with the PESSIMISTIC_WRITE lock mode (make sure to indicate hibernate that fresh copy should be read instead of the one stored in session cache)
If required check the current value of the target field if it meets your invariants
Perform the change/update on the field (eg, increment the value of a field )
Save the entity (and make sure to flush the value to the DB if you do not want to wait for the auto-flush)
Commit the transaction (done automatically when using #Transactional)
The essential difference of this sequence when compared with the posted method is that the update of the property value is performed while your transaction holds a lock on the target entity/db row, hence preventing other transactions from reading it while your update is in progress.
Hope this helps .
UPDATE:
I believe something like the code snippet bellow should work as expected :
#Transactional
#Override
public void incrementNoteCounterForGroupCanvas(int canvasId) {
final Session session = this.sessionFactory.getCurrentSession();
final GroupCanvas groupCanvas = session.get(GroupCanvas.class, canvasId,LockMode.PESSIMISTIC_WRITE);
session.refresh(groupCanvas);
groupCanvas.setNoteCount(groupCanvas.getNoteCount()+1);
session.saveOrUpdate(groupCanvas);
session.flush();
}

Why is Hibernate Save updating the records?

In hibernate, session.save() is supposed to save the records.It generates "insert" queries. However, I have written below simple program to check this. I am observing the save() can also be used to update the records. It is generating "update" query. Isn't save() doing the same job as saveorupdate() in the below program ?
SessionFactory sf = conf.buildSessionFactory();
Session session = sf.openSession();
Transaction trans = session.beginTransaction();
Vehicle veh = new Vehicle();
veh.setId(1);
veh.setModel("Veh_mod");
veh.setName("Veh_Name");
Serializable obj = session.save(veh);
veh.setModel("Veh_mod_change");
obj = session.save(veh);
session.flush();
trans.commit();
session.close();
------------------------- in the console--------------------------------
Hibernate:
/* insert com.anvesh.test.Vehicle
*/ insert
into
VEHICLE
(NAME, MODEL, ID)
values
(?, ?, ?)
Hibernate:
/* update
com.anvesh.test.Vehicle */ update
VEHICLE
set
NAME=?,
MODEL=?
where
ID=?
After your first call to save(), object veh becomes an attached object (aka. persistent object state). Subsequently mutating that object with setModel() and committing the transaction would cause hibernate to fire an update even without calling save() a second time.
Here's an example for reference: http://www.dineshonjava.com/p/transient-persistent-and-detached.html#.VEfGCme8G7E
Or perhaps a short video tutorial: http://javabrains.koushik.org/tutorials/hibernate_run/Hibernate-Tutorial-22---Transient,-Persistent-and-Detached-Objects.html
save() can do an update, if id is set on the object it saves. Check out this thread for differences between various saving methods. To quote from an accepted answer
save Persists an entity. Will assign an identifier if one doesn't exist. If one does, it's essentially doing an update. Returns the generated ID of the entity.
When you first called session.save(veh), your object becomes associated with the session.
Hibernate will then know that it needs to use an "UPDATE" query when you save the object again.
Try:
Vehicle veh1 = new Vehicle();
veh1.setId(1);
veh1.setModel("Veh_mod");
veh1.setName("Veh_Name");
Vehicle veh2 = new Vehicle();
veh2.setId(1);
veh2.setModel("Veh_mod");
veh2.setName("Veh_Name");
session1.save(veh1);
session2.save(veh2); // try changing this to session2.saveOrUpdate()
then you'll see the difference between session.save() and session.saveOrUpdate()

batch saveORupdate using hibernate

I have a batch operation where in i have to either insert or update a record.I want to inser larger number of records so i need to commit batch after batch
1)Insert if new
2)Update if existing.
I can typically do it using
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.saveOrUpdat(customer);
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();
The problem is hibernate generates a select before each saveOrUpdate which seems to be a issue.
The primarykey of object is always populated before passing to hibernate.As it primaryKey is never generated by hibernate using sequencer or anything else.
How can i avoid this exta select for each saveOrupdate?
I dnt want to use stored procedure.
Following are the steps that it takes for Hibernate to decide whether to update or insert a record in to database.
saveOrUpdate() does the following:
if the object is already persistent in this session, do nothing
if another object associated with the session has the same identifier, throw an exception
if the object has no identifier property, save() it
if the object's identifier has the value assigned to a newly instantiated object, save() it
if the object is versioned by a <version> or <timestamp>, and the version property value is the same value assigned to a newly instantiated object, save() it
otherwise update() the object.
If in any case there is a conflict and hibernate is not able to decide on what operation to perform it does a select.
Coming to your question, try giving an hint to Hibernate like using the fields like timestamp or version
Credits - Jboss HIbernate Docs, StackOverFlow

How do you update a foreign key value directly via Hibernate?

I have a couple of objects that are mapped to tables in a database using Hibernate, BatchTransaction and Transaction. BatchTransaction's table (batch_transactions) has a foreign key reference to transactions, named transaction_id.
In the past I have used a batch runner that used internal calls to run the batch transactions and complete the reference from BatchTransaction to Transaction once the transaction is complete. After a Transaction has been inserted, I just call batchTransaction.setTransaction(txn), so I have a #ManyToOne mapping from BatchTransaction to Transaction.
I am changing the batch runner so that it executes its transactions through a Web service. The ID of the newly inserted Transaction will be returned by the service and I'll want to update transaction_id in BatchTransaction directly (rather than using the setter for the Transaction field on BatchTransaction, which would require me to load the newly inserted item unnecessarily).
It seems like the most logical way to do it is to use SQL rather than Hibernate, but I was wondering if there's a more elegant approach. Any ideas?
Here's the basic mapping.
BatchQuery.java
#Entity
#Table(name = "batch_queries")
public class BatchQuery
{
#ManyToOne
#JoinColumn(name = "query_id")
public Query getQuery()
{
return mQuery;
}
}
Query.java
#Entity
#Table(name = "queries")
public class Query
{
}
The idea is to update the query_id column in batch_queries without setting the "query" property on a BatchQuery object.
Using a direct SQL update, or an HQL update, is certainly feasible.
Not seeing the full problem, it looks to me like you might be making a modification to your domain that's worth documenting in your domain. You may be moving to having a BatchTransaction that has as a member just the TransactionId and not the full transaction.
If in other activities, the BatchTransaction will still be needing to hydrate that Transaction, I'd consider adding a separate mapping for the TransactionId, and having that be the managing mapping (make the Transaction association update and insert false).
If BatchTransaction will no longer be concerned with the full Transaction, just remove that association after adding a the TransactionId field.
As you have writeen, we can use SQL to achieve solution for above problem. But i will suggest not to update the primary keys via SQL.
Now, as you are changing the key, which means you are creating alltogether a new object, for this, you can first delete the existing object, with the previous key, and then try to insert a new object with the updated key(in your case transaction_id)

Why JPA persist() does not generated auto-increment primary ID?

I'm using JPA toplink-essential and SQL Server 2008
My goal is to get auto-increment primary key value of the data that is going to be inserted into the table. I know in JDBC, there is getInsertedId() like method that give you the id of auto-increment primary id (but that's after the insert statement executed though)
In JPA, I found out #GenratedValue annotation can do the trick.
#Entity
#Table(name = "tableOne")
public class TableOne implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "tableId")
private Integer tableId;
Now if I run the code below it should give me the auto incremented id but it returns NULL...
EntityManager em = EmProvider.getInstance().getEntityManagerFactory().createEntityManager();
EntityTransaction txn = em.getTransaction();
txn.begin();
TableOne parent = new TableOne();
em.persist(parent); //here I assume that id is pre-generated for me.
System.out.println(parent.getTableId()); //this returns NULL :(
The problem is you are using IDENTITY id generation. IDENTITY id generation cannot do preallocation as they require the INSERT to generate the id. TABLE and SEQUENCE id generation support preallocation, and I would always recommend usage of these, and never using IDENTITY because of this issue and because of performance.
You can trigger the id to be generated when using IDENTITY id generation by calling flush().
just simply do this :
public void create(T entity) {
getEntityManager().persist(entity);
getEntityManager().flush();
getEntityManager().refresh(entity);
}
After refreshing the entity you have the ID field with proper value.
We are also using SQL Server 2008 and it never worked for me so I always execute separate query "SELECT ##IDENTY" to get the inserted id.
The reason I found on the net was that auto id (IDENTITY) is managed by database and never fetched in Entity until unless you commit the row or manually retrieve the info from database.

Categories

Resources