Escolar Documentos
Profissional Documentos
Cultura Documentos
Introducción
Este apartado se da un tutorial sobre
Relaciones entre entidades
Herencia de entidades
EJB-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
Project
- projectIdentifier : String
- name : String
- startDate : Calendar
- endDate : Calendar
0..n
0..n
DistinguishedEmployee
- mentionDate : Calendar
- comment : String
Estructura de paquetes
es.udc.fbellas.j2ee.advancedejbtutorial.psa
model
department
entity
vo
employee
entity
vo
project
entity
vo
psafacade
ejb
exceptions
vo
util
testclient
Fachada del modelo y cliente de prueba
0..n
0..n
Employee
Department
Dirigido por
- employeeIdentifier : Long
- firstName : String - departmentIdentifier : String
1 0..1
- surname : String - name : String
- positionIdentifier : String - creationDate : Calendar
- salary : int 0..n 1 - employees : List<Employee>
- department : Department - director : Employee
- projects : List<Project> - version : long
- version : long + Constructores
+ Constructores + Métodos get/set
+ Métodos get/set
DistinguishedEmployee
Entidades (y 2)
Project
- projectIdentifier : String
- name : String
- startDate : Calendar
- endDate : Calendar
- employees : List<Employee>
- version : long
+ Constructores
+ Métodos get/set
0..n
0..n
Employee
DistinguishedEmployee
- mentionDate : Calendar
- comment : String
+ Constructores
+ Métodos get/set
Tablas
(PK) (FK)
Relación “Department(1)<-->Employee(0..N)”
(PK) (FK)
empId depId firstName surname posId salary version type Tabla = Employee
(PK, FK)
(PK, FK)
empId prjId Tabla = EmpPrj
Relación “Employee(0..N)<-->Project(0..N)”
(PK, FK)
(PK)
En Department
@Entity
public class Department {
// ...
@OneToOne
@JoinColumn(name="dirId")
public Employee getDirector() {
return director;
}
// ...
}
Relación “Department(0..1)--[Dirigido por]-->Employee(1)” (y 2)
Relaciones Uno-a-Uno
Se 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 Department
Especifica la columna que actúa como clave foránea para
mantener la relación
Se 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
@Entity
public class Employee {
// ...
@ManyToOne(optional=false)
@JoinColumn(name="depId")
public Department getDepartment() {
return department;
}
// ...
}
Relación “Department(1)<-->Employee(0..N)” (2)
En Department
@Entity
public class Department {
public Department() {
employees = new ArrayList<Employee>();
}
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;
}
// ...
}
Relación “Department(1)<-->Employee(0..N)” (4)
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 @OneToMany
con 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ón
Las 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 @OneToMany
y @ManyToMany
targetEntity permite especificar el tipo de la clase entidad
relacionada
Cuando 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
@Entity
public class Employee {
// ...
@ManyToMany
@JoinTable(
name="EmpPrj",
joinColumns=@JoinColumn(name="empId"),
inverseJoinColumns=@JoinColumn(name="prjId"))
public List<Project> getProjects() {
return projects;
}
// ...
}
Relación “Employee(0..N)<-->Project(0..N)” (2)
En Project
@Entity
public class Project {
// ...
@ManyToMany(mappedBy="projects")
public List<Employee> getEmployees() {
return 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 otro
En 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ón
joinColumns: claves foráneas (normalmente una) que
referencian las claves primarias de la tabla en la que se mapea
la entidad del lado propietario
inverseJoinColumns: 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.setDirector(employee);
}
Establecimiento de relaciones (y 2)
Ejemplo: en PSAFacadeEJB
public void assignEmployeeToProject(Long employeeIdentifier,
String projectIdentifier) throws InstanceNotFoundException {
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)
@Inheritance
Permite especificar la estrategia de herencia
Se usa en la clase padre
@DiscriminatorColumn
Permite 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 entidad
Se usa en cada entidad concreta de la jerarquía
EJB-QL
Lenguaje de consultas de búsqueda y
borrados/actualizaciones en masa
Sintaxis 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
destino
Las 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 departamentos
SELECT d FROM Department d
Ejecución:
List<Employee> employees = entityManager.createQuery(
"SELECT e FROM Employee e " +
"WHERE e.positionIdentifier = :posId").
setParameter("posId", positionIdentifier).
getResultList();
Subconsultas
Es posible anidar subconsultas en las cláusulas WHERE y
HAVING
Iremos viendo ejemplos ...
Expresiones condicionales (1)
Operadores matemáticos (+, -, *, /), de
comparación (=, >, <, >=, <=, <>) y lógicos (AND,
OR, NOT)
Ejemplo
SELECT e FROM Employee e WHERE
e.positionIdentifier = 'atp' AND e.salary >= 1000
Explicación
Obtener todos los empleados que ocupan el cargo de atp y su
salario es >= 1000
[NOT] BETWEEN
Ejemplo
SELECT e FROM Employee e WHERE e.salary BETWEEN 1000 AND 2000
Explicación
SELECT e FROM Employee e WHERE
e.salary >= 1000 AND e.salary <= 2000
Expresiones condicionales (2)
[NOT] IN
Ejemplo
SELECT d FROM Department d WHERE
d.departmentIdentifier IN ('tic', 'dc')
Explicación
SELECT d FROM Department d WHERE
d.departmentIdentifier = 'tic' OR
d.departmentIdentifier = 'dc'
[NOT] LIKE
Ejemplo
SELECT e FROM Employee e WHERE e.firstName LIKE 'F%o'
Explicación
Devuelve todos los empleados cuyo nombre empieza por F y
termina en o
Metacaracteres
% (secuencia de 0 o más caracteres), _ (cualquier carácter)
Se puede utilizar la cláusula ESCAPE para indicar un carácter de
escape
Ejemplo: e.firstName LIKE '%\_%' ESCAPE '\' devuelve
TRUE para cualquier nombre que incluya un subrayado (_)
Expresiones condicionales (3)
IS [NOT] NULL
Ejemplo
SELECT d FROM Department d WHERE d.name IS NULL
Explicación
Devuelve todos los departamentos para los que no se ha
especificado un valor para el atributo name
IS [NOT] NULL permite comprobar si un campo no colección
es NULL
IS [NOT] EMPTY
Ejemplo
SELECT d FROM Department d WHERE d.employees IS NOT EMPTY
Explicación
Devuelve todos los departamentos que tienen empleados
IS [NOT] EMPTY permite comprobar si un campo colección
es vacío
Expresiones condicionales (4)
[NOT] EXISTS
Ejemplo
SELECT d FROM Department d WHERE
EXISTS (SELECT e FROM Employee e WHERE
e.positionIdentifier = :posId AND e.department = d)
Explicación
Devuelve todos los departamentos que tengan al menos un
empleado desempeñando un determinado cargo
EXISTS devuelve TRUE si la subconsulta devuelve uno o más
resultados
Expresiones condicionales (5)
ALL y ANY/SOME
Ejemplo
SELECT e FROM Employee e WHERE e.salary >= ALL
(SELECT e.salary FROM Employee e)
Explicación
Devuelve todos los empleados que tengan el salario más alto
ALL
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/SOME
TRUE 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 cadenas
CONCAT(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 primero
El 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úsculas
Ejemplo: obtener los empleados cuyo nombre empieza por F/f
y termina en O/o
SELECT e FROM Employee e WHERE UPPER(e.firstName) LIKE 'F%O'
Expresiones condicionales (y 8)
Funciones aritméticas
ABS(number)
Valor absoluto de un int, float o double
SQRT(number)
Raíz cuadrada
Recibe 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ón
Devuelve un int
Ejemplo: obtener todos los departamentos que tienen
empleados
SELECT d FROM Department d WHERE SIZE(d.employees) > 0
Funciones agregadas (1)
Son funciones que se pueden usar como resultado de
una consulta
Todas aceptan como argumento una expresión que
haga referencia a un atributo/propiedad no relación
Adicionalmente, COUNT acepta como argumento una
variable o una expresión que haga referencia a un
atributo/propiedad relación
AVG
Calcula la media
Recibe un argumento numérico y devuelve un Double
Ejemplo: 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 {
return averageSalary.intValue();
} catch (NoResultException e) {
return 0;
}
}
Funciones agregadas (3)
COUNT
Devuelve el número (Long) de resultados
Ejemplo: calcular el número de departamentos
SELECT COUNT(d) FROM Department d
MAX/MIN
Calcula el valor máximo/mínimo
Requiere un argumento de tipo numérico, String, char o
fechas
Ejemplo: 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)
SUM
Calcula la suma
Devuelve Long si se aplica a enteros, Double si se aplica a
reales y BigInteger/BigDecimal cuando se aplica a
argumentos BigInteger/BigDecimal
Ejemplo: calcula el salario total de los empleados
SELECT SUM(e.salary) FROM Employee e
JOIN (1)
INNER JOIN implícito
Producto 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 WHERE
e.department = d AND e.positionIdentifier = :posId
NOTAS:
Equivalente a la consulta SQL
SELECT 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ícito
Usa la cláusula [INNER] JOIN
INNER JOIN y JOIN son sinónimos
Ejemplo: el anterior
SELECT DISTINCT d
FROM Department d JOIN d.employees e
WHERE e.positionIdentifier = :posId
Explicación
Devuelve el salario medio para los departamentos tic y dc
GROUP BY, HAVING (y 2)
Otro ejemplo
SELECT 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 e
GROUP BY d.departmentIdentifier
Explicación
Devuelve estadísticas para cada departamento
Los 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 individuales
Borrado de una entidad
EntityManager.remove
Actualización de una entidad
Modificar 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 1
Localizar las entidades y eliminar/actualizar cada una
Ineficiente
Opción 2
Utilizar el soporte de EJB-QL para borrados y actualizaciones en
masa
Sentencias DELETE y UPDATE
Borrados y actualizaciones en masa (2)
Ejemplos
Eliminar todos los departamentos
DELETE FROM Department
}
Notas sobre los ejemplos (2)
Subsistema AdvancedEJBTutorial
Simplificaciones (cont)
También se podría haber lanzado ...
SELECT d FROM Department d ORDER BY d.departmentIdentifier
return PSAFacadeHelper.toEmployeeVOs(employees);
}
Notas sobre los ejemplos (3)
Subsistema AdvancedEJBTutorial
Simplificaciones (cont)
En PSAFacadeHelper