Anotaciones de Hibernate: ¿cuál es mejor, acceso a campo o propiedad?

Esta pregunta está relacionada de alguna manera con la pregunta de Hibernate Annotation Placement .

Pero quiero saber que es mejor ? ¿Acceso a través de propiedades o acceso por campos? ¿Cuales son las ventajas y desventajas de cada uno?

Prefiero los usuarios de acceso, ya que puedo agregar un poco de lógica comercial a mis accesorios cuando lo necesito. Aquí hay un ejemplo:

@Entity public class Person { @Column("nickName") public String getNickName(){ if(this.name != null) return generateFunnyNick(this.name); else return "John Doe"; } } 

Además, si se lanzan otras libs a la mezcla (como alguna lib de conversión a JSON o BeanMapper o Dozer u otra mapeo de frijol / lib de clonación basada en propiedades getter / setter), tendrás la garantía de que la lib está sincronizada con la persistencia gerente (ambos usan el getter / setter).

Existen argumentos para ambos, pero la mayoría de ellos se derivan de ciertos requisitos de usuario “qué sucede si necesita agregar lógica para” o “xxxx rompe la encapsulación”. Sin embargo, nadie realmente ha comentado sobre la teoría, y se le ha dado un argumento debidamente razonado.

¿Qué está haciendo realmente Hibernate / JPA cuando persiste un objeto? Bueno, persiste el estado del objeto. Eso significa almacenarlo de forma que pueda reproducirse fácilmente.

¿Qué es la encapsulación? Encapsulations significa encapsular los datos (o estados) con una interfaz que la aplicación / cliente puede usar para acceder a los datos de forma segura, manteniéndolo constante y válido.

Piense en esto como MS Word. MS Word mantiene un modelo del documento en la memoria: los documentos ESTADO. Presenta una interfaz que el usuario puede usar para modificar el documento: un conjunto de botones, herramientas, comandos de teclado, etc. Sin embargo, cuando elige mantener (guardar) ese documento, guarda el estado interno, no el conjunto de pulsaciones de tecla y clics del mouse utilizados para generarlo.

Guardar el estado interno del objeto NO interrumpe la encapsulación; de lo contrario, no se comprende realmente qué significa la encapsulación y por qué existe. Es como la serialización de objetos realmente.

Por esta razón, EN LA MAYORÍA DE LOS CASOS, es apropiado persistir los CAMPOS y no los ACCESORES. Esto significa que un objeto puede recrearse con precisión desde la base de datos exactamente de la manera en que fue almacenado. No debería necesitar ninguna validación, porque esto se hizo en el original cuando se creó, y antes de que se almacenara en la base de datos (a menos que, Dios no lo quiera, usted está almacenando datos no válidos en el DB !!!!). Del mismo modo, no debería haber necesidad de calcular valores, ya que ya se calcularon antes de almacenar el objeto. El objeto debería verse exactamente como lo hacía antes de guardarse. De hecho, al agregar cosas adicionales en los getters / setters, en realidad está aumentando el riesgo de que vuelva a crear algo que no sea una copia exacta del original.

Por supuesto, esta funcionalidad se agregó por una razón. Puede haber algunos casos de uso válidos para persistir en los usuarios de acceso, sin embargo, normalmente serán raros. Un ejemplo puede ser que desee evitar la persistencia de un valor calculado, aunque es posible que desee plantear la pregunta de por qué no lo calcula a demanda en el captador del valor o inicializarlo de forma diferida en el captador. Personalmente no puedo pensar en ningún buen caso de uso, y ninguna de las respuestas aquí realmente da una respuesta de “Ingeniería de Software”.

Prefiero el acceso de campo, porque de esa manera no estoy obligado a proporcionar getter / setter para cada propiedad.

Una encuesta rápida a través de Google sugiere que el acceso de campo es la mayoría (por ejemplo, http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype ).

Creo que el acceso al campo es el idioma recomendado por Spring, pero no puedo encontrar una referencia para respaldarlo.

Hay una pregunta relacionada con SO que intentó medir el rendimiento y llegó a la conclusión de que “no hay diferencia”.

