Validación en aplicaciones Java

Este texto está dedicado a varios enfoques para la validación de datos: qué dificultades puede encontrar un proyecto y qué métodos y tecnologías deben seguirse al validar datos en aplicaciones Java.


Validación


A menudo vi proyectos cuyos creadores no se molestaron en elegir un enfoque para la validación de datos. Los equipos trabajaron en el proyecto bajo una presión increíble en forma de plazos y requisitos vagos, y como resultado, simplemente no tuvieron tiempo para una validación precisa y consistente. Por lo tanto, su código de validación está disperso en todas partes: en fragmentos de Javascript, controladores de pantalla, en contenedores de lógica de negocios, entidades de dominio, disparadores y restricciones de la base de datos. Este código estaba lleno de declaraciones if-else, arrojó un montón de excepciones, y trató de averiguar dónde se valida ese dato en particular allí ... Como resultado, a medida que el proyecto se desarrolla, se vuelve difícil y costoso cumplir con los requisitos (a menudo bastante confusos), y uniformidad de los enfoques para la validación de datos.


Entonces, ¿hay alguna forma simple y elegante de validar los datos? ¿Hay alguna manera que nos proteja del pecado de la imposibilidad de leer, una forma que junte toda la lógica de validación y que los desarrolladores de marcos populares de Java ya nos hayan creado?


Sí, existe tal manera.


Para nosotros, los desarrolladores de la plataforma CUBA , es muy importante que pueda utilizar las mejores prácticas. Creemos que el código de validación debe:


  1. Sea reutilizable y siga el principio SECO;
  2. Sea natural y comprensible;
  3. Colocado donde el desarrollador espera verlo;
  4. Poder verificar datos de diferentes fuentes: interfaz de usuario, llamadas SOAP, REST, etc.
  5. Trabajar en un entorno multiproceso sin problemas;
  6. Llamado dentro de la aplicación automáticamente, sin la necesidad de ejecutar controles manualmente;
  7. Para dar al usuario mensajes claros y localizados en cuadros de diálogo concisos;
  8. Sigue los estándares.

Veamos cómo se puede implementar esto usando una aplicación de ejemplo escrita usando el marco de la Plataforma CUBA. Sin embargo, dado que CUBA se basa en Spring y EclipseLink, la mayoría de las técnicas utilizadas aquí funcionarán en cualquier otra plataforma Java que admita las especificaciones JPA y Bean Validation.


Validación utilizando restricciones de base de datos


Quizás la forma más común y obvia de validar datos es usar restricciones en el nivel de la base de datos, por ejemplo, el indicador requerido (para campos cuyo valor no puede estar vacío), longitud de cadena, índices únicos, etc. Este método es el más adecuado para aplicaciones empresariales, ya que este tipo de software generalmente se centra estrictamente en el procesamiento de datos. Sin embargo, incluso aquí los desarrolladores a menudo cometen errores al establecer límites por separado para cada nivel de la aplicación. Muy a menudo, la razón radica en la distribución de responsabilidades entre los desarrolladores.


Considere un ejemplo que la mayoría de nosotros conocemos, algunos incluso por nuestra propia experiencia ... Si la especificación dice que debe haber 10 caracteres en el campo del número de pasaporte, es muy probable que todos lo verifiquen: el arquitecto de la base de datos en DDL, el desarrollador del backend en la Entidad correspondiente y Servicios REST, y finalmente, el desarrollador de la interfaz de usuario directamente en el lado del cliente. Luego, este requisito cambia y el campo aumenta a 15 caracteres. Los desarrolladores cambian los valores de restricciones en la base de datos, pero nada cambia para el usuario, porque en el lado del cliente, la restricción es la misma ...


Cualquier desarrollador sabe cómo evitar este problema: ¡la validación debe estar centralizada! En CUBA, dicha validación se encuentra en las anotaciones de entidad JPA. En función de esta metainformación, CUBA Studio generará el script DDL correcto y aplicará los validadores apropiados del lado del cliente.


Ejemplo de restricciones


Si las anotaciones cambian, CUBA actualizará los scripts DDL y generará los scripts de migración, por lo que la próxima vez que implemente el proyecto, entrarán en vigencia nuevas restricciones basadas en JPA tanto en la interfaz como en la base de datos de la aplicación.


