DEV Community

Raja
Raja

Posted on

Simplifying CRUD Operations with Generics: Abstract CRUD Service

Let us explore how the intelligent use of generics can help us avoid writing repetitive boilerplate code. We'll demonstrate this by creating an abstract CRUD (Create, Read, Update, Delete) service that allows developers to enhance their productivity and concentrate more on the essential business logic. Let's get started!

Step 1: Understanding the Problem
When developing software, it's common to encounter repetitive code patterns, particularly when implementing CRUD operations. These operations often involve similar structures and functionalities that can be generalized. To address this issue, we can leverage the power of generics to create an abstract CRUD service that eliminates the need for redundant code.

This helps us to write a simplified service class to focus on business logic only. example as below..


@Service
@RequiredArgsConstructor
public class MyService extends AbstractCrudService<Employee, EmployeeEntity, Integer>{
    private final EmployeeRepository employeeRepository;
    @Override
    protected JpaRepository<EmployeeEntity, Integer> getJpaRepository() {
        return employeeRepository;
    }

    @Override
    protected Optional<EmployeeEntity> findUnique(Employee model) {
        return employeeRepository.findByRegistrationNumber(model.getRegistrationNumber());
    }

    @Override
    public Employee preCreate(Employee model) {
        // Generate and assign registration number here
        model.setRegistrationNumber(generate(16));
        return model;
    }
}

Enter fullscreen mode Exit fullscreen mode

How to achieve this?

let us define an abstract class with the following properties and initialization. This is required to determine the model and entity classes in the runtime.

@Service
public abstract class AbstractCrudService<Model, Entity, ID> implements CrudService<Model, Entity, ID>, 
        CrudExtnService<Model, Entity, ID>{

    @Autowired
    private ModelMapper modelMapper;
    private Class<Model> modelClass;
    private Class<Entity> entityClass;

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public AbstractCrudService() {
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Type[] typeArgs = type.getActualTypeArguments();

        this.modelClass = ((Class) typeArgs[0]);
        this.entityClass = ((Class) typeArgs[1]);
    }
Enter fullscreen mode Exit fullscreen mode

now let us define some abstract methods that must be implemented by the services. this is required get the jparepository and finding the unique records.

    protected abstract JpaRepository<Entity, ID> getJpaRepository();

    protected abstract Optional<Entity> findUnique(Model model);

Enter fullscreen mode Exit fullscreen mode

the below generic methods would provide All the CRUD operations..

    @Override
    public List<Model> findAll() {
        return getJpaRepository().findAll().stream().map(e -> onList(toDto(e), e)).toList();
    }

    @Override
    public Optional<Model> getById(ID id) {
        return toDto(getJpaRepository().findById(id));
    }

    @Override
    public Model save(Model model) {
        Optional<Entity> dbInstance = findUnique(model);
        if (dbInstance.isPresent()) {
            return update(model, dbInstance.get());
        } else {
            return create(model);
        }
    }

    @Override
    public void delete(ID id) {
        getJpaRepository().deleteById(id);
    }

    private Optional<Model> toDto(Optional<Entity> entity) {
        if (entity.isPresent()) {
            Entity e = entity.get();
            return Optional.of(onGet(modelMapper.map(e, getModelClass()), e));
        } else
            return Optional.empty();
    }

    private Model toDto(Entity entity) {
        return modelMapper.map(entity, getModelClass());
    }

    private Model create(Model model) {
        model = preCreate(model);
        Entity entity = modelMapper.map(model, getEntityClass());
        getJpaRepository().save(entity);
        return postCreate(toDto(entity), entity);
    }

    private Model update(Model model, Entity entity) {
        model = preUpdate(model);
        modelMapper.map(model, entity);
        getJpaRepository().save(entity);
        return postUpdate(toDto(entity), entity);
    }
Enter fullscreen mode Exit fullscreen mode

you can define an interface with the default methods (as hooks) of preUpdate, preCreate methods etc and use this methods to perform custom operations before saving, querying the data etc.,

By following this tutorial, you've learned how to harness the power of generics to create an abstract CRUD service. This approach allows you to eliminate repetitive boilerplate code and focus more on implementing the core business logic of your application. As a result, your development process becomes more efficient and maintainable. Experiment with generics in your future projects and unlock the potential for increased productivity!

Top comments (0)