Simplifying CRUD Operations with Generics: Abstract CRUD Service
Tags: java, springboot, jpa
Published: 2023-06-15 00:46:58

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;
	}
}

```

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]);
	}
```

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);

```

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);
	}
```

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!