SpringBoot (7) Spring Data JPA (2). Creating a custon Repository for several Entities

0. Introduction

When a lot of entities are used, many operations in a repository are the same and a lot of boilerplate code is produced. This idea of using a Repository for many entities has been rated as heretical for the Spring philosophy.

But for the sake of simplicity, I'll be using an interface and a class implementing this interface for a reusable Repository.

1. The IBaseRepositoryInterface

Here is the interface:

package ximo.repositories;

import java.util.List;

import jakarta.transaction.Transactional;
import ximo.xotherapps.model.base.Base;


public interface IBaseRepository {
    
    public <S extends Base> S findEdu( Class<S> clazz, long id) ;
    
    public <S extends Base> S findByDescriptionEdu( Class<S> clazz, String description) ;
        
    @Transactional
    public void insertEdu (Base entity) ;
    
    
    @Transactional
    public Base updateEdu (Base entity) ;
    
    @Transactional 
    public Base persistOrUpdate (Base entity) ;
    
    @Transactional 
    public Base persistOrUpdate (Base entity, boolean removeEntitiesWithEstatNotZero) ;
    
    @Transactional
    public void deleteEdu (Base entity);
    
    @Transactional
    public void flushAndClearEdu ();
    
    
    public <S extends Base> List<S> findByJQL( Class<S> clazz, String JQL) ;
    
    public <S extends Base> S findOneByJQL( Class<S> clazz, String JQL) ;
    
}


2. The IBaseRepositoryInterface

Here is class that implements the interface:

package ximo.repositories;

import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.stereotype.Repository;

import jakarta.persistence.EntityManager;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;

import ximo.xotherapps.model.base.Base;


@Repository
public class BaseRepositoryImplementation implements IBaseRepository{
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public <S extends Base> S findEdu( Class<S> clazz, long id) {
        return this.entityManager.find(clazz, id);
    }
    
