I've got problem with managing transactions using Spring. In single transaction I save two new entities and only one can be found.
Here's my method annotated with #Transactional
#Transactional(propagation = Propagation.REQUIRES_NEW)
public User createUser(CreateUserRequest request) {
return userCreator.createUser(request);
}
and createUser:
User createUser(CreateUserRequest request) {
// some validation and business processing
User user = newUser(request);
User savedUser = userRepository.save(user);
publishNewUserCreatedEvent(savedUser);
return savedUser;
}
and publishNewUserCreatedEvent:
private void publishNewUserCreatedEvent(User user) {
UserCreatedEvent event = new UserCreatedEvent(user);
eventRepository.save(event);
}
Then I've got a worker thread that simply fetches the latest event from database and calls a handler for given event.
The problem is that for above example when the UserCreatedEvent's handler is calling UserRepository for userId it got EntityNotFound exception. So the event was persisted, but the user wasn't (yet).
I've tried multiple things (like having two methods annotated with #Transactional(propagation = Propagation.REQUIRES_NEW) where first one saves user to the repository so it should be commited first and the second saves event to the repository, I've tried with TransactionTemplate), but none has worked.
Most of the time retry works, meaning that executing the same event for the second time succeeds, so the user can be found. However it's annoying and I'd like to have control over transaction execution, so I'd expect that user will be saved before the event is saved, so when event handler loads event by ID and user by ID then both can be found.
Related
I am implementing spring security in my application and I want to prevent brute force password attempts for a particular user account.
Whenever a user tries to login with invalid credentials, I am throwing an AuthException() with a basic Invalid Username Or Password message and in addition to I now want to increase a counter in my database for password_attempt to handle brute force attack.
I tried implementing a case where I run a separate transaction using Propagation.REQUIRES_NEW but when throwing exception from outer transaction, inner transaction is also rolled back. How to prevent this?
Also I read it somewhere when searching about it to use spring application events to handle this implementation. But will it be a bad design? Because from UserAuthenticationService I will publish an event, the event will again #Autowired UserAuthenticationService and call the function to increase the counter in the database.
Or is there a way to call the spring event automatically whenever I throw AuthException Or BadCredentialException so that I dont have to publish it explicitly?
PS: I have added #Transactional on class level (UserAuthenticationService.class) snd both methods are in the same class
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updatePasswordAttempt(Long userId, Integer attemptCount){
userRepository.updatePasswordAttempt(userId, attemptCount);
}
public void validatePassword(String password, UserEntity user){
if(Boolean.TRUE.equals(matchPassword(password, user.getPassword()))){
if(user.getPasswordAttempt().equals(0)) return;
updatePasswordAttempt(user.getId(), 0);
return;
}
int attemptCount = user.getPasswordAttempt() + 1;
updatePasswordAttempt(user.getId(), attemptCount);
throw new AuthException(INVALID_PASSWORD.getMessage(), INVALID_PASSWORD.getCode());
}
I have a big Service method in my Spring/Hibernate app which previously wasn't transactional, and was working OK. As soon as I added #Transactional(readOnly = false, rollbackFor = {Exception.class}) to it, the Select (Fetch) part of that method started breaking at a Select statement (even prior to any persistence):
org.hibernate.PersistentObjectException: detached entity passed to persist: app.domain.Activities
in this code:
#Transactional(readOnly = false, rollbackFor = {Exception.class})
public String save(PlansModel plan, String id, Model model,HttpServletRequest request) throws IllegalAccessException, InvocationTargetException {
//...
//... some initial Selects - NO Persistence yet
Project proj = planService.getProjectByID(projId); // <-- BROKE HERE "detached entity passed to persist"
// ... NO Persistence yet
// ...
// At the end, some persistence starts
planService.savePlan(plan); //etc.
}
We're maintaining this messy code and it's hard to analyze what may have caused it. Some objects are being modified there.
We decided to work around it by putting #Transactional just around the actual persistence bottom part, which is all that matters for transactionality. (Our goal is to ensure that this whole method has transactionality/rollback.)
But when we did that, we found that in this new design, transactions don't get rolled back in case of errors. We've split it up into 2 methods, and just the 2nd method is #Transactional, but there's no rollback from here, and the Transaction mechanism doesn't kick in. Everywhere else it works.
public String save(PlansModel plan, String id, Model model,HttpServletRequest request) throws IllegalAccessException, InvocationTargetException {
//...
//... some initial Selects - NO Persistence yet
Project proj = planService.getProjectByID(projId); // <-- WORKING AGAIN without #Transactional
// ... NO Persistence yet
// ...
return persistPart(..); // for persistence
}
#Transactional(readOnly = false, rollbackFor = {Exception.class})
public String persistPart(..) {
planService.savePlan(plan); // <-- NO ROLLBACK from here
}
It seems like we're dealing with a method which has some issues as soon as #Transactional is added, but we need to ensure transactionality/rollback is enabled for it. Based on these symptoms, does anyone know what the solution might be?
#Transactional doesn't work when you call this method from inside the service class like you do. It's true not only for #Transactional. It's a limitation of Spring AOP.
Your question is almost a duplicate of this one. So you either need to inject this service into itself and call this transactional method on the injected object OR create another service which will call this transactional method
I have a simple listener with 3 methods. and a repository with autowired on that. While saving an object from afterWrite it works nicely. but when saving item from onError methods no exception occurs, however it is not saving any data. Thankful for suggestions.
public class WriteListener implements ItemWriteListener{
public void beforeWrite(List items) {
System.out.println("Going to write following items: "+ items.toString());
}
public void onWriteError(Exception exception, List items) {
System.out.println("Error occurred when writing items!");
testRepository.save(items.get(0)); //not working
}
public void afterWrite(List items) {
testRepository.save(items.get(0)); //not nicely and save data
Based on the limited information provided, most likely the cause is the exception itself. The exception would have marked current transaction as dirty thus spring would have rolled it back.
If you still want to store data in your listener despite existing exception, use it in a separate transaction context. Simplest way for that would be to use #Async annotation on your listener and marking it Transactional explicitly to ensure it initiate a new transaction. Check out Spring Event which covers this topic in little bit more depth.
I would like to use #TransactionalEventListener to complete a unit of work. In other word, I want to register a transaction callback, which is triggered before the transaction is committed. What I have in mind is a Repository class, which keeps track of the changes done to an aggregate root and all dependant entities. When the transaction is about to be committed it writes the entire set of changes to the DB, as follows.
#Component
#Transactional
class FlightRepository implements Repository{
Flight findById(int id){
// return Flight and start tracking changes
}
void add(Flight f){
// add to list of flights to be created before transaction commit
}
void remove(Flight f){
// add to list of flights to be deleted before transaction commit
}
#TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void saveChanges(){
// write all changes to db (adds/removes/updates)
}
}
What I need to confirm is whether it is OK to still perform CRUD operations in a TransactionPhase.BEFORE_COMMIT callback?
Edit: Changed saveChanges() access modifier from private to public as noted in comment
Yes. It is OK. What you do in the BEFORE_COMMIT callback will be at the same transaction of the unit of work.
By the way, in order for #TransactionalEventListener to work , you should change its method access modifiers to non private.
I am doing few hibernate save operations in spring's transactional service class.
My expectation is that by the time method execution finishes hibernate should write data to database.
But hibernate is actually executing those queries only when controller is about to return a response.
My sample code is as follows.
#Controller
public class TestController {
#Autowired
private SaveService saveService;
#RequestMapping(value = "saveData", method = RequestMethod.POST)
public String saveData(HttpServletRequest request, Model model) {
try {
saveService.saveData(object1, object2, object3); // LINE 1
sendEmail(); // LINE 2
//some code here // LINE 3
} catch(Exception e) {
//log exception
//return error message
}
}
}
#Service("saveService")
#Transactional
public class SaveServiceImpl implements SaveService {
#Autowired
private SaveDAO saveDAO;
public void saveData(Object objec1, Object objec2, Object objec3) {
saveDAO.save(object1);
saveDAO.save(object2);
saveDAO.save(object3);
}
}
In above code I am calling SaveService.saveData() method from controller. And if save is successful I want to go ahead and send an email. But if for some reason SaveService.saveData() throws an exception i don't want
to send the email.
When I performed some tests it was observed that even if SaveService.saveData() throws an exception it's not thrown
until the email is sent from controller. I want that if a call to saveService.saveData() at 'LINE 1' in controller
throws an exception the next line that sends email should not get executed.
I wanted to know if this is expected hibernate behavior and if it is what can I do to tell hibernate to execute
queries before exiting service methods.
Thank you.
This behavior is due to hibernate optimizations. Hibernate will wait until the last time possible to execute the sentences.
You can avoid this with session.flush(), Flushing the session forces Hibernate to synchronize the in-memory state of the Session with the database (i.e. to write changes to the database).
The problem here is when an exception occurs, your the variables/objects are not initialized in the catch block and you are accessing it.
As it looks like you have just added a snippet of the code in question, so I guess the variables object1, object2, object3 needs to initalized to null.
for example: object1=null