Agrupaciones en consultas con javax.persistence.criteria.CriteriaBuilder
Vamos a poner un par de ejemplos de uso del CriteriaBuilder en JPA para poder hacer consultas usando JpaSpecificationExecutor e incluso usar agrupaciones con groupBy.
Se van a ir poniendo las diferentes clases desde el Entity hasta el Service:
Entity de la tabla cuenta con un par de campos.
@Entity(name="cuentaEntity") @Table(name="CUENTA") public class CuentaEntity implements Serializable { @Id private Integer id; @Column(name="balance") private Integer balance; @Column(name="empresa") private String empresa; @Column(name="fecha") @Temporal(TemporalType.TIMESTAMP) private Date fecha; //getter setter }
Su correspondiente JPAMetalModelEntityProcessor, la vamos a utilizar para las consultas.
@Generated(value = "org.hibernate.jpamodelgen.JPAMetalModelEntityProcessor") @StaticMetamodel(CuentaEntity.class) public abstract class CuentaEntity_ { public static volatile SingularAttribute< CuentaEntity, Integer> id; public static volatile SingularAttribute< CuentaEntity, Integer> balance; public static volatile SingularAttribute< CuentaEntity, String> empresa; public static volatile SingularAttribute< CuentaEntity, Date> fecha; }
Un clase repositorio. Hay que hacer que herede tanto de la JpaRepository como del JpaSpecificationExecutor.
@Repository public interface CuentaRepository extends JpaRepository< CuentaEntity, Integer>, JpaSpecificationExecutor< CuentaEntity> { }
Creamos nuestra clase criteria donde meteremos las especificaciones de las querys. Se añaden unos ejemplos de condicionales
public class CuentaCriteria implements Serializable{ private Integer id; private Integer balance; private String empresa; private Date fecha; ...... public static Specification< CuentaEntity> search(final CuentaCriteria cri){ return new Specification< CuentaEntity>(){ @Override public Predicate toPredicate(Root< CuentaEntity> root, CriteriaQuery< ?> criQuery, CriteriaBuilder criBuilder) { List< Predicate> predicates = new ArrayList< Predicate>(); //el nombre de la empresa sea igual al que se pasa en CuentaCriteria. Si no está vacia. if( !StringUtils.isEmpty( cri.getEmpresa())){ predicates.add( criBuilder.like(root.get(CuentaEntity_.empresa), cri.getEmpresa()) ); } //la fecha debe ser igual a la que se pasa en CuentaCriteria. Si no está vacia. if( !StringUtils.isEmpty( cri.getFecha())){ predicates.add( criBuilder.equal(root.get(StatementEntity_.fecha),cri.getFecha()) ); } //podemos añadir tambien una condicion de tipo OR ( id = 0 OR id = 1). List< Predicate> predOr = new ArrayList< Predicate>(); predOr.add( criBuilder.equal(root.get(CuentaEntity_.id), 0) ); predOr.add( criBuilder.equal(root.get(CuentaEntity_.id), 1) ); predicates.add( criBuilder.or(predOr.toArray(new Predicate[predOr.size()]) ) ); //Si tubieramos un objeto Embeddable u otro tipo de objeto podriamos acceder //a las propiedades de ese objeto //predicates.add( criBuilder.like(root.get("OBJETO").get("CAMPO_OBJETO"), "qqqqqq") ); .............. //Concatenamos todos los predicados como ands return criBuilder.and(predicates.toArray(new Predicate[predicates.size()])); } }; } }
Interfaz con nuestro servicio con unos métodos de prueba.
public interface CuentaService{ public Page< CuentaEntity> find(CuentaCriteria criteria, Pageable pageable); public List< Object[]> findGroupBy(CuentaCriteria criteria); public Long count(CuentaCriteria criteria); ......... }
Y su implementación:
@Component("cuentaService") @Transactional public class CuentaServiceImpl implements CuentaService { @PersistenceContext EntityManager em; @Autowired private CuentaRepository cuentaRepository; //En este método permite realizar una consulta que devuelve el listado de todas las cuentas que //lo cumplen. Las condiciones de la consulta se le pasan con CuentaCriteria.search(criteria). //No devolverá todos los elementos al pasarle un objeto Pageable para paginación. public Page< CuentaEntity> find(CuentaCriteria criteria, Pageable pageable){ return cuentaRepository.findAll( CuentaCriteria.search(criteria), pageable); } //En este método deveulve la cuenta de la consulta anterior. Las condiciones de la consulta //se le pasan con CuentaCriteria.search(criteria). public Long count(StatementCriteria criteria){ return this.statementRepository.count( StatementCriteria.search(criteria) ); } //El groupBy no funciona bien si se mete en StatementCriteria.search(criteria), eso es porque //aunque hagas un multiselect() con sólo los elementos que quieres sacar en la consulta te lo va //a sobreescribir por el select(). //Una posible solución sería hacerlo como se hace en este método. public List< Object[]> findGroupBy(CuentaCriteria criteria){ CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery> cq = cb.createQuery(); Rootroot = cq.from(StatementEntity.class); //declaramos el multiselect con las columnas a devolver cq.multiselect( //fecha mayor cb.greatest( root.get(CuentaEntity_.fecha).as(java.util.Date.class) ), //fecha menor cb.least( root.get(CuentaEntity_.fecha).as(java.util.Date.class) ), //balance mayor cb.max( root.get(CuentaEntity_.balance) ), //balance menor cb.min( root.get(CuentaEntity_.balance) ), //media del balance cb.avg( root.get(CuentaEntity_.balance) ), //suma del balance total cb.sum( root.get(CuentaEntity_.balance).as(java.lang.Integer.class) ), //suma del balance siempre que sea mayor de cero, es decir, positivo cb.sum( cb. selectCase() .when(cb.greaterThan(root.get(CuentaEntity_.balance), 0), root.get(CuentaEntity_.balance) ).otherwise(0)), //contador con todas las filas agrupadas cb.count( root.get(CuentaEntity_.id) ) ); //aplicamos todas la condiciones de consulta al where cq.where( CuentaCriteria.search(criteria).toPredicate(root, cq, cb) ); //le decimos que agrupe por el nombre de las empresas cq.groupBy( root.get( CuentaEntity_.empresa) ); Query query = em.createQuery(cq); //Por último lo podemos retornar como una lista de objetos return (List< Object[]>)query.getResultList(); } }
@ElementCollection vs @OneToMany
Estas dos anotaciones nos permiten elaborar relaciones 1 a N entre dos objetos con JPA. A continuación se va a exponer su funcionamiento:
@ElementCollection
Te permite de una forma muy simple definir una relación de una entidad con otros objetos. Se puede por ejemplo, dado un empleado, definir una lista de strings que se corresponden con los telefonos del empleado. Sería así:
@Entity(name="Employee") @Table(name="EMPLOYEE") public class Employee { @Id @Column(name = "_id") private String id; @Column(name="name") private String name; @ElementCollection @CollectionTable(name="PHONE", joinColumns=@JoinColumn(name="phone_id")) @Column(name="phone_number") private List< String> phones; ..... }
Teniendo en cuenta este ejemplo tendremos en BD:
– Una tabla EMPLOYEE con las columnas -> _id y name.
– Una tabla PHONE con las columnas -> phone_id y phone_number. (el campo phone_id hara de foreign key, mientras que phone_number contendrá el String del número de teléfono)
En el ejemplo anterior usábamos una lista de String pero podríamos usar un objeto @Embeddable, tal que así:
@Entity public class Employee { @Id @Column(name = "_id") private String id; @Column(name="name") private String name; @ElementCollection @CollectionTable(name="PHONE",joinColumns=@JoinColumn(name="phone_id")) private List< Phone> phones; ... } @Embeddable public class Phone { @Column(name="phone_number") private String number; ... }
El resultado de hacerlo así en la base de datos sería exactamente el mismo pero nos va a permitir añadir mas campos en el objeto PHONE.
@OneToMany
Te permite relacionar dos objetos Entity.
@Entity public class Employee { @Id @Column(name = "_id") private String id; @Column(name="name") private String name; @OneToMany(cascade={CascadeType.ALL}, mappedBy="empl", fetch=FetchType.LAZY, targetEntity = Phone.class) @OnDelete(action = OnDeleteAction.CASCADE) private List< Phone> phones; ... } @Entity public class Phone { @Id private long id; @Column(name="phone_number") private String number; @ManyToOne @JoinColumn(name="phone_id") private Employee empl; ... }
Como se puede ver hay que marcar la relacion OneToMany en la entidad Employee, marcándole el campo donde debe apuntar (en este caso «empl»). En ese campo de la entidad Phone se debe marcar la ManyToOne indicandole el nombre de la columna (phone_id) que hará de foreign key.
Además, se ha añadido la anotación @OnDelete para indicarle que haga un borrado en cascada de la lista de teléfonos al eliminar al empleado. Esto es significativo por el hecho de que en el caso del ElementCollection no se permite indicar el tipo de borrado.
Nota: Hay que decir que en el borrado en cascada se eliminan de golpe todos los relacionados mientras que sin él, va haciendo un borrado de cada uno de sus relacionados (es decir, si un empleado tiene 10 teléfonos al hacerlo en cascada se lanzaría un único delete mientras que en el otro caso se lanzan 10 sentencias delete).
Resumen final
– BD: el esquema de tablas y relaciones que va a construir JPA en nuestra BD va a ser muy similar en ambos casos.
– Código: Se puede decir que es mucho más cómodo utilizar @ElementCollection al simplificar el código.
– Potencia: Si queremos poder utilizar toda la potencia de las relaciones optimizando nuestros recursos es mejor decantarse por @OneToMany.
Fuente
– @ElementCollection
– @OneToMany
Categorias
- adobe (2)
- agile (1)
- Alfresco (1)
- Android (26)
- Angular (6)
- angularjs (10)
- apache (1)
- axis (2)
- Bases de datos (14)
- Bootstrap (1)
- C# (3)
- Cámara (1)
- chrome (3)
- Codeigniter (2)
- Control de Versiones (2)
- CSS (25)
- CVS (1)
- Django (9)
- Django Rest Framework (1)
- DNS (1)
- Docker (3)
- dominio (1)
- eclipse (5)
- Entity Framework (2)
- ETL (1)
- Firefox (6)
- flash (1)
- freecad (1)
- Git (12)
- GitHub (4)
- gpg (2)
- Groovy (1)
- Handlebars (1)
- hibernate (4)
- hosting (1)
- HTML (50)
- HTML 5 (26)
- Impresión 3D (9)
- Inkscape (1)
- IOS (2)
- ireports (3)
- Java (44)
- Javascript (55)
- JBoss (5)
- JPA (2)
- JQuery (20)
- Json (7)
- JSP (6)
- Keycloak (1)
- Lamp (1)
- LDAP (2)
- lean (1)
- linkedin (1)
- LINQ (1)
- linux (13)
- Livecycle (1)
- log (1)
- microcontroladores (1)
- MongoDB (4)
- MySQL (8)
- Node.js (5)
- OC4J (1)
- Openshift (2)
- Oracle (6)
- Patrones de Diseño (1)
- Photoshop (2)
- php (20)
- PostgreSQL (1)
- python (19)
- rabbitmq (1)
- Raspberry PI (13)
- Raspherry PI (5)
- React (6)
- seguridad (3)
- Selenium (3)
- Sencha Touch (1)
- Sin categoría (29)
- Spring (17)
- spring-boot (3)
- SQL (7)
- SQLServer (1)
- SSO (1)
- struts (2)
- SVN (1)
- Talend (1)
- Tomcat (6)
- unity (3)
- Visual Studio Code (2)
- vmware (5)
- Web Services (11)
- windows (18)
- wordpress (10)
- Xiaomi (1)
- xml (2)
Trabajos Realizados
- App Android – Autoka Fr
- App Android – Cartelera Cántabra
- App Android – Gramática y Vocabulario Ingles
- App Android – Hoja de Gastos
- App Android – Hotel Torre Cristina
- App Android – OcioEnjoy
- App Android – Visor CardBoard
- App Firefox – Managapp
- DiamanteBomba – DisasterCode
- Generador de Partes de Trabajo
- GitHub – Android Web Generator
- GitHub – Dynamic Angular Gallery
- GitHub – Dynamic React Gallery
- GitHub – Sotilizator
- GitHub – SpringAngularJS
- GitHub – Swiper Dynamic Angular Gallery
- HazParejas – DisasterCode
- RompeCabezas – DisasterCode
- Unity Game – English Couple
- Unity Game – Kill Wasp
- WordPress – El Buen Apicultor
- WordPress – El Cajón de los Retales
- WordPress – El Vestidito Azul
- WordPress – Feuchas
- WordPress – Fragua de Navajas Ponce
- WordPress – Humor a las Tres
- WordPress – Photo Places