    @Override
    public <S extends Base> S findByDescriptionEdu( Class<S> clazz, String description) {
        List <S> lstObjects = null;
        
        S newBase = null;
        try {
            newBase = clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        String fldDescription= newBase.ggetDescriptionField().trim();
        
        String hql="select o from " + newBase.getClass().getSimpleName() + " o where o."+ fldDescription + " $OPERATOR$ :pDescription";
        String operator="like";
        if (newBase.iisDescriptionANumber()) operator = "=";
        hql= hql.replace("$OPERATOR$", operator);
        
        if (newBase.iisFilteredByUser() ) hql=hql + " and o.lastUser='" + newBase.getLastUser().trim() + "'";
        
        System.out.println("findObjectDescription:"+hql+ "    "+ description);
        try {
                                
            lstObjects = this.entityManager.createQuery(hql)
                    //.setParameter("pDescription", obj.getDescription())
                    .setParameter("pDescription", description)
                    .getResultList();
            
            if (lstObjects.size() == 1){
                newBase = lstObjects.get(0);
            }    
        
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return newBase;
        
    }
    
    @Override
    @Transactional
    public void insertEdu (Base entity) {
        this.entityManager.persist(entity);
    }
    
    @Override
    @Transactional
    public Base updateEdu (Base entity) {
        return this.entityManager.merge(entity);
    }
    
    @Transactional 
    public Base persistOrUpdate (Base entity) {
        Base instance=null;
        Class<Base> clazz= (Class<Base>) entity.getClass();
        if (entity.getId()==null) {
            this.entityManager.persist(entity);
            instance=findByDescriptionEdu(clazz, entity.getDescription());
        }
        else {
            this.entityManager.detach(entity);
            instance=this.entityManager.merge(entity);
        }
        return instance;
    }
    
    //To avoid the error:"detached entity passed to persist: ximo.xotherapps.model.rrhhv3.RHConcepte" when deleting an enum
    private  void recursiveDeleteEstatNotZero (Base entity, Long id) {
        for(Iterator<Field> iFld=FieldUtils.getAllFieldsList(entity.getClass()).iterator(); iFld.hasNext();) {
            Field fld=iFld.next();
            if (List.class.isAssignableFrom(fld.getType())) {
                List<?> fldCol=null;    
                try {
                    fldCol = (List<?>)FieldUtils.readField(fld, entity , true);
                    for(Iterator<?> iObj=fldCol.iterator(); iObj.hasNext();) {
                        Object obj=iObj.next();
                        if( Base.class.isAssignableFrom(obj.getClass())) {
                            if (((Base)obj).getEstat()!=0) {
                                
                                iObj.remove(); //Remove from list
                                //@see https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
                                for(Field fldP: FieldUtils.getFieldsWithAnnotation(obj.getClass(), ManyToOne.class)) {
                                    FieldUtils.writeField(obj, fldP.getName(), null, true);
                                }
                                
                                
                            } else {
                                recursiveDeleteEstatNotZero((Base) obj, id);    
                            }
                        }
                    }    
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }    
        }
    }    
    @Transactional 
    public Base persistOrUpdate (Base entity, boolean removeEntitiesWithEstatNotZero) {
        if (removeEntitiesWithEstatNotZero) this.recursiveDeleteEstatNotZero(entity, entity.getId());
        Base instance=null;
        Class<Base> clazz= (Class<Base>) entity.getClass();
        if (entity.getId()==null) {
            this.entityManager.persist(entity);
            instance=findByDescriptionEdu(clazz, entity.getDescription());
        }
        else {
            Base instance1= this.findEdu(entity.getClass(), entity.getId());
            instance1=entity;
            instance=this.entityManager.merge(instance1);
        }
        return instance;
    }
    
    @Override
    @Transactional
    public  void deleteEdu (Base entity) {
        this.entityManager.remove(entity);
    }
    
    @Override
    @Transactional
    public  void flushAndClearEdu () {
        this.entityManager.flush();
        this.entityManager.clear();
    }
    
    
    @Override
    @SuppressWarnings("unchecked")
    public <S extends Base> List<S> findByJQL( Class<S> clazz, String JQL) {
        List <S> lstObjects = null;
        String hql= "SELECT a FROM "+ clazz.getSimpleName() + " a  WHERE " + JQL;
        lstObjects = this.entityManager.createQuery(hql)
                .getResultList();
        return lstObjects;
    }
    
    public <S extends Base> S findOneByJQL( Class<S> clazz, String JQL) {
        S obj=null;
        List <S> lstObjects = findByJQL( clazz, JQL);
        if (lstObjects!=null && !lstObjects.isEmpty()) obj=lstObjects.get(0);
        return obj;
    }
}


3. Using the repository

Here is a simple web controller that uses the repository 

package ximo.controllers;

import java.lang.reflect.InvocationTargetException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import ximo.repositories.IBaseRepository;
import ximo.xotherapps.model.base.Base;


@Controller
public class AController {
    
    @Autowired
    private IBaseRepository baseRepository;

    @GetMapping("/greeting")
    public <S extends Base>String greeting(
            @RequestParam(name="modelClassName", defaultValue="") String modelClassName,
            @RequestParam(name="pkg", defaultValue="") String pkg,
            Model model, 
            RedirectAttributes attributes) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            
        Class<S> klass;
        try {
            klass = (Class<S>) Class.forName(pkg+"."+modelClassName);
            String myWHERE="id = 1";
            //Calling our own baseRepository
            S baseBean=baseRepository.findOneByJQL(klass, myWHERE);
            System.out.println(baseBean.getId());
        } catch (Exception e) {
            e.printStackTrace();
            return "error_page";
        }
        return "greeting";
    }
}    






Comentarios

Entradas populares de este blog

SpringBoot (14) Let's start (2/10). Defining users

SpringBoot (6) Spring Data JPA (1)