JPA merge: Not sure what of these two methods below is better. Am getting data loss - jpa

I'm not sure if I should be setting the object to the result of the merge and then return that, or just do a merge. I'm using the technique in the first block below but I am sometimes losing data and I don't know why.
#Override
public T save(T object) {
EntityManager em = null;
EntityTransaction tx = null;
try {
em = getEntityManager();
tx = em.getTransaction();
tx.begin();
object = em.merge(object);
tx.commit();
return object;
} catch (RuntimeException e) {
log.severe(e.getMessage());
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
if (em != null && em.isOpen()) {
em.close();
}
}
}
Or should I do a merge this way?
#Override
public void save(T object) {
EntityManager em = null;
EntityTransaction tx = null;
try {
em = getEntityManager();
tx = em.getTransaction();
tx.begin();
object = em.merge(object);
tx.commit();
} catch (RuntimeException e) {
log.severe(e.getMessage());
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
if (em != null && em.isOpen()) {
em.close();
}
}
}
After the top method is used the object that was passed in may get modified and then another save is done. This object has one to one and one to many relationships with other entities. Sometimes the data in one of the one-to-many entities get lost or isn't saved. But I can't reliably reproduce the problem.

Do you really need to merge at all, merge is normally used when serializing objects, if you don't need to serialize objects, you may not need to merge. If you query the objects from the same EntityManager, you just need to change them and then commit.

Related

roll back all inserts if an exception occured

i am trying to persist multiple entities to database. but i need to roll back all inserts if one of them faces an exception. how can i do that?
here is what i did:
public class RoleCreationApplyService extends AbstractEntityProxy implements EntityProxy {
#Inject
#Override
public void setEntityManager(EntityManager em) {
super.entityManager = em;
}
#Resource
UserTransaction utx;
public Object acceptAppliedRole(String applyId, Role parentRole, SecurityContext securityContext) throws Exception {
utx.begin();
try {
FilterWrapper filter = FilterWrapper.createWrapperWithFilter("id", Filter.Operator._EQUAL, applyId);
RoleCreationApply roleCreationApply = (RoleCreationApply) getByFilter(RoleCreationApply.class, filter);
Role appliedRole = new Role();
appliedRole.setRoleUniqueName(roleCreationApply.getRoleName());
appliedRole.setRoleName(roleCreationApply.getRoleName());
appliedRole.setRoleDescription(roleCreationApply.getRoleDescription());
appliedRole.setRoleDisplayName(roleCreationApply.getRoleDisplayName());
appliedRole.setCreationTime(new Date());
appliedRole.setCreatedBy(securityContext.getUserPrincipal().getName());
Role childRole = (Role) save(appliedRole);
parentRole.setCreationTime(new Date());
parentRole.setCreatedBy(securityContext.getUserPrincipal().getName());
parentRole = (Role) save(parentRole);
RoleRelation roleRelation = new RoleRelation();
roleRelation.setParentRole(parentRole);
roleRelation.setChildRole(childRole);
RoleRelation savedRoleRelation = (RoleRelation) save(roleRelation);
PostRoleRelation postRoleRelation = new PostRoleRelation();
postRoleRelation.setPost(roleCreationApply.getPost());
postRoleRelation.setRoleRelation(savedRoleRelation);
ir.tamin.framework.domain.Resource result = save(postRoleRelation);
utx.commit();
return result;
} catch (Exception e) {
utx.rollback();
throw new Exception(e.getMessage());
}
}
}
and this is save method in AbstractEntityProxy class:
#Override
#ProxyMethod
public Resource save(Resource clientObject) throws ProxyProcessingException {
checkRelationShips((Entity) clientObject, Method.SAVE, OneToOne.class, ManyToOne.class);
try {
entityManager.persist(clientObject);
} catch (PersistenceException e) {
throw new ResourceAlreadyExistsException(e);
}
return clientObject;
}
but when an exception occures for example Unique Constraint Violated and it goes to catch block, when trying to execute utx.rollback() it complains transaction does not exist and so some entities will persist. but i want all to roll back if one fails.
PS: i don't want to use plain JDBC. what is JPA approach?

PostgreSQL JDBC transaction isolation

