Spring Transactions - how to split my method - spring-boot

I have the following method that is called by controller:
#Transactional
public Note getById(Long noteId, Collection<Authority> authorities) {
permissionService.actionAllowed(authorities, Permission.NOTE);
return getNote(noteId);
}
private Note getNote(Long noteId) {
return repository.findById(noteId)
.orElseThrow(() -> new MyException("Note does not exist"));
}
The problem is that we should keep transactions as short as possible so I have to call my validation method permissionService.actionAllowed outside of the trasaction scope. How to do it in the best way?
I don't want to create additional public method with #Transactional... I don't have any idea

Related

I am getting internal server error dont konw reason for that

my code.....this was controller
controller
#Getmapping("/Transferabalance/{id}")
public List<Transaction> transfer(#PathVariable("Accbalance") long Accbalance) throws ResourceNotFoundException
{
List<Transaction> balance=Transservice.findByAccbalance(Accbalance);
if(balance==null)
throw new ResourceNotFoundException("NO BALANCE");
else
return balance;
}
This was service service
public List<Transaction> findByAccbalance(long Accbalance)
{
// TODO Auto-generated method stub
return transrepo.findByAccbalance(Accbalance);
}
transaction repository i used an query to retrive the elemnt from data base
public interface TransactionRepository extends CrudRepository<Transaction,Long>
{
#Query(value="select ACCBALANCE from TRANSACTION",nativeQuery = true)
List<Transaction> findByAccbalance(long Accbalance);
}
1st of all, CrudRepository itself provides you lots of methods to perform operation then why use JPQL though your query is simple.
you can simply use findByAccbalance(long Accbalance) that will return you expected result in your case.
List<Transaction> findByAccbalance(long Accbalance);
but if you wanna use Query then can you check with below Query:--
#Query(value="select c from TRANSACTION c")
List<Transaction> findByAccbalance(long Accbalance);

Inject custom OidcUser wrapper with #AuthenticationPrincipal

I'm using springboot security with Okta for authentication. I'm currently trying to create an abstract class implementing OidcUser to encapsulate some methods. For instance:
public abstract class OktaUser implements OidcUser {
public UUID getUserId() {
return UUID.fromString(Objects.requireNonNull(this.getAttribute("user_id")));
}
}
I normaly inject OidcUser (Working)
#GetMapping
public User currentUser(#AuthenticationPrincipal OidcUser oidcUser) {
UUID id = UUID.fromString(Objects.requireNonNull(oidcUser.getAttribute("user_id")));
return userService.getUser(id);
}
But I want to do it like this (Not working)
#GetMapping
public User currentUser(#AuthenticationPrincipal OktaUser oktaUser) {
return userService.getUser(oktaUser.getMicaUserId());
}
However, oktaUser is always null. Is there a way to register OktaUser as AuthenticationPrincipal?
I finally found a solution from this post
With the ControllerAdvice, I was able to build and return my custom OidcUser.
#ModelAttribute
public OktaUser oktaUser(#AuthenticationPrincipal OidcUser oidcUser) {
return oidcUser == null
? null
: new OktaUser(oidcUser.getAuthorities(), oidcUser.getIdToken(), oidcUser.getUserInfo());
}
And i can inject it like that
#GetMapping
public void getAll(#ModelAttribute OktaUser oktaUser) {
}
Take a look at the Spring Security guide:
https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/#mvc-authentication-principal
It walks through exactly this. You can even define your annotation like #MicaUser which would resolve your custom implementation.

Spring force #Cacheable to use putifAbsent instead of put

I've Spring cache implemented as below
#Component
public class KPCacheExample {
private static final Logger LOG = LoggerFactory.getLogger(KPCacheExample.class);
#CachePut(value="kpCache")
public String saveCache(String userName, String password){
LOG.info("Called saveCache");
return userName;
}
#Cacheable(value="kpCache")
public String getCache(String userName, String password){
LOG.info("Called getCache");
return "kp";
}
}
And Java Config file
#Configuration
#ComponentScan(basePackages={"com.kp"})
public class GuavaCacheConfiguration {
#Bean
public CacheManager cacheManager() {
GuavaCacheManager guavaCacheManager = new GuavaCacheManager("kpCache");
guavaCacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterAccess(2000, TimeUnit.MILLISECONDS).removalListener(new KPRemovalListener()));
return guavaCacheManager;
}
}
By default the spring uses put method in the cache interface to update/put values in the cache. How can I force the spring to use putifabsent method to be invoked, such that I can get null value if cache is missed or in other wards first request to the method with unique username and password should return null and subsequent request to that username and password should return username.
Well, looking through Spring's Cache Abstraction source, there does not appear to be a configuration setting (switch) to default the #CachePut to use the "atomic" putIfAbsent operation.
You might be able to simulate the "putIfAbsent" using the unless (or condition) attribute(s) of the #CachePut annotation, something like (based on the Guava impl)...
#CachePut(value="Users", key="#user.name" unless="#root.caches[0].getIfPresent(#user.name) != null")
public User save(User user){
return userRepo.save(user);
}
Also note, I did not test this expression, and it would not be "atomic" or portable using a different Cache impl. The expression ("#root.caches[0].get(#user.name) != null") maybe more portable.
Giving up the "atomic" property may not be desirable so you could also extend the (Guava)CacheManager to return a "custom" Cache (based on GuavaCache) that overrides the put operation to delegate to "putIfAbsent" instead...
class CustomGuavaCache extends GuavaCache {
CustomGuavaCache(String name, com.google.common.cache.Cache<Object, Object> cache, boolean allowNullValues) {
super(name, cache, allowNullValues);
}
#Override
public void put(Object key, Object value) {
putIfAbsent(key, value);
}
}
See the GuavaCache class for more details.
Then...
class CustomGuavaCacheManager extends GuavaCacheManager {
#Override
protected Cache createGuavaCache(String name) {
return new CustomGuavaCache(name, createNativeGuavaCache(name), isAllowNullValues());
}
}
See GuavaCacheManager for further details, and specifically, have a look at line 93 and createGuavaCache(String name).
Hope this helps, or at least gives you some more ideas.

