I have got a problem with transaction rollback.
I need to first insert a user into the database, and then insert into another table some kind of log of who inserted the user.
If there is an exception when inserting this log-message, the whole transaction should be rolled back (i.e. user needs to be removed again).
The .ear is deployed on Wildfly 8.1, persistence is achieved through Hibernate with MySQL.
My class looks like this
#Stateless
#Remote(AdministratorBiznesowyService.class)
#Transactional(rollbackOn = Exception.class)
public class AdministratorBiznesowyServiceBean implements AdministratorBiznesowyService {
The method looks like this
#Override
#Transactional(rollbackOn = Exception.class)
public void insertUser(String userSessionId, User user) throws AdministratorBiznesowyException {
checkPermission(userSessionId);
try {
userBusiness.insertUser(user);
log.insertLog(
authenticationObjectBusiness.getUserIdForSessionId(userSessionId),
LogEnum.CREATE, user);
} catch (AuthenticationException e) {
ServerLogModule.logActionError(Messages
.getErrorMessage("server.authorization.noPermission"));
throw new AdministratorBiznesowyException();
} catch (Exception e1) {
ServerLogModule.logActionError(Messages
.getErrorMessage("server.exception"));
throw new AdministratorBiznesowyException();
}*/
}
The insertUser implementation in userBusiness:
#Override
public void insertUser(User user) throws AdministratorBiznesowyException {
try {
UserEntity userEntity = adminConvertUtils.convertUserToEntity(user);
userEntityFacade.create(userEntity);
} catch (Exception e) {
throw new AdministratorBiznesowyException();
}
}
And my log.insertLog currently for test-purposes just throws an exception.
throw new LogException();
and its implementation is like this:
#ApplicationException(rollback=true)
public class LogException extends RuntimeException {
AdministratorBiznesowyException:
#ApplicationException(rollback = true)
public class AdministratorBiznesowyException extends CommunicationException {
CommunicationException:
public class CommunicationException extends Exception {
So now, when I successfully call insertUser and directly after that get an exception, the new user still gets added to the database (I check through MySQL Workbench).
What else can I do to get this to rollback?
If any other code is needed, please comment, thank you in advance.
Related
i want save data and check the data after call save method
but the value is not present in same request
i have two method depend each other
the two function communcation with each other by kafka
the first method save the data and after save using jpa call second method
find the recourd from database using jpa
and check the instanse using isPresent()
but in the second method i cant find the data save
but after this request i can find data
return exciption NoSuchElement
Try out several ways like:
1-use flush and saveAndFlush
2-sleep method 10000 milsec
3-use entityManger with #Transactional
but all of them not correct
i want showing you my two method from code:
i have producer and consumer
and this is SaveOrder method (first method):
note : where in the first method have all ways i used
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void saveOrder(Long branchId,AscOrderDTO ascOrderDTO) throws Exception {
ascOrderDTO.validation();
if (ascOrderDTO.getId() == null) {
ascOrderDTO.setCreationDate(Instant.now());
ascOrderDTO.setCreatedBy(SecurityUtils.getCurrentUserLogin().get());
//add user
ascOrderDTO.setStoreId(null);
String currentUser=SecurityUtils.getCurrentUserLogin().get();
AppUser appUser=appUserRepository.findByLogin(currentUser);
ascOrderDTO.setAppUserId(appUser.getId());
}
log.debug("Request to save AscOrder : {}", ascOrderDTO);
AscOrder ascOrder = ascOrderMapper.toEntity(ascOrderDTO);
//send notify to branch
if(!branchService.orderOk())
{
throw new BadRequestAlertException("branch not accept order", "check order with branch", "branch");
}
ascOrder = ascOrderRepository.save(ascOrder);
/*
* log.debug("start sleep"); Thread.sleep(10000); log.debug("end sleep");
*/
entityManager.setFlushMode(FlushModeType.AUTO);
entityManager.flush();
entityManager.clear();
//ascOrderRepository.flush();
try {
producerOrder.addOrder(branchId,ascOrder.getId(),true);
stateMachineHandler.stateMachine(OrderEvent.EMPTY, ascOrder.getId());
stateMachineHandler.handling(ascOrder.getId());
//return ascOrderMapper.toDto(ascOrder);
}
catch (Exception e) {
// TODO: handle exception
ascOrderRepository.delete(ascOrder);
throw new BadRequestAlertException("cannot deliver order to Branch", "try agine", "Try!");
}
}
in this code go to producer :
producerOrder.addOrder(branchId,ascOrder.getId(),true);
and this is my producer:
public void addOrder(Long branchId, Long orderId, Boolean isAccept) throws Exception {
ObjectMapper obj = new ObjectMapper();
try {
Map<String, String> map = new HashMap<>();
map.put("branchId", branchId.toString());
map.put("orderId", orderId.toString());
map.put("isAccept", isAccept.toString());
kafkaTemplate.send("orderone", obj.writeValueAsString(map));
}
catch (Exception e) {
throw new Exception(e.getMessage());
}
}
and in this code go to consumer:
kafkaTemplate.send("orderone", obj.writeValueAsString(map));
this is my consumer:
#KafkaListener(topics = "orderone", groupId = "groupId")
public void processAddOrder(String mapping) throws Exception {
try {
log.debug("i am in consumer add Order");
ObjectMapper mapper = new ObjectMapper(); Map<String, String> result = mapper.readValue(mapping,
HashMap.class);
branchService.acceptOrder(Long.parseLong(result.get("branchId")),Long.parseLong(result.get("orderId")),
Boolean.parseBoolean(result.get("isAccept")));
log.debug(result.toString());
}
catch (Exception e) {
throw new Exception(e.getMessage());
}
}
**and this code go to AcceptOrder (second method) : **
branchService.acceptOrder(Long.parseLong(result.get("branchId")),Long.parseLong(result.get("orderId")),
Boolean.parseBoolean(result.get("isAccept")));
this is my second method :
public AscOrderDTO acceptOrder(Long branchId, Long orderId, boolean acceptable) throws Exception {
ascOrderRepository.flush();
try {
if (branchId == null || orderId == null || !acceptable) {
throw new BadRequestAlertException("URl invalid query", "URL", "Check your Input");
}
if (!branchRepository.findById(branchId).isPresent() || !ascOrderRepository.findById(orderId).isPresent()) {
throw new BadRequestAlertException("cannot find branch or Order", "URL", "Check your Input");
}
/*
* if (acceptable) { ascOrder.setStatus(OrderStatus.PREPARING); } else {
* ascOrder.setStatus(OrderStatus.PENDING); }
*/
Branch branch = branchRepository.findById(branchId).get();
AscOrder ascOrder = ascOrderRepository.findById(orderId).get();
ascOrder.setDiscount(50.0);
branch.addOrders(ascOrder);
branchRepository.save(branch);
log.debug("///////////////////////////////Add order sucess////////////////////////////////////////////////");
return ascOrderMapper.toDto(ascOrder);
} catch (Exception e) {
// TODO: handle exception
throw new Exception(e.getMessage());
}
}
Adding Thread.sleep() inside saveOrder makes no sense.
processAddOrder executes on a completely different thread, with a completely different persistence context. All the while, your transaction from saveOrder might still be ongoing, with none of the changes made visible to other transactions.
Try splitting saveOrder into a transactional method and sending the notification, making sure that the transaction ends before the event handling has a chance to take place.
(Note that this approach introduces at-most-once semantics. You have been warned)
I have a webapp with a controller layer, a service layer, and a data access layer.
Checkmarx complains about improper error handling when I call getSingleResult in my data access layer where methods look like this :
public FilterWorkflow getNextStatusesForAction(final Long currentStatus, final String actionRequested) {
Query query = this.getEntityManager().createQuery(GET_NEXT_STATUSES_FOR_ACTION);
query.setParameter("currentStatus_Id", currentStatus);
query.setParameter("actionRequested", actionRequested);
return (FilterWorkflow) query.getSingleResult();
}
This is called from the service layer like this :
#Override
#Transactional(value="txManager", rollbackFor = Exception.class)
public SomeFilter executeAction(SomeFilter bf, final String action requested) throws Exception {
Long currentStatusID = bf.getFilteStatus().getTableId();
FilterWorkflow fw = this.someDAO.getNextStatusesForAction(currentStatusID, actionRequested);
return this.updateFilterStatus(fw, bf, actionRequested);
}
which gets called in the controller layer :
public String execute(SomeFilter bf, final String command) {
try {
bf = this.someService.executeAction(bf, command);
} catch (Exception e) {
LOGGER.info(e.getMessage());
FacesUtil.addErrorMessage(this.msgApp.getMessage("error_message"));
return null;
}
return null;
}
I think the exception is handled, because there is nothing more to do about the exceptions that getSingleResult can throw, other than displaying an error message to the user, and logging that error.
Am I missing something ?
I getting
ERROR: cannot execute UPDATE in a read-only transaction
in the following method at the line with user.update() call:
User user;
try {
user = User.finder.where().eq("phoneNumber", PhoneHelper.normalizePhoneNumber(phoneNumber)).findUnique();
} catch (NumberFormatException e) {
final ErrorMessage errorMessage = new ErrorMessage("Invalid phone number");
errorMessage.addField("phoneNumber");
return badRequest(Json.toJson(errorMessage));
}
...
some checks
...
final String newApiKey;
try {
newApiKey = UUID.randomUUID().toString();
user.addApiKey(newApiKey);
if (!isDemoUser(phoneNumber)) user.setSecurityCode(null);
user.update();
} catch (Exception e) {
Logger.warn("Can't create session", e);
return internalServerError(Json.toJson(new ErrorMessage("Can't create api_key. Please contact service administrator")));
}
return ok(Json.toJson(new ApiKey(newApiKey)));
What could be a reason of this error? I didn't set readOnly value of the transaction to 'true' by myself in this method. Please point me at the direction to search.
User it's just an entity:
#Entity
#Table(name = "userr")
public class User extends Model {
...fields...
public static Finder<Long, User> finder = new Finder<>(User.class);
}
Version of Ebean ORM is
"org.avaje.ebeanorm" % "avaje-ebeanorm" % "6.10.4"
It was just a missing #Transactional on service layer in my case..
I use Kundera-Cassandra 3.2 and want to use the transaction management from Kundera.
My handling looks like this:
EntityManager manager = repo.getEntityManagerFactory().createEntityManager(CassandraRepository.getProperties());
try{
manager.getTransaction().begin();
this.repo.update(account1, manager); //calls the merge method of the Entitymanager
this.repo.save(account2, manager); //calls the persist method of the Entitymanager
manager.getTransaction().commit();
} catch(Exception e){
if(manager.getTransaction().isActive()){
manager.getTransaction().rollback();
}
} finally {
manager.clear();
manager.close();
}
When an error in the this.repo.save(account2, manager); occurs, the manager rollbacks the transaction, but does not do a update statement, he makes a delete statement for the merge method. The reason for this is, when calling the merge methode, kundera creates an insert statement and not an update. But how to say Kundera to make an update to rollback the transaction also with an update.
Logs:
12:42:41.185 [http-bio-8080-exec-3] INFO com.impetus.client.cassandra.CassandraClientBase - Returning delete query DELETE FROM "account" WHERE "id" = 'MCSP-000000000004'.
12:42:41.211 [http-bio-8080-exec-3] INFO com.impetus.client.cassandra.CassandraClientBase - Returning delete query DELETE FROM "account" WHERE "id" = 'MCSP-000000000005'.
EDIT (my repository):
public class CassandraRepository<T> {
#PersistenceUnit
private EntityManagerFactory entityManagerFactory;
public static Map<String, String> getProperties() {
final Map<String, String> properties = new HashMap<String, String>();
properties.put(CassandraConstants.CQL_VERSION, CassandraConstants.CQL_VERSION_3_0);
return properties;
}
public void update(T entity, EntityManager manager) throws Exception{
try {
manager.merge(entity);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public void save(T entity, EntityManager manager) throws Exception{
try {
manager.persist(entity);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
According to JPA, to update an entity you have to first bring it into managed state (by fetching it)
Example:-
PersonCassandra p = entityManager.find(PersonCassandra.class, "2");
entityManager.getTransaction().begin();
p.setMonth(Month.JAN);
entityManager.merge(p);
entityManager.persist(p3);
entityManager.getTransaction().commit();
Issue is not with INSERT and UPDATE statements since both are similar for Cassandra, under the hood.
I have web service, developed using JAX-WS. Now i wanted to throw SOAPFault with customized error codes on certain conditions.
I have a webfault:
#WebFault(name = "BankExceptionFault1_Fault", targetNamespace = NS.namespace)
public class BankException extends Exception {
private WebMethodStatus faultInfo;
public BankException(Errors error) {
this(error, error.name());
}
public WebMethodStatus getFaultInfo() {
return faultInfo;
}
public BankException(Errors error, String description) {
super(error.getErrorCode());
this.faultInfo = new WebMethodStatus(error, description);
}
}
And In some method, for a given condition, throws exception:
#Override
#WebMethod(operationName = "UpdateAccountRecord")
#WebResult(name = "Result")
#LogExecution
public WebMethodStatus updateAccountRecord(
#WebParam(name = "Request") UpdateAccountRequest request) throws BankException {
if (!Boolean.parseBoolean(specialMode)) {
throw new BankException(Errors.INVALID_RUNNING_MODE,
"Can't update account record. For updating need special running mode");
}
service.updateAccountRecord(request);
return new WebMethodSuccessStatus();
}
In spring-mvc app, I want to catch my exception:
try {
wsPort.updateAccountRecord(updateAccountRequest);
} catch (BankException e) {
throwException(e);
}
catch(RemoteAccessException e){
throwException(e);
}
But always return RemoteAccessException, if try to update account using sring-mvc app.
detailMessage:Could not access remote service at [http://localhost:8080/my-app-2.1.1-SNAPSHOT/app/MyApp]
cause: java.lang.IllegalStateException: Current event not START_ELEMENT or END_ELEMENT
But if I use soapui for update account, returns correct exception:
BNK00017
Can't update account record. For updating need special running mode
If wsPort is something like an injected JaxWsPortProxyFactoryBean, then it's likely that your exception is being wrapped by RemoteAccessException. Try using RemoteAccessException.getCause() and see what you get...