How to force AbstractRoutingDataSource to redirect? - spring

I have an implementation of AbstractRoutingDataSource.
During a request, I need to extract data from 2 different databases:
https://github.com/fabricio-ruiz/test-abstract-routing-ds
#RestController
public class Controller {
private final CompanyRepository companyRepository;
private final UserRepository userRepository;
public Controller(CompanyRepository companyRepository, UserRepository userRepository) {
this.companyRepository = companyRepository;
this.userRepository = userRepository;
}
#GetMapping("/")
public String test(){
String value = "";
MultitenantContext.setCurrentTenant(Schema.company.name()); // This is the lookup key for the AbstractRoutingDataSource
value += countCompanies();
MultitenantContext.setCurrentTenant(Schema.user.name());
value += countUsers();
return value;
}
#Transactional(Transactional.TxType.REQUIRES_NEW)
private Long countUsers(){
return userRepository.count();
}
#Transactional(Transactional.TxType.REQUIRES_NEW)
private Long countCompanies(){
return companyRepository.count();
}
}
The AbstractRoutingDataSource use the current value of a ThreadLocal Object in MultitenantContext.
On the call of countUsers(), it throws an error because is trying to find the table users in the previous schema. Is there a way to force it to look again for the datasource?
Thanks in advance!
I've tried to include #Transactional(Transactional.TxType.REQUIRES_NEW) in the methods but it does not work.

Related

Mocked JPA repository bean is not returning assigned value from a mocked method call

I have a interface CalculatorService whose implementation contains business logic.
Interface:
public interface CalculatorService {
LoanWeb calculateSimpleLoan(LoanWeb loanWeb) throws Exception;
}
Implementation:
#Service
#AllArgsConstructor
public class CalculatorServiceImpl implements CalculatorService {
private final LoanRepository loanRepository; //this extends JpaRepository
private final PropertiesConfig propertiesConfig;
private final CalculatorUtility calculatorUtility;
private final LoanInfoRepository loanInfoRepository;
#Override
public LoanWeb calculateSimpleLoan(LoanWeb loanWeb) throws Exception {
//validation
if (loanWeb.getLoanTerm() == null || loanWeb.getLoanTerm() <= 0) throw new LeanPayException(ErrorCode.INVALID_INTEGER_ERROR.code, List.of("loanTerm"));
var payments = someCalculation();
Loan loan = loanRepository.findFirstByAmountAndRateAndPaymentsAndFrequency
(loanWeb.getLoanAmount(), loanWeb.getInterestRate(), payments, Frequency.MONTHLY); //loan is null???!!!
if (loan != null) return LoanWeb.builder() //this is false, I need it to be true!
.monthlyPayment(loan.getPmt())
.totalInterestPaid(loan.getLoanInfos().stream().mapToDouble(LoanInfo::getInterest).sum()).build();
}
}
JpaRepository:
#Repository
public interface LoanRepository extends JpaRepository<Loan, Long> {
Loan findFirstByAmountAndRateAndPaymentsAndFrequency(Double amount, Double rate, Integer payments, Frequency frequency);
}
Test class:
It looks like this:
#SpringBootTest(properties = {"spring.cloud.config.enabled: false", "logging.level.com.package.calculator: OFF"},
classes= {ObjectMapper.class, CalculatorServiceImpl.class, LoanRepository.class})
public class CalculatorServiceTest {
#Autowired private ObjectMapper objectMapper;
#Autowired
private CalculatorService calculatorService;
#MockBean(classes = LoanRepository.class) private LoanRepository loanRepository;
#MockBean private PropertiesConfig propertiesConfig;
#MockBean private CalculatorUtility calculatorUtility;
#MockBean private LoanInfoRepository loanInfoRepository;
#MockBean private TestService testService;
private Loan loan1; //this is not null, it is correctly loaded from a file.
#PostConstruct
public void init() throws IOException{
String jsonString =
IOUtils.toString(
Objects.requireNonNull(this.getClass().getResourceAsStream("/json/PreExistingLoan.json")), StandardCharsets.UTF_8);
this.loan1 = objectMapper.readValue(jsonString, Loan.class);
}
#Test
void calculateSimpleLoanWithPreExistingLoan() throws LeanPayException {
Mockito.doReturn(this.loan1).when(loanRepository).findFirstByAmountAndRateAndPaymentsAndFrequency( ArgumentMatchers.anyDouble(), ArgumentMatchers.anyDouble(),
ArgumentMatchers.anyInt(), ArgumentMatchers.any(Frequency.class));
LoanWeb result = this.calculatorService.calculateSimpleLoan(LoanWeb.builder().loanTerm(10).simpleLoanTerm(SimpleLoanTerm.MONTH).build());
Assertions.assertEquals(result.getTotalInterestPaid(), 10);
Assertions.assertEquals(result.getMonthlyPayment(), 107.0);
}
}
Application context is brought up, test runs, but mocked repo method call returns null in service. Where am I wrong? I tried million things, this is code from beginning, don't have any ideas.
ArgumentMatchers.anyDouble() matches any double (primitive) or non-null Double (boxed) value.
If you happen to pass a null value, it won't be matched.
You have a couple of options:
use ArgumentMatchers.any() which matches anything, including nulls
use ArgumentMatchers.eq(null) on the argument where the null is passed
modify your test to pass a non-null value

