I have an event handler class as follows:
#Component
#RepositoryEventHandler(DonationOffer.class)
public class DonationOfferEventHandler {
#Autowired
DonationOfferNotificationService notificationService;
#HandleAfterSave
public void handleAfterSave(DonationOffer donationOffer){
Integer donationOfferId = donationOffer.getDonationOfferId();
System.out.println("updating donation offer");
switch (donationOffer.getOfferStatus()){
case Accepted:
notificationService.generateAcceptedNotification(donationOfferId);
break;
case Rejected:
notificationService.generateRejectedNotification(donationOfferId);
break;
case Cancelled:
notificationService.generateCancelledNotification(donationOfferId);
break;
}
}
}
And spring repo as follows:
#Transactional
public interface DonationOfferRepo extends PagingAndSortingRepository<DonationOffer,Integer>{
#Modifying
#Query(
"update DonationOffer d " +
"set offerStatus = :offerStatus " +
"where d.donationOfferId = :donationOfferId"
)
#RestResource(path="updateStatus")
int updateStatus(
#Param("donationOfferId") Integer donationOfferId,
#Param("offerStatus") OfferStatus offerStatus
);
}
Event handler is called in case of PUT request by the problem is that #HandleAfterSave method is not being called when I call updateStatus() i.e.,
a get request to /api/donationOffers/search/updateStatus?donationOfferId=45&offerStatus=Cancelled
Can any one help if I am missing something or is it the default behavior that #HandleAfterSave would be called only with PUT request ?
UPDATE:
I have created a method to test event handler with repo save method as follows. Event handler is not called in this case also.
#GetMapping(value="/savetest")
#Transactional
public ResponseEntity saveTesT(){
DonationOffer donationOffer = donationOfferRepo.findOne(1);
donationOffer.setOfferedBottles(donationOffer.getOfferedBottles()+1);
donationOfferRepo.save(donationOffer);
return new ResponseEntity(new StringWrapper("OK"), HttpStatus.OK);
}
In order to trigger event from HandleBeforeSave you need to execute PUT request. If you are doing POST request than the correct event handler would be HandleBeforeCreate.
UPDATE:i would guess that the listener method does not get invoked for the same reason as to why Queries do not invoke JPA prePersist, postPersist, preUpdate ... and so on events. Queries have different execution route as to EntityManager.persis , update. Entity manager works on the foundation of identity , therefore it can guarantee that an entity has actually been updates. Same can not be told when you use a Query. Normally entity manager would execute one query to fetch the entity than it will be analysed for changes and then UPDATE will happen, or will not happen in case of no changes. There is no mechanism to tell if update has really happened when query is executed.
According to the documentation #Modifying annotation is not guaranteeing that modification will actually happen. What it is guaranteeing is that the session will be cleared after method invokation. The reasoning behind this is again that the queries have different execution route than the normal persist, merge methods and you can not guarantee that the session will keep integrity. Therefore #Modifying will guarantee it has been cleared.
Related
I have the following method in an #Service class which has #Transactional defined:
#Override
public Result add(#NonNull final UserSaveRequest request) {
final Result<Email> emailResult = Email.create(request.getEmail());
final Result<UserFirstName> userFirstNameResult = UserFirstName.create(request.getFirstName());
final Result<UserLastName> userLastNameResult = UserLastName.create(request.getLastName());
final Result combinedResult = Result.combine(emailResult, userFirstNameResult, userLastNameResult);
if (combinedResult.isFailure()) {
return Result.fail(combinedResult.getErrorMessage());
}
final Result<User> userResult = User.create(emailResult.getValue(), userFirstNameResult.getValue(), userLastNameResult.getValue());
if (userResult.isFailure()) {
return Result.fail(userResult.getErrorMessage());
}
this.userRepository.save(userResult.getValue());
return Result.ok();
}
Now as you can see I utilize a Result class which can contain a return value or an error message as I don't think using exceptions for flow control is very clean.
The problem I now have is; the complete method is bound in one transaction and if one database call should fail the whole transaction will be rolled back. In my model however, after the this.userRepository.save(userResult.getValue()); call, if something would happen that would force me to return a failed result, I can't undo that save(userResult.getVlaue()); call seeing as I don't use exceptions for flow control.
Is this a problem that has an elegant solution, or is this a place where I need to make a trade-off between using exceptions as flow control and having to mentally keep track of the ordering of my statements in these kind of situations?
Yes, you can trigger rollback manually. Try this:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
More information: https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/data-access.html#transaction-declarative-rolling-back
I have a code where event is published in a method annotated with Spring #Transactional annotation.
#Override
#Transactional
public Task updateStatus(Integer taskId, ExecutionStatus newStatus) {
Task task = Task.builder().executionStatus(newStatus).build();
return updateStatusInternal(taskId, rteWithMetadata);
}
private TaskExecution updateStatusInternal(Integer taskId,
Task newStatus) {
Task task = taskService.findById(taskId);
TaskExecution te = task.getFirstExecution();
TaskExecution.ExecutionStatus oldStatus = te.getExecutionStatus();
TaskExecution.ExecutionStatus newStatus = newStatus.getExecutionStatus();
log.info(
"Task Execution status changed. Task id={}, from={}, to={}. Manual override : {}",
task.getId(), oldStatus, newStatus,
newStatus.isManualOverrideInitiated());
te.setExecutionStatus(newStatus);
if (te.getExecutionStatus() == ExecutionStatus.COMPLETED
|| te.getExecutionStatus() == ExecutionStatus.FAILED) {
te.setEndDate(DateTimeHelper.getUtcNow());
if (rte.isManualOverrideInitiated()) {
rte.setManualOverrideEndDate(DateTimeHelper.getUtcNow());
}
}
publisher.publishEvent(TaskStatusChanged.of(task, oldStatus, newStatus));
log.info("Published TaskStatusChanged event. task Id={}", task.getId());
// Send STOMP message
final Object payload = StompMessageHelper.getTaskExecutionUpdateMessage(task);
messageTemplate.convertAndSend(taskDestination(task), payload);
log.info("STOMP message for task status update sent. task Id={}",
task.getId());
return te;
}
There is a corresponding listener method for the application event which is annotated with #TransactionalEventListener.
#Async("changeEventExecutor")
#TransactionalEventListener(phase=TransactionPhase.AFTER_COMMIT)
public void taskStatusChanged(final TaskStatusChanged e) {
log.info("taskStatusChanged called");
}
Problem is listener is not fired on one of our production boxes. It works fine consistently on local dev environment but fails consistently in production.
Did somebody face this issue earlier? Only solution I can think of is to manually fire the application event.
Note: I have checked the existing similar posting. My scenario does not match with any of the existing posting.
The only thing I can think of comes from Spring's javadoc:
If the event is not published within the boundaries of a managed
transaction, the event is discarded unless the fallbackExecution()
flag is explicitly set. If a transaction is running, the event is
processed according to its TransactionPhase.
Could there be no transaction running? I assume your code sample isn't complete, so perhaps the transaction is being rolled-back when the event is fired or something along those lines.
In any case, you could try with the following (I know you are referring to a production box, so I'm not sure what are your options in trying things out):
#TransactionalEventListener(fallbackExecution=true, phase=TransactionPhase.AFTER_COMMIT)
I can't seem to find this on their site or here. I'm hoping the answer is YES. Specifically I want to write a Verifier to check for unique key violations (I know I will have to raise the isolation level to Serializable). This won't work unless the Verifier runs in the same transaction as the Save.
DevForce does not use the same transaction for validation and save processing. Within the context of an EntityServerSaveInterceptor, authorization and validation are performed and then a TransactionScope is opened when doing the actual save. If you do a query within a verifier it will use a separate TransactionScope.
You can work around this behavior with a little extra work in your custom EntityServerSaveInterceptor. Override the ValidateSave function to bypass validation, then override the ExecuteSave method to open a TransactionScope and then do your validation logic before calling the base save logic. The TransactionScope opened by DF during the save will enlist in your TransactionScope. Something like this:
public class EntityServerSaveManager : EntityServerSaveInterceptor {
protected override bool ValidateSave() {
// will do validation later
return true;
}
protected override bool ExecuteSave() {
using (var ts = new TransactionScope(TransactionScopeOption.Required, this.SaveOptions.TransactionSettings.ToTransactionOptions())) {
// Do validation logic now
...
// Now do save
base.ExecuteSave();
ts.Complete();
}
}
}
I'm prototyping some simple audit logging functionality. I have a mid sized entity model (~50 entities) and I'd like to implement audit logging on about 5 or 6. Ultimately I'd like to get this working on Inserts & Deletes as well, but for now I'm just focusing on the updates.
The problem is, when I do session.Save (or SaveOrUpdate) to my auditLog table from within the EventListener, the original object is persisted (updated) correctly, but my AuditLog object never gets inserted.
I think it's a problem with both the Pre and Post event listeners being called to late in the NHibernate save life cycle for the session to still be used.
//in my ISessionFactory Build method
nHibernateConfiguration.EventListeners.PreUpdateEventListeners =
new IPreUpdateEventListener[]{new AuditLogListener()};
//in my AuditLogListener
public class AuditLogListener : IPreUpdateEventListener
{
public bool OnPreUpdate(PreUpdateEvent #event)
{
string message = //code to look at #event.Entity & build message - this works
if (!string.IsNullOrEmpty(message))
AuditLogHelper.Log(message, #event.Session); //Session is an IEventSource
return false; //Don't veto the change
}
}
//In my helper
public static void Log(string message, IEventSource session)
{
var user = session.QueryOver<User>()
.Where(x => x.Name == "John")
.SingleOrDefault();
//have confirmed a valid user is found
var logItem = new AdministrationAuditLog
{
LogDate = DateTime.Now,
Message = message,
User = user
};
(session as ISession).SaveOrUpdate(logItem);
}
When it hits the session.SaveOrUpdate() in the last method, no errors occur. No exceptions are thrown. it seems to succeed and moves on. But nothing happens. The audit log entry never appears in the database.
The only way I've been able to get this to work it to create a completely new Session & Transaction inside this method, but this isn't really ideal, as the code proceeds back out of the listener method, hits the session.Transaction.Commit() in my main app, and if that transaction fails, then I've got an orphaned log message in my audit table for somethign that never happened.
Any pointers where I might be going wrong ?
EDIT
I've also tried to SaveOrUpdate the LogItem using a child session from the events based on some comments in this thread. http://ayende.com/blog/3987/nhibernate-ipreupdateeventlistener-ipreinserteventlistener
var childSession = session.GetSession(EntityMode.Poco);
var logItem = new AdministrationAuditLog
{
LogDate = DateTime.Now,
Message = message,
User = databaseLogin.User
};
childSession.SaveOrUpdate(logItem);
Still nothing appears in my Log table in the db. No errors or exceptions.
You need to create a child session, currentSession.GetSession(EntityMode.Poco), in your OnPreUpdate method and use this in your log method. Depending on your flushmode setting, you might need to flush the child session as well.
Also, any particular reason you want to roll out your own solution? FYI, NHibernate Envers is now a pretty mature library.
I am in reference to Spring Roo In Action (book from Manning). Somewhere in the book it says "Roo marks the test class as #Transactional so that the unit tests automatically roll back any change.
Here is the illustrating method:
#Test
#Transactional
public void addAndFetchCourseViaRepo() {
Course c = new Course();
c.setCourseType(CourseTypeEnum.CONTINUING_EDUCATION);
c.setName("Stand-up Comedy");
c.setDescription(
"You'll laugh, you'll cry, it will become a part of you.");
c.setMaxiumumCapacity(10);
c.persist();
c.flush();
c.clear();
Assert.assertNotNull(c.getId());
Course c2 = Course.findCourse(c.getId());
Assert.assertNotNull(c2);
Assert.assertEquals(c.getName(), c2.getName());
Assert.assertEquals(c2.getDescription(), c.getDescription());
Assert.assertEquals(
c.getMaxiumumCapacity(), c2.getMaxiumumCapacity());
Assert.assertEquals(c.getCourseType(), c2.getCourseType());
}
However, I don't understand why changes in this method would be automatically rolled back if no RuntimeException occurs...
Quote from documentation:
By default, the framework will create and roll back a transaction for each test. You simply write code that can assume the existence of a transaction. [...] In addition, if test methods delete the contents of selected tables while running within a transaction, the transaction will roll back by default, and the database will return to its state prior to execution of the test. Transactional support is provided to your test class via a PlatformTransactionManager bean defined in the test's application context.
So, in other words, SpringJUnit4ClassRunner who runs your tests always do transaction rollback after test execution.
I'm trying to find a method that allows me to do a rollback when one of the elements of a list fails for a reason within the business rules established (ie: when throw my customize exception)
Example, (the idea is not recording anything if one element in list fails)
public class ControlSaveElement {
public void saveRecords(List<MyRecord> listRecords) {
Boolean status = true;
foreach(MyRecord element: listRecords) {
// Here is business rules
if(element.getStatus() == false) {
// something
status = false;
}
element.persist();
}
if(status == false) {
// I need to do roll back from all elements persisted before
}
}
...
}
Any idea? I'm working with Roo 1.2.2..