I have these two operations:
public class Car {
...
public void delete() throws SQLException {
Connection c = Db.getConnection();
c.setAutoCommit(false);
if (c.getTransactionIsolation() == 0) {
c.setTransactionIsolation(c.TRANSACTION_READ_COMMITTED);
}
String sql = "DELETE FROM cars WHERE id = ?";
try(PreparedStatement s = c.prepareStatement(sql)) {
s.setInt(1, id);
s.executeUpdate();
c.commit();
}
}
}
and second one:
public class CarTransfer {
public static boolean transfer(int person_id, int car_id, int other_shop_id) throws SQLException, Exception {
Car car = FindCar.getInstance().findById(car_id);
Person person = FindPerson.getInstance().findById(person_id);
try {
if (car == null) {
throw new CallException("Car doesn't exist");
}
} catch (CallException e) {
System.out.println(e.getMessage());
}
Connection c = Db.getConnection();
c.setAutoCommit(false);
if (c.getTransactionIsolation() == 0) {
c.setTransactionIsolation(c.TRANSACTION_READ_COMMITTED);
}
String sql = "";
try {
sql = "UPDATE car_belongs_shop SET shop_id = "+other_shop_id+" WHERE car_id = "+car.getId();
} catch (NullPointerException e) {
System.out.println("Did not find a car / shop");
return false;
}
try(PreparedStatement s = c.prepareStatement(sql)) {
s.executeUpdate();
try {
if (person.getCredit() < 100) {
c.rollback();
throw new CallException("Not enough credit");
}
else {
if (car == null) {
c.rollback();
throw new CallException("Car doesn't exist");
}
else {
c.commit();
person.buy(100);
}
}
} catch (CallException | NullPointerException e) {
System.out.println(e.toString());
return false;
}
}
c.setAutoCommit(true);
return true;
}
}
So what I want to do is to transfer a car from one shop to another. But in that time, some other transaction can be done on the other side, when someone removes that car from database (that's first method delete()). What I want to do is to block any delete() method in the time when transfer is running. I'm trying to do that by this code, and it's transaction isolation (in level read committed). However, this does not work as intended, because it's still possible to remove a car whilst transfer method is running. Can you help me, whether I use sufficient isolation level or have whole transactions in the right place of the code?

EF6 using transaction in inner methods

I've been seen many posts about using ef6 transactions but all the SaveChanges() are in the same block.
What I want is to use a transaction and call multiple functions inside a block, each one having SaveChanges() but belonging to the main transaction block.
I already tried code like the following:
using(var transaction = context.Database.BeginTransaction())
{
try
{
doSomething(); //Has SaveChanges() and also sub functions with also SaveChanges()
doSomethingElse(); //Same as before
}
catch (Exception exp)
{
transaction.Rollback();
}
transaction.Commit();
}
What happens is that transaction.Rollback() does nothing at all.
I assume that the inner functions have their own transaction scope and don't care about this one. So how can I put this to work?
I did a quick check in LinqPad:
void Main()
{
using (var transaction = Database.BeginTransaction())
{
var z = z_pdd_log.First(p => p.id == 100001);
Console.WriteLine(z.result);
z.result = "TEST";
this.SaveChanges();
Console.WriteLine(z.result);
transaction.Rollback();
DetachAll();
z = z_pdd_log.First(p => p.id == 100001);
Console.WriteLine(z.result);
}
}
public void DetachAll()
{
foreach (DbEntityEntry dbEntityEntry in ChangeTracker.Entries())
{
if (dbEntityEntry.Entity != null)
{
dbEntityEntry.State = System.Data.Entity.EntityState.Detached;
}
}
}
which results in:
OK
TEST
OK
The rollback works.
Maybe your doSomthing-Methods did not throw an exception so the rollback never happened. Could you please check?

JPA remove detached entity

I've just begun working with JPA and hibernate, and I have been able to insert query and update on one table, but I cannot seem to delete an entity. Even after I attach the entity with the merge function, the program insists it is detached. Here is the relevant code:
public User getUserByEmail(String email) throws DAOException{
User user = null;
EntityManager em = null; //declare here for use in finally block
try{
//get em for use
EntityManagerFactory factory = JPAUtil.getEntityManagerFactory();
em = factory.createEntityManager();
//setup return type as user
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> criteriaQuery = cb.createQuery(User.class);
Root<User> userRoot = criteriaQuery.from(User.class);
//populate the where clause
criteriaQuery.where(cb.equal(userRoot.get("email"), cb.parameter(String.class, "email")));
Query query = em.createQuery(criteriaQuery);
query.setParameter("email", email);
//actually run the query on db
user = (User) query.getSingleResult();
}catch(Exception ex){
DAOException dE = new DAOException(2, "getUserByEmail failed", ex);
//TODO log
throw dE;
}
//cleanup
finally{
if(em != null && em.isOpen()){
em.close();
}
}
return user; //if not found, will still be null
}
#Override
/*
* (non-Javadoc)
* #see dao.IUserDAO#deleteUser(java.lang.String)
*/
public void deleteUser(String email) throws DAOException {
EntityManagerFactory entityManagerFactory;
EntityManager em = null;
EntityTransaction trans = null;
try{
//search user on email
User user = getUserByEmail(email);
//check we found user with specified email
if(user != null){
//setup for interaction with database
entityManagerFactory = JPAUtil.getEntityManagerFactory();
em = entityManagerFactory.createEntityManager();
//alterations of db must occur in scope of transaction
trans = em.getTransaction();
trans.begin();
em.merge(user); //TODO update find to take transaction as parameter
em.remove(user);
trans.commit();
//explicitly flush here
em.flush();
}else{
//TODO we didn't find the user
}
}catch(Exception ex){
DAOException dE = new DAOException(6, "delete failed", ex);
//TODO log
trans.rollback();
throw dE;
}finally{
if(em != null && em.isOpen()){
em.close();
}
}
}
And here is the output from the eclipse console:
"Removing a deattached instance of User#1".
Could someone explain why this bean is still deattached after I explicitly called merge? Also, how would I fix this problem? Thanks.
The content of the specified detached entity object is copied into an
existing managed entity object with the same identity (i.e. same type
and primary key). If the EntityManager does not manage such an entity
object yet a new managed entity object is constructed. The detached
object itself, however, remains unchanged and detached.
Even after merge the detached object itself remains detached, merge will return the new created merged entity
user = em.merge(user); //TODO update find to take transaction as parameter
em.remove(user);

playframework1.2.3 save data into database without transaction

I cannot save my entity data into database without transaction.
I know PersistenceContextType.Extend, But I cannot success.
#NoTransaction
public class Application extends Controller {
public static void create(String body) {
// EntityTransaction tm = JPA.em().getTransaction();
if (!JPA.isEnabled()) {
System.out.println("JPA is not initialized");
}
EntityManager manager = JPA.entityManagerFactory.createEntityManager();
//manager.setFlushMode(FlushModeType.COMMIT);
manager.setProperty("org.hibernate.readOnly", false);
//new Customer("001").save();
if (!JPA.isInsideTransaction()) {
// manager.getTransaction().begin();
}
createContext(manager, false);
new Customer("001").save();
//manager.getTransaction().commit();
/*
* if (tm.equals(null)) { System.out.println("success"); }
*/
}
static void createContext(EntityManager entityManager, boolean readonly) {
if (JPA.local.get() != null) {
try {
JPA.local.get().entityManager.close();
} catch (Exception e) {
// Let's it fail
}
JPA.local.remove();
}
JPA context = new JPA();
context.entityManager = entityManager;
// context.readonly = readonly;
JPA.local.set(context);
}
}
I initialed the JPA by myself to prevent play from starting a transaction.
I want to save my data into database, but I get a TransactionRequiredException error.
I known that JPA operation need a transaction, but I want to know whether has a exception.
I am not really sure what you are trying to achieve here. It is best to let Play handle transactions. You will not be able to commit your changes without a transaction.
If you need more control as to when the transaction is commited you could use a utility method like:
public static void commit() {
if (JPA.em().getTransaction().getRollbackOnly()) {
JPA.em().getTransaction().rollback();
} else {
JPA.em().getTransaction().commit();
}
JPA.em().getTransaction().begin();
JPA.em().flush();
JPA.em().clear();
}