java - namedquery - Crea la entidad JPA perfecta
jpql (4)
Intentaré responder varios puntos clave: esto se debe a una larga experiencia de Hibernación / persistencia que incluye varias aplicaciones importantes.
Clase de entidad: implementar Serializable?
Claves necesita implementar Serializable. Las cosas que se incluirán en HttpSession, o que se enviarán por cable mediante RPC / Java EE, deben implementar Serializable. Otras cosas: no tanto. Pasa tu tiempo en lo que es importante.
Constructores: crear un constructor con todos los campos requeridos de la entidad?
El (los) constructor (es) para la lógica de la aplicación, deben tener solo unos pocos campos críticos de "clave externa" o "tipo / clase" que siempre se conocerán al crear la entidad. El resto debe establecerse llamando a los métodos de establecimiento, para eso son.
Evite poner demasiados campos en los constructores. Los constructores deben ser convenientes y darles una cordura básica al objeto. Nombre, Tipo y / o Padres son típicamente útiles.
OTOH si las reglas de aplicación (hoy) requieren que un Cliente tenga una Dirección, déjela a un configurador. Ese es un ejemplo de una "regla débil". Tal vez la próxima semana, ¿desea crear un objeto de Cliente antes de ir a la pantalla Ingresar detalles? No se confunda, deje la posibilidad de datos desconocidos, incompletos o "parcialmente ingresados".
Constructores: también, paquete constructor privado por defecto?
Sí, pero use ''protegido'' en lugar de un paquete privado. Subclasificar cosas es un verdadero dolor cuando los elementos internos necesarios 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 la reflexión estándar, el método más simple y básico para Hibernar, funcione.
En cuanto a los campos ''inmutables'' a la aplicación, Hibernate aún necesita poder cargarlos. Puede intentar 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 un acceso no deseado.
Nota: al escribir una función equals (), ¡use captadores para los valores en la ''otra'' instancia! De lo contrario, llegará a los campos sin inicializar / vacíos en las instancias de proxy.
¿Protegido es mejor para el rendimiento (hibernación)?
Improbable.
Equals / HashCode?
Esto es relevante para trabajar con entidades, antes de que se hayan guardado, lo cual es un tema espinoso. Hashing / comparando en valores inmutables? En la mayoría de las aplicaciones de negocios, no hay ninguna.
Un cliente puede cambiar la dirección, cambiar el nombre de su negocio, etc., no es común, pero sucede. Las correcciones también deben ser posibles de realizar, cuando los datos no se ingresaron correctamente.
Las pocas cosas que normalmente se mantienen inmutables son la crianza de los hijos y tal vez el tipo / clase: normalmente el usuario recrea el registro, en lugar de cambiarlo. ¡Pero estos no identifican únicamente a la entidad!
Por lo tanto, largos y cortos, los datos "inmutables" reclamados no son realmente. Los campos de clave / ID primarios se generan con el propósito preciso de proporcionar dicha estabilidad e inmutabilidad garantizadas.
Debe planificar y considerar su necesidad de fases de trabajo de comparación, hash y procesamiento de solicitudes cuando A) trabaje con "datos modificados / enlazados" desde la interfaz de usuario si compara / hash en "campos con poca frecuencia", o B) trabajando con " datos no guardados ", si compara / hash en ID.
Equals / HashCode: si una clave de negocio única no está disponible, use un UUID no transitorio que se crea cuando se inicializa la entidad
Sí, esta es una buena estrategia cuando sea necesario. Tenga en cuenta que los UUID no son gratuitos, aunque el rendimiento es inteligente, y la agrupación en clústeres complica las cosas.
Equals / HashCode - nunca se refieren a entidades relacionadas
"Si la entidad relacionada (como una entidad principal) debe formar parte de la Clave comercial, a continuación, 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!
He estado trabajando con JPA (implementación Hibernate) por algún tiempo y cada vez que necesito crear entidades me encuentro luchando con problemas como AccessType, propiedades inmutables, igual a / hashCode, ....
Así que decidí intentar conocer la mejor práctica general para cada problema y escribirlo para uso personal.
Sin embargo, no me importaría que alguien comente al respecto o me diga dónde me equivoco.
Clase de entidad
Implementar Serializable
Razón: la especificación dice que debes hacerlo, pero algunos proveedores de JPA no hacen cumplir esto. Hibernate como proveedor de JPA no hace cumplir esto, pero puede fallar en algún lugar profundo de su estómago con ClassCastException, si Serializable no se ha implementado.
Constructores
Crea un constructor con todos los campos requeridos 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 por defecto privado
Motivo: se requiere que el constructor predeterminado haga que Hibernate inicialice la entidad; se permite lo privado, pero se requiere visibilidad privada (o pública) del paquete para la generación de proxy en tiempo de ejecución y la recuperación eficiente de datos sin instrumentación de bytecode.
Campos / Propiedades
Utilice el acceso al campo en general y el acceso a la propiedad cuando sea necesario
Razón: este es probablemente el problema 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 en general debido a un código más claro, mejor encapsulación y no es necesario crear definidores para campos inmutables
Omitir configuradores para campos inmutables (no requerido para el campo de tipo de acceso)
- propiedades pueden ser privadas
Razón: 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 adaptarla para que se ajuste al diseño de su aplicación.
Equals / 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 de negocio única y use esto para probar la igualdad
- si una clave comercial única no está 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 refieren a entidades relacionadas (ManyToOne); Si esta entidad (como una entidad matriz) debe ser parte de la clave de negocio, compare solo los ID. Llamar a getId () en un proxy no activará la carga de la entidad, siempre y cuando esté utilizando el tipo de acceso a la propiedad .
Ejemplo de entidad
@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 , he adaptado mi forma de implementar eq / hC:
- Si hay disponible una clave de negocios simple e inmutable: use esa
- en todos los demás casos: use un uuid
Interfaz de entidad
public interface Entity<I> extends Serializable {
/**
* @return entity identity
*/
I getId();
/**
* @return HashCode of entity identity
*/
int identityHashCode();
/**
* @param other
* Other entity
* @return true if identities of entities are equal
*/
boolean identityEquals(Entity<?> other);
}
Implementación básica para todas las entidades, simplifica implementaciones de Equals / Hashcode:
public abstract class AbstractEntity<I> implements Entity<I> {
@Override
public final boolean identityEquals(Entity<?> other) {
if (getId() == null) {
return false;
}
return getId().equals(other.getId());
}
@Override
public final int identityHashCode() {
return new HashCodeBuilder().append(this.getId()).toHashCode();
}
@Override
public final int hashCode() {
return identityHashCode();
}
@Override
public final boolean equals(final Object o) {
if (this == o) {
return true;
}
if ((o == null) || (getClass() != o.getClass())) {
return false;
}
return identityEquals((Entity<?>) o);
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + identity();
// OR
// return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}
Entidad de la habitación impl:
@Entity
@Table(name = "ROOM")
public class Room extends AbstractEntity<Integer> {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@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;
}
public Integer getId(){
return id;
}
public Building getBuilding() {
return building;
}
public String getNumber() {
return number;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
No veo el punto de comparar la igualdad de entidades según los campos de negocios en todos los casos de Entidades JPA. Eso podría ser más un caso si se piensa que estas entidades JPA son ValueObjects controlados por el dominio, en lugar de entidades controladas por el dominio (para lo que se incluyen estos ejemplos de código).
La especificación JPA 2.0 establece que:
- La clase de entidad debe tener un constructor sin argumentos. Puede tener otros constructores también. El constructor no arg debe ser público o protegido.
- La clase de entidad debe ser una clase de nivel superior. Un enum o interfaz no debe ser designado como una entidad.
- La clase de entidad no debe ser definitiva. Ningún método o variable de instancia persistente de la clase de entidad puede ser final.
- Si se va a pasar una instancia de entidad por valor como un objeto separado (por ejemplo, a través de una interfaz remota), la clase de entidad debe implementar la interfaz Serializable.
- Tanto las clases abstractas como las concretas pueden ser entidades. Las entidades pueden extender las clases que no son de entidad así como las clases de entidad, y las clases que no son de entidad pueden extender las clases de entidad.
La especificación no contiene requisitos sobre la implementación de los métodos equals y hashCode para las entidades, solo para las clases de clave primaria y las claves de mapa que yo sepa.
Mis 2 centavos además de las respuestas aquí son:
Con referencia al acceso a la propiedad o al campo (lejos de las consideraciones de rendimiento), a ambos se accede legítimamente por medio de captadores y definidores, por lo que mi lógica modelo puede configurarlos / obtenerlos de la misma manera. La diferencia viene a jugar cuando el proveedor de tiempo de ejecución de persistencia (Hibernate, EclipseLink o de otro tipo) necesita persistir / establecer algún registro en la Tabla A, que tiene una clave externa que se refiere a alguna columna en la Tabla B. En el 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 en 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 de la Tabla B directamente. Esta diferencia no es importante en el contexto de una relación unidireccional, sin embargo, DEBE utilizar mi propio método de establecimiento codificado (tipo de acceso a la propiedad) para una relación bidireccional, siempre que el método esté bien diseñado para tener en cuenta la coherencia . La consistencia es un tema crítico para las relaciones bidireccionales. Consulte este link para ver un ejemplo simple para un instalador bien diseñado.
Con referencia a Equals / hashCode: es imposible utilizar los métodos Equal / hashCode generados automáticamente por Eclipse para entidades que participan en una relación bidireccional, de lo contrario, tendrán una referencia circular que dará como resultado una excepción de . Una vez que intente una relación bidireccional (por ejemplo, OneToOne) y genere automáticamente Equals () o hashCode () o incluso toString (), quedará atrapado en esta excepción de .