I'm wondering if the way I use to save my entities with OpenJpa 2 is correct.
I receive object from a rest service that I deserialize, then to save this instance :
Begin the transaction
Retrieve the instance from the DB (even though I've already got this instance)
Copy the attributes from the detached instance to the instance retrieved from the DB
Call the merge
Commit the transaction
In case of a complex entity model with compsitions, it becomes painful!
public boolean save(Collection<Event> events) throws DataException {
if (events == null) {
throw new DataException("Cannot save null events");
}
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
for (Event event : events) {
boolean add = true;
if(event.getKey() > 0){
Event previousEvent = em.find(Event.class, event.getKey());
if (previousEvent != null) {
//update
previousEvent.setTitle(event.getTitle());
previousEvent.setDate(event.getDate());
previousEvent.setDescription(event.getDescription());
List<Member> participants = new ArrayList<>();
for(Member m : event.getParticipants()){
Member participant = em.find(Member.class, m.getKey());
if(participant != null){
participants.add(participant);
}
}
previousEvent.setParticipants(participants);
List<Member> registrants = new ArrayList<>();
for(Member m : event.getRegistrants()){
Member registrant = em.find(Member.class, m.getKey());
if(registrant != null){
participants.add(registrant);
}
}
previousEvent.setRegistrants(registrants);
em.merge(previousEvent);
add = false;
}
}
if(add) {
//add
em.persist(event);
}
}
em.getTransaction().commit();
} catch (PersistenceException pe) {
pe.printStackTrace();
throw new DataException("An error occured while saving the event", pe);
} finally {
em.close();
}
return true;
}
Any suggestion?
THanks
Per Chris' comment - you should be able to just call em.merge(event) and let JPA handle all of it. It should automatically handle creating a new Event row if needed, updating the Event attributes, and updating or creating the contained objects as well.
Related
I have 5 tables data those needs to be saved at a same time into database.My code snippet is as below.
public boolean addStudentDetail(RegisterLoginDetail registerLoginDetail,
StudentRegisterBasicDetail studentRegisterBasicDetail, StudentBoardDetail studentBoardDetail,
StudentSchoolDetail studentSchoolDetail, StudentAdditionalDetail studentAdditionalDetail,
StepCompletionMatrix stepCompletionMatrix) {
boolean isSuceess = true;
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
session.saveOrUpdate(registerLoginDetail);
session.saveOrUpdate(studentRegisterBasicDetail);
session.saveOrUpdate(studentBoardDetail);
session.saveOrUpdate(studentSchoolDetail);
session.saveOrUpdate(studentAdditionalDetail);
session.saveOrUpdate(stepCompletionMatrix);
transaction.commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
isSuceess = false;
} finally {
if (session != null && session.isOpen()) {
session.close();
}
}
return isSuceess;
}
But for my two transaction data entry for insert operation not found in StudentRegisterBasicDetail table and all other table contains entry for one common id shared between each table.My webapplication is in pilot testing mode and concurrent users are making entry through form.So I am not able to figure out for which reason my entry being skipped in that table.There is no exception log for that table entry and if exception occurs then rollback for all table should be happen or not?
Please help me...
I want to use construction
import org.hibernate.Session;
...
try (Session session){
}
How can I do that?
Because "The resource type Session does not implement java.lang.AutoCloseable"
I know, that I need to extend Session and override AutoCloseable method, but when I've try to do that, there is error "The type Session cannot be the superclass of SessionDAO; a superclass must be a class"
Update
I've wrote my own DAO framework, but will be use Spring for that
First, you should use a much more solid session/transaction handling infrastructure, like Spring offers you. This way you can use the Same Session across multiple DAO calls and the transaction boundary is explicitly set by the #Transactional annotation.
If this is for a test project of yours, you can use a simple utility like this one:
protected <T> T doInTransaction(TransactionCallable<T> callable) {
T result = null;
Session session = null;
Transaction txn = null;
try {
session = sf.openSession();
txn = session.beginTransaction();
result = callable.execute(session);
txn.commit();
} catch (RuntimeException e) {
if ( txn != null && txn.isActive() ) txn.rollback();
throw e;
} finally {
if (session != null) {
session.close();
}
}
return result;
}
And you can call it like this:
final Long parentId = doInTransaction(new TransactionCallable<Long>() {
#Override
public Long execute(Session session) {
Parent parent = new Parent();
Child son = new Child("Bob");
Child daughter = new Child("Alice");
parent.addChild(son);
parent.addChild(daughter);
session.persist(parent);
session.flush();
return parent.getId();
}
});
Check this GitHub repository for more examples like this one.
First time that I ran into this error I've surrounded my tx.commit() with a if condition but am not sure why I am still receiving this error.
Struts Problem Report
Struts has detected an unhandled exception:
Messages:
Transaction not successfully started
File: org/hibernate/engine/transaction/spi/AbstractTransactionImpl.java
Line number: 200
Stacktraces
org.hibernate.TransactionException: Transaction not successfully started
org.hibernate.engine.transaction.spi.AbstractTransactionImpl.rollback(AbstractTransactionImpl.java:200)
After a product has been selected by user, in my main function I will call two functions as following.
First function to retrieve the object of selected product.
Second function to check if selected user has the product therefore it returns true if client has the product otherwise returns false;
Function 1
....
Product pro = new Product();
final Session session = HibernateUtil.getSession();
try {
final Transaction tx = session.beginTransaction();
try {
pro = (Product) session.get(Product.class, id);
if (!tx.wasCommitted()) {
tx.commit();
}
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
} finally {
HibernateUtil.closeSession();
}
.....
Function 2
.....
final Session session = HibernateUtil.getSession();
try {
final Transaction tx = session.beginTransaction();
try {
User user = (User) session.get(User.class, id);
if (!tx.wasCommitted()) {
tx.commit();
}
if(client.hasProduct(proId)){
return client.getProduct(proId);
}
return false;
} catch (Exception e) {
tx.rollback(); <<<Error is on this line
e.printStackTrace();
}
} finally {
HibernateUtil.closeSession();
}
....
Take a look at Transaction.isActive() method. You can wrap call to rollback() method with condition, checking whether transaction is still active. And the second, I'd prefer the following code:
final Session session = HibernateUtil.getSession();
try {
final Transaction tx = session.beginTransaction();
// do things
tx.commit();
} finally {
if (tx.isActive()) {
try {
tx.rollback();
} catch (Exception e) {
logger.log("Error rolling back transaction", e);
}
}
try {
session.close();
} catch (Exception e) {
logger.log("Error closing session", e);
}
}
Of course, code in the finally section better to wrap into public static method and just call it in every finally.
BTW, why are you doing something outside tranaction? I usually commit after all things get done, to achieve a better consistency and avoid LazyInitializationException.
One possibility is that the exception you are catching in the second functions is from the code after the commit(), so you end up trying to rollback a transaction that is already committed, which is not allowed.
You could try reorganizing your code to make sure that rollback is never called after commit. Maybe even something simple like reducing the scope of the inner try-catch:
final Session session = HibernateUtil.getSession();
try {
final Transaction tx = session.beginTransaction();
try {
User user = (User) session.get(User.class, id);
if (!tx.wasCommitted()) {
tx.commit();
}
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
if(client.hasProduct(proId)){
return client.getProduct(proId);
}
return false;
} finally {
HibernateUtil.closeSession();
}
The error indicates the transaction wasn't started at the time tried to roll back - and the problem may be that you are trying to wrap a get, which does not alter the db state and does not leave behind garbage that needs to be committed or rolled back. Nothing changes when you perform select *.
In addition to this, you may want to extract this transaction handling into a common method that is independent of the work being done, so you don't have to write this over and over again, that leaves your code open for bugs. Basically, it seems like you are getting DB objects but then intermingling some business logic withing the same method. Perhaps consider doing something like below:
DB Handling Function
public static <T> T getDBObject( Class<T> clazz, Serializable id )
throws SQLException
{
Session session = null;
try
{
session = HibernateUtil.getSession();
return (T)session.get( clazz, id );
}
finally
{
if ( session != null )
{
session.close();
}
}
}
Now that you can pull object of the DB (note that they will be detached, but still valid), you can then perform work on the objects. I many not have captured exactly what you need to check, but it seems like it is something like:
Example Comparison Function
public boolean doesUserHaveProduct(Serializable userId, Serializable productId)
{
try
{
User user = getDBObject(User.class, userId);
Product product = getDBObject( Product.class, productId );
return user.hasProduct( product );
}
catch (SQLException e)
{
e.printStackTrace();
return false;
}
}
I am getting a
org.hibernate.TransactionException: nested transactions not supported
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(AbstractTransactionImpl.java:152)
at org.hibernate.internal.SessionImpl.beginTransaction(SessionImpl.java:1395)
at com.mcruiseon.server.hibernate.ReadOnlyOperations.flush(ReadOnlyOperations.java:118)
Code that throws that exception. I am calling flush from a thread that runs infinite until there is data to flush.
public void flush(Object dataStore) throws DidNotSaveRequestSomeRandomError {
Transaction txD;
Session session;
session = currentSession();
// Below Line 118
txD = session.beginTransaction();
txD.begin() ;
session.saveOrUpdate(dataStore);
try {
txD.commit();
while(!txD.wasCommitted()) ;
} catch (ConstraintViolationException e) {
txD.rollback() ;
throw new DidNotSaveRequestSomeRandomError(dataStore, feedbackManager);
} catch (TransactionException e) {
txD.rollback() ;
} finally {
// session.flush();
txD = null;
session.close();
}
// mySession.clear();
}
Edit :
I am calling flush in a independent thread as datastore list contains data. From what I see its a sync operation call to flush, so ideally flush should not return until transaction is complete. I would like it that way is the least I want to expect. Since its a independent thread doing its job, all I care about it flush being a sync operation. Now my question is, is txD.commit a async operation ? Does it return before that transaction has a chance to finish. If yes, is there a way to get commit to "Wait" until the transaction completes ?
public void run() {
Object dataStore = null;
while (true) {
try {
synchronized (flushQ) {
if (flushQ.isEmpty())
flushQ.wait();
if (flushQ.isEmpty()) {
continue;
}
dataStore = flushQ.removeFirst();
if (dataStore == null) {
continue;
}
}
try {
flush(dataStore);
} catch (DidNotSaveRequestSomeRandomError e) {
e.printStackTrace();
log.fatal(e);
}
} catch (HibernateException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Edit 2 : Added while(!txD.wasCommitted()) ; (in code above), still I get that freaking nested transactions not supported. Infact due to this exception a record is not being written to by table too. Is there something to do with the type of table ? I have INNODB for all my tables?
Finally got the nested transaction not supported error fixed. Changes made to code are
if (session.getTransaction() != null
&& session.getTransaction().isActive()) {
txD = session.getTransaction();
} else {
txD = session.beginTransaction();
}
//txD = session.beginTransaction();
// txD.begin() ;
session.saveOrUpdate(dataStore);
try {
txD.commit();
while (!txD.wasCommitted())
;
}
Credits of above code also to Venkat. I did not find HbTransaction, so just used getTransaction and beginTransaction. It worked.
I also made changes in the hibernate properties due to advice on here. I added these lines to the hibernate.properties. This alone did not solve the issue. But I am leaving it there.
hsqldb.write_delay_millis=0
shutdown=true
You probably already began a transaction before calling this method.
Either this should be part of the enclosing transaction, and you should thus not start another one; or it shouldn't be part of the enclosing transaction, and you should thus open a new session and a new transaction rather than using the current session.
I have a DAO below, with a transactional delete per entity and in batch.
Deleting one entity at a time works just fine.
Batch delete has NO effect whatsoever :
the code below is simple and straightforward IMO, but the call to deleteMyObjects(Long[] ids) - which calls delete(Iterable keysOrEntities) of Objectify - has no effect !
public class MyObjectDao {
private ObjectifyOpts transactional = new ObjectifyOpts().setBeginTransaction(true);
private ObjectifyOpts nonTransactional = new ObjectifyOpts().setBeginTransaction(false);
private String namespace = null;
public MyObjectDao(String namespace) {
Preconditions.checkNotNull(namespace, "Namespace cannot be NULL");
this.namespace = namespace;
}
/**
* set namespace and get a non-transactional instance of Objectify
*
* #return
*/
protected Objectify nontxn() {
NamespaceManager.set(namespace);
return ObjectifyService.factory().begin(nonTransactional);
}
/**
* set namespace and get a transactional instance of Objectify
*
* #return
*/
protected Objectify txn() {
NamespaceManager.set(namespace);
Objectify txn = ObjectifyService.factory().begin(transactional);
log.log(Level.FINE, "transaction <" + txn.getTxn().getId() + "> started");
return txn;
}
protected void commit(Objectify txn) {
if (txn != null && txn.getTxn().isActive()) {
txn.getTxn().commit();
log.log(Level.FINE, "transaction <" + txn.getTxn().getId() + "> committed");
} else {
log.log(Level.WARNING, "commit NULL transaction");
}
}
protected void rollbackIfNeeded(Objectify txn) {
if (txn != null && txn.getTxn() != null && txn.getTxn().isActive()) {
log.log(Level.WARNING, "transaction <" + txn.getTxn().getId() + "> rolling back");
txn.getTxn().rollback();
} else if (txn == null || txn.getTxn() == null) {
log.log(Level.WARNING, "finalizing NULL transaction, not rolling back");
} else if (!txn.getTxn().isActive()) {
log.log(Level.FINEST, "transaction <" + txn.getTxn().getId() + "> NOT rolling back");
}
}
public void deleteMyObject(Long id) {
Objectify txn = null;
try {
txn = txn();
txn.delete(new Key<MyObject>(MyObject.class, id));
commit(txn);
} finally {
rollbackIfNeeded(txn);
}
}
public void deleteMyObjects(Long[] ids) {
Objectify txn = null;
List<Key<? extends MyObject>> keys = new ArrayList<Key<? extends MyObject>>();
for (long id : ids) {
keys.add(new Key<MyObject>(MyObject.class, id));
}
try {
txn = txn();
txn.delete(keys);
commit(txn);
} finally {
rollbackIfNeeded(txn);
}
}
}
When I call deleteMyObjects(Long[] ), I see nothing suspicious in the logs below. The transaction commits just fine without errors. But the data is not effected. Looping through the same list of Ids and deleting the objects one at a time, works just fine.
Feb 29, 2012 8:37:42 AM com.test.MyObjectDao txn
FINE: transaction <6> started
Feb 29, 2012 8:37:42 AM com.test.MyObjectDao commit
FINE: transaction <6> committed
Feb 29, 2012 8:37:42 AM com.test.MyObjectDao rollbackIfNeeded
FINEST: transaction <6> NOT rolling back
But the data is unchanged and present in the datastore !?!?!
Any help welcome.
UPDATE
Stepping into the Objectify code, I wonder wether this has something to do with the namespace ? Right here in the objectify code :
#Override
public Result<Void> delete(Iterable<?> keysOrEntities)
{
// We have to be careful here, objs could contain raw Keys or Keys or entity objects or both!
List<com.google.appengine.api.datastore.Key> keys = new ArrayList<com.google.appengine.api.datastore.Key>();
for (Object obj: keysOrEntities)
keys.add(this.factory.getRawKey(obj));
return new ResultAdapter<Void>(this.ads.delete(this.txn, keys));
}
When I inspect this.factory.getRawKey(obj) in debug, I notice that the namespace of the key is empty. NamespaceManager.get() however returns the correct namespace !?
Namespace was not set when creating the keys.
The namespace must be set BEFORE creating a key !
So rewriting it like this, fixed my problem :
public void deleteMyObjects(Long[] ids) {
Objectify txn = null;
try {
txn = txn();
List<Key<MyObject>> keys = new ArrayList<Key<MyObject>>();
for (long id : ids) {
keys.add(new Key<MyObject>(MyObject.class, id));
}
txn.delete(keys);
commit(txn);
} finally {
rollbackIfNeeded(txn);
}
}
Then I call this :
new MyObjectDAO("somenamespace").delete({ 1L, 34L, 116L });