2012-07-09 50 views
5

Relativamente nuevo para JPA, así que tengo un tipo de pregunta arquitectónica. Digamos que tengo tablas EMPLEADO y DEPARTAMENTO con relación de muchos a uno (es decir, muchos empleados trabajan para un departamento):JPA - columna calculada como propiedad de clase de entidad?

EMPLOYEE 
    EMPLOYEE_ID 
    EMPLOYEE_NAME 
    DEPARTMENT_ID 

DEPARTMENT 
    DEPARTMENT_ID 
    DEPARTMENT_NAME 

, así que puede definir entidades adecuadas para los empleados y Departamento, no hay problema. Sin embargo, en una sola vista me gustaría mostrar lista de departamentos con el número de empleados que trabajan para ese departamento, algo como esto:

SELECT D.DEPARTMENT_NAME, 
     (SELECT COUNT(*) FROM EMPLOYEE E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID) NUMBER_OF_EMPLOYEES 
FROM DEPARTMENT D 

No estoy seguro de lo que es la estrategia adecuada para lograr esto usando JPA. .. No quiero buscar siempre el número de empleados para la entidad del Departamento, ya que solo hay una vista cuando es necesaria.

Parece que la @Formula de Hibernate sería un posible enfoque, pero afaik no cumple con el estándar JPA.

Respuesta

4

Puede crear cualquier objeto en su QL usando la sintaxis "nueva" - su clase solo necesita un constructor que tome los valores devueltos por su consulta.

Por ejemplo, con una clase como DepartmentEmployeeCount, con un constructor:

public DepartmentEmployeeCount(String departmentName, Integer employeeCount) 

usted podría utilizar QL algo como:

SELECT NEW DepartmentEmployeeCount(D.DEPARTMENT_NAME, count(E.id)) from Department D left join D.employees E GROUP BY D.DEPARTMENT_NAME 

O si se acaba de seleccionar la cuenta (*) se simplemente podría convertir el resultado de la consulta en un Número.

alternativa, para hacer lo mismo sin la clase DepartmentEmployeeCount, se podía dejar de lado el nuevo, así:

SELECT D.DEPARTMENT_NAME, count(E.id)  

Esto devolvería un List<Object[]> la que cada elemento de la lista era una matriz de 2 elementos, DepartmentName y recuento .

Para responder a su pregunta posterior en los comentarios, para completar todos los campos de un Departamento más un campo employeeCount transitorio, una sugerencia sería hacer 2 consultas. Esto aún sería más eficiente que su consulta original (una subselección para cada recuento de empleados).

Así que una consulta a leer los departamentos

SELECT D from Department D 

que le da una List<Department>

A continuación, una segunda consulta devolviendo una matriz temporal:

SELECT D.DEPARTMENT_ID, count(E.id) from Department D left join D.employees E GROUP BY D.DEPARTMENT_ID 

que le da una List<Object[]> con DEPARTMENT_ID y recuento en eso.

Luego usa la segunda lista para actualizar la propiedad del conteo transitorio en su primera lista. (Puede intentar seleccionar en un Mapa para facilitar esta búsqueda, pero creo que es una función de Hibernación).

+0

Gracias @MattR por su respuesta. Esta solución podría funcionar para este simple ejemplo. Pero, ¿qué pasa si (a) la tabla DEPARTAMENTO tiene muchas otras columnas además de NOMBRE y, obviamente, no quiero enumerarlas todas en el constructor y (b) todavía quiero tenerlo como propiedad de la entidad del Departamento (con búsqueda PERDIDA? tipo) para ser accesible en otro lugar por si acaso. – AndreiM

+0

Buena pregunta, no lo he intentado pero creo que * puede usar clases de entidad en el constructor (por lo que no necesitaría seleccionar todas las propiedades de la entidad). Si tengo la oportunidad de probarlo, actualizaré mi respuesta ... es decir, podría tener una clase de DepartmentEmployees (por ejemplo) que tuviera un constructor DepartmentEmployees (Department, Integer) y usar SELECT nuevos DepartmentEmployees (D, count (E.id))) - no es exactamente lo que estás pidiendo pero está más cerca ... – MattR

+0

OK Estaba pensando en esto durante el almuerzo, así que actualicé algo de información extra. Si intenta alguna de las sugerencias o encuentra una solución mejor, informe de nuevo – MattR

3

Opción 1: Sugerí esto porque no te gustaba la ruta del constructor que MattR sugería.Usted mencionó la palabra "ver" varias veces, y sé que estaba hablando de la vista para el usuario, pero ¿por qué no configurar una vista en su base de datos que incluye las columnas calculadas y luego crear una entidad de solo lectura que se correlaciona con el columnas?

Opción 2: En respuesta a su comentario acerca de no querer crear una vista. Puede crear un objeto contenedor que contenga la entidad y la columna calculada, entonces, de forma muy parecida a como sugiere MattR, use una nueva en su selección. Algo así como:

public class DepartmentInfo { 
    private Department department; 

    // this might have to be long or something 
    // just see what construct JPA tries to call 
    private int employeeCount; 

    public DepartmentInfo(Department d, int count) { 
     department = d; 
     employeeCount = count; 
    } 
    // getters and setters here 
} 

Entonces su selecto convierte

SELECT new my.package.DepartmentInfo(D, 
     (SELECT COUNT(*) FROM EMPLOYEE E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID)) 
FROM DEPARTMENT D 

Con eso se puede utilizar el DepartmentInfo para obtener las propiedades que le interesan

+0

Eso es posible, de hecho. Pero lo que quiero es solo una propiedad extra para mi entidad existente, que puedo usar en una página (y, posiblemente, en otra parte). ¿Realmente vale la pena crear otra vista y otra entidad para eso? – AndreiM

+0

Lo que quiere _hasta ahora_ es solo una propiedad extra para su entidad existente para usar en una página, y posiblemente en otra. Y en el futuro posiblemente 3 más con posiblemente 4 más propiedades adicionales y así sucesivamente. En cualquier caso, he agregado la opción 2 a mi respuesta. – digitaljoel

+0

Gracias, @digitaljoel, hasta ahora parece el enfoque más óptimo en este escenario. – AndreiM

1

Se puede crear un miembro en su entidad como. una columna adicional, y luego hacer referencia a ella con un alias en su consulta. El nombre de la columna en la anotación @Column debe coincidir con el alias.

Supongamos que para su consulta original, puede agregar un miembro countEmployees de la siguiente manera. También añadir insertable=false y updatable=false por lo que el gestor de la entidad suele tratar de incluirlo en las declaraciones de inserción o actualización:

public class Department { 

    @Column(name="DEPARTMENT_ID") 
    Long departmentId; 

    @Column(name="DEPARTMENT_NAME") 
    String departmentName; 

    @Column(name="countEmployees", insertable=false, updatable=false) 
    Long countEmployees; 

    //accessors omitted 

} 

Y su consulta:

SELECT D.DEPARTMENT_NAME, 
(SELECT COUNT(*) FROM EMPLOYEE E WHERE E.DEPARTMENT_ID = D.DEPARTMENT_ID) AS countEmployees 
FROM DEPARTMENT D 

Esto también se aplica cuando se trabaja con datos de primavera Jpa repositorios.

+0

¿Puedes hacer lo mismo usando una consulta JPA, no una nativa? – Arthur

+0

Esta estrategia no funciona. – Partha

Cuestiones relacionadas