i have an issue with using #Transactional(propagation = Propagation.REQUIRES_NEW) during the processor step in the batch chunk processing.
What I try to achieve is to persist custom data during the processor step:
public class AbstractImportRecordProcessor implements InitializingBean, ImportContextAware,
ItemProcessor<ImportRecord, BatchReportBasket> {
#Autowired
private TransactionBasketService transactionBasketService;
#Override
public final BatchReportBasket process(final ImportRecord item) {
...
transactionBasketService.save(transactionBasket);
...
}
}
And the service impl:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(final TransactionBasket transactionBasket) {
}
If i don't add the #Transactional, the data is not persisted on exceptions because of the rollback.
If I use the #Transactional the exception handling of the base processing seems to be broken. If an exception occurs then, it will be not processed as before.
So somehow, the #Transactional has an effect on the chunk transaction processing and the exception handling. From my point of view, this should not happen.
Any ideas why this happens?
EDIT: more info on why we need this
We process import records and send them to the writer. In our process we get 90 % the same "data" every day. Therefore, we need to save which data we already processed during the last run. If we know in the processor that we already processed the data, we can just skip it and do not send it to the writer. in order to do this, we want to save the primary key of the data into a dedicated database table. This information should be persistent regardless of any exceptions in the writer.
If we would put that into a listener, we are already done with the processing and could not do such a thing.
Thanks in advance for your input.
Related
I need to send a request to other microService once the object got created in the database. I only send the object id so other microService needs to call the db again for the info with bunch of other stuff.
But, when the other microService try to lookup for the record using the received id it cannot find the saved record in the database.
I tried debug seems like record does not persist even though #postPersist got called.
It will be saved after #PostPersist got executed.
Has anyone could give a workaround for this. I really need to query the database again as this is a custom requirement. I use mysql and spring boot
public class EmployeeListener {
#PostPersist
public void sendData(Employee employee){
Long id = employee.getEmployeeId();
RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange("http://localhost:8081/service/employee"+id, HttpMethod.POST, null, String.class);
}
}
#Entity
#EntityListeners(EmployeeListener.class)
public class Employee {
//
}
The problem is that JPA lifecycle events happen in the same transaction as your save operation, but the lookup, since it happens with a different server must only happen after your transaction is closed.
I therefore recommend the following setup: Gather the ids that need informing in a Collection and then when the transaction is completed send the data.
If you want to have the send operation and save operation in one method, the [TransactionTemplate][1] might be nicer to use than transaction management by annotation.
You also might consider Domain Events. Note that they trigger only when save is actually called. The benefit of these events is that they get published using a ApplicationEventPublisher for which listeners are Spring Beans so you may inject whatever bean you find helpful. They still need a way to break out of the transaction as described above
#PostPersist annotated method is called within the same transaction and the default flash mode is AUTO, that's why you don't see the record in the database. You need to force a flush:
#Component
public class EmployeeListener {
#PersistenceContext
private EntityManager entityManager;
#PostPersist
public void sendData(Employee employee){
// Send it to database
entityManager.flush();
Long id = employee.getEmployeeId();
RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange("http://localhost:8081/service/employee"+id, HttpMethod.POST, null, String.class);
}
}
Notice that EmployeeListener needs to be a Spring managed bean.
Using Spring MVC, assume I have implemented a controller that handles a POST request, performs a database operation inside a transaction, and returns a result in the response body.
Here is the controller and service layer:
#RestController
#RequiredArgsConstructor
public class SomeController {
private final SomeService someService;
#PostMapping("/something")
public SomeResult postSomething(Something something) {
return someService.handle(something);
}
}
#Service
#RequiredArgsConstructor
public class SomeService {
private final SomeRepository someRepository;
#Transactional
public SomeResult handle(Something something){
// changes to the database
}
}
Questions:
Assuming someone pulls the network cable right after the service call, so the transaction is comitted.
1) Will Spring throw an exception if the response cannot be delivered?
2) Is it possible to rollback the transaction if the response cannot be delivered?
3) How can I make sure the database stays consistent when the client retries? (the POST is not idempotent).
Thanks!
I'll try to answer you questions:
1) Maybe. It depends on size of answer and exact moment when connection is lost.
Exception will be thrown if when spring try to write response to socket OS detect that TCP/IP connection is closed. TCP protocol do not contain internal procedure of detection such situation so OS use heuristics like timeouts.
So I see only one option here. Spring try to write response to socket, but response too big to fit in buffer. In this situation write operation will be blocked. Then after some time it will be interrupted because of timeout and exception is raised.
I'm not 100% sure that my answer accurate, so you better check it yourself.
I suggest you not rely on this mechanics because it depend on many different factors like buffer size, timeout and Spring implementation.
2) With Spring answer will be NO.
3) It is up to you. There is no universal answer. For example Hibernate can be configured to use versioning on your objects.
I am trying to find a solution to build reliability into our webapp. The plan is to dump sql along with data if network connectivity/database connection is lost. In current implementation we have Rest controller, Service, DAO. The DAO throws PersistenceExcetpion, and that is propagated till the Controller layer.
Example code:
public MyDAOClass {
public void save(Object object) {
try {
entityManager.persist(object);
} catch (PersistenceException e) {
throw new DBException("Error occurred in save", e);
}
}
}
The DBException is a runtime exception.
Now the comes the actual question. One of the teammate suggested to have custom exceptions like for eg. InsertException, UpdateException etc. And if we encounter any of these exceptions we know which operation was performed on that entity so that it can be saved to a file as appropriate sql.
For example. Lets say the code failed to save Employee entity. This will throw InsertException, and will create an entry in file as insert sql statement for that entity. insert into employeee values ('firstname','lastname');
For me the idea of implementing the creation of sql file when connectivity is lost doest not seem to be as simple as implementing the above.
The questions that I have put forward are
1) How do you handle when multiple actions (like any combination of insert, update, delete) are performed in the service method ?
2) What about different exceptions ? I mean the reason for PerisistenceException can be anything like constraint failure, entity not found etc and not just the connection issue.
Is there any way to implement the above scenario which also considers all the different conditions.
Thanks.
Update:
Based on comments by chrylis. I should have already added this to the question. It's a webapp running locally in different retail stores. And the application can't have a downtime, so if any connectivity issues, the app should keep work. The file will be later synched with the central database server.
With spring you have Hibernate ORM that will store the data to the database. If an exception occurs during any request it will be rolled back by hibernate. This depends on where you'we put the #Transnational annotation.
We use a Service layer that handles the transaction. So if a database operation or any other operation fails in the service layer and throws an exception the transaction is auto rolled back by hibernate. We then use a spring exception resolver to handle any exception and write custom errors in the log and to the user. I guess you could store the exception in another database as well if that is interesting I think logging them should suffice though.
This article teaches you more about general exception handling.
Here is our exception resolver.
import ...
#ControllerAdvice
public class SpringExceptionResolver {
Logger logger = LoggerFactory.getLogger("com.realitylabs.event.controller.RecoverController");
#ExceptionHandler({CorruptedSessionUserException.class})
#ResponseBody
#ResponseStatus(value=HttpStatus.FORBIDDEN)
public ErrorObject userNotFoundExceptionHandler() {
// Handle exception
// log using the logger.
// We usually return an error object in JSON so that we can show custom // error messages.
}
}
Here is how a service might look. We usually call our services from the controllers. If an exception is thrown when coming from a controller the advice will handle it.
import ...
#Service(value="ObjectService")
#Transactional
public class ObjectServiceImpl implements ObjectService {
#Autowired
private ObjectDAO objectDAO;
#Override
public Object get(int id) {
Object o = objectDAO.get(id);
Hibernate.initialize(o.getVoters());
return o;
}
}
I hope this helps.
Consider the following code snippet. (I am using Spring 3.1 and Hibernate 3.6)
#Override
#Transactional
public <T extends Termination> void progressToPendingStage(Class<T> entity,
Long terminationId, String userName) throws Exception {
Termination termination = findTerminationById(entity, terminationId);
//TODO improvise such that email does not get sent if data is not saved
if (termination.getStatus().equals(TerminationStatus.BEING_PREPARED.toString())) {
termination.setStatus(TerminationStatus.PENDING.toString());
termination.setSubmittedDate(new Date());
termination.setSubmittedBy(userName);
saveOrUpdateTermination(termination);
//Send an email to SAS
emailHelper.configureEmailAndSend(termination);
}
}
Unit tests for the above method indicate that email will be sent regardless that the saveOrUpdateTermination(termination) throws an exception or not. On further testing and some research I have uncovered that this behavior is the expected behavior. This is not what the business rules desire. An email should be sent only if the termination record was saved successfully. Any suggestions on how to make this behave in the desired manner? One way I can think of is to make the caller handle the exception thrown by the progressToPendingStage method and if no exception was thrown send an email. Am I on the right track or can we alter the way #Transaction behaves.
I have solved this issue by designing around the problem. Sending an Email was never meant to be part of the transaction. I created an object that performed post saving tasks. The object will catch the exception thrown upon saving the termination and if no exceptions were thrown I would then trigger an email to be sent out. One could also put this in an Spring Aspect which could be executed upon successfully returning after a successful save.
Lessons learn't: Don't include steps that don't belong in a method marked with #transaction. If its included in a transaction Spring will silently handle the exception and not throw the exception till the transaction is finished. In short if a method is annotated with #Transaction every line in that method will be execute even though a line in the middle of the method throws an exception.
How must I handle exceptions inside a mdb? I have the funny feeling that the exception happens after the try catch block so I'm not able to catch and log it. Glassfish v3 decides to repeat the whole message. It runns into a infinite loop and writes lot's of logfiles on the harddrive.
I'm using Glassfishv3.01 + Eclipselink 2.0.1
public class SaveAdMessageDrivenBean implements MessageListener {
#PersistenceContext(unitName="QIS")
private EntityManager em;
#Resource
private MessageDrivenContext mdc;
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage obj = (ObjectMessage)message;
AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
save(alyzres);
}
} catch (Throwable e) {
mdc.setRollbackOnly();
log.log(Level.SEVERE, e);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {
Some s = em.find(Some.class, somepk);
s.setSomeField("newvalue");
// SQL Exception happens after leaving this method because of missing field for ex.
}
}
You got a bad case of message poisoning...
The main issues I see are that:
you are calling directly the save() method in your onMessage(): this means thet the container has no way to inject the proper transaction handling proxy around the save method
in any case the save() method should have #TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) in order to commit in a separate transaction, otherwise it will join the onMessage transaction (which default to REQUIRED) and bypass your exception handling code, beign committed after the successful execution of onMessage
What I woud do is:
Move the save method to a new Stateless session bean:
#Stateless
public class AnalyzerResultSaver
{
#PersistenceContext(unitName="QIS")
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {
Some s = em.find(Some.class, somepk);
s.setSomeField("newvalue");
// SQL Exception happens after leaving this method
}
}
Inject this bean in your MDB:
public class SaveAdMessageDrivenBean implements MessageListener {
#Inject
private AnalyzerResultSaver saver;
#Resource
private MessageDrivenContext mdc;
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage obj = (ObjectMessage)message;
AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
saver.save(alyzres);
}
} catch (Throwable e) {
mdc.setRollbackOnly();
log.log(Level.SEVERE, e);
}
}
}
Another tip: in this code the message poisoning still exists. Now it derives from the line invoking mdc.setRollbackOnly();.
I'd suggest here to log the exception and transfer the message to a poison queue, thus preventing the container to resubmit the message ad infinitum.
UPDATE:
A 'poison queue' or 'error queue' is simply a mean to guarantee that your (hopefully recoverable) discarded messages will not be completely lost. It is used heavily in integration scenarios, where the correctness of the message data is not guaranteed.
Setting up a poison queue implies defining a destination queue or topic and redeliver the 'bad' messages to this destination.
Periodically, an operator should inspect this queue (via a dedicated application) and either modify the messages and resubmit to the 'good' queue, or discard the message and ask for a resumbit.
I believe that the code that you have posted is mostly OK.
Your use of
#TransactionAttribute(TransactionAttributeType.REQUIRED)
is completely ignored because this (and most other) annotations can only be applied to business methods (including onMessage). That doesn't matter though because your onMessage method gets an implicit one for free.
This leads to the fact that message handling is transactional in a Java EE container. If the transaction fails for any reason the container is required to try and deliver the message again.
Now, your code is catching the exception from the save method, which is good. But then you're explicitly marking the transaction for rollback. This has the effect of telling the container that message delivery failed and that it should try again.
Therefore, if you remove:
mdc.setRollbackOnly();
the container will stop trying to redeliver the message.
If I'm not mistaken, you're letting the container handle the transactions. This way, the entity manager will queue the operations that will be flushed after the method finishes, that's why you're having exceptions after the method is finished.
Using em.flush() directly as a final step in the method will execute all the related queries of the transaction, throwing the exceptions there instead of being thrown later when the flush() is made by the container while commiting the transaction.