When I go to /confirmation-account link, in tomcat console I can see that if and else block is also executed. I can see:
print from ColorConsoleHelper.getGreenLog("loginView") and from ColorConsoleHelper.getGreenLog("confirmationAccountView")
This is really strange behavior. Why?
#RequestMapping(value = "/confirmation-account", method = RequestMethod.GET)
#Transactional
public ModelAndView displayConfirmationAccountPage(ModelAndView modelAndView, #RequestParam Map<String, String> requestParams) {
final int ACTIVE_USER = 1;
// find the user associated with the confirmation token
UserEntity userEntity = userService.findUserByConfirmationToken(requestParams.get("token"));
// this should always be non-null but we check just in case
if (userEntity!=null) {
// set the confirmation token to null so it cannot be used again
userEntity.setConfirmationToken(null);
// set enabled user
userEntity.setEnabled(ACTIVE_USER);
// save data: (token to null and active user)
saveAll(userEntity.getTrainings());
/*
RedirectAttributes won't work with ModelAndView but returning a string from the redirecting handler method works.
*/
modelAndView.addObject("successMessage", "Konto zostało pomyślnie aktywowane!");
modelAndView.setViewName("loginView");
ColorConsoleHelper.getGreenLog("loginView");
} else {
ColorConsoleHelper.getGreenLog("confirmationAccountView");
modelAndView.addObject("errorMessage", "Link jest nieprawidłowy...");
modelAndView.setViewName("confirmationAccountView");
}
return modelAndView;
}
public void saveAll(List<TrainingUserEntity> trainingUserEntityList) {
for ( TrainingUserEntity trainingUserEntity : trainingUserEntityList) {
entityManagerService.mergeUsingPersistenceUnitB(trainingUserEntity);
}
}
public void mergeUsingPersistenceUnitB(Object object) {
EntityManager entityManager = getEntityManagerPersistenceUnitB();
EntityTransaction tx = null;
try {
tx = entityManager.getTransaction();
tx.begin();
entityManager.merge(object);
tx.commit();
}
catch (RuntimeException e) {
if ( tx != null && tx.isActive() ) tx.rollback();
throw e; // or display error message
}
finally {
entityManager.close();
}
}
Below solution & explanation:
Because of /confirmation-account link is invoke twice, what is caused by dynamic proxy and #Transactional method annotated in controller It is mandatory to check how many displayConfirmationAccountPage method is invoked. It is workaround.
What do you think it is good or not to annotated #Transactional controller method?
Related
I am developing REST API in Spring boot with Hibernate.
I have this function in my controller
#PostMapping("/profile")
public ResponseEntity<String> saveProfile(#Valid #RequestBody SaveProfileVM saveProfileVM,
BindingResult bindingResult)
throws JsonProcessingException {
if (bindingResult.hasErrors()) return super.fieldExceptionResponse(bindingResult);
Profile profile;
boolean optimisticLockException = true;
int retryCount = 0;
do {
try {
profile = accountService.saveProfile(saveProfileVM.getAccountId(),
saveProfileVM.getName(),
saveProfileVM.getEmail());
optimisticLockException = false;
retryCount++;
} catch (ObjectOptimisticLockingFailureException exception) {
retryCount++;
System.out.println(exception.getMessage());
}
} while (optimisticLockException && retryCount < MAX_OPTIMISTIC_LOCK_EXCEPTION_RETRY_COUNT);
return ResponseEntity.status(HttpStatus.OK).body(objectMapper.writeValueAsString(profile));
}
and MAX_OPTIMISTIC_LOCK_EXCEPTION_RETRY_COUNT is 3
I don't want to duplicate the do..while and try..catch blocks in every method where I need to check ObjectOptimisticLockingFailureException
do {
try{}
catch{}
} while()
Is there any way that I can pass accountService.saveProfile() to a general method that has the do..while and try..catch block so that I don't have to copy and paste the blocks to every method I need?
Every controller extends a BaseController so, it might be good to have the general method in BaseController?
#RestController
#RequestMapping("/account")
public class AccountController extends BaseController {
Can you guys give an idea please?
You can use spring-retry. More details
#Retryable(value = ObjectOptimisticLockingFailureException.class, maxAttempts = 3)
public void saveProfile(Long accountId, String name, String email){..}
I am new to Spring AOP. I need to execute methods only if the user is authorized.
Here's my code.
#Before("some pointcut")
public HttpStatus checkUserAuthentication(String userName)
{
if( userAuthorized(userName) )
{
//go on executing method
} else {
return HttpStatus.FORBIDDEN;
}
}
Is there any alternative for ProceedingJoinPoint.proceed when using JoinPoint or can I use ProceedingJoinPoint with #Before advice? How to proceed with executing the if statement if the user is authorized.
I solved this using #Around advice. and changing the return type to Object so that it can return ProceedingJoinPoint on successfull verification.
#Around("some pointcut")
public Object checkUserAuthentication(ProceedingJoinPoint pjp, String userName)
{
if( userAuthorized(userName) )
{
return pjp.roceed();
} else {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
}
using #Around as advice the control can be passed to the method after verification.
I'm looking at some existing code and wanted to know what happen's in the following scenario with Spring's #Transactional annotation? Consider the following example:
A POST request hits a #Controller annotated with #Transactional:
#ResponseBody
#Transactional
#RequestMapping(value="/send", method=RequestMethod.POST)
public void send(#RequestBody Response response) {
try {
DBItem updatedDbItem = repository.updateResponse(response);
if (updatedDbItem == null){
//some logging
}
} catch(Exception ex) {
//some logging
}
}
The controller calls a non #transactional repository method which sets a value and in turns calls a another #Transactional method:
#Override
public DBItem updateResponse(Response response) {
try {
DBItem dBItem = findResponseById(response.getKey());
if (dBItem != null){
dBItem.setSomeField(response.getValue());
return updateDataBaseItem(response);
}
} catch (Exception ex) {
//some logging
}
return null;
}
The following updateDataBaseItem() method is common and called from other non transactional methods as well as the above method:
#Transactional
#Override
public DBItem updateDataBaseItem(Response response){
try {
DBItem dBItem = em.merge(response);
return dBItem;
} catch (Exception ex) {
//some logging
}
return null;
}
send() => spring detect #transaction with default parameters
actually Propagation setting is REQUIRED and the spring join the exist transaction or create new if none.
repository.updateResponse(..) => No transactions params the method execute within the same transaction already exist
updateDataBaseItem(..) => calling the method in same repository , spring will not recognize the #Transaction annotation because the use of proxy mode, so this method will be executed within the same transaction
a method within the target object calling another method of the target
object, will not lead to an actual transaction at runtime even if the
invoked method is marked with #Transactional
I am working on a Spring-MVC application in which the user can register products and productImages. Now there are 3 tables, User, Product, ProductImages. It is not always necessary to pull all the productImages until and unless the user explicitly goes into the product, where there is a modal, and the user can then select the images to load.
So I thought of using LazyLoading instead of EagerFetching. But I get lazyLoadException with that. So I opened a session manually in both Product and ProductImages, and I get a ObjectNotFound Exception. The problem is, productImages has a foreign key relation with product, so I must save product first before saving its images, and that is where I am having the problem. Kindly suggest me how to use lazy load in this situation. Error log and code goes below :
Error log :
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.WirTauschen.model.ProductImage#1150]
org.hibernate.internal.SessionFactoryImpl$1$1.handleEntityNotFound(SessionFactoryImpl.java:253)
com.WirTauschen.dao.ProductBasicDaoImpl.updateProduct(ProductBasicDaoImpl.java:50)
Controller :
#RequestMapping(value = "/product/addimages", method = RequestMethod.POST)
public #ResponseBody String addProductImages(#RequestParam("productImages") MultipartFile[] uploadedFiles){
if(uploadedFiles != null && uploadedFiles.length>0) {
for (MultipartFile uploadedFile : uploadedFiles) {
try {
if (!(uploadedFile.isEmpty())) {
imagesList.add(uploadedFile.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
return "image failed to upload";
}
}
}
return "done";
}
#RequestMapping(value="/product/add",method = RequestMethod.POST)
public String addProduct(#ModelAttribute("product") ProductBasic productBasic,Model model){
model.addAttribute("product", new ProductBasic());
productBasic.setProductimage(productprofileimage);
int productid = productBasicService.addProduct(productBasic);
ProductBasic productBasic1 = this.productBasicService.getProductById(productid);
for (int index = 0; index < imagesList.size(); index++) {
if (index == 0) {
productBasic1.setProductimage(imagesList.get(0));
}
ProductImage productImage = new ProductImage();
productImage.setProductimage(imagesList.get(index));
this.productImageService.addProductImage(productBasic1, productImage);
}
productBasicService.updateProduct(productBasic1);
imagesList.clear();
productprofileimage =null;
return "redirect:/product/show";
}
ProductDAOImpl :
#Override
#Transactional
public int addProduct(User user, ProductBasic productBasic) {
// I was using getSessionBefore with Eager, it worked, thought of trying openSession
session = this.sessionFactory.openSession();
user.getProductBasics().add(productBasic);
productBasic.setUser1(user);
session.save(productBasic);
System.out.println("Returned product information is"+productBasic.getProductid());
session.flush();
//session.close();
return productBasic.getProductid();
}
#Override
#Transactional
public void updateProduct(User user,ProductBasic productBasic) {
logger.debug("Editing product information");
session = this.sessionFactory.getCurrentSession();
// User user1 = (User) session.get(User.class,id);
user.getProductBasics().add(productBasic);
productBasic.setUser1(user);
session.saveOrUpdate(productBasic);
session.flush();
}
ProductImageDAOImpl :
#Override
#Transactional
public boolean addProductImage(ProductBasic productBasic,ProductImage productImage) {
session = sessionFactory.openSession();
productBasic.getProductImageSet().add(productImage);
productImage.setProductimageupload(productBasic);
productBasic.setImagecount((productBasic.getImagecount()+1));
session.merge(productBasic);
session.saveOrUpdate(productImage);
return true;
}
The controller code shouldn't contain that much service side information, but this is just an attempt to make sure it works..I have defined LazyLoading in model, I can post the code if required, kindly tell me what am I doing wrong. Any pointers are welcome. Thank you for your time.
Extension to my comments
Remove the saveOrUpdate above.
Change the cascade type to ALL
PS: You got exception at UpdateProduct method. but you hve posted code for addProduct.
Suppose I have a Website that is used in normal mode (browser) and in some other mode, like a MobileView mode (inside a mobile app). For each Controller I create, there might be correspondent controller for MobileView, processing the same url.
The easiest solution is to create ifs in all the Controllers that have MobileView logic. Another solution would be to use a correspondent url for MobileView (similar to the normal url) and two separate Controllers (possible where one extends from another; or use some other way to recycle common code)
But, a more elegant solution would be to have some extra annotations, like #SupportsMobileView (to mark a controller, and tell the app that this will have a correspondent MobileView Controller) and #MobileViewController (to mark a second controller, and tell the app that this controller needs to run immediately after the initial controller marked with #SupportsMobileView). The link between a normal controller and a MobileView controller would be through the url they process (defined with #RequestMapping).
Is it possible to extend Spring MVC (A)? Where to inject new annotation scanners (B) and annotation handlers / component handlers (C)? How should the MobileView controller be executed (D) (right now I am thinking that it could be executed through AOP, where the new handler of my new controller type programatically creates a Join-Point on the corresponding normal controller)
Note that I did not mention how this MobileView mode is triggered and detected. Let's just say that there a Session boolean variable (flag) for that.
Critics on any points (A), (B), (C) or (D) are welcomed, as well as technical hints and alternative solution to any point or the whole solution.
HandlerInterceptor can be used to intercept the RequestMapping handling. This is a simple example how to configure and implement one.
You can check for your session variable and will have a bunch of methods that will allow you to do custom processing or just exchange the view from the normal controller handling with your mobile view.
Ok, warnings:
this is only a proof of concept of what I understood must be done so:
+#MobileViewEnable and #MobileView annotated (and related) methods need to stay in the same controller
+there's no check for the httpAction used
+the two methods must have the same signature
+mobileView annotation value and requestMapping annotation value must be equals and uniques
+the logic inside callYourLogic(..) defines which method is going to be called, at the moment there's a very simple logic that check if exist the parameter ("mobile") in the request, just to test
+this code is not intended to be used as is (at all)
+don't know if it works at all outside my pc (joke :D, ehm..)
SO:
Annotations:
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileView {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileViewEnable {
}
ExampleController:
#Controller
public class MainController extends BaseController {
private final static Logger logger = LoggerFactory.getLogger(MainController.class);
private final static String PROVA_ROUTE = "prova";
#MobileViewEnable
#RequestMapping(PROVA_ROUTE)
public String prova() {
logger.debug("inside prova!!!");
return "provaview";
}
#MobileView(PROVA_ROUTE)
public String prova2() {
logger.debug("inside prova2!!!");
return "prova2view";
}
}
Aspect definition:
<bean id="viewAspect" class="xxx.yyy.ViewAspect" />
<aop:config>
<aop:pointcut expression="#annotation(xxx.yyy.MobileViewEnable)" id="viewAspectPointcut" />
<aop:aspect ref="viewAspect" order="1">
<aop:around method="around" pointcut-ref="viewAspectPointcut" arg-names="viewAspectPointcut"/>
</aop:aspect>
</aop:config>
Aspect implementation:
public class ViewAspect implements BeforeAdvice, ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(ViewAspect.class);
private ApplicationContext applicationContext;
public Object around(ProceedingJoinPoint joinPoint) {
Method mobileViewAnnotatedMethod = null;
HttpServletRequest request = getCurrentHttpRequest();
String controllerName = getSimpleClassNameWithFirstLetterLowercase(joinPoint);
Object[] interceptedMethodArgs = getInterceptedMethodArgs(joinPoint);
String methodName = getCurrentMethodName(joinPoint);
Method[] methods = getAllControllerMethods(joinPoint);
Method interceptedMethod = getInterceptedMethod(methods, methodName);
String interceptedMethodRoute = getRouteFromInterceptedMethod(interceptedMethod);
if (callYourLogic(request)) {
mobileViewAnnotatedMethod = getMobileViewAnnotatedMethodWithRouteName(methods, interceptedMethodRoute);
if (mobileViewAnnotatedMethod != null)
return invokeMethod(mobileViewAnnotatedMethod, interceptedMethodArgs, controllerName);
}
return continueInterceptedMethodExecution(joinPoint, interceptedMethodArgs);
}
private Object continueInterceptedMethodExecution(ProceedingJoinPoint joinPoint, Object[] interceptedMethodArgs) {
try {
return joinPoint.proceed(interceptedMethodArgs);
} catch (Throwable e) {
logger.error("unable to proceed with intercepted method call: " + e);
}
return null;
}
private Object[] getInterceptedMethodArgs(JoinPoint joinPoint) {
return joinPoint.getArgs();
}
private boolean callYourLogic(HttpServletRequest request) {
// INSERT HERE YOUR CUSTOM LOGIC (e.g.: is the server accessed from a mobile device?)
// THIS IS A STUPID LOGIC USED ONLY FOR EXAMPLE
return request.getParameter("mobile")!= null;
}
private HttpServletRequest getCurrentHttpRequest() {
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
private String invokeMethod(Method method, Object[] methodArgs, String className) {
if (method != null) {
try {
Object classInstance = getInstanceOfClass(method, className);
return (String) method.invoke(classInstance, methodArgs);
} catch (Exception e) {
logger.error("unable to invoke method" + method + " - " + e);
}
}
return null;
}
private Object getInstanceOfClass(Method method, String className) {
return applicationContext.getBean(className);
}
private Method getMobileViewAnnotatedMethodWithRouteName(Method[] methods, String routeName) {
for (Method m : methods) {
MobileView mobileViewAnnotation = m.getAnnotation(MobileView.class);
if (mobileViewAnnotation != null && mobileViewAnnotation.value().equals(routeName))
return m;
}
return null;
}
private String getRouteFromInterceptedMethod(Method method) {
RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
if (requestMappingAnnotation != null)
return requestMappingAnnotation.value()[0];
return null;
}
private String getCurrentMethodName(JoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}
private Method[] getAllControllerMethods(JoinPoint joinPoint) {
return joinPoint.getThis().getClass().getSuperclass().getMethods();
}
private String getSimpleClassNameWithFirstLetterLowercase(JoinPoint joinPoint) {
String simpleClassName = joinPoint.getThis().getClass().getSuperclass().getSimpleName();
return setFirstLetterLowercase(simpleClassName);
}
private String setFirstLetterLowercase(String simpleClassName) {
String firstLetterOfTheString = simpleClassName.substring(0, 1).toLowerCase();
String restOfTheString = simpleClassName.substring(1);
return firstLetterOfTheString + restOfTheString;
}
private Method getInterceptedMethod(Method[] methods, String lookingForMethodName) {
for (Method m : methods)
if (m.getName().equals(lookingForMethodName))
return m;
return null;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}