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
Publicar un comentario