#PreAuthorize how do i add a parameter into the expression

Maybe somebody has an idea
I have an abstract controller providing me the endpoints i need.
In the #PreAuthorized i would execute the check if the user has the required roles.
Problem is that i have only one function and i want to check which endpoint is currently evaluated.
This is the code:
public abstract class CoreController<T> {
private final JpaRepository repository;
private final CoreService service;
public String endpoint;
private String view;
public CoreController(CoreService service, JpaRepository repository, String endpoint, String view) {
this.service=service;
this.repository = repository;
this.endpoint=endpoint;
this.view=view;
}
#PreAuthorize("#checkAccess.isAllowedToGet(#endpoint)")
#RequestMapping(method = RequestMethod.GET, value = "/get")
public ResponseEntity<CrudPage<Map<String, Object>>> get(CoreCriteria criteria) {
criteria.setView(view);
CrudPage<Map<String, Object>> data = service.getPage(criteria);
return ResponseEntity.ok(data);
}
The problem is the endpoint is always null when the function is called.
How should i change the expression to make this work.

What is the CLI command to view inside of a set data type in redis

I user a CRUDRepository in my spring data redis project to persist a redis hash in my redis cluster. i have rest api written to persist and get thte values of the data. this works fine.
however my entity annotated with RedisHash is being saved as a set / and i am not able to look inside the value using redis cli.
how do i look inside a set data type(without popping) in redis cli
i looked at redis commands page https://redis.io/commands#set
i only get operations which can pop value . i neeed to simply peek
EDIT:
to make things clearer, i am using spring crudrepo to save the user entity into redis data store. the user entity gets saved as a set data type.
when i query back the user details, i can see entire details of the user
{
userName: "somak",
userSurName: "dattta",
age: 23,
zipCode: "ah56h"
}
i essentially want to do the same using redis cli... but all i get is
127.0.0.1:6379> smembers user
1) "somak"
how do i look inside the somak object.
#RestController
#RequestMapping("/immem/core/user")
public class UserController {
#Autowired
private UserRepository userRepository;
#RequestMapping(path = "/save", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(HttpStatus.OK)
public void saveUserDetails() {
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
}
#RequestMapping(path="/get/{username}", method = RequestMethod.GET, produces = "application/json")
public User getUserDetails(#PathVariable("username") String userName) {
return userRepository.findById(userName).get();
}
}
#Repository
public interface UserRepository extends CrudRepository<User, String>{
}
#RedisHash("user")
public class User {
private #Id String userName;
private #Indexed String userSurName;
private #Indexed int age;
private String zipCode;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserSurName() {
return userSurName;
}
public void setUserSurName(String userSurName) {
this.userSurName = userSurName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
I don't understant your descr with your problem, but I understand your title.
In redis set, the member is always string type.
I hope you can offer more info about UserRepository.save:
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
And you can check your redis data and check data type when rest api invoked.

Best Approach to load application.yml in spring boot application

I am having Spring Boot application and having application.yml with different properties and loading as below.
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
//Getters & Setters
}
My Service or Controller Class in which I get this properties like below.
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
Map<String,String> mapProperty = applicationPropHolder.getMapProperty();
String userName = mapProperty.get("user.name");
List<String> listProp = applicationPropHolder.getMyListProperty();
}
}
My application.yml
spring:
profile: dev
applicationprops:
mapProperty:
user.name: devUser
myListProperty:
- DevTestData
---
spring:
profile: stagging
applicationprops:
mapProperty:
user.name: stageUser
myListProperty:
- StageTestData
My questions are
In my Service class i am defining a variable and assigning Propertymap for every method invocation.Is it right appoach?
Is there any other better way I can get these maps without assigning local variable.
There are three easy ways you can assign the values to instance variables in your bean class.
Use the #Value annotation as follows
#Value("${applicationprops.mapProperty.user\.name}")
private String userName;
Use the #PostConstruct annotation as follows
#PostConstruct
public void fetchPropertiesAndAssignToInstanceVariables() {
Map<String, String> mapProperties = applicationPropHolder.getMapProperty();
this.userName = mapProperties.get( "user.name" );
}
Use #Autowired on a setter as follows
#Autowired
public void setApplicationPropHolder(ApplicationPropHolder propHolder) {
this.userName = propHolder.getMapProperty().get( "user.name" );
}
There may be others, but I'd say these are the most common ways.
Hope, you are code is fine.
Just use the below
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
public String getUserName(){
return mapProperty.get("user.name");
}
public String getUserName(final String key){
return mapProperty.get(key);
}
}
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
final String userName = applicationPropHolder.getUserName();
final List<String> listProp = applicationPropHolder.getMyListProperty();
}
}

