This is very possible! I am probably very late to the party. But this will certainly help someone in the future. Here is a complete solution that works like a charm!
Create BaseEntity
class for your entities as follows:
@MappedSuperclass
public class AbstractBaseEntity implements Serializable{
@Id @GeneratedValue
private Long id;
@Version
private int version;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public AbstractBaseEntity() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// getters and setters
}
Create a generic JPA Repository interface for your DAO persistence as follows:
NB. Remember to put the @NoRepositoryBean
so that JPA will not try to find an implementation for the repository!
@NoRepositoryBean
public interface AbstractBaseRepository<T extends AbstractBaseEntity, ID extends Serializable>
extends JpaRepository<T, ID>{
}
Create a Base Service class that uses the above base JPA repository. This is the one that other service interfaces in your domain will simply extend as follows:
public interface AbstractBaseService<T extends AbstractBaseEntity, ID extends Serializable>{
public abstract T save(T entity);
public abstract List<T> findAll(); // you might want a generic Collection if u prefer
public abstract Optional<T> findById(ID entityId);
public abstract T update(T entity);
public abstract T updateById(T entity, ID entityId);
public abstract void delete(T entity);
public abstract void deleteById(ID entityId);
// other methods u might need to be generic
}
Then create an abstract implementation for the base JPA repository & the basic CRUD methods will also be provided their implementations as in the following:
@Service
@Transactional
public abstract class AbstractBaseRepositoryImpl<T extends AbstractBaseEntity, ID extends Serializable>
implements AbstractBaseService<T, ID>{
private AbstractBaseRepository<T, ID> abstractBaseRepository;
@Autowired
public AbstractBaseRepositoryImpl(AbstractBaseRepository<T, ID> abstractBaseRepository) {
this.abstractBaseRepository = abstractBaseRepository;
}
@Override
public T save(T entity) {
return (T) abstractBaseRepository.save(entity);
}
@Override
public List<T> findAll() {
return abstractBaseRepository.findAll();
}
@Override
public Optional<T> findById(ID entityId) {
return abstractBaseRepository.findById(entityId);
}
@Override
public T update(T entity) {
return (T) abstractBaseRepository.save(entity);
}
@Override
public T updateById(T entity, ID entityId) {
Optional<T> optional = abstractBaseRepository.findById(entityId);
if(optional.isPresent()){
return (T) abstractBaseRepository.save(entity);
}else{
return null;
}
}
@Override
public void delete(T entity) {
abstractBaseRepository.delete(entity);
}
@Override
public void deleteById(ID entityId) {
abstractBaseRepository.deleteById(entityId);
}
}
How to use the above abstract entity
, service
, repository
, and implementation
:
Example here will be a MyDomain
entity. Create a domain entity that extends the AbstractBaseEntity
as follows:
NB. ID
, createdAt
, updatedAt
, version
, etc will be automatically be included in the MyDomain
entity from the AbstractBaseEntity
@Entity
public class MyDomain extends AbstractBaseEntity{
private String attribute1;
private String attribute2;
// getters and setters
}
Then create a repository
for the MyDomain
entity that extends the AbstractBaseRepository
as follows:
@Repository
public interface MyDomainRepository extends AbstractBaseRepository<MyDomain, Long>{
}
Also, Create a service
interface for the MyDomain
entity as follows:
public interface MyDomainService extends AbstractBaseService<MyDomain, Long>{
}
Then provide an implementation for the MyDomain
entity that extends the AbstractBaseRepositoryImpl
implementation as follows:
@Service
@Transactional
public class MyDomainServiceImpl extends AbstractBaseRepositoryImpl<MyDomain, Long>
implements MyDomainService{
private MyDomainRepository myDomainRepository;
public MyDomainServiceImpl(MyDomainRepository myDomainRepository) {
super(myDomainRepository);
}
// other specialized methods from the MyDomainService interface
}
Now use your `MyDomainService` service in your controller as follows:
@RestController // or @Controller
@CrossOrigin
@RequestMapping(value = "/")
public class MyDomainController {
private final MyDomainService myDomainService;
@Autowired
public MyDomainController(MyDomainService myDomainService) {
this.myDomainService = myDomainService;
}
@GetMapping
public List<MyDomain> getMyDomains(){
return myDomainService.findAll();
}
// other controller methods
}
NB. Make sure that the AbstractBaseRepository
is annotated with @NoRepositoryBean
so that JPA
does not try to find an implementation for the bean.
Also the AbstractBaseServiceImpl
must be marked abstract, otherwise JPA will try to autowire all the children daos of the AbstractBaseRepository
in the constructor of the class leading to a NoUniqueBeanDefinitionException
since more than 1 daos (repository) will be injected when the bean is created!
Now your service
, repository
, and implementations
are more reusable. We all hate boilerplate!
Hope this helps someone.