He estado trabajando con JPA (implementación de Hibernate) durante algún tiempo y cada vez que necesito crear entidades me encuentro luchando con problemas como AccessType, propiedades inmutables, equals / hashCode, ....
Así que decidí intentar encontrar las mejores prácticas generales para cada problema y escribir esto para uso personal.
Sin embargo, no me importaría que nadie lo comentara o me dijera dónde me equivoco.
Clase de entidad
implementar serializable
Motivo: La especificación dice que tiene que hacerlo, pero algunos proveedores de JPA no lo hacen cumplir. Hibernate como proveedor de JPA no aplica esto, pero puede fallar en algún lugar profundo de su estómago con ClassCastException, si no se ha implementado Serializable.
Constructores
crear un constructor con todos los campos obligatorios de la entidad
Motivo: Un constructor siempre debe dejar la instancia creada en un estado sano.
además de este constructor: tener un paquete constructor privado predeterminado
Motivo: el constructor predeterminado es necesario para que Hibernate inicialice la entidad; se permite la privacidad, pero se requiere la visibilidad del paquete privado (o público) para la generación de proxy en tiempo de ejecución y la recuperación eficiente de datos sin instrumentación de código de bytes.
Campos / Propiedades
Use el acceso de campo en general y el acceso a la propiedad cuando sea necesario
Motivo: este es probablemente el tema más discutible ya que no hay argumentos claros y convincentes para uno u otro (acceso a la propiedad frente al acceso al campo); sin embargo, el acceso a los campos parece ser el favorito general debido a un código más claro, una mejor encapsulación y sin necesidad de crear setters para campos inmutables
Omitir configuradores para campos inmutables (no es necesario para el campo de tipo de acceso)
- las propiedades pueden ser privadas
Motivo: una vez escuché que protegido es mejor para el rendimiento (Hibernate) pero todo lo que puedo encontrar en la web es: Hibernate puede acceder a métodos de acceso públicos, privados y protegidos, así como a campos públicos, privados y protegidos directamente . La elección depende de usted y puede combinarla para adaptarse al diseño de su aplicación.
Igual / hashCode
- Nunca use una identificación generada si esta identificación solo se establece cuando persiste la entidad
- Por preferencia: use valores inmutables para formar una clave comercial única y úsela para probar la igualdad
- si no hay una Clave comercial única disponible, use un UUID no transitorio que se crea cuando se inicializa la entidad; Vea este gran artículo para más información.
- nunca se refiera a entidades relacionadas (ManyToOne); Si esta entidad (como una entidad matriz) necesita ser parte de la clave comercial, compare solo los ID. Llamar a getId () en un proxy no activará la carga de la entidad, siempre que esté utilizando el tipo de acceso a la propiedad .
Entidad de ejemplo
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
Otras sugerencias para agregar a esta lista son más que bienvenidas ...
ACTUALIZAR
Desde que leí este artículo, adapté mi forma de implementar eq / hC:
- si hay disponible una clave comercial simple inmutable: utilícela
- en todos los demás casos: use un líquido
final
(a juzgar por su omisión de establecedores, supongo que usted también).notNull
vieneRespuestas:
Trataré de responder varios puntos clave: esto se debe a la larga experiencia de Hibernación / persistencia, incluidas varias aplicaciones importantes.
Clase de entidad: implementar Serializable?
Keys necesita implementar Serializable. Las cosas que van a ir en la HttpSession, o que RPC / Java EE enviará por cable, deben implementar Serializable. Otras cosas: no tanto. Dedica tu tiempo a lo que es importante.
Constructores: ¿crear un constructor con todos los campos obligatorios de la entidad?
Los constructores para la lógica de la aplicación deben tener solo unos pocos campos críticos de "clave externa" o "tipo / tipo" que siempre se conocerán al crear la entidad. El resto debe establecerse llamando a los métodos de establecimiento; para eso están.
Evite poner demasiados campos en constructores. Los constructores deben ser convenientes y dar cordura básica al objeto. Nombre, tipo y / o padres son típicamente útiles.
OTOH si las reglas de la aplicación (hoy) requieren que un Cliente tenga una Dirección, déjela a un establecedor. Ese es un ejemplo de una "regla débil". Tal vez la próxima semana, ¿desea crear un objeto Cliente antes de ir a la pantalla Introducir detalles? No se tropiece, deje la posibilidad de datos desconocidos, incompletos o "parcialmente ingresados".
Constructores: también, paquete constructor privado predeterminado?
Sí, pero use 'protegido' en lugar de paquete privado. Subclasificar cosas es un verdadero dolor cuando las partes internas necesarias no son visibles.
Campos / Propiedades
Utilice el acceso al campo 'propiedad' para Hibernate y desde fuera de la instancia. Dentro de la instancia, use los campos directamente. Motivo: permite que funcione la reflexión estándar, el método más simple y básico para Hibernate.
En cuanto a los campos 'inmutables' para la aplicación, Hibernate todavía necesita poder cargarlos. Puede intentar hacer que estos métodos sean 'privados' y / o poner una anotación en ellos, para evitar que el código de la aplicación haga accesos no deseados.
Nota: cuando escriba una función equals (), ¡use getters para valores en la 'otra' instancia! De lo contrario, presionará campos no inicializados / vacíos en instancias de proxy.
¿Protegido es mejor para el rendimiento (Hibernate)?
Improbable.
Igual / HashCode?
Esto es relevante para trabajar con entidades, antes de que se hayan guardado, lo cual es un problema espinoso. Hashing / comparación de valores inmutables? En la mayoría de las aplicaciones comerciales, no hay ninguna.
Un cliente puede cambiar la dirección, cambiar el nombre de su negocio, etc., etc., no es común, pero sucede. También es necesario realizar correcciones cuando los datos no se ingresaron correctamente.
Las pocas cosas que normalmente se mantienen inmutables son Parenting y quizás Type / Kind, normalmente el usuario recrea el registro, en lugar de cambiarlo. ¡Pero estos no identifican únicamente a la entidad!
Entonces, a corto y largo plazo, los datos "inmutables" afirmados no son realmente. Los campos de clave principal / ID se generan con el propósito preciso, de proporcionar tal estabilidad garantizada e inmutabilidad.
Debe planificar y considerar su necesidad de comparar y procesar las fases de trabajo de procesamiento de solicitudes cuando A) trabaja con "datos modificados / enlazados" de la interfaz de usuario si compara / hash en "campos modificados con poca frecuencia", o B) trabaja con " datos no guardados ", si compara / hash en ID.
Equals / HashCode: si una clave comercial única no está disponible, use un UUID no transitorio que se crea cuando se inicializa la entidad
Sí, esta es una buena estrategia cuando se requiere. Tenga en cuenta que los UUID no son gratuitos, sin embargo, en cuanto al rendimiento, y la agrupación complica las cosas.
Equals / HashCode: nunca haga referencia a entidades relacionadas
"Si la entidad relacionada (como una entidad principal) necesita ser parte de la clave comercial, agregue un campo no insertable y no actualizable para almacenar la identificación principal (con el mismo nombre que ManytoOne JoinColumn) y use esta identificación en la verificación de igualdad "
Suena como un buen consejo.
¡Espero que esto ayude!
fuente
La especificación JPA 2.0 establece que:
La especificación no contiene requisitos sobre la implementación de métodos igual y hashCode para entidades, solo hasta donde sé, hasta donde yo sé.
fuente
Mis 2 centavos adicionales a las respuestas aquí son:
Con referencia al acceso de campo o propiedad (lejos de las consideraciones de rendimiento), a ambos se accede legítimamente mediante getters y setters, por lo tanto, la lógica de mi modelo puede establecerlos / obtenerlos de la misma manera. La diferencia viene a jugar cuando el proveedor de tiempo de ejecución de persistencia (Hibernate, EclipseLink u otro) necesita persistir / establecer algún registro en la Tabla A que tiene una clave foránea que hace referencia a alguna columna en la Tabla B. En caso de un tipo de acceso a la Propiedad, la persistencia El sistema de tiempo de ejecución utiliza mi método de establecimiento codificado para asignar un nuevo valor a la celda de la columna de la Tabla B. En el caso de un tipo de acceso de campo, el sistema de tiempo de ejecución de persistencia establece la celda en la columna Tabla B directamente. Esta diferencia no es importante en el contexto de una relación unidireccional, Sin embargo, es NECESARIO usar mi propio método de establecimiento codificado (Tipo de acceso de propiedad) para una relación bidireccional, siempre que el método de establecimiento esté bien diseñado para tener en cuenta la coherencia. La consistencia es un tema crítico para las relaciones bidireccionales.enlace para un ejemplo simple para un setter bien diseñado.
Con referencia a Equals / hashCode: es imposible utilizar los métodos Equals / hashCode autogenerados de Eclipse para las entidades que participan en una relación bidireccional, de lo contrario tendrán una referencia circular que dará como resultado una excepción de stackoverflow. Una vez que intente una relación bidireccional (digamos OneToOne) y genere automáticamente Equals () o hashCode () o incluso toString (), quedará atrapado en esta excepción de stackoverflow.
fuente
Interfaz de la entidad
Implementación básica para todas las entidades, simplifica las implementaciones de Equals / Hashcode:
Entidad de habitación impl:
No veo el punto de comparar la igualdad de entidades basadas en campos de negocios en todos los casos de Entidades JPA. Eso podría ser más un caso si estas entidades JPA se consideran Objetos de valor controlados por dominio, en lugar de Entidades controladas por dominio (para lo que son estos ejemplos de código).
fuente