2010-12-30 20 views
14

En nuestro proyecto de hibernación, las entidades se codifican utilizando el patrón de beans Java. Hay bastantes puntos en nuestro código donde alguien ha olvidado establecer un mutador y obtenemos una excepción debido a un campo NOT NULL.Posible tener entidades JPA inmutables?

¿Alguien está usando un constructor para construir sus entidades o hacerlas inmutables?

Estoy tratando de encontrar un patrón efectivo que no esté en el estilo del patrón de los granos de Java.

Gracias

Respuesta

14

si usted hace sus granos inmutable, entonces tienen que utilizar el acceso a nivel de campo y esto viene con su propio conjunto de problemas como se discute a fondo here. El enfoque que tomamos es tener un Builder/Factory aplicando/validando las reglas de requerimiento, etc. para nosotros.

+0

es así, estás usando un constructor para construir el entidad, pero todavía tienen mutadores? puedes elaborar un poco? –

+3

Esto es lo que hacemos (esto podría no ser aplicable globalmente a todas las aplicaciones). Digamos que tenemos una persona de clase. Luego tendremos PersonFactoryBuilder que creará un objeto Person pero lo lanzará a ReadablePerson. ReadablePerson es una interfaz que solo expone los métodos "get" y lo hace inmutable. Mientras recuperamos de la base de datos, tenemos el PersonRepository (como DAO), que recupera a la persona de la base de datos y, en función de la intención (actualización o visualización), arroja/devuelve una persona legible o persona (mutable). Este diseño fue inspirado de la biblioteca joda-time. http://joda-time.sourceforge.net –

+1

¡Casting a una interfaz! hurra por la abstracción. Esa es una gran idea. Gracias. – b3bop

8

En nuestro proyecto usamos el enfoque de constructores de vainilla (@see Effective Java). Consideremos el siguiente ejemplo:

@Entity 
public class Person { 
    public static class Builder { 
     private String firstName; 
     private String lastName; 
     private PhoneNumber phone; 

     public Builder() {} 

     public Builder withFullName(String fullName) { 
      Preconditions.notNull(fullName); 
      String[] split = fullName.split(" "); 
      if (split == null || split.length != 2) { 
       throw new IllegalArgumentException("Full name should contain First name and Last name. Full name: " + fullName); 
      } 
      this.firstName = split[0]; 
      this.lastName = split[1]; 
      return this; 
     } 

     public Builder withPhone(String phone) { 
      // valueOf does validation 
      this.phone = PhoneNumber.valueOf(phone); 
      return this; 
     } 

     public Person build() { 
      return new Person(this); 
     } 
    } 

    //@Columns 
    private long id;//@Id 
    private String firstName; 
    private String lastName; 
    private String phoneNumber; 

    // hibernate requires default constructor 
    private Person() {} 

    private Person(Builder builder) { 
     this.firstName = Preconditions.notNull(builder.firstName); 
     this.lastName = Preconditions.notNull(builder.lastName); 
     this.phoneNumber = builder.phone != null ? builder.phone : null; 
    } 

    //Getters 
    @Nonnull 
    public String getFirstName() { return firstName;} 
    @Nonnull 
    public String getLastName() { return lastName;} 
    @Nullable 
    public String getPhoneName() { return phone;} 
    public long getId() { return id;} 
} 

En caso de que quiera a mutar en ocasiones la entidad lo recomiendo para introducir new Builder(Person person) que copiar todos los datos de nuevo, por lo que puede mutar con el constructor. Por supuesto, producirá una nueva entidad, por lo que la anterior sigue siendo de solo lectura.

El uso (con mutación) es tan sencillo como:

Person.Builder personBuilder = new Person.Builder(); 
Person person = personBuilder.withFullName("Vadim Kirilchuk").withPhone("12345678").build(); 

Person modified = new Person.Builder(person).withPhone("987654321").build(); 

También es importante tener en cuenta que en este ejemplo la persona no está 100% inmutable (y no puede ser) de clase: en primer lugar porque id se establecerá por jpa, también se pueden obtener asociaciones diferidas en tiempo de ejecución y, por último, porque no se pueden tener campos finales (debido al constructor predeterminado requerido) :(Este último punto también es una preocupación para entornos multiproceso, es decir, Es posible que la entidad pase a otra cadena justo después de que #build() pueda generar todo tipo de errores, ya que no se garantiza que el hilo abother vea el objeto completamente construido.

La especificación 2.1, JPA sección "2.1 La clase de entidad", dice:

No hay métodos o variables de instancia persistentes de la clase de entidad pueden ser final.

Uno más enfoque similar: http://vlkan.com/blog/post/2015/03/21/immutable-persistence/

En mi caso sería simplemente añadir id para el constructor lugar la construcción de servicio en la parte superior de los proyectos ..

Cuestiones relacionadas