[java] Crear la entidad JPA perfecta



Answers

Trataré de responder varios puntos clave: esto es de una larga experiencia de Hibernación / persistencia que incluye varias aplicaciones importantes.

Clase de entidad: implementar Serializable?

Keys necesita implementar Serializable. Las cosas que van a ir en la HttpSession, o ser enviadas por RPC / Java EE, deben implementar Serializable. Otras cosas: no tanto. Dedica tu tiempo a lo que es importante.

Constructores: ¿crear un constructor con todos los campos requeridos 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 setter, para eso están.

Evite poner demasiados campos en constructores. Los constructores deben ser convenientes y dar cordura básica al objeto. El nombre, el tipo y / o los padres son típicamente útiles.

OTOH si las reglas de aplicación (hoy) requieren que un Cliente tenga una Dirección, déjelas a un instalador. 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 privado por defecto constructor?

Sí, pero use 'protegido' en lugar de paquete privado. Subclasificar cosas es un verdadero dolor cuando los elementos internos necesarios no son visibles.

Campos / Propiedades

Utilice el acceso de campo 'propiedad' para Hibernate y desde fuera de la instancia. Dentro de la instancia, usa los campos directamente. Motivo: permite la reflexión estándar, el método más simple y básico para Hibernate, para funcionar.

En cuanto a los campos 'inmutables' para la aplicación, Hibernate aún debe poder cargarlos. Podría 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: cuando se escribe una función equals (), ¡use getters para valores en la 'otra' instancia! De lo contrario, tocará campos no inicializados / 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 / comparar en valores inmutables? En la mayoría de las aplicaciones comerciales, no hay ninguna.

Un cliente puede cambiar de dirección, cambiar el nombre de su empresa, etc., etc., no es común, pero sucede. Las correcciones también deben poder realizarse 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 a la entidad de manera única!

Entonces, largo y corto, los datos reclamados como "inmutables" no son realmente. Los campos de clave principal / ID se generan para 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) trabaja con "datos modificados / encuadernados" desde la UI 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 comercial única no está disponible, utilice un UUID no transitorio que se crea cuando la entidad se inicializa

Sí, esta es una buena estrategia cuando se requiere. Tenga en cuenta que los UUID no son gratuitos, por lo que respecta al rendimiento, y la agrupación complica las cosas.

Equals / HashCode: nunca hacer 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 ID 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!

Question

He estado trabajando con JPA (implementación de Hibernate) desde hace un tiempo y cada vez que necesito crear entidades me encuentro luchando con problemas como AccessType, propiedades inmutables, equals / hashCode, ....
Así que decidí tratar de averiguar las mejores prácticas generales para cada tema y escribirlas para uso personal.
Sin embargo, no me importaría que alguien lo comentara o me dijera dónde estoy equivocado.

Clase de entidad

  • implementar Serializable

    Motivo: la especificación dice que debe 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

  • crear un constructor con todos los campos requeridos de la entidad

    Motivo: Un constructor siempre debe dejar la instancia creada en un estado de estado.

  • además de este constructor: tener un paquete private default constructor

    Motivo: se requiere el constructor predeterminado para que Hibernate inicialice la entidad; se permite la privacidad, pero se requiere visibilidad privada (o pública) del paquete para la generación del proxy de tiempo de ejecución y la recuperación eficiente de datos sin instrumentación de bytecode.

Campos / Propiedades

  • Utilice el acceso de campo en general y el acceso a la propiedad cuando sea necesario

    Motivo: este es probablemente el tema más debatible ya que no hay argumentos claros y convincentes para uno u otro (acceso a la propiedad frente a acceso a campo); sin embargo, el acceso de campo parece ser el favorito general debido a un código más claro, una mejor encapsulación y no es necesario crear setters para campos inmutables

  • Omitir incubadores para campos inmutables (no es obligatorio para el tipo de campo de acceso)

  • las propiedades pueden ser privadas
    Motivo: Una vez escuché que la protección es mejor para el rendimiento (Hibernación), pero todo lo que puedo encontrar en la web es: Hibernate puede acceder a métodos de acceso público, privado y protegido, 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.

Equals / hashCode

  • Nunca use una identificación generada si esta identificación solo se establece cuando persiste la entidad
  • De preferencia: use valores inmutables para formar una clave comercial única y utilícela para probar la igualdad
  • si una clave comercial única no está disponible, use un UUID no transitorio que se crea cuando la entidad se inicializa; Vea este gran artículo para más información.
  • nunca se refieren a entidades relacionadas (ManyToOne); si esta entidad (como una entidad principal) necesita ser parte de la Clave comercial, entonces compare los ID únicamente. Llamar a getId () en un proxy no activará la carga de la entidad, siempre que use 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 una clave comercial simple inmutable disponible: use eso
  • en todos los demás casos: use un uuid



Interfaz de la 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);
}

La implementación básica para todas las entidades simplifica las 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);
}
}

Room Entity 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 las entidades en función de los campos comerciales en cada caso de entidades JPA. Eso podría ser más un caso si se considera que estas entidades JPA son ValueObjects controlados por el dominio, en lugar de entidades controladas por el dominio (que son ejemplos de estos códigos).






Related