2009-10-26 21 views
10

Un desafío al utilizar hibernación es que las clases gestionadas deben tener un constructor predeterminado . El problema es que no hay un punto explícito en el que la clase se inicialice y se puedan verificar invariantes.Comprobar invariantes en las clases mapeadas de hibernación

Si una clase tiene invariantes que dependen de más de una propiedad, el diseño de clase se vuelve complejo. Vamos a empezar con el diseño hipotético-campo verde:

public class A { 
    private int x; 
    private int y; 

    public A(int x, int y) { 
     this.x = x; 
     this.y = y; 
     checkInvariants(this.x, this.y); 
    } 

    private void checkInvariants(int x, int y) { 
     if (x + y « 0) throw new IllegalArgumentException(); 
    } 
} 

Esta es la implementación base que no cumpla con los requisitos de hibernación. El invariante se verifica en el constructor. (Método El contenido de los checkInvariants() No importa que sólo ha presentado para ilustrar que los invariantes de clase pueden depender más de una propiedad.)

La clase se puede utilizar de la siguiente manera:

new A(0, 0); 
new A(-1, 0); //invalid 

Para cumplir con los requisitos de hibernación una solución alternativa es agregar un constructor predeterminado privado y usar el acceso de campo. (I omitido las asignaciones de hibernación.)

public class H { 
    int x; 
    int y; 

    public H(int x, int y) { 
     this.x = x; 
     this.y = y; 
     checkInvariants(this.x, this.y); 
    } 

    H(){} 

    private void checkInvariants(int x, int y) { 
     if (x + y « 0) throw new IllegalArgumentException(); 
    } 
} 

Esto tiene dos inconvenientes principales: * usted está comenzando a implementar el código que depende del cliente (Hibernate). Idealmente, una clase no conoce a sus llamadores. * Un problema específico con esta solución alternativa es que las instancias iniciadas por hibernación son no marcadas si cumplen con las invariantes. Confía en los datos que se cargan desde la base de datos que es problemático. Incluso si su aplicación es la única que usa este esquema de base de datos específico, siempre existe la posibilidad de cambios ad hoc por parte de los administradores.

Una segunda solución consiste en invariantes de verificación de código de usuario:

Obviamente, esto hace que el código de usuario más complejo y propenso a errores. Este diseño no cumple con la expectativa de que una instancia sea consistente después de la creación y se mantenga constante después de cada cambio de estado (llamada a método). Cada usuario tiene que verificar las invariantes para cada instancia que crea (tal vez indirectamente con hibernación).

¿Hay una mejor solución a este problema, que es:

  • no excesivamente compleja
  • sin conocimiento explícito sobre sus usuarios
  • sin una dependencia con el marco de hibernación?

Supongo que algunas de las limitaciones deben aflojarse para llegar a una solución pragmática. La única restricción difícil es que no hay dependencia al marco de hibernación. (El código específico de Hibernate fuera de los objetos de dominio está bien).

(Solo por curiosidad: ¿hay un marco ORM que soporte la "inyección de constructor"?)

+0

Este tipo de regla debería aplicarse mediante una restricción de comprobación en la base de datos, lo que evitaría que las instancias cargadas contravinieran la regla. – araqnid

+0

La pregunta no es productiva. Está tratando de imponer una 'filosofía arbitraria' de aplicación de restricciones perpetuas a datos variables que, por definición, no son actualizables en una única actualización atómica. No está tratando de resolver un problema comercial o de diseño válido, simplemente empujando BS en un carro. –

+1

@ThomasW Todos tienen un mal día de vez en cuando. ¡Aclamaciones! (Tal vez podría ayudar si descubres qué es un invariante). –

Respuesta

5

En primer lugar, permítanme abordar los "inconvenientes" que ha enumerado en su primera aproximación:

Estás empezando a implementar el código que depende del cliente (Hibernate). Idealmente, una clase no conoce sus llamadores .

Usted está usando la palabra "dependencia" un poco rápido y suelto aquí. Hibernate no es un "cliente"; Es un marco que (como desarrollador/arquitecto/con el que tiene) eligió implementar su persistencia. Por lo tanto, va a tener algún código en alguna parte que use (y, por lo tanto, dependa de) Hibernate. Dicho esto, hay NO dependencia de Hibernate en su objeto de dominio anterior. Tener un constructor no-arg es un requisito semántico si se quiere; no introduce una dependencia real. Cambia Hibernate por JPA/TopLink/raw jdbc/lo que tienes y no tendrás que cambiar nada en el código de objeto de tu dominio.

