Reputation: 375
After a while reading and testing differents forms of load EntityManager instances and beans to make some nested transactions, finally I don't find a way where, after a Runtime exception, the data made full rollback, the must near stage to what I want was when only one entity remains persisted on the DB. The situation is the follow: I have one class entity Car and one class entity CarPart, one instance of Car could contain a list of CarPart, I tested to persist and when no runtime error is trown, all works like a charm. The problem is when throw a runtime error, like a trigger error controller by the DB, sometimes the objects are partially persisted. I'm using JPA with JavaEE and EclipseLink. I need a way to control that the object will persist completely or do rollback of every persist action. Any help will be appreciated.
Upvotes: 0
Views: 2480
Reputation: 375
Finally after some time testing I arrive to one solution that works for my scenario. I don't post all the code but only the main parts corresponding to problem solution:
Manager Class
@ManagedBean
@ApplicationScoped
@TransactionManagement(TransactionManagementType.BEAN)
public class CarsManager implements Serializable {
@Resource
private UserTransaction userTransaction;
private CarsRepositorySameEM carsRepoSameEM;
private CarsPartsRepositorySameEM carsPartsRepoSameEM;
private EntityManager entityManager;
private Cars car;
private List<CarsParts> carPartList;
@Inject
public CarsManager(@MainEM EntityManager em) {
this.entityManager = em;
this.carsRepoSameEM = new CarsRepositorySameEM(this.entityManager);
this.carsPartsRepoSameEM = new CarsPartsRepositorySameEM(this.entityManager);
}
public Cars getCar() {
return car;
}
public void setCar(Cars car) {
this.car = car;
}
public List<CarsParts> getCarPartList() {
return carPartList;
}
public void setCarPartList(List<CarsParts> carPartList) {
this.carPartList = carPartList;
}
public Boolean save() {
Boolean result = Boolean.TRUE;
try {
this.userTransaction.begin();
this.car = carsRepoSameEM.saveCar(this.car);
if(carsPartsRepoSameEM.getEntityException().getCode() != 0) {
this.userTransaction.setRollbackOnly();
}
for (int i = 0; i < carPartList.size(); i++) {
carPartList.get(i).setCarsId(car.getID());
}
this.carPartList = carsPartsRepoSameEM.saveParts(carPartList);
if(carsPartsRepoSameEM.getEntityException().getCode() != 0) {
this.userTransaction.setRollbackOnly();
}
car.setCarsPartsList(this.carPartList);
this.userTransaction.commit();
} catch (Exception e) {
//e.printStackTrace();
result = Boolean.FALSE;
try {
this.userTransaction.rollback();
} catch (Exception ex) {
}
}
return result;
}
}
Repositories classes:
@Stateless
public class CarsRepositorySameEM extends AbstractRepository<Cars, Long> {
...
public Cars saveCar(Cars car) throws SystemException {
try {
this.entityManager.joinTransaction();
car = this.create(car);
this.entityManager.flush();
} catch (Exception e) {
this.setEntityException(GenericExceptionType.EX_JPA, -1, "CarsRepositorySameEM", "saveCar()", "Error al persistir el Car", null, e.getMessage());
//throw e;
}
return car;
}
}
-
@Stateless
public class CarsPartsRepositorySameEM extends AbstractRepository<CarsParts, Long> {
...
public List<CarsParts> saveParts(List<CarsParts> carsPartsList) throws SystemException {
try {
this.entityManager.joinTransaction();
for (int i = 0; i < carsPartsList.size(); i++) {
carsPartsList.set(i, this.create(carsPartsList.get(i)));
}
this.entityManager.flush();
} catch (Exception e) {
this.setEntityException(GenericExceptionType.EX_JPA, -1, "CarsPartsRepositorySameEM", "saveParts()", "Error al persistir las CarsParts", null, e.getMessage());
//throw e;
}
return carsPartsList;
}
}
Observations: the EntityManager is injected from a Producer method that dynamically creates the EntityManagerFactory depending on configuration.
Also as the EclipseLink documentation refers for configure the ServerPlatform that will be used to enable integration with a host container, I had to put in the persistence.xml the property
<property name="eclipselink.target-server" value="Glassfish"/>
Using this property make sure that the dynamically created EntityManager will use transaction type JTA.
Thanks by the answers and hope this example can help anyone that face similar problem.
Upvotes: 0
Reputation: 12505
There is no support for nested transactions in JPA and no known way of doing it "correctly". Any partial rollback of the database data would also require a partial rollback of the object state, which is usually the only sensible thing to do after an exception is to throw your EntityManager and the associated objects away.
If you are using JavaEE and have the whole "create car with parts" wrapped in a single transaction, this behavior should work out of the box. If you are using separate transactions (as I stated before: there is no way to make them nested), then it is possible that the car is persisted and the part are not.
The obvious solution is to just use a single container-managed transaction for both.
Upvotes: 1