Get an estimate
Krzysztof
2020/12/30
Ever got that feeling, that you are writing the same thing over and over again? Logic behind your controllers and services are basically the same? That's a sign you can create more general approach with generics. Pros? You can keep your sanity and you keep your code DRY.
In this tutorial we will create generic controller and service that implements CRUD operations for all entities you want. Of course you can add more complex tasks in services, but here we will keep it simple.
First, we will create interface that will represent our entities in all other classes and expose crucial methods:
public interface GenericEntity<T> { // update current instance with provided data void update(T source); Long getId(); // based on current data create new instance with new id T createNewInstance(); }
@NoRepositoryBean public interface GenericRepository<T extends GenericEntity<T>> extends JpaRepository<T, Long> { }
public abstract class GenericService<T extends GenericEntity<T>> { private final GenericRepository<T> repository; public GenericService(GenericRepository<T> repository) { this.repository = repository; } public Page<T> getPage(Pageable pageable){ return repository.findAll(pageable); } public T get(Long id){ return repository.findById(id).orElseThrow( () -> new NotFound(id) ); } @Transactional public T update(T updated){ T dbDomain = get(updated.getId()); dbDomain.update(updated); return repository.save(dbDomain); } @Transactional public T create(T newDomain){ T dbDomain = newDomain.createNewInstance(); return repository.save(dbDomain); } @Transactional public void delete(Long id){ //check if object with this id exists get(id); repository.deleteById(id); } }
public abstract class GenericController<T extends GenericEntity<T>> { private final GenericService<T> service; public GenericController(GenericRepository<T> repository) { this.service = new GenericService<T>(repository) {}; } @GetMapping("") public ResponseEntity<Page<T>> getPage(Pageable pageable){ return ResponseEntity.ok(service.getPage(pageable)); } @GetMapping("/{id}") public ResponseEntity<T> getOne(@PathVariable Long id){ return ResponseEntity.ok(service.get(id)); } @PutMapping("") public ResponseEntity<T> update(@RequestBody T updated){ return ResponseEntity.ok(service.update(updated)); } @PostMapping("") public ResponseEntity<T> create(@RequestBody T created){ return ResponseEntity.ok(service.create(created)); } @DeleteMapping("/{id}") public ResponseEntity<String> delete(@PathVariable Long id){ service.delete(id); return ResponseEntity.ok("Ok"); } }
Ok, we have this fancy classes, but how to use them?
First, we create our Entity. Here we will use book as an example. All we have to do is to implement GenericEntity methods. Here we are using Data annotation from Lombok to generate getters, setters and other functions.
@Data @Entity public class Book implements Serializable, GenericEntity<Book> { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; private String author; @Lob private String description; private Date releaseDate; @Override public Long getId(){ return this.id; } @Override public void update(Book source) { this.title = source.getTitle(); this.author =source.getAuthor(); this.description = source.getDescription(); this.releaseDate = source.getReleaseDate(); } @Override public Book createNewInstance() { Book newInstance = new Book(); newInstance.update(this); return newInstance; } }
Then we will have to create repository bean:
public interface BookRepository extends GenericRepository<Book> { }
And finally, our book controller:
@RestController @RequestMapping("/api/book") public class BookController extends GenericController<Book> { public BookController(BookRepository bookRepository) { super(bookRepository); } }
We annotate it with RestController and RequestMapping with mapping we want. And that's it! You can create as many entities as you want!