Spring service class contains too many finder methods - java

I am using Spring framework and Spring Data JPA to develop an application. Below are one of the repository interface and service class.
public interface UserRepository extends JpaRepository<User, Long>
User findByName(String name);
User findByEmail(String email);
}
public class DefaultUserService implements UserService {
#Inject protected UserRepository userRepo;
#Override
public User getUserById(Long id) {
return userRepo.findOne(id);
}
#Override
public User getUserByName(String name) {
return userRepo.findByName(name);
}
#Override
public User getUserByEmail(String email) {
return userRepo.findByEmail(email);
}
}
As stated by many experts, service layer design should be coarse grained and focused on application operations. Looking at the service class above, I believe that is not a good design as it directly expose all finder methods from the repository. Since all 3 service methods above are returning the same object type (User), I would want to expose only one finder method instead of three that able to encapsulate all finder logic.
public class DefaultUserService implements UserService {
#Inject protected UserRepository userRepo;
// what would be the arguments and logic for this method.
#Override
public User getUser() {
}
}
I appreciate if anyone can point me the solution on how to solve this design issue?

I think the design is not so bad, I mean I was seeing that kind of approach several times, in fact you have several finder methods but each one use different property to obtain the User, if you want to make a service method which encapsulate the logic to retrieve the user I would suggest something like this.
public class DefaultUserService implements UserService {
#Inject protected UserRepository userRepo;
enum UserFindEnum{
ID, EMAIL, NAME;
}
public User getUser(UserFindEnum e, Object obj){
switch(e.ordinal()){
case 0:
return userRepo.findOne(obj);
case 1:
return userRepo.findByName(obj);
case 2:
return userRepo.findByEmail(obj);
default:
break;
}
}
}
I mean you need to know which property you will use to find the User so at least one parameter need to be sent to the service layer so getUser() it is not enough. Probably using some kind of logic as above you will have only one service method and needed logic within it.

Related

How can I create a personalized method using JPA?