Un problema específico con esta solución es que casos iniciados por de hibernación no se comprueban si el cumplen los invariantes. Confía en los datos que se cargan desde la base de datos que es problemático. Incluso si su aplicación es la única que utiliza este esquema de base de datos específica existe siempre existe la posibilidad de cambios ad-hoc por parte de los administradores.

Usted no tiene que "confianza" los datos (más sobre esto más adelante). Sin embargo, no creo que este argumento tenga mérito. Si está modificando sus datos en múltiples aplicaciones, debe realizar validaciones en alguna capa inferior común en lugar de confiar en cada aplicación individual para validar los datos. Dicha capa común podría ser la propia base de datos (en casos simples) o una capa de servicio que proporciona una API común para ser utilizada por sus múltiples aplicaciones.

Por otra parte, la noción de administradores hacer cambios directamente a la base de datos como parte de la rutina diaria es completamente ridícula.Si está hablando de casos especiales (correcciones de errores, ¿qué tiene?) Deberían tratarse como tales (es decir, presumiblemente algo como esto ocurre muy raramente y la carga de validar tales cambios "críticos" recae en quien realiza los cambios; no en cada una de tus aplicaciones en la pila).

Dicho todo esto, si desea validar su objeto cuando están cargados, es razonablemente fácil de lograr. Defina una interfaz Valid que tenga el método validate() y haga que cada objeto de dominio afectado la implemente. Puede invocar ese método desde:

  1. Su DAO/servicio después de cargar el objeto.
  2. Hibernate Interceptor or Listener - ambos están configurados en la configuración de Hibernate; todo lo que necesita hacer es implementar cualquiera de los dos para verificar si el objeto que se está carga implementa Valid y, si es así, invocar el método.
  3. O puede usar Hibernate Validator, sin embargo, eso atará los objetos de su dominio a Hibernate como los anotaría.

Por último, en lo que "la inyección de constructor" va - No sé de cualquier marco que lo soporta directamente . El motivo es bastante simple: solo tiene sentido para las entidades inmutables (porque una vez que tienes instaladores tienes que lidiar con la validación de todos modos) y significa mucho trabajo (manejo de asignaciones de parámetros de constructor, etc.) para casi cero efecto neto. De hecho, si usted es que preocupado por tener un constructor sin argumentos para objetos inmutables que no siempre se puede asignar como entidades y en lugar de cargarlos a través de la cual HQL hace apoyo constructor injection:

select new A(x, y) from ... 

Actualización (para abordar los puntos de los comentarios de Thomas):

  1. Solo he mencionado que el interceptor está completo; oyente es mucho más apropiado en este caso. PostLoadEventListener se invoca después de que la entidad esté completamente inicializada.
  2. Una vez más, tener el constructor no-arg no es una dependencia. Es un contrato, sí, pero no vincula su código a Hibernate de ninguna manera. Y en lo que respecta a los contratos, es una parte de la especificación javabean (de hecho, es menos restrictivo porque el constructor no tiene que ser público) por lo que en la mayoría de los casos lo seguiría de todos modos.
  3. Accediendo a la base de datos. "La refacturación de bases de datos y las migraciones son comunes", sí, lo son. Pero es solo código; lo escribe, lo prueba, ejecuta las pruebas de integración, lo despliega en producción. Si su objeto de modelo de dominio usa algún método StringUtil.compare() que escribió en alguna clase de utilidad, no tiene que volver a verificar los resultados, ¿verdad? Del mismo modo, el objeto de dominio no debería tener que comprobar que su secuencia de comandos de migración no haya roto nada; debe tener las pruebas adecuadas para eso. "La capacidad de hacer consultas ad-hoc ... es una de las características" - absolutamente. Consultas. Al igual que en las consultas de "solo lectura" utilizadas para informar, por ejemplo (aun así, en muchos casos es más apropiado pasar por la API). Pero manual de datos manipulación como no de emergencia - absolutamente no.
  4. La mutabilidad hace que la inyección del constructor sea irrelevante. No estoy diciendo que no puedas tener un constructor que no sea el predeterminado, puedes, y puedes usarlo en tu código.Pero si tienes métodos setter no puedes usar tu constructor para la validación, por lo tanto, si realmente es o no, realmente no importa.
  5. HQL constructor injection and associations. Los constructores anidados no funcionarán por lo que yo sé (por ejemplo, no puede escribir select new A(x, y, new B(c, d)); por lo tanto, para obtener asociaciones, tendrá que recuperarlas como entidades en la cláusula select, lo que significa que deben tener no- arg constructores mismos :-) O usted puede tener un constructor en la entidad "principal" que tiene todas las propiedades necesarias anidados como argumentos y construye/puebla asociaciones interno, sino que es el límite :-) loca