How to define Spring Data Repository scope to Prototype?

I'm using Spring data jpa & hibernate for data access along with Spring boot. All the repository beans are singleton by default. I want to define the scope of all my repositories to Prototype. How can I do that?
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
Edit 1
The problem is related to domain object being shared in 2 different transactions which is causing my code to fail. I thought it is happening because repository beans are singleton. That's the reason I asked the question. Here is the detailed explanation of the scenario.
I have 2 entities User and UserSkill. User has 1-* relationship with UserSkills with lazy loading enabled on UserSkill relation.
In a UserAggregationService, I first make a call to fetch an individual user skill by id 123 which belongs to user with id 1.
public class UserAggregationService {
public List<Object> getAggregatedResults() {
resultList.add(userSkillService.getUserSkill(123));
//Throws Null Pointer Exception. See below for more details.
resultList.add(userService.get(1));
}
}
Implementation of UserSkillService method looks like
#Override
public UserSkillDTO getUserSkill(String id) {
UserSkill userSkill = userSkillService.get(id);
//Skills set to null avoid recursive DTO mapping. Dozer mapper is used
//for mapping.
userSkill.getUser().setSkills(null);
UserSkillDTO result = mapper.map(userSkill, UserSkillDTO.class);
return result;
}
In the call of user aggregation service, I call UserService to fetch userDetails. UserService code looks like
#Override
public UserDTO getById(String id) {
User user = userService.getByGuid(id);
List<UserSkillDTO> userSkillList = Lists.newArrayList();
//user.getSkills throws null pointer exception.
for (UserSkill uSkill : user.getSkills()) {
//Code emitted
}
....
//code removed for conciseness
return userDTO;
}
UserSkillService method implementation
public class UserSkillService {
#Override
#Transactional(propagation = Propagation.SUPPORTS)
public UserSkill get(String guid) throws PostNotFoundException {
UserSkill skill = userSkillRepository.findByGuid(guid);
if (skill == null) {
throw new SkillNotFoundException(guid);
}
return skill;
}
}
UserService method implementation:
public class UserService {
#Override
#Transactional(readOnly = true)
public User getByGuid(String guid) throws UserNotFoundException {
User user = userRepo.findByGuid(guid);
if (user == null) {
throw new UserNotFoundException(guid);
}
return user;
}
}
Spring boot auto configuration is used to instantiate entity manager factory and transaction manager. In the configuration file spring.jpa.* keys are used to connect to the database.
If I comment the below line of code, then I do not get the exception. I am unable to understand why change in the domain object is being affecting the object fetch in a different transaction.
userSkill.getUser().setSkills(null);
Please suggest If I have missed something.

Force Initialization of #ModelAttributes in Spring MVC 3.1

I am writing a wizard-like controller that handles the management of a single bean across multiple views. I use #SessionAttributes to store the bean, and SessionStatus.setComplete() to terminate the session in the final call. However, if the user abandons the wizard and goes to another part of the application, I need to force Spring to re-create the #ModelAttribute when they return. For example:
#Controller
#SessionAttributes("commandBean")
#RequestMapping(value = "/order")
public class OrderController
{
#RequestMapping("/*", method=RequestMethod.GET)
public String getCustomerForm(#ModelAttribute("commandBean") Order commandBean)
{
return "customerForm";
}
#RequestMapping("/*", method=RequestMethod.GET)
public String saveCustomer(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the customer data ];
return "redirect:payment";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String getPaymentForm(#ModelAttribute("commandBean") Order commandBean)
{
return "paymentForm";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String savePayment(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the payment data ];
return "redirect:confirmation";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String getConfirmationForm(#ModelAttribute("commandBean") Order commandBean)
{
return "confirmationForm";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String saveOrder(#ModelAttribute("commandBean") Order commandBean, BindingResult result, SessionStatus status)
{
[ Save the payment data ];
status.setComplete();
return "redirect:/order";
}
#ModelAttribute("commandBean")
public Order getOrder()
{
return new Order();
}
}
If a user makes a request to the application that would trigger the "getCustomerForm" method (i.e., http://mysite.com/order), and there's already a "commandBean" session attribute, then "getOrder" is not called. I need to make sure that a new Order object is created in this circumstance. Do I just have to repopulate it manually in getCustomerForm?
Thoughts? Please let me know if I'm not making myself clear.
Yes, sounds like you may have to repopulate it manually in getCustomerForm - if an attribute is part of the #SessionAttributes and present in the session, then like you said #ModelAttribute method is not called on it.
An alternative may be to define a new controller with only getCustomerForm method along with the #ModelAttribute method but without the #SessionAttributes on the type so that you can guarantee that #ModelAttribute method is called, and then continue with the existing #RequestMapped methods in the existing controller.

Resources