Search code examples
spring-bootspring-repositories

Spring Boot - Create Generics Repositories


I have many services in my web application that do a classic CRUD operations, theses are Parameters section. In order to avoid creating for each entity class, a repository interface, I want to create a generic repository. I tried the code below but that only works if I have one controller.

public class BaseController<T extends BaseEntity> {

    @Autowired
    protected JpaRepository<T, Integer> dao;
}
@RestController
@RequestMapping("matieres")
@Api(value = "Matieres", tags = {"Parametrages"})
public class MatiereController extends BaseController<Matiere> {

    @GetMapping
    public Page<Matiere> find(
            @RequestParam(defaultValue = "0", required = false, name="page") Integer page,
            @RequestParam(defaultValue = "20", required = false, name="size") Integer size) {
        return this.dao.findAll(PageRequest.of(page, size));
    }

    @PostMapping
    public ResponseEntity<Matiere> create(@RequestBody Matiere matiere) {
        return ResponseEntity.ok(this.dao.save(matiere));
    }
}

Solution

  • Unless you register your repos as Spring beans the Spring couldn't work with them. So first you should create repo interfaces (

    public interface UserRepo extends JpaRepository<User, Long> {}
    
    public interface PersonRepo extends JpaRepository<Person, Long> {}
    

    But there is a good news - you can implement all typical (CRUD) methods in the abstract controller only, for example:

    public abstract class AbstractController<T> {
    
        protected final JpaRepository<T, Long> repo;
    
        public AbstractController(JpaRepository<T, Long> repo) {
            this.repo = repo;
        }
    
        @GetMapping
        public List<T> getAll() {
            return repo.findAll();
        }
    
        @GetMapping("/{id}")
        public ResponseEntity getOne(@PathVariable("id") Long id) {
            return repo.findById(id)
                    .map(ResponseEntity::ok)
                    .orElse(ResponseEntity.notFound().build());
        }
    
        @PostMapping
        public T create(@RequestBody T entity) {
            return repo.save(entity);
        }
    
        @PatchMapping("/{id}")
        public ResponseEntity update(@PathVariable("id") Long id, @RequestBody T source) {
            return repo.findById(id)
                    .map(target -> { BeanUtils.copyProperties(source, target, "id"); return target; })
                    .map(repo::save)
                    .map(ResponseEntity::ok)
                    .orElse(ResponseEntity.notFound().build());
        }
    
        @DeleteMapping("/{id}")
        public ResponseEntity delete(@PathVariable("id") Long id) {
            return repo.findById(id)
                    .map(entity -> { repo.delete(entity); return entity; })
                    .map(t -> ResponseEntity.noContent().build())
                    .orElse(ResponseEntity.notFound().build());
        }
    }
    

    Then just register your concrete controllers to get working with all your entities:

    @RestController
    @RequestMapping("/people")
    public class PersonController extends AbstractController<Person> {
        public PersonController(PersonRepo repo) {
            super(repo);
        }
    }
    
    @RequestMapping("/users")
    public class UserController extends AbstractController<User> {
        public UserController(UserRepo repo) {
            super(repo);
        }
    }
    

    Demo: sb-generic-controller-demo.

    P.S. Of cause this code has a demo purpose. In the real project you should move your business logic to the transactional service layer.