+0

La combinación de interceptores de hibernación y una interfaz de validación es una buena solución. El código de usuario se puede dejar sin cambios utilizando el constructor A (x, y) y el constructor privado + el acceso de campo es + la hibernación tiene acceso a la validación. (La implementación del interceptor no será tan fácil. La entidad no se inicializa cuando se llama a onLoad). –

+0

Dependencia: su código de dominio no es independiente del marco ORM. Hay un contrato subyacente que debes cumplir para trabajar con el marco y de eso dependes. (No importa que el contrato sea el mismo para diferentes marcos. Todavía está allí). –

+0

Accediendo a la base de datos directamente: Una vez es suficiente. Su aplicación es inconsistente con todas las consecuencias. Por lo tanto, mi trabajo consiste en verificar que todos los datos sean coherentes (de lo contrario, la empresa estaría duplicada en las secuencias de comandos de la base de datos). La refacturación de bases de datos y las migraciones son comunes y no un caso de esquina. La capacidad de realizar consultas ad-hoc en una base de datos relacional es una de las características de por qué las bases de datos relacionales aún se utilizan. –

2

Un enfoque se basaría en su "clase I", pero con la clase misma llamando a checkInvariantes() la primera vez que se usan realmente los campos. Esto permite que los campos sean temporalmente inválidos durante la asignación (después de llamar a setX() pero antes de setY() por ejemplo). Pero aún asegura que solo se usan datos válidos.

Su clase de ejemplo no utiliza los valores, así que voy a presumir valores podrían acceder a través de getX(), getY(), y doSomething(), tales como:

public int getX(){ 
    return x; 
} 

public int getY(){ 
    return y; 
} 

public void doSomething(){ 
    x = x + y; 
} 

Usted lo cambiaría de esta manera:

private boolean validated = false; 

public int getX(){ 
    if (!validated) { 
     checkInvariants(); 
    } 

    return x; 
} 

public int getY(){ 
    if (!validated) { 
     checkInvariants(); 
    } 
    return y; 
} 

public void doSomething(){ 
    if (!validated) { 
     checkInvariants(); 
    } 
    x = x + y; 
} 

public void checkInvariants() { 
    validated = true; 
    if (x + y « 0) throw new IllegalArgumentException(); 
} 

Se podía mover el 'si (validado!)' en checkInvariants() si lo desea.

También puede consultar el Validador de Hibernate, aunque puede no cumplir con los requisitos de independencia de su estructura. http://docs.huihoo.com/hibernate/annotations-reference-3.2.1/validator.html

3

la coherencia de las El modelo relacional es un concepto de importancia crítica para gasp. Debido a la inherente estabilidad matemática de los principios que subyacen a los datos de relación modelado, podemos estar totalmente seguro que el resultado de cualquier consulta de nuestra base de datos original será de hecho generar Qually hechos válidos. - Del libro El arte de SQL - Stephane Faroult

Su base de datos debe consistir en hechos válidos o verdades, datos que se carga desde la base de datos no deberán exigir ningún tipo de validaciones adicionales, el hecho de que te sacó salir de la base de datos debería ser lo suficientemente bueno.

Pero si le preocupa que haya datos incorrectos, entonces sugeriría que hay un problema más serio.

He visto soluciones que van desde tener una base de datos de etapas donde se borran todos los datos, se validan antes de entrar en la base de datos real hasta que se revisan los arreglos manuales antes de ingresarlos.

De cualquier manera, una vez que haya dañado datos o declaraciones falsas en su base de datos, ¿en qué más puede confiar?

+0

Disculpa si recibí predicación – GDR

+0

Amen a eso (y +1) – ChssPly76

Cuestiones relacionadas