A pesar de la simplicidad y la implementación en el nivel de la base de datos, lo que brinda una confiabilidad absoluta a este método, el alcance de las anotaciones JPA se limita a los casos más simples que se pueden expresar en el estándar DDL y no incluyen disparadores de bases de datos o procedimientos almacenados. Por lo tanto, las restricciones basadas en JPA pueden hacer que un campo de entidad sea único u obligatorio o establecer una longitud máxima de columna. Incluso puede establecer una restricción única en la combinación de columnas utilizando la anotación @UniqueConstraint . Pero eso es probablemente todo.


Sea como fuere, en los casos que requieren una lógica de validación más compleja, como verificar un campo para un valor mínimo / máximo, validar con una expresión regular o realizar una verificación personalizada específica solo para su aplicación, se aplica el enfoque conocido como "Validación de Bean" .


Validación de frijol


Todos saben que es una buena práctica seguir los estándares que tienen un ciclo de vida largo, cuya efectividad ha sido probada en miles de proyectos. Java Bean Validation es un enfoque documentado en JSR 380, 349 y 303 y sus aplicaciones: Hibernate Validator y Apache BVal .


Aunque este enfoque es familiar para muchos desarrolladores, a menudo se subestima. Esta es una manera fácil de integrar la validación de datos incluso en proyectos heredados, lo que le permite crear validaciones de manera clara, simple, confiable y lo más cercana posible a la lógica de negocios.


Usar Bean Validation le da al proyecto muchas ventajas:


  • La lógica de validación se encuentra al lado del área temática: la definición de restricciones para los campos y métodos del contenedor ocurre de una manera natural y verdaderamente orientada a objetos.
  • El estándar Bean Validation nos brinda docenas de anotaciones de validación @NotNull para @NotNull , por ejemplo: @NotNull , @Size , @Min , @Max , @Pattern , @Email , @Past , no del todo estándar @URL , @Length , el @ScriptAssert más poderoso y muchos otros .
  • El estándar no nos limita a las anotaciones ya hechas y nos permite crear las nuestras. También podemos crear una nueva anotación combinando varias otras, o definirla usando una clase Java separada como validador.
    Por ejemplo, en el ejemplo anterior, podemos establecer la anotación del nivel de clase @ValidPassportNumber para verificar que el número de pasaporte coincida con el formato según el valor del campo del country .
  • Las restricciones se pueden establecer no solo en campos o clases, sino también en métodos y sus parámetros. Este enfoque se llama "validación por contrato" y se discutirá un poco más adelante.

Cuando el usuario envía la información ingresada, la Plataforma CUBA (como algunos otros marcos) inicia Bean Validation automáticamente, por lo que muestra instantáneamente un mensaje de error si la validación falla, y no necesitamos ejecutar los validadores bin manualmente.


Volvamos al ejemplo con el número de pasaporte, pero esta vez lo complementaremos con varias restricciones de la entidad Persona:


  • El campo de name debe tener 2 o más caracteres y debe ser válido. (Como puede ver, regexp no es simple, pero "Charles Ogier de Batz de Castelmore Comte d'Artagnan" pasará la prueba, pero "R2D2" no lo hará);
  • height (altura) debe estar en el siguiente intervalo: 0 < height <= 300 cm;
  • El campo de email debe contener una cadena que coincida con el formato del correo electrónico correcto.

