Download - EJB-Apartado5 - Tutorial de EJB avanzado
Tutorial de EJB avanzado
Introducción
Este apartado se da un tutorial sobreRelaciones entre entidadesHerencia de entidadesEJB-QL
El tutorial gira entorno a la capa modelo de una aplicación PSA (Professional Service Automation) muy simplificada
Gestión de los recursos humanos de una empresa
Análisis: objetos del dominio
Employee
- employeeIdentifier : Long- firstName : String- surname : String- positionIdentifier : String- salary : int
Department
- departmentIdentifier : String- name : String- creationDate : Calendar10..n
1 0..1
DistinguishedEmployee
- mentionDate : Calendar- comment : String
Project
- projectIdentifier : String- name : String- startDate : Calendar- endDate : Calendar
0..n
0..n
Dirigido por
Estructura de paqueteses.udc.fbellas.j2ee.advancedejbtutorial.psa
model
department
entity
testclient
employee
entity
vo
psafacade
util
vo
ejb
exceptions
vo
entity
vo
project
Fachada del modelo y cliente de prueba
Fachada del modeloes.udc.fbellas.j2ee.advancedejbtutorial.psa.model.psafacade.ejb.PSAFacade
Fachada remotaDefine operaciones para
Crear departamentos, empleados y proyectosHacer asignaciones (director de un departamento y empleado a un proyecto)Realizar consultasRealizar borrados en masa
La clase de implementación (PSAFacadeEJB) es un SLSB
Cliente de pruebaes.udc.fbellas.j2ee.advancedejbtutorial.psa.testclient.TestClient
Invoca las operaciones de la fachada
Entidades (1)
Employee
- employeeIdentifier : Long- firstName : String- surname : String- positionIdentifier : String- salary : int- department : Department- projects : List<Project>- version : long
Department
- departmentIdentifier : String- name : String- creationDate : Calendar- employees : List<Employee>- director : Employee- version : long
DistinguishedEmployee
Project
10..n
1 0..1
0..n
0..n
+ Constructores+ Métodos get/set+ Constructores
+ Métodos get/set
Dirigido por
Entidades (y 2)
Employee
Project
- projectIdentifier : String- name : String- startDate : Calendar- endDate : Calendar- employees : List<Employee>- version : long
DistinguishedEmployee
- mentionDate : Calendar- comment : String
0..n
0..n
+ Constructores+ Métodos get/set
+ Constructores+ Métodos get/set
Tablas
versioncreationDatenamedirIddepIdTabla = Department
(PK)
posId salary version typesurnamefirstNamedepIdempId Tabla = Employee
(PK) (FK)
(FK)
Relación “Department(0..1)--[Dirigido por]-->Employee(1)”
Relación “Department(1)<-->Employee(0..N)”
commentmentionDateempId Tabla = DSTEmployee
(PK, FK)
Herencia entre Employee y DistinguishedEmployee
prjIdempId
(PK, FK)
(PK, FK)
Tabla = EmpPrj
versionendDatestartDatenameprjIdTabla = Project
(PK)
Relación “Employee(0..N)<-->Project(0..N)”
Relaciones (1)
Tipos de relaciónUno-a-Uno
E.g. Department(0..1)--[Dirigido por]-->Employee(1)
Uno-a-Muchos / Muchos-a-UnoE.g. Department(1)<-->Employee(0..N)
Muchos-a-Muchos E.g. Employee(0..N)<-->Project(0..N)
NOTA: Uno significa 0..1 o 1Muchos significa 0..N o N
Atributos/propiedades que representan relacionesTipos: clases entidad o colecciones de clases entidad (Collection, List, Set y Map)Se usan anotaciones para especificar cómo mapear las relaciones a columnas/tablas
Relaciones (2)
DireccionalidadUnidireccionales
Sólo se puede navegar desde una entidad a la otraE.g. Department(0..1)--[Dirigido por]-->Employee(1)
Employee director = department.getDirector();
BidireccionalesDesde cualquiera de las dos entidades es posible navegar hacia la otraE.g. Department(1)<-->Employee(0..N)
Department department = employee.getDepartment();List<Employee> employees = department.getEmployees();
E.g. Employee(0..N)<-->Project(0..N)List<Project> projects = employee.getProjects();List<Employee> employees = project.getEmployees();
Relaciones (y 3)
Lado propietario (“owning side”) y lado inverso(“inverse side”) en una relación
IntuiciónEl lado propietario es la entidad cuya tabla asociada tiene la clave foránea que mantiene la relación
Una relación unidireccional sólo tiene lado propietarioEl lado propietario es la entidad que permite navegar hacia la otra
Una relación bidireccional tiene un lado propietario y un lado inverso
Si es Uno-a-Muchos o Muchos-a-Uno, el lado propietario es el lado MuchosSi es Uno-a-Uno, la entidad cuya tabla contiene la clave foránea es el lado propietarioSi es Muchos-a-Muchos, cualquier lado puede ser el lado propietario
Relación “Department(0..1)--[Dirigido por]-->Employee(1)” (1)
En Department
@Entitypublic class Department {
// ...
@OneToOne@JoinColumn(name="dirId")public Employee getDirector() {
return director;}
public void setDirector(Employee director) {this.director = director;
}
// ...
}
Relación “Department(0..1)--[Dirigido por]-->Employee(1)” (y 2)
Relaciones Uno-a-UnoSe utiliza @OneToOne sobre los atributos/propiedades que definen la relación
En el ejemplo, dado que la relación es unidireccional, sólo se aplica sobre el método getDirector de la entidad Department (en otro caso, se aplicaría en ambas entidades)
Se utiliza @JoinColumn sobre el atributo/propiedad que define la relación en el lado propietario
En el ejemplo, dado que la relación es unidireccional, el lado propietario es DepartmentEspecifica la columna que actúa como clave foránea para mantener la relaciónSe puede usar el elemento referencedColumnName para especificar el nombre de la columna a la que hace referencia la clave foránea
Por defecto se asume que es la clave primaria de la tabla de la otra entidad
Relación “Department(1)<-->Employee(0..N)” (1)
En Employee
@Entitypublic class Employee {
// ...
@ManyToOne(optional=false)@JoinColumn(name="depId")public Department getDepartment() {
return department;}
public void setDepartment(Department department) {this.department = department;
}
// ...
}
Relación “Department(1)<-->Employee(0..N)” (2)
En Department
@Entitypublic class Department {
public Department() {employees = new ArrayList<Employee>();
}
public Department(String departmentIdentifier, String name,Calendar creationDate) {
this.departmentIdentifier = departmentIdentifier;this.name = name;this.creationDate = creationDate;employees = new ArrayList<Employee>();
}
Relación “Department(1)<-->Employee(0..N)” (3)
En Department (cont)
// ...
@OneToMany(mappedBy="department", cascade=CascadeType.REMOVE)public List<Employee> getEmployees() {
return employees;}
public void setEmployees(List<Employee> employees) {this.employees = employees;
}
// ...
}
Relación “Department(1)<-->Employee(0..N)” (4)
Relaciones Uno-a-Muchos / Muchos-a-UnoSe utiliza @OneToMany (lado Uno) o @ManyToOne (lado Muchos) sobre los atributos/propiedades que definen la relación
Si la relación es unidireccional, sólo se anota el lado que permite navegar hacia el otro
En las relaciones bidireccionales, el lado inverso tiene que usar el elemento mappedBy en @OneToOne, @OneToManyy @ManyToMany
No se puede usar en @ManyToOne porque en una relación bidireccional el lado Muchos siempre es el lado propietarioEspecifica el nombre del atributo/propiedad del otro lado (ladopropietario) de la relación
En el ejemplo, el elemento mappedBy está diciendo que la propiedad employees en Department conjuntamente con la propiedad department en Employee forman conjuntamente una relación bidireccional
Relación “Department(1)<-->Employee(0..N)” (5)
Relaciones Uno-a-Muchos / Muchos-a-Uno (cont)Al igual que en las relaciones Uno-a-Uno, se utiliza @JoinColumn sobre el atributo/propiedad que define la relación en el lado propietario
Especifica la columna que actúa como clave foránea para mantener la relación
NOTASoptional=false
En las anotaciones @OneToOne, @OneToMany, @ManyToOne y @ManyToMany, por defecto optional es trueEn getDepartment de Employee se ha usado @ManyToOnecon optional=false para especificar que getDepartmentsiempre devuelve un departamento, es decir, todo empleado está asignado necesariamente a un departamentoNinguna fila en la tabla Employee puede contener un NULL en la clave foránea depId
Relación “Department(1)<-->Employee(0..N)” (6)
NOTAS (cont)cascade=CascadeType.REMOVE
Sólo se puede aplicar portablemente en @OneToOne y @OneToMany (por defecto no se realiza ninguna operación en cascada)En getEmployees de Department se ha usado @OneToManycon cascade=CascadeType.REMOVE para especificar que cuando se elimine un departamento se eliminen sus empleados
Los dos constructores de Department inicializan employees a una lista vacía (y no a null)
Las propiedades/atributos de tipo colección (relaciones Uno/Muchos-a-Muchos) devuelven una colección vacía cuando no hay asociaciónLas propiedades/atributos de tipo clase entidad (relaciones Uno/Muchos-a-Uno) devuelven null cuando no hay asociación
Relación “Department(1)<-->Employee(0..N)” (y 7)
NOTAS (cont)El uso de generics en las colecciones (e.g. List<Employee>) hace que no sea necesario emplear el elemento targetEntity en las anotaciones @OneToManyy @ManyToMany
targetEntity permite especificar el tipo de la clase entidad relacionadaCuando se usan generics, el tipo de la clase entidad relacionada está implícito en el tipo colección
Relación “Employee(0..N)<-->Project(0..N)” (1)
En Employee
@Entitypublic class Employee {
// ...
@ManyToMany@JoinTable(
name="EmpPrj",joinColumns=@JoinColumn(name="empId"),inverseJoinColumns=@JoinColumn(name="prjId"))
public List<Project> getProjects() {return projects;
}
public void setProjects(List<Project> projects) {this.projects = projects;
}
// ...}
Relación “Employee(0..N)<-->Project(0..N)” (2)
En Project
@Entitypublic class Project {
// ...
@ManyToMany(mappedBy="projects")public List<Employee> getEmployees() {
return employees;}
public void setEmployees(List<Employee> employees) {this.employees = employees;
}
// ...
}
Relación “Employee(0..N)<-->Project(0..N)” (y 3)
Relaciones Muchos-a-Muchos Se utiliza @ManyToMany sobre los atributos/propiedades que definen la relación
Si la relación es unidireccional, sólo se anota el lado que permite navegar hacia el otroEn el ejemplo se ha elegido Project como el lado inverso de la relación
@ManyToMany(mappedBy="projects") sobre getEmployees en Project
Se utiliza @JoinTable sobre el atributo/propiedad que define la relación en el lado propietario
name: nombre de la tabla en la que se mapea la relaciónjoinColumns: claves foráneas (normalmente una) que referencian las claves primarias de la tabla en la que se mapea la entidad del lado propietarioinverseJoinColumns: claves foráneas (normalmente una) que referencian las claves primarias de la tabla en la que se mapea la entidad del lado inverso
Establecimiento de relaciones (1)
Ejemplo: en PSAFacadeEJB
public void setDepartmentDirector(String departmentIdentifier,Long employeeIdentifier) throws InstanceNotFoundException {
Department department = PSAFacadeHelper.findDepartment(entityManager, departmentIdentifier);
Employee employee = PSAFacadeHelper.findEmployee(entityManager,employeeIdentifier);
department.setDirector(employee);
}
Establecimiento de relaciones (y 2)
Ejemplo: en PSAFacadeEJB
public void assignEmployeeToProject(Long employeeIdentifier,String projectIdentifier) throws InstanceNotFoundException {
/* Find employee and project. */Employee employee = PSAFacadeHelper.findEmployee(entityManager,
employeeIdentifier);Project project = PSAFacadeHelper.findProject(entityManager,
projectIdentifier);
/* Assign employee to project. */employee.getProjects().add(project);
}
NOTA: en los ejemplos, el código es un poco más complejo porque se comprueba si el empleado ya está asignado a ese proyecto, y en caso afirmativo, se lanza una excepción
Integridad referencial: ejemplos
null-emp1depA-emp2depB-emp3depB-emp4
depA.getEmployees().remove(emp1);
depA-emp1depA-emp2depB-emp3depB-emp4
Department-Employee (1:N)
depB-emp1depA-emp2depB-emp3depB-emp4
depB.getEmployees().add(emp1);
depA-emp1depA-emp2depB-emp3depB-emp4
Department-Employee (1:N)
depB-emp1depA-emp2depB-emp3depB-emp4
emp1.setDepartment(emp3.getDepartment());
depA-emp1depA-emp2depB-emp3depB-emp4
Department-Employee (1:N)
null-emp1null-emp2depA-emp3depA-emp4depB-colección vacía
depA.setEmployees(depB.getEmployees());
depA-emp1depA-emp2depB-emp3depB-emp4
Department-Employee (1:N)
depA-emp24depB-NULL
depA.setDirector(depB.getDirector());
depA-emp1depB-emp24
Department-Director (1:1)
DespuésOperaciónAntesTipo de relación
Relaciones unidireccionales Uno-a-Muchos
El mapping del API de Persistencia asume el uso de una tabla intermedia
Similar a la tabla usada para el mapping de un relación Muchos-a-MuchosEn realidad, podrían mapearse de manera similar a las relaciones bidireccionales Uno-a-Muchos, es decir, con una clave foránea en la tabla destinoPuede que algunas implementaciones permitan que se especifique que se desea el mismo mapping que las relaciones bidireccionales Uno-a-Muchos, pero eso no es portable
Si se desea utilizar ese mapping, es mejor modelarlas como relaciones bidireccionales Uno-a-Muchos
NOTA: las relaciones unidireccionales Muchos-a-Uno utilizan el mapping normal
Carga de instancias relacionadas (1)
E.g. Cuando se carga una instancia de Departmenten memoria, ¿se cargan en memoria también sus empleados (instancias de Employee)?El enumerado FetchType define dos políticas de carga
FetchType.EAGER: la instancia o instancias relacionadas se cargan automáticamenteFetchType.LAZY: actúa como una indicación (la implementación puede aplicar FetchType.EAGER) para especificar que la instancia o instancia relacionadas no se carguen hasta el primer momento en que se precisen
Cuando se invoca department.getEmployees(), la implementación del API de Persistencia, carga el los empleadosPara lograrlo, la implementación del API de Persistencia puede generar subclases o usar frameworks de Programación Orientada a Aspectos (interceptando la invocación a getEmployees)
Carga de instancias relacionadas (y 2)
La política de carga se puede especificar con el elemento fetch de las anotaciones
@OneToOne (por defecto FetchType.EAGER)@ManyToOne (por defecto FetchType.EAGER)@OneToMany (por defecto FetchType.LAZY)@ManyToMany (por defecto FetchType.LAZY)
Estrategias de mapeo de herencia (1)
El tipo enumerado InheritanceType define tres estrategias para mapear una relación de herenciaInheritanceType.SINGLE_TABLE
Utiliza una única tabla que contiene una columna por cada atributo/propiedad presente en las clases entidadSe necesita incluir una columna que actúe como discriminador
Permite saber a qué entidad corresponde una filaVentaja: ejecución eficiente de consultas polimórficas
E.g. encontrar todos los empleados (del tipo que sean) que ocupen un determinado cargoSe pueden implementar con una sola consulta
Desventaja: las columnas correspondientes a los atributos/propiedades de las clases que extienden (directa o indirectamente) de la clase raíz tienen que admitir NULL
Quizás haya muchas filas con valor NULL para algunas de esas columnas
Estrategias de mapeo de herencia (y 2)
InheritanceType.TABLE_PER_CLASSUtiliza una tabla por cada entidad, que contiene una columna por cada atributo/propiedad, propio o heredado, de la entidadDesventaja: ejecución ineficiente de consultas polimórficas
Requiere lanzar consultas sobre cada tablaEl soporte para este tipo de persistencia es opcional
InheritanceType.JOINEDUtiliza una tabla por cada clase entidad, que contiene una columna por cada atributo/propiedad específico a esa claseLa clave primaria de las tablas no raíz actúa como clave foránea de la clave primaria de la tabla raízVentaja: ahorro de espacio y ejecución razonablemente eficiente de consultas polimórficasDesventaja: requiere uno o varios JOINs para resolver las consultas polimórficas (prohibitivo en jerarquías profundas)
Herencia entre Employee y DistinguishedEmployee (1)
En Employee
@Entity@Inheritance(strategy=InheritanceType.JOINED)@DiscriminatorColumn(name="type",
discriminatorType=DiscriminatorType.STRING)@DiscriminatorValue("STD") // "STD" stands for "standard employee".public class Employee {
// ...}
En DistinguishedEmployee
@Entity@Table(name="DSTEmployee")@DiscriminatorValue("DST") // "DST" stands for "distinguished employee".public class DistinguishedEmployee extends Employee {
// ...}
Herencia entre Employee y DistinguishedEmployee (y 2)
@InheritancePermite especificar la estrategia de herenciaSe usa en la clase padre
@DiscriminatorColumnPermite especificar una columna que actúe como discriminador
Especifica el nombre de la columna y el tipo de discriminador (DiscriminatorType.STRING, DiscriminatorType.CHAR o DiscriminatorType.INTEGER)
Obligatoria con InheritanceType.SINGLE_TABLE y opcional (aunque típicamente usada) en InheritanceType.JOINED
NOTA: con la estrategia InheritanceType.JOINED, JBoss no da valor a la columna discriminador
Se usa en la clase padre@DiscriminatorValue
Especifica el valor que tomará la columna discriminador para las filas que correspondan a instancias de esta entidadSe usa en cada entidad concreta de la jerarquía
EJB-QL
Lenguaje de consultas de búsqueda y borrados/actualizaciones en masaSintaxis parecida a SQL (para facilitar el aprendizaje)Las consultas EJB-QL usan los nombre de las entidades y los atributos/propiedades (y no los nombres de las tablas y columnas)La implementación del API de Persistencia traduce las consultas EJB-QL al SQL de la BD relacional destinoLas consultas EJB-QL se ejecutan con el objeto Query
EntityManager dispone del método createQuery, que crea un objeto Query a partir de un String que contiene la consulta EJB-QL
Conceptos básicos (1)
Ejemplo: obtener todos los departamentosSELECT d FROM Department d
Devuelve una lista de objetos DepartmentDepartment d
Representa la declaración de la variable dEn las palabras clave (e.g. SELECT, FROM, etc.) no se distingue entre mayúsculas y minúsculas
EjecuciónList<Department> departments = entityManager.createQuery(
"SELECT d FROM Department d").getResultList();
El método createQuery devuelve un objeto QueryEl método getResultList permite ejecutar una consulta de lectura y devuelve una lista con los resultados
Si no hay resultados, devuelve una lista vacía
Conceptos básicos (2)
Ejecución (cont)Query también dispone del método getSingleResult, que permite ejecutar un consulta de lectura que sólo devuelva un resultado (o ninguno)
Lanza NoResultException (excepción de runtime) si no hay ningún resultadoSiempre se puede usar getResultList, pero getSingleResult es más cómodo cuando es seguro que la consulta sólo devuelve un resultado (o ninguno)Ejemplo: obtener los datos del departamento tic
try { Department department =
(Deparment) entityManager.createQuery("SELECT d FROM Department d " +"WHERE d.departmentIdentifier = 'tic'").getSingleResult();
// ...catch (NoResultException e) {
// ...}Es posible usar d.departmentIdentifier porque Departmenttiene el atributo/propiedad departmentIdentifier
Conceptos básicos (3)
LiteralesNuméricos (enteros o reales)
Ejemplo: e.salary >= 1000
Los literales de tipo cadena de caracteres se entrecomillan con comillas simples
Ejemplo: d.departmentIdentifier = 'tic'
Si el literal incluye una comilla simple, se sustituye por dosEjemplo: d.name = 'uno''dos'
Literales booleanos: TRUE y FALSE
Conceptos básicos (4)
Parámetros nombrados (“named parameters”)Tienen la forma :nombeParámetroEjemplo: obtener los empleados que ocupan un determinado cargo
SELECT e FROM Employee e WHERE e.positionIdentifier = :posId
Ejecución:
List<Employee> employees = entityManager.createQuery("SELECT e FROM Employee e " +"WHERE e.positionIdentifier = :posId").setParameter("posId", positionIdentifier).getResultList();
Query dispone del método setParameter, que da valor a un parámetro nombrado y devuelve otra vez el objeto Query
Permite escribir de manera “compacta” la construcción de la consulta y su ejecución
Conceptos básicos (5)
Consultas polimórficasEn la consulta ...
SELECT e FROM Employee e WHERE e.positionIdentifier = :posId
... los empleados devueltos pueden ser empleados normales (instancias de Employee) o empleados distinguidos (instancias de DistinguishedEmployee)
Es posible navegar a través de las relacionesEjemplo: obtener todos los empleados de un departamento
SELECT e FROM Employee e WHERE e.department.departmentIdentifier = :depId
e.department navega a la entidad Department
Conceptos básicos (6)
Expresiones constructorObtener los datos de todos los departamentos
SELECT NEW es.udc...DepartmentVO(d.departmentIdentifier, d.name, d.creationDate) FROM Department d
Después de la cláusula NEW se especifica un constructor de una clase
Se tiene que usar su nombre completoLa anterior consulta devuelve una lista con instancias de DeparmentVO (una por cada departamento)Cada elemento devuelto por Query.getResultList, o el único elemento devuelto por Query.getSingleResult(aunque en este ejemplo no es aplicable), es una instancia de DepartmentVO
Especialmente útil cuando se quiere devolver un CustomValue Object (CVO) o una lista de CVOs
Alternativamente se podría lanzar una consulta que devolviese una entidad o una lista de entidades, y finalmente crear los objetos CVOs a partir de las entidades
Conceptos básicos (7)
Si se desea, no es necesario emplear expresiones constructor cuando cada resultado de la consulta consta de varios items
EjemploSELECT d.departmentIdentifier, d.name, d.creationDate
FROM Department d
Cada elemento devuelto por Query.getResultList, o el único elemento devuelto por Query.getSingleResult(aunque en este ejemplo no es aplicable), es una instancia de Object[]Cada elemento del vector corresponde a un item (en el mismo orden que figuran en la consulta)En el ejemplo
Cada elemento de la lista devuelta por getResultList es una instancia de Object[]Cada Object[] tiene tres elementos: el identificador, el nombre y la fecha (en este orden)
Conceptos básicos (y 8)
ORDER BYEjemplo: recuperar todos los departamentos ordenados por identificador
SELECT d FROM Department d ORDER BY d.departmentIdentifier
Al igual que en SQL, se puede ordenar por varios criterios simultáneamente, especificando opcionalmente ASC (por defecto) o DESC
Ejemplos
SELECT e FROM Employee ORDER BY e.surname, e.firstName
SELECT e FROM Employee ORDER BY e.surname DESC,e.firstName DESC
SubconsultasEs posible anidar subconsultas en las cláusulas WHERE y HAVINGIremos viendo ejemplos ...
Expresiones condicionales (1)
Operadores matemáticos (+, -, *, /), de comparación (=, >, <, >=, <=, <>) y lógicos (AND, OR, NOT)
EjemploSELECT e FROM Employee e WHERE
e.positionIdentifier = 'atp' AND e.salary >= 1000
ExplicaciónObtener todos los empleados que ocupan el cargo de atp y su salario es >= 1000
[NOT] BETWEENEjemploSELECT e FROM Employee e WHERE e.salary BETWEEN 1000 AND 2000
ExplicaciónSELECT e FROM Employee e WHERE
e.salary >= 1000 AND e.salary <= 2000
Expresiones condicionales (2)
[NOT] INEjemploSELECT d FROM Department d WHERE
d.departmentIdentifier IN ('tic', 'dc')
ExplicaciónSELECT d FROM Department d WHERE
d.departmentIdentifier = 'tic' OR d.departmentIdentifier = 'dc'
[NOT] LIKEEjemploSELECT e FROM Employee e WHERE e.firstName LIKE 'F%o'
ExplicaciónDevuelve todos los empleados cuyo nombre empieza por F y termina en oMetacaracteres
% (secuencia de 0 o más caracteres), _ (cualquier carácter)Se puede utilizar la cláusula ESCAPE para indicar un carácter de escapeEjemplo: e.firstName LIKE '%\_%' ESCAPE '\' devuelve TRUE para cualquier nombre que incluya un subrayado (_)
Expresiones condicionales (3)
IS [NOT] NULLEjemploSELECT d FROM Department d WHERE d.name IS NULL
ExplicaciónDevuelve todos los departamentos para los que no se ha especificado un valor para el atributo nameIS [NOT] NULL permite comprobar si un campo no colección es NULL
IS [NOT] EMPTYEjemploSELECT d FROM Department d WHERE d.employees IS NOT EMPTY
ExplicaciónDevuelve todos los departamentos que tienen empleadosIS [NOT] EMPTY permite comprobar si un campo colección es vacío
Expresiones condicionales (4)
[NOT] EXISTSEjemplo
SELECT d FROM Department d WHERE EXISTS (SELECT e FROM Employee e WHERE
e.positionIdentifier = :posId AND e.department = d)
ExplicaciónDevuelve todos los departamentos que tengan al menos un empleado desempeñando un determinado cargoEXISTS devuelve TRUE si la subconsulta devuelve uno o más resultados
Expresiones condicionales (5)
ALL y ANY/SOMEEjemploSELECT e FROM Employee e WHERE e.salary >= ALL
(SELECT e.salary FROM Employee e)
ExplicaciónDevuelve todos los empleados que tengan el salario más altoALL
TRUE si la comparación es TRUE para todos los valores devueltos por la subconsulta, o si la subconsulta no devuelven ningún resultado
ANY/SOMETRUE si la comparación es TRUE para alguno de los valores devueltos por la subconsulta (si la subconsulta no devuelve ningún resultado, la expresión es FALSE)ANY y SOME son sinónimos
Expresiones condicionales (6)
Funciones de cadenasCONCAT(String, String)
Devuelve un String que es una concatenación de los dos pasados como parámetro
LENGTH(String)Devuelve el número (int) de caracteres del String
LOCATE(String, String, [start])Busca el segundo String en el primeroEl tercer parámetro (opcional) indica la posición (de 1 en adelante) desde la que comenzar la búsqueda (por defecto, desde el primer carácter)Devuelve la posición (int) en la que lo encontró (0 si no lo encontró)
SUBSTRING(String, start, length) devuelve un String
Devuelve el subcadena que comienza en la posición start y tiene longitud length
Expresiones condicionales (7)
Funciones de cadenas (cont)TRIM
Por defecto, TRIM(String) devuelve el String sin los blancos iniciales y finales
LOWER(String)Devuelve el String en minúsculas
UPPER(String)Devuelve el String en mayúsculasEjemplo: obtener los empleados cuyo nombre empieza por F/fy termina en O/oSELECT e FROM Employee e WHERE UPPER(e.firstName) LIKE 'F%O'
Expresiones condicionales (y 8)
Funciones aritméticasABS(number)
Valor absoluto de un int, float o double
SQRT(number)Raíz cuadradaRecibe un argumento numérico y devuelve un double
MOD(number, base)Módulo Recibe dos int y devuelve un int
SIZE(collection)Tamaño de una colecciónDevuelve un intEjemplo: obtener todos los departamentos que tienen empleadosSELECT d FROM Department d WHERE SIZE(d.employees) > 0
Funciones agregadas (1)
Son funciones que se pueden usar como resultado de una consultaTodas aceptan como argumento una expresión que haga referencia a un atributo/propiedad no relaciónAdicionalmente, COUNT acepta como argumento una variable o una expresión que haga referencia a un atributo/propiedad relaciónAVG
Calcula la mediaRecibe un argumento numérico y devuelve un DoubleEjemplo: calcular el salario medio de los empleados
SELECT AVG(e.salary) FROM Employee e
Funciones agregadas (2)
AVG (cont)Ejemplo: ejecución de la anterior consulta
public int getAverageSalary() {
try {
Double averageSalary = (Double) entityManager.createQuery("SELECT AVG(e.salary) FROM Employee e").getSingleResult();
return averageSalary.intValue();
} catch (NoResultException e) {return 0;
}
}
Funciones agregadas (3)
COUNTDevuelve el número (Long) de resultadosEjemplo: calcular el número de departamentos
SELECT COUNT(d) FROM Department d
MAX/MINCalcula el valor máximo/mínimoRequiere un argumento de tipo numérico, String, char o fechasEjemplo: obtener todos los empleados que tengan el salario más alto
SELECT e FROM Employee e WHERE e.salary >=(SELECT MAX(e.salary) FROM Employee e)
Funciones agregadas (y 4)
SUMCalcula la sumaDevuelve Long si se aplica a enteros, Double si se aplica a reales y BigInteger/BigDecimal cuando se aplica a argumentos BigInteger/BigDecimalEjemplo: calcula el salario total de los empleados
SELECT SUM(e.salary) FROM Employee e
JOIN (1)
INNER JOIN implícitoProducto cartesiano en la cláusula FROM + condición en la cláusula WHERE
Ejemplo: obtener todos los departamentos que tengan al menos un empleado desempeñando un determinado cargo
SELECT DISTINCT d FROM Department d, Employee e WHEREe.department = d AND e.positionIdentifier = :posId
NOTAS:Equivalente a la consulta SQLSELECT DISTINCT d.* FROM Department d, Employee e
WHERE e.depId = d.depId AND e.posId = 'XXX'
e.department = d es equivalente a e.department.departmentIdentifier = d.departmentIdentifier
DISTINCT: mismo significado que en SQL (evita que se repitan departamentos cuando hay más de un empleado en un mismo departamento desempeñando el cargo especificado)
JOIN (2)
INNER JOIN explícitoUsa la cláusula [INNER] JOIN
INNER JOIN y JOIN son sinónimosEjemplo: el anterior
SELECT DISTINCT dFROM Department d JOIN d.employees e WHERE e.positionIdentifier = :posId
Equivalente a la consulta SQL
SELECT DISTINCT d.* FROM Department d JOIN Employee e ON e.depId = d.depId WHERE e.posId = 'XXX'
Con respecto a la consulta SQL, la sintaxis EJB-QL evita la condición sobre las claves (entre otras cosas)Con respecto a un INNER JOIN implícito, el uso de la cláusula JOIN evita la condición e.department = d(equivalente a la condición sobre las claves)
JOIN (y 3)
Operador INEjemplo: el anteriorSELECT DISTINCT d FROM Department d, IN(d.employees) e
WHERE e.positionIdentifier = :posId
Es otra manera de hacer un INNER JOINLa sintaxis del INNER JOIN explícito es más parecida a la de SQL
Otros ejemplosObtener todos los proyectos en los que trabaja un empleado
SELECT p FROM Project p JOIN p.employees e WHEREe.employeeIdentifier = :empId
Obtener todos los proyectos en los que trabaja un departamento
SELECT DISTINCT p FROM Project p JOIN p.employees e
JOIN e.department d WHERE d.departmentIdentifier = :depId
GROUP BY, HAVING (1)
Similares a las cláusulas SQLGROUP BY forma grupos en función de uno o varios atributos/propiedadesHAVING (opcional) permite especificar una condición (usando atributos/propiedades especificados en GROUP BY) para filtrar los elementos que irán dentro de cada grupo
EjemploSELECT e.department.departmentIdentifier, AVG(e.salary)
FROM Employee e GROUP BY e.department.departmentIdentifier
HAVING e.department.departmentIdentifier IN ('tic', 'dc')
ExplicaciónDevuelve el salario medio para los departamentos tic y dc
GROUP BY, HAVING (y 2)
Otro ejemploSELECT NEW es.udc...DepartmentStatisticsVO(
d.departmentIdentifier, COUNT(e), AVG(e.salary),MIN(e.salary), MAX(e.salary), SUM(e.salary))FROM Department d JOIN d.employees eGROUP BY d.departmentIdentifier
ExplicaciónDevuelve estadísticas para cada departamentoLos datos estadísticos de cada departamento incluyen: identificador del departamento, número de empleados, salario medio, salario mínimo, salario máximo y salario total
Borrados y actualizaciones en masa (1)
Para borrados o actualizaciones individualesBorrado de una entidad
EntityManager.remove
Actualización de una entidadModificar los atributos/propiedades Antes de terminar la ejecución del caso de uso, la implementación del API de Persistencia actualiza en BD
¿Y si queremos eliminar/actualizar un conjunto (potencialmente) grande de entidades?
Opción 1Localizar las entidades y eliminar/actualizar cada unaIneficiente
Opción 2Utilizar el soporte de EJB-QL para borrados y actualizaciones en masaSentencias DELETE y UPDATE
Borrados y actualizaciones en masa (2)
EjemplosEliminar todos los departamentosDELETE FROM Department
También provoca que se eliminen los empleadosRecordar que en Department
@OneToMany(mappedBy="department", cascade=CascadeType.REMOVE)
public List<Employee> getEmployees()
Eliminar todos los empleados con cargo atp
DELETE FROM Employee e WHERE e.positionIdentifier = 'atp'
Subirle el sueldo (en 100) a todos los empleados atp
UPDATE Employee e SET e.salary = e.salary + 100 WHERE e.positionIdentifier = 'atp'
Borrados y actualizaciones en masa (3)
Las sentencias UPDATE y DELETE trabajan directamente contra la BD
La sentencia UPDATE no actualiza automáticamente los atributos/propiedades anotados con @Version
¡No se aplica la estrategia Optimistic Locking!Conclusión: no usar la sentencia UPDATE cuando puede haber actualizaciones concurrentes
Al ejecutar las sentencias UPDATE y DELETE dentro de un caso de uso, las entidades que puedan estar cargadas en memoria (durante la ejecución del caso de uso) no se sincronizan con el nuevo estado en BD
Conclusión: las sentencias UPDATE y DELETE se deben usar en una transacción (caso de uso) independiente o al principio de la ejecución de una transacción (antes de que se haya recuperado alguna mediante EntityManager.find o Query.get{ResultList/SingleResult})
Borrados y actualizaciones en masa (y 4)
Ejecución de sentencias UPDATE y DELETEMediante Query.executeUpdate()
Devuelve el número de entidades actualizadas/borradasEjemplo: en PSAFacadeEJB ...
public void removeAllDepartments() {entityManager.createQuery("DELETE FROM Department").
executeUpdate();}
public void removeAllProjects() {entityManager.createQuery("DELETE FROM Project").
executeUpdate();}
Notas sobre los ejemplos (1)
Subsistema AdvancedEJBTutorialMuchas de las consultas que incluyen estas transparencias aparecen en la implementación de la fachada (PSAFacadeEJB)Simplificaciones
Por sencillez, las consultas ilustradas en las transparencias se han simplificado ligeramente con respecto a las de los ejemplosEn los ejemplos, la mayor parte de las consultas utilizan la cláusula ORDER BY (para ordenar los resultados)En los ejemplos, las consultas que devuelven los datos de un departamento utilizan una expresión constructor
public List<DepartmentVO> findAllDepartments() {
return entityManager.createQuery("SELECT NEW " + DepartmentVO.class.getName() + "(d.departmentIdentifier, d.name, " +"d.creationDate) FROM Department d ORDER BY " +"d.departmentIdentifier").getResultList();
}
Notas sobre los ejemplos (2)
Subsistema AdvancedEJBTutorialSimplificaciones (cont)
También se podría haber lanzado ...
SELECT d FROM Department d ORDER BY d.departmentIdentifier
... y luego crear una lista de DepartmentVO a partir de la lista de DepartmentSin embargo, las consultas que devuelven empleados no usan expresiones constructor porque los empleados pueden ser de tipo Employee o DistinguishedEmployee
public List<EmployeeVO> findEmployeesInPosition(String positionIdentifier) {
List<Employee> employees = entityManager.createQuery("SELECT e FROM Employee e WHERE " +"e.positionIdentifier = :posId ORDER BY e.surname").setParameter("posId", positionIdentifier).getResultList();
return PSAFacadeHelper.toEmployeeVOs(employees); }
Notas sobre los ejemplos (3)
Subsistema AdvancedEJBTutorialSimplificaciones (cont)
En PSAFacadeHelper
public final static List<EmployeeVO> toEmployeeVOs(List<Employee> employees) {
List<EmployeeVO> employeeVOs = new ArrayList<EmployeeVO>();
for (Employee e : employees) {employeeVOs.add(e.toEmployeeVO());
} return employeeVOs;
}
DistinguishedEmployee redefine toEmployeeVO para devolver un DistinguishedEmployeeVO (que extiende a EmployeeVO)
Notas sobre los ejemplos (4)
MiniBankNo se ha usado el soporte de relaciones para modelar la relación Uno-a-Muchos que conceptualmente existe entre Account y AccountOperation
Account no dispone de getAcountOperations()El número de operaciones devueltas podría ser excesivamente grandeEs más lógico utilizar el patrón Page-by-Page Iterator para recuperar las operaciones bancarias asociadas a una cuenta (apartado 5.4)
Notas sobre los ejemplos (y 5)
MiniBank (cont)Se podría haber modelado la relación (unidireccional) Muchos-a-Uno entre AccountOperation y Account
AccountOperation dispondría del método getAccount()AccountOperation no tendría la propiedad/atributo accountIdentifier
La consulta lanzada en la implementación del patrón Page-by-PageIterator quedaría como
SELECT o FROM AccountOperation oWHERE o.account.accountIdentifier = :accountIdentifier ANDo.date >= :startDate AND o.date <= :endDateORDER BY o.date
o (si antes se recupera la cuenta)
SELECT o FROM AccountOperation oWHERE o.account = :account ANDo.date >= :startDate AND o.date <= :endDateORDER BY o.date