Aquí hay una situación en la que TIENE que usar los descriptores de acceso a la propiedad. Imagine que tiene una clase abstracta GENERIC con muchas bondades de implementación para heredar en 8 subclases concretas:

 public abstract class Foo { T oneThing; T anotherThing; // getters and setters ommited for brevity // Lots and lots of implementation regarding oneThing and anotherThing here } 

¿Ahora exactamente cómo debe anotar esta clase? La respuesta es USTED NO PUEDE anotar en absoluto con el campo o el acceso a la propiedad porque no puede especificar la entidad objective en este punto. TIENE que anotar las implementaciones concretas. Pero dado que las propiedades persistentes se declaran en esta superclase, DEBE utilizar el acceso a la propiedad en las subclases.

El acceso de campo no es una opción en una aplicación con superclases genéricas abstractas.

Tiendo a preferir y usar accesadores de propiedades:

  • Puedo agregar lógica si surge la necesidad (como se menciona en la respuesta aceptada).
  • me permite llamar a foo.getId() sin inicializar un proxy (importante al usar Hibernate, hasta que HHH-3718 se resuelva).

Retirarse:

  • hace que el código sea menos legible, por ejemplo, puedes navegar por toda una clase para ver si hay @Transient por allí.

Eso realmente depende de un caso específico; ambas opciones están disponibles por una razón. IMO se reduce a tres casos:

  1. setter tiene una lógica que no debería ejecutarse en el momento de cargar una instancia desde una base de datos; por ejemplo, una validación de valor ocurre en el setter, sin embargo, los datos provenientes de db deben ser válidos (de lo contrario no llegarían allí (:); en este caso, el acceso de campo es el más apropiado;
  2. setter tiene alguna lógica que siempre debe invocarse, incluso durante la carga de una instancia desde db; por ejemplo, la propiedad que se inicializa se usa en el cálculo de algún campo calculado (por ejemplo, propiedad – una cantidad monetaria, propiedad calculada – un total de varias propiedades monetarias de la misma instancia); en este caso, se requiere acceso a la propiedad.
  3. Ninguno de los casos anteriores: entonces ambas opciones son aplicables, solo mantente constante (p. Ej., Si el acceso de campo es la opción en esta situación, úsala todo el tiempo en una situación similar).

Recomiendo encarecidamente el acceso de campo y NO anotaciones en los captadores (acceso a la propiedad) si desea hacer algo más en los instaladores que simplemente establecer el valor (por ejemplo, cifrado o cálculo).

El problema con el acceso a la propiedad es que los setters también se llaman cuando se carga el objeto. Esto funcionó bien durante muchos meses hasta que quisimos introducir el cifrado. En nuestro caso de uso, queríamos encriptar un campo en el setter y descifrarlo en el getter. El problema ahora con el acceso a la propiedad era que cuando Hibernate cargaba el objeto, también llamaba al colocador para rellenar el campo y, por lo tanto, volvía a encriptar el valor encriptado. Esta publicación también menciona esto: Java Hibernate: comportamiento de función de conjunto de propiedades diferente según quién lo llame

Esto me ha causado dolores de cabeza hasta que recordé la diferencia entre el acceso de campo y el acceso a la propiedad. Ahora he movido todas mis anotaciones del acceso a la propiedad al acceso de campo y ahora funciona bien.

Creo que anotar la propiedad es mejor porque actualizar los campos directamente rompe la encapsulación, incluso cuando lo hace su ORM.

Aquí hay un gran ejemplo de dónde te quemará: probablemente quieras tus anotaciones para el validador de hibernación y la persistencia en el mismo lugar (ya sea campos o propiedades). Si desea probar las validaciones activadas de su validador de hibernación que se anotan en un campo, no puede usar un simulacro de su entidad para aislar la prueba de su unidad solo para el validador. Ay.

Creo que el acceso a la propiedad frente al acceso de campo es sutilmente diferente con respecto a la inicialización perezosa.

Considere las siguientes asignaciones para 2 beans básicos:

                 

Y las siguientes pruebas unitarias:

 @Test public void testFieldBean() { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); FieldBean fb = new FieldBean("field"); Long id = (Long) session.save(fb); tx.commit(); session.close(); session = sessionFactory.openSession(); tx = session.beginTransaction(); fb = (FieldBean) session.load(FieldBean.class, id); System.out.println(fb.getId()); tx.commit(); session.close(); } @Test public void testPropBean() { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); PropBean pb = new PropBean("prop"); Long id = (Long) session.save(pb); tx.commit(); session.close(); session = sessionFactory.openSession(); tx = session.beginTransaction(); pb = (PropBean) session.load(PropBean.class, id); System.out.println(pb.getId()); tx.commit(); session.close(); } 