I've been trying to create a personalized get request to find an user by email using Jpa Repository and a Controller class but it doesn't seem to work. Everytime I make a request using an email that exists (!!!!) on the database my method can't find it!
this is my interface:
#Repository
#Transactional
public interface TheUserRepository extends JpaRepository<User, Integer> {
User findByEmail(#Param("email") String email);
}
this is the controllers method:
#GetMapping(value="emails/{email}")
public ResponseEntity<User> findByEmail(String email){
User entity=repo.findByEmail(email);
if(entity!=null) {
return ResponseEntity.ok().body(entity);
}
else {
return ResponseEntity.noContent().build();
}
}
I have tried many other combinations but none of them are working, if anyone has any idea of how to do this request I would appreciate it so much!

Secure method of spring data rest repository

I am currently developping a REST API server based on Spring Boot. Thanks to Spring Data Rest, the 10-ish entities can easily have their own controller via a simple repository (#RepositoryRestResource plus JpaRepository and JpaSpecificationExecutor). Now i need to integrate the security control with #PreAuthorize.
The question here is which method should I put the annotation on to restrain GET / POST / etc. ?
For example, if I limit the permission of delete, does it affect similarly on deleteById, deleteInBatch, deleteAll? I see in the documentation the annotation of exported is put on deleteById and delete without any further explanation, which confuses me.
For example, if I limit the permission of delete, does it affect similarly on deleteById, deleteInBatch, deleteAll?
To the best of my knowledge: no. Check this sample code where searches are authorized, but deletion is strictly limited to admins only:
public interface RecordRepository<T extends Record> extends MongoRepository<T, String> {
// paginated queries
#RestResource(path = "names", rel = "name")
public Page<T> findByName(#Param("name") String name, Pageable pageable);
#RestResource(path = "types", rel = "types")
public Page<T> findByTypeIn(#Param("type") List<String> types, Pageable pageable);
// restrict delete operations to administrators only
#PreAuthorize("hasRole('ADMIN')")
#Override
void deleteById(String id);
#PreAuthorize("hasRole('ADMIN')")
#Override
void delete(T entity);
#PreAuthorize("hasRole('ADMIN')")
#Override
void deleteAll(Iterable<? extends T> records);
#PreAuthorize("hasRole('ADMIN')")
#Override
void deleteAll();
}
That being said, if your purpose is to restrict deletion to admins only, you can extend WebSecurityConfigurerAdapter and configure it to block all http DELETE requests:
public class WebSecurityBaseConfiguration extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.DELETE).hasRole("ADMIN");
}
}
Note that this is a quick and dirty copy paste that may not work out of the box (you will probably need to configure a role hierarchy).

Spring IOC DI with runtime parameters

I'm relativity new to IOC and DI, so I'm guessing that I am missing some high-level design principle here, but I cannot figure out how to get my architecture working.
I have a REST API endpoint that accepts two pieces of POST data: customer ID, and Type ID. The rest api then needs to return a set of data for that specific customer/type combo.
Here is a crude picture of what I am doing:
The controller is taking the entity IDs passed in via post data, and via a JPA repository getting the appropriate Entities for them.
I then construct a data generator object (that takes the entities as constructor parameters), and use that to handle all of the data gathering for the API.
The Problem: because the Data Generator takes the two dynamic constructor parameters, it cannot be DI'ed into the Controller, but instead must be made with new. Inside of the Data Generator, however, I need access to JPA repositories. The only way to get access to these repositories is via DI. I cannot DI however, as the object was new'ed not DI'ed by the IOC container.
Is there a way to architect this so that I don't have this problem? Am I breaking some rule regarding IOC? Do I have wrong assumptions somewhere? Any advice is appreciated.
Thanks!
Edit: Pseudo code for Data Generator
public class DataGenerator {
private Customer customer;
private Type type
public DataGenerator(Customer customer, Type type) {
this.cusomter = customer;
this.type = type;
}
public generateData() {
if(customer == x && type == y) {
//JPA REPOSITORY QUERY
} else {
//DIFFERENT JPA REPOSITORY QUERY
}
}
}
I think you may have gotten confused somewhere along the line. You should have a Service that hits your repositories, and provide the information to the controller. One crude setup would be something like this.
#Controller
public MyController {
#AutoWired
private DataService dataService;
#RequestMapping(value = "/", method = RequestMethod.GET)
private DataGenerator readBookmark(#PathVariable Long customerId, #PathVariable Integer typeId) {
return dataService.getData(customerId, typeId);
}
}
#Service
public class DataService {
#AutoWired
private JPARepository repository;
public DataGenerator getData(long customerId, int typeId) {
Type typeDetails = repository.getType(typeId);
Customer customerDetails = repository.getCustomer(customerId);
return new DataGenerator(customerDetails, typeDetails);
}
}

Populate model with default attributes

I am building a website using the Spring-MVC framework. In the template I want to include some 'generic' information. Generic in the way that this is not specific to the current controller, but used on every page. This can be something like a generated menu, the number of minutes a user is logged in or the current temperature on the north pole.
What I am currently doing
I've made an abstract class with a method to populate the model with the default values. My controllers extend this class and I call this method in every (#RequestMapping-ed) method, which is a downside. It feels kinda hacky and is not very flexible.
#Component
public abstract class AbstractBaseController {
#Autowired private SomeService someService;
protected void populateModel(Model model) {
model.addAttribute("value", someService.getGenericValue());
}
}
#Controller
public class HomeController extends AbstractBaseController {
#RequestMapping("/") public String index(Model model) {
populateModel(model);
return "home";
}
}
My question
How should I provide these generic attributes to my view. Is there a better way than what I am doing now?
(I am using Spring/Spring-MVC 4, JavaConfig, Thymeleaf with thymeleaf-layout-dialect)
Okay, so I actually wasn't that hard. You can annotate a method with #ModelAttribute which makes it available in the template.
#Component
public abstract class AbstractBaseController {
#Autowired private SomeService someService;
#ModelAttribute("value")
protected String getValue() {
return someService.getGenericValue());
}
}
Mind you, this method will always be called. Also when ${value} is not called in the template. So it is not more 'flexible' than the method I described in the question.

Making transactional calls to the DB when using UserDetailsService

I have a User class marked as entity which also implements UserDetails. I want to be able to grab some properties of a certain user, and based on their values, to return specific roles in the getAuthorities method. Many of those properties are however lazy-loaded and require a Hibernate transaction.
I tried anything from making the user class #Transactional to making the UserDetailsService and RememberMeService which I use #Transactional. None of those works!
All of my other DAO and Service classes mapped as transactional work (and they are just simple classes - no other annotations besides a #Transactional on top)
UPDATE: This is the overriden getUserDetails in class User
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
GrantedAuthority auth = new GrantedAuthority() {
#Override
public String getAuthority() {
// TODO Auto-generated method stub
return "ROLE_USER";
}
};
ArrayList<GrantedAuthority> result = new ArrayList<GrantedAuthority>();
if (options.size() > 0) {
for (Option o : options) {
result.add(createAuthority(Option.getStringType(o.type)));
}
}
result.add(auth);
return result;
}
The UserDetails is not a Spring bean and thus is Spring is not going to look for #Transactional on it.
I would need to see a stack trace but what I'm assuming is that getAuthorities() is being called by another UserDetailsService method directly, i.e., not going through the transactional proxy.
Inject the PlatformTransactionManager into your UserDetailsService and use a TransactionTemplate in getAuthorities() to wrap your DB accessing code in a transaction.

Categories