How to add an object to existing List using Spring Mongo db?

this is class A
#Document
class User{
private String id ;
private String name;
#Dbref
private List<Socity> Socitys;
}
and this is class Socity
#Document
class Socity{
private String id ;
private String name;
}
and this is the add user function
public User addUser(User user) {
List<Socity> socity = new ArrayList<>();
user.setsocitys (socity );
return userRepository.save(user);
}
I want to add a socity to an existing user
i try this but it doesn't work
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run (App.class, args);
SocityDao SDao = ctx.getBean(SocityDao .class);
UserRepository userRepository = ctx.getBean(UserRepository.class);
User u = userRepository.findOne("");
Socity s = new Socity("soc1");
SDao .addSocity(e);
u.getSocitys().add(e);
}
this is the rest service
#RequestMapping(value = "up/{id}", method = RequestMethod.POST ,produces =
"application/json")
public User addSocityToUser(#RequestBody Socity, #PathVariable String id)
{
return SocityDAO.addSocityToUser(e, id);
}
In the end of your code add userRepository.save(u) to persist your changes to the DB.
As long as it as an ID (because it is a persisted object) it will be updated. if it has no ID it will be saved as a new object in the DB.
Looks like you forget to save user, after you add new socity. Please check my updates
#Document
public class Socity {
private String id ;
private String name;
}
#Document
public class User {
private String id;
private String name;
#DBRef
private List<Socity> socitys = new ArrayList<>();
}
Then you don't need to use your addUser() method. When you want to add new user just use
userRepository.save(user);
You also need two repositories
public interface SocityRepository extends MongoRepository<Socity, String> {
}
and
public interface UserRepository extends MongoRepository<User, String> {
}
And what you need in the main method
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run (App.class, args);
UserRepository userRepository = ctx.getBean(UserRepository.class);
SocityRepository socityRepository = ctx.getBean(SocityRepository.class);
User u = userRepository.findOne("");
Socity s = socityRepository.save(new Socity("soc1"));
u.getSocitys().add(s);
userRepository.save(u);
}
It is always better to Use the MongoTemplate to write an update Query and use Push function to add to a list.
#Autowired
private MongoTemplate mongoTemplate;
function(String id, Socity socity){
Query query = new Query(Criteria.where("id").is(id));
Update update = new Update();
update.push("Socitys", socity);
mongoTemplate.updateFirst(query, update, User.class);
}

Resources