Verá la sutil diferencia en las selecciones requeridas:

 Hibernate: call next value for hibernate_sequence Hibernate: insert into FIELD_BEAN (message, id) values (?, ?) Hibernate: select fieldbean0_.id as id1_0_, fieldbean0_.message as message1_0_ from FIELD_BEAN fieldbean0_ where fieldbean0_.id=? 0 Hibernate: call next value for hibernate_sequence Hibernate: insert into PROP_BEAN (message, id) values (?, ?) 1 

Es decir, llamar a fb.getId() requiere una selección, mientras que pb.getId() no.

Prefiero usar el acceso de campo por los siguientes motivos:

  1. El acceso a la propiedad puede provocar errores muy desagradables al implementar equals / hashCode y al hacer referencia directamente a los campos (a diferencia de sus getters). Esto se debe a que el proxy solo se inicializa cuando se accede a los getters, y un acceso de campo directo simplemente devuelve null.

  2. El acceso a la propiedad requiere que anote todos los métodos de utilidad (por ejemplo, addChild / removeChild) como @Transient .

  3. Con el acceso de campo, podemos ocultar el campo @Version al no exponer un getter. Un getter también puede llevar a agregar un setter, y el campo de version nunca debe configurarse manualmente (lo que puede llevar a problemas muy desagradables). Todo el incremento de la versión debe activarse a través del locking explícito OPTIMISTIC_FORCE_INCREMENT o PESSIMISTIC_FORCE_INCREMENT .

Ya llegamos

Esa es una presentación anterior, pero Rod sugiere que la anotación sobre el acceso a la propiedad fomenta modelos de dominio anémicos y no debe ser la forma “predeterminada” de anotar.

Otro punto a favor del acceso de campo es que si no, se ve obligado a exponer colecciones de recostackdores, lo que para mí es una mala idea, ya que cambiar la instancia de colección persistente a un objeto no gestionado por Hibernate definitivamente romperá la coherencia de los datos.

Por lo tanto, prefiero tener colecciones como campos protegidos inicializados en implementaciones vacías en el constructor predeterminado y exponer solo sus captadores. Entonces, solo son posibles las operaciones administradas como clear() , remove() , removeAll() etc. que nunca harán que Hibernate desconozca los cambios.

Yo prefiero los campos, pero me he topado con una situación que parece obligarme a colocar las anotaciones en getters.

Con la implementación de Hibernate JPA, @Embedded no parece funcionar en los campos. Así que eso tiene que ir en el getter. Y una vez que lo pones en el getter, las diversas anotaciones de @Column tienen que ir también a los getters. (Creo que Hibernate no quiere mezclar campos y captadores aquí.) Y una vez que @Column en getters en una clase, probablemente tenga sentido hacerlo todo el tiempo.

Yo prefiero los descriptores de campo. El código es mucho más limpio. Todas las anotaciones se pueden colocar en una sección de una clase y el código es mucho más fácil de leer.

Encontré otro problema con los descriptores de acceso a propiedades: si tienes métodos getXYZ en tu clase que NO están anotados como asociados con propiedades persistentes, hibernate genera sql para intentar obtener esas propiedades, lo que da como resultado algunos mensajes de error muy confusos. Dos horas perdidas. No escribí este código; Siempre he usado accesos de campo en el pasado y nunca me he encontrado con este problema.

Versiones de Hibernate utilizadas en esta aplicación:

  3.3.2.GA 3.4.0.GA 3.1.0.GA 3.4.0.GA 

Tuve la misma pregunta con respecto a accesstype en hibernate y encontré algunas respuestas aquí .