Con todos estos controles, la clase Persona se verá así:


 @Listeners("passportnumber_PersonEntityListener") @NamePattern("%s|name") @Table(name = "PASSPORTNUMBER_PERSON") @Entity(name = "passportnumber$Person") @ValidPassportNumber(groups = {Default.class, UiCrossFieldChecks.class}) @FraudDetectionFlag public class Person extends StandardEntity { private static final long serialVersionUID = -9150857881422152651L; @Pattern(message = "Bad formed person name: ${validatedValue}", regexp = "^[AZ][az]*(\\s(([az]{1,3})|(([az]+\\')?[AZ][az]*)))*$") @Length(min = 2) @NotNull @Column(name = "NAME", nullable = false) protected String name; @Email(message = "Email address has invalid format: ${validatedValue}", regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$") @Column(name = "EMAIL", length = 120) protected String email; @DecimalMax(message = "Person height can not exceed 300 centimeters", value = "300") @DecimalMin(message = "Person height should be positive", value = "0", inclusive = false) @Column(name = "HEIGHT") protected BigDecimal height; @NotNull @Column(name = "COUNTRY", nullable = false) protected Integer country; @NotNull @Column(name = "PASSPORT_NUMBER", nullable = false, length = 15) protected String passportNumber; ... } 

Person.java


Creo que el uso de anotaciones como @NotNull , @DecimalMin , @Length , @Pattern y similares es bastante obvio y no requiere comentarios. Echemos un vistazo más de cerca a la implementación de la anotación @ValidPassportNumber .


Nuestro nuevo @ValidPassportNumber verifica que Person#passportNumber coincida con el patrón regexp para cada país especificado por el campo Person#country .


Primero, veamos la documentación (los manuales de CUBA o Hibernate están bien), de acuerdo con esto, debemos marcar nuestra clase con esta nueva anotación y pasarle el parámetro de groups , donde UiCrossFieldChecks.class significa que esta validación debe ejecutarse en la cruz. validaciones: después de verificar todos los campos individuales, Default.class guarda la restricción en el grupo de validación predeterminado.


La descripción de la anotación se ve así:


 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = ValidPassportNumberValidator.class) public @interface ValidPassportNumber { String message() default "Passport number is not valid"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } 

ValidPassportNumber.java


Aquí @Target(ElementType.TYPE) dice que el propósito de esta anotación en tiempo de ejecución es la clase, y @Constraint(validatedBy = … ) determina que la validación la realiza la clase ValidPassportNumberValidator que implementa la interfaz ConstraintValidator<...> . El código de validación en sí está en el isValid(...) , que realiza la verificación real de una manera bastante directa:


 public class ValidPassportNumberValidator implements ConstraintValidator<ValidPassportNumber, Person> { public void initialize(ValidPassportNumber constraint) { } public boolean isValid(Person person, ConstraintValidatorContext context) { if (person == null) return false; if (person.country == null || person.passportNumber == null) return false; return doPassportNumberFormatCheck(person.getCountry(), person.getPassportNumber()); } private boolean doPassportNumberFormatCheck(CountryCode country, String passportNumber) { ... } } 

ValidPassportNumberValidator.java


Eso es todo Con la Plataforma CUBA, no necesitamos escribir nada más que una línea de código que hará que nuestra validación personalizada funcione y le dé mensajes de error al usuario.
Nada complicado, ¿verdad?


Ahora veamos cómo funciona todo. Aquí CUBA tiene otros nishtyaki: no solo le muestra al usuario un mensaje de error, sino que también lo resalta en los campos rojos que no pasaron la validación del bean:


Representación UI


¿No es esa una solución elegante? Obtiene una visualización adecuada de los errores de validación en la interfaz de usuario al agregar solo un par de anotaciones de Java a las entidades del área temática.


Para resumir la sección, enumeremos brevemente las ventajas de Bean Validation para las entidades una vez más:


  1. Es comprensible y legible;
  2. Le permite definir restricciones de valor directamente en clases de entidad;
  3. Se puede personalizar y complementar;
  4. Integrado en ORM populares, y las comprobaciones se ejecutan automáticamente antes de guardar los cambios en la base de datos;
  5. Algunos marcos también ejecutan la validación de beans automáticamente cuando el usuario envía datos a la interfaz de usuario (y si no, es fácil llamar a la interfaz Validator manualmente);
  6. Bean Validation es un estándar reconocido y está lleno de documentación en Internet.

Pero, ¿qué sucede si necesita establecer una restricción en un método, constructor o dirección REST para validar los datos provenientes de un sistema externo? ¿O si necesita verificar declarativamente los valores de los parámetros del método, sin escribir código aburrido con muchas condiciones if-else en cada método que se está probando?


La respuesta es simple: ¡La validación de frijoles también se aplica a los métodos!


Validación por contrato


A veces es necesario ir más allá de la validación del estado del modelo de datos. Muchos métodos pueden beneficiarse de la validación automática de parámetros y el valor de retorno. Esto puede ser necesario no solo para verificar los datos que van a las direcciones REST o SOAP, sino también en aquellos casos en los que queremos anotar las condiciones previas y posteriores de las llamadas a métodos para asegurarnos de que los datos ingresados ​​se verificaron antes de que se ejecutara el cuerpo del método, o que el valor de retorno está en el rango esperado, o por ejemplo, solo necesitamos describir declarativamente los rangos de valores de los parámetros de entrada para mejorar la legibilidad del código.


Usando la validación de beans, se pueden aplicar restricciones a los parámetros de entrada y valores de retorno de métodos y constructores para verificar las condiciones previas y posteriores de sus llamadas en cualquier clase de Java. Esta ruta tiene varias ventajas sobre los métodos tradicionales de verificar la validez de los parámetros y los valores de retorno:


  1. No es necesario realizar comprobaciones manuales en un estilo imperativo (por ejemplo, lanzando IllegalArgumentException y similares). Puede definir restricciones declarativamente y hacer que el código sea más comprensible y expresivo;
  2. Las restricciones se pueden configurar, reutilizar y configurar: no es necesario escribir la lógica de validación para cada verificación. Menos código significa menos errores.
  3. Si la clase, el valor de retorno del método o su parámetro se marca con la anotación @Validated , la plataforma realizará las comprobaciones automáticamente cada vez que se llame al método.
  4. Si el módulo ejecutable está marcado con la anotación @Documented , sus condiciones @Documented y posteriores se incluirán en el JavaDoc generado.

Usando 'validación de contrato' obtenemos un código claro, compacto y fácil de mantener.


Por ejemplo, veamos la interfaz del controlador REST de una aplicación CUBA. La interfaz PersonApiService permite obtener una lista de personas de la base de datos usando el método getPersons() y agregar una nueva persona usando la addNewPerson(...) .


¡Y no olvide que la validación del bean se hereda! En otras palabras, si anotamos una determinada clase, campo o método, todas las clases que heredan esta clase o implementan esta interfaz estarán sujetas a la misma anotación de validación.


 @Validated public interface PersonApiService { String NAME = "passportnumber_PersonApiService"; @NotNull @Valid @RequiredView("_local") List<Person> getPersons(); void addNewPerson( @NotNull @Length(min = 2, max = 255) @Pattern(message = "Bad formed person name: ${validatedValue}", regexp = "^[AZ][az]*(\\s(([az]{1,3})|(([az]+\\')?[AZ][az]*)))*$") String name, @DecimalMax(message = "Person height can not exceed 300 cm", value = "300") @DecimalMin(message = "Person height should be positive", value = "0", inclusive = false) BigDecimal height, @NotNull CountryCode country, @NotNull String passportNumber ); } 

PersonApiService.java


¿Es este fragmento de código lo suficientemente claro?
_ (Excepto la anotación @RequiredView(“_local”) , que es específica de la Plataforma CUBA y verifica que el objeto Person devuelto contiene todos los campos de la tabla PASSPORTNUMBER_PERSON ) ._


La @Valid define que cada objeto de colección devuelto por el método getPersons() también debe validarse contra las restricciones de la clase Person .


En una aplicación CUBA, estos métodos están disponibles en las siguientes direcciones:


  • / app / rest / v2 / services / passportnumber_PersonApiService / getPersons
  • / app / rest / v2 / services / passportnumber_PersonApiService / addNewPerson

Abramos la aplicación Postman y asegurémonos de que la validación funcione como debería:


Aplicación de cartero


Como habrás notado, el número de pasaporte no está validado en el ejemplo anterior. Esto se debe a que este campo requiere una verificación cruzada de los parámetros del método addNewPerson , ya que la elección de una plantilla de expresión regular para validar passportNumber depende del valor del campo del country . ¡Esta validación cruzada es un análogo completo de las restricciones de entidad a nivel de clase!


La validación cruzada de parámetros es compatible con JSR 349 ​​y 380. Puede leer la documentación de hibernación para aprender cómo implementar su propia validación cruzada de métodos de clase / interfaz.


Validación de frijol exterior


No hay perfección en el mundo, por lo que la validación de beans tiene sus inconvenientes y limitaciones:


  1. A veces solo necesitamos verificar el estado de un gráfico complejo de objetos antes de guardar los cambios en la base de datos. Por ejemplo, debe asegurarse de que todos los elementos de un pedido del cliente se coloquen en un paquete. Esta es una operación bastante difícil, y realizarla cada vez que el usuario agrega nuevos artículos al pedido no es una buena idea. Por lo tanto, tal verificación puede ser necesaria solo una vez: antes de guardar el objeto Order y sus subobjetos OrderItem en la base de datos.
  2. Algunas comprobaciones deben hacerse dentro de una transacción. Por ejemplo, el sistema de tienda electrónica debe verificar si hay suficientes copias de los productos para cumplir con el pedido antes de enviarlo a la base de datos. Tal verificación solo se puede realizar dentro de una transacción, porque El sistema es multiproceso y la cantidad de productos en stock puede cambiar en cualquier momento.

La plataforma CUBA ofrece dos mecanismos de validación de datos previos al compromiso llamados escuchas de entidades y escuchas de transacciones . Consideremos con más detalle.


Oyentes de la entidad


Los oyentes de entidad en CUBA son muy similares a los PreInsertEvent , PreUpdateEvent y PredDeleteEvent que JPA ofrece al desarrollador. Ambos mecanismos le permiten verificar los objetos de entidad antes y después de que estén almacenados en la base de datos.


En CUBA, es fácil crear y conectar un escucha de entidad, para esto necesita dos cosas:


  1. Cree un bean administrado que implemente una de las interfaces de escucha de la entidad. 3 interfaces son importantes para la validación:
    BeforeDeleteEntityListener<T> ,
    BeforeInsertEntityListener<T> ,
    BeforeUpdateEntityListener<T>
  2. Agregue la anotación @Listeners al objeto de entidad que planea rastrear.

Y eso es todo.


En comparación con el estándar JPA (JSR 338, Sección 3.5), las interfaces de escucha de la Plataforma CUBA están escritas, por lo que no es necesario emitir un argumento de tipo Object a un tipo de entidad para comenzar a trabajar con él. La plataforma CUBA agrega entidades relacionadas o personas que llaman EntityManager la capacidad de cargar y modificar otras entidades. Todos estos cambios también invocarán al oyente de la entidad correspondiente.


La plataforma CUBA también admite "eliminación suave" , un enfoque en el que, en lugar de eliminar registros de la base de datos, solo se marcan como eliminados y quedan inaccesibles para el uso normal. Entonces, para la eliminación suave, la plataforma llama a los oyentes BeforeDeleteEntityListener / AfterDeleteEntityListener , mientras que las implementaciones estándar llamarían a los PostUpdate PreUpdate / PostUpdate .


Veamos un ejemplo. Aquí, el bean de escucha de eventos se conecta a la clase de entidad con solo una línea de código: la anotación @Listeners , que toma el nombre de la clase de escucha:


 @Listeners("passportnumber_PersonEntityListener") @NamePattern("%s|name") @Table(name = "PASSPORTNUMBER_PERSON") @Entity(name = "passportnumber$Person") @ValidPassportNumber(groups = {Default.class, UiCrossFieldChecks.class}) @FraudDetectionFlag public class Person extends StandardEntity { ... } 

Person.java


La implementación del oyente en sí se ve así:


 /** * Checks that there are no other persons with the same * passport number and country code * Ignores spaces in the passport number for the check. * So numbers "12 45 768007" and "1245 768007" and "1245768007" * are the same for the validation purposes. */ @Component("passportnumber_PersonEntityListener") public class PersonEntityListener implements BeforeDeleteEntityListener<Person>, BeforeInsertEntityListener<Person>, BeforeUpdateEntityListener<Person> { @Override public void onBeforeDelete(Person person, EntityManager entityManager) { if (!checkPassportIsUnique(person.getPassportNumber(), person.getCountry(), entityManager)) { throw new ValidationException( "Passport and country code combination isn't unique"); } } @Override public void onBeforeInsert(Person person, EntityManager entityManager) { // use entity argument to validate the Person object // entityManager could be used to access database // if you need to check the data // throw ValidationException object if validation check failed if (!checkPassportIsUnique(person.getPassportNumber(), person.getCountry(), entityManager)) throw new ValidationException( "Passport and country code combination isn't unique"); } @Override public void onBeforeUpdate(Person person, EntityManager entityManager) { if (!checkPassportIsUnique(person.getPassportNumber(), person.getCountry(), entityManager)) throw new ValidationException( "Passport and country code combination isn't unique"); } ... } 

PersonEntityListener.java


Los oyentes de entidades son una gran opción si:


  • Es necesario verificar los datos dentro de la transacción antes de que el objeto de entidad se almacene en la base de datos;
  • Es necesario verificar los datos en la base de datos durante el proceso de validación, por ejemplo, para verificar que haya suficiente producto en stock para aceptar el pedido;
  • OrderItems mirar no solo el objeto de la entidad, como Order , sino también las entidades relacionadas, por ejemplo, OrderItems para la entidad Order ;
  • Queremos rastrear las operaciones de inserción, actualización o eliminación solo para ciertas clases de entidad, por ejemplo, solo para OrderItem Order y OrderItem , y no necesitamos verificar los cambios en otras clases de entidades durante la transacción.

Oyentes de transacciones


Los oyentes de transacciones CUBA también actúan en el contexto de transacciones, pero, en comparación con los oyentes de entidades, se les llama para cada transacción de base de datos.


Esto les da superpoder:


  • nada puede escapar de su atención.

Pero esto está determinado por sus defectos:


  • son más difíciles de escribir;
  • pueden reducir significativamente el rendimiento;
  • Deben escribirse con mucho cuidado: un error en el escucha de transacciones puede interferir incluso con la carga inicial de la aplicación.

Por lo tanto, los oyentes de transacciones son una buena solución cuando necesita inspeccionar diferentes tipos de entidades utilizando el mismo algoritmo, por ejemplo, verificando todos los datos en busca de fraude cibernético con un solo servicio que sirve a todos sus objetos comerciales.


¡No pasarás!


Eche un vistazo a una muestra que verifica si la entidad tiene una anotación @FraudDetectionFlag y, si la hay, inicia un detector de fraude. Repito: tenga en cuenta que este método se llama en el sistema antes de confirmar cada transacción de la base de datos , por lo que el código debe intentar verificar la menor cantidad posible de objetos.


 @Component("passportnumber_ApplicationTransactionListener") public class ApplicationTransactionListener implements BeforeCommitTransactionListener { private Logger log = LoggerFactory.getLogger(ApplicationTransactionListener.class); @Override public void beforeCommit(EntityManager entityManager, Collection<Entity> managedEntities) { for (Entity entity : managedEntities) { if (entity instanceof StandardEntity && !((StandardEntity) entity).isDeleted() && entity.getClass().isAnnotationPresent(FraudDetectionFlag.class) && !fraudDetectorFeedAndFastCheck(entity)) { logFraudDetectionFailure(log, entity); String msg = String.format( "Fraud detection failure in '%s' with id = '%s'", entity.getClass().getSimpleName(), entity.getId()); throw new ValidationException(msg); } } } ... } 

ApplicationTransactionListener.java


Para convertirse en un escucha de transacciones, un bean administrado debe implementar la interfaz BeforeCommitTransactionListener y el método beforeCommit . Los oyentes de transacciones se vinculan automáticamente cuando se inicia la aplicación. CUBA registra todas las clases que implementan BeforeCommitTransactionListener o BeforeCommitTransactionListener como escuchas de transacciones.


Conclusión


La validación de frijoles (JPA 303, 349 y 980) es un enfoque que puede servir como una base confiable para el 95% de los casos de validación de datos encontrados en un proyecto corporativo. La principal ventaja de este enfoque es que la mayor parte de la lógica de validación se concentra directamente en las clases de modelo de dominio. Por lo tanto, es fácil de encontrar, fácil de leer y fácil de mantener. Spring, CUBA y muchas otras bibliotecas son compatibles con estos estándares y realizan automáticamente verificaciones de validación cuando reciben datos en la capa de IU, llaman a métodos validados o guardan datos a través de ORM, por lo que la validación de Bean a menudo parece mágica desde el punto de vista del desarrollador.


Algunos desarrolladores de software consideran que la validación a nivel de clases del modelo de asignatura no es natural y es demasiado complicada, dicen que la validación de datos a nivel de interfaz de usuario es una estrategia bastante efectiva. Sin embargo, creo que numerosos puntos de validación en componentes y controladores de UI no son el enfoque más racional. , , , , , listener' .


, , :


  1. JPA , , DDL.
  2. Bean Validation — , , , . , .
  3. bean validation, . , , REST.
  4. Entity listeners: , Bean Validation, . , . Hibernate .
  5. Transaction listeners — , , . , , .

PS: , Java, , , .




Enlaces utiles


Source: https://habr.com/ru/post/es427543/


All Articles