He resuelto la inicialización diferida y el acceso de campo aquí Hibernate uno-a-uno: getId () sin recuperar el objeto completo

Creamos beans de entidad y utilizamos anotaciones getter. El problema que encontramos es el siguiente: algunas entidades tienen reglas complejas para algunas propiedades con respecto a cuándo se pueden actualizar. La solución era tener cierta lógica comercial en cada incubadora que determina si el valor real cambió o no, y si es así, si el cambio debería permitirse. Por supuesto, Hibernate siempre puede establecer las propiedades, por lo que terminamos con dos grupos de setters. Muy feo.

Al leer publicaciones anteriores, también veo que hacer referencia a las propiedades desde el interior de la entidad podría generar problemas con las colecciones que no se cargan.

En pocas palabras, me inclinaría a anotar los campos en el futuro.

Por defecto, los proveedores de JPA acceden a los valores de los campos de entidad y asignan esos campos a las columnas de la base de datos utilizando los métodos de acceso de propiedad (getter) y mutador (setter) de la entidad JavaBean. Como tal, los nombres y tipos de los campos privados en una entidad no importan a JPA. En cambio, JPA solo mira los nombres y los tipos de devolución de los accesos de propiedad de JavaBean. Puede modificar esto utilizando la anotación @javax.persistence.Access , que le permite especificar explícitamente la metodología de acceso que debe emplear el proveedor de JPA.

 @Entity @Access(AccessType.FIELD) public class SomeEntity implements Serializable { ... } 

Las opciones disponibles para la enumeración AccessType son PROPERTY (valor predeterminado) y FIELD. Con PROPERTY, el proveedor obtiene y establece valores de campo utilizando los métodos de propiedad JavaBean. FIELD hace que el proveedor obtenga y establezca valores de campo usando los campos de instancia. Como práctica recomendada, solo debe apegarse a los valores predeterminados y usar las propiedades de JavaBean a menos que tenga una razón convincente para hacerlo de otra manera.

Puede poner estas anotaciones de propiedad en los campos privados o en los métodos de acceso público. Si usa AccessType.PROPERTY (predeterminado) y anota los campos privados en lugar de los accesadores JavaBean, los nombres de los campos deben coincidir con los nombres de las propiedades de JavaBean. Sin embargo, los nombres no tienen que coincidir si anota los accesadores JavaBean. Del mismo modo, si usa AccessType.FIELD y anota los AccessType.FIELD JavaBean en lugar de los campos, los nombres de los campos también deben coincidir con los nombres de las propiedades de JavaBean. En este caso, no tienen que coincidir si anota los campos. Lo mejor es ser coherente y anotar los AccessType.PROPERTY JavaBean para AccessType.PROPERTY y los campos para AccessType.FIELD .

Es importante que nunca mezcles las anotaciones de propiedades JPA y las anotaciones de campo JPA en la misma entidad. Hacerlo da como resultado un comportamiento no especificado y es muy probable que cause errores.

Normalmente, los frijoles son POJO, por lo que tienen acceso de todos modos.

Entonces, la pregunta no es “¿cuál es mejor?”, Sino simplemente “¿cuándo usar el acceso de campo?”. Y la respuesta es “¡cuando no necesitas un setter / getter para el campo!”.

Estoy pensando en esto y elijo el acceso al método

¿por qué?

porque el campo y el acceso de Methos es el mismo, pero si más adelante necesito un poco de lógica en el campo de carga, guardo todas las anotaciones colocadas en los campos

Saludos

Grubhart

Para que sus clases sean más limpias, coloque la anotación en el campo y luego use @Access (AccessType.PROPERTY)

Ambos :

La especificación EJB3 requiere que usted declare las anotaciones en el tipo de elemento al que se accederá, es decir, el método getter si usa el acceso a la propiedad, el campo si usa el acceso al campo.

https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping

AccessType.PROPERTY: la implementación de persistencia EJB cargará estado en su clase a través de los métodos “setter” de JavaBean, y recuperará el estado de su clase utilizando los métodos “getter” de JavaBean. Este es el predeterminado.

AccessType.FIELD: El estado se carga y recupera directamente de los campos de su clase. No tiene que escribir JavaBean “getters” y “setters”.