2010-11-24 16 views
8

Estoy migrando un sistema heredado para utilizar Hibernate 3. Actualmente genera sus propios identificadores. Para seguir con lo que el sistema hace actualmente antes de intentar moverlo a algo un poco mejor, ¿cómo podría especificar (utilizando anotaciones) mi propia clase que devolverá los identificadores generados personalizados cuando se produzca una inserción?Asignar identificador personalizado a una propiedad @id

algo como:

@Id 
@CustomIdGenerator(Foo.class) // obviously this is not a real annotation 
public String getId() { ... } 

donde la clase Foo tiene un método que genera el identificador.

Actualmente estoy llamando al método setId(String id) manualmente, pero esperaba una mejor forma de lidiar con esta situación.

Respuesta

7

no creo que hay un apoyo fuera de la caja para la generación de ID personalizados utilizando anotaciones personalizadas utilizando directamente la API JPA-2. Pero si desea usar una API específica del proveedor, entonces el trabajo es bastante simple. Sample Example

Para ser independiente del proveedor pruebe cualquiera de los siguientes trucos ....

IdGeneratorHolder interfaz

public abstract class IdGeneratorHolder { 
    /* PersistentEntity is a marker interface */ 
    public static IdGenerator getIdGenerator(Class<? extends PersistentEntity> entityType) { 
      /* sample impelementation */ 
     if(Product.class.isAssignableFrom(entityType)) { 
      return new ProductIdGenerator(); 

     } 
     return null; 
    } 
} 

general IdGenerator

public interface IdGenerator { 
    String generate(); 
} 

específico IdGenerator - Id de producto generador

public class ProductIdGenerator implements IdGenerator { 
    public String generate() { 
      /* some complicated logic goes here */ 
     return ${generatedId}; 
    } 
} 

Ahora establezca el id. Generado en el constructor sin argumentos O en el método @PrePersist.

Product.java

public class Product implements PersistentEntity { 

    private String id; 

    public Product() { 
     id = IdGeneratorHolder.getIdGenerator(getClass()).generate(); 
    } 

    @PrePersist 
    public void generateId() { 
     id = IdGeneratorHolder.getIdGenerator(getClass()).generate(); 
    } 

} 

En el ejemplo por encima de todos los identificadores son del mismo tipo, es decir java.lang.String. Si las entidades persistentes tienen ID de diferentes tipos .....

IdGenerator.java

public interface IdGenerator { 
    CustomId generate(); 
} 

CustomId.java

public class CustomId { 

    private Object id; 

    public CustomId(Object id) { 
     this.id = id; 
    } 

    public String toString() { 
     return id.toString(); 
    } 
    public Long toLong() { 
     return Long.valueOf(id.toString()); 
    } 
} 

Item.java

@PrePersist 
    public void generateId() { 
     id = IdGeneratorHolder.getIdGenerator(getClass()).generate().toLong(); 
    } 

También puede utilizar su anotación personalizada ...

CustomIdGenerator.java

public @interface CustomIdGenerator { 
    IdStrategy strategy(); 
} 

IdStrategy.java

enum IdStrategy { 
     uuid, humanReadable,  
    } 

IdGeneratorHolder.java

public abstract class IdGeneratorHolder { 
    public static IdGenerator getIdGenerator(Class<? extends PersistentEntity> entityType) { 
     try { // again sample implementation 
      Method method = entityType.getMethod("idMethod"); 
      CustomIdGenerator gen = method.getAnnotation(CustomIdGenerator.class); 
      IdStrategy strategy = gen.strategy(); 
      return new ProductIdGenerator(strategy); 
     } 

Una cosa más .... Si configuramos la identificación en el método @PrePersist, el método equals() no puede depender del campo de identificación (es decir, clave sustituta), tenemos que usar la clave comercial/natural para implementar el método equals(). Pero si configuramos el campo id con algún valor único (uuid o "app-uid" único dentro de la aplicación) en el constructor no-arg, nos ayuda a implementar el método equals().

public boolean equals(Object obj) { 
     if(obj instanceof Product) { 
      Product that = (Product) obj; 
      return this.id ==that.id; 
     } 
     return false; 
    } 

Si nosotros o alguien que llame (intencionalmente o por error) el método @PrePersist anotado más de un momento, el "identificador único seremos transformados !!!" Por lo tanto, es preferible establecer el id en el constructor no-arg. O para abordar este problema ponga un cheque no nulo ...

@PrePersist 
    public void generateId() { 
     if(id != null) 
      id = IdGeneratorHolder.getIdGenerator(getClass()).generate(); 
    } 
} 

ACTUALIZACIÓN

Si ponemos la generación id en un constructor sin argumentos , no habría que causar un problema al cargar entidades de la base de datos? porque hibernación llamará al constructor sin argumentos que causan los identificadores existentes para ser re- generada

Sí tienes razón, me perdí esa parte. :(En realidad, quería decirte que: en mi aplicación, cada objeto Entity está asociado a una Entidad Organizadora, así que he creado una superclase abstracta con dos constructores, y cada Entidad (excepto Organización) extiende esta clase.

protected PersistentEntityImpl() { 
    } 

    protected PersistentEntityImpl(Organization organization) { 
     String entityId = UUIDGenerator.generate(); 
     String organizationId = organization.getEntityId(); 
     identifier = new EntityIdentifier(entityId, organizationId); 
    } 

el constructor sin argumentos es para el proveedor JPA, nunca invoca sin argumentos constructor, pero el constructor basado otra organización. Como se puede ver. ID se le asigna en el constructor basado organización. (extrañé mucho a este punto al escribir la respuesta, perdón por eso).

Consulte si puede implementar esta o una estrategia similar en su aplicación.

La segunda opción fue usar la anotación @PrePersist. Lo puse en y el método nunca fue golpeado y le di una excepción que decía que necesitaba para configurar el id manualmente. ¿Hay alguna otra cosa que debería hacer? ?

Idealmente, el proveedor de JPA debe invocar los métodos @PrePersist (uno declarado en clase y también todos los otros métodos que se declaran en superclases) antes de persistir el objeto de entidad. No puedo decirle lo que está mal, a menos que muestre algún código y consola.

+1

¡Gracias por su respuesta! Estoy un poco confundido acerca de sus dos sugerencias. Si ponemos la generación de ID en un constructor no-arg, ¿no sería eso un problema al cargar entidades desde la base de datos? porque hibernate llamará al constructor no-arg causando que los identificadores existentes se vuelvan a generar, que no es lo que quiero (incluso si puse un cheque nulo). La segunda opción era usar la anotación @PrePersist. Lo puse y el método nunca fue golpeado y me dio una excepción indicando que necesitaba configurar el ID manualmente. ¿Hay algo más que debería estar haciendo? – digiarnie

+0

@digiarnie +1 por señalar el error. Verifique la parte ACTUALIZAR en la respuesta. – dira

+0

en aplicaciones multiproceso, seguramente tendrá problemas –

1

Puedes.

En primer lugar, poner en práctica org.hibernate.id.IdentifierGenerator

entonces tendría que hacer un mapa en un archivo de asignación XML. No pude encontrar una manera de hacer esto con anotaciones:

<!-- 
    <identifier-generator.../> allows customized short-naming 
     of IdentifierGenerator implementations. 
--> 
<!ELEMENT identifier-generator EMPTY> 
    <!ATTLIST identifier-generator name CDATA #REQUIRED> 
    <!ATTLIST identifier-generator class CDATA #REQUIRED> 

Por último, utilizar @GeneratedValue(generator="identifier-name")

Tenga en cuenta que se trata de hibernación-específico (no JPA)

Actualización: Tomé una mira las fuentes de Hibernate, y parece que en un solo lugar, después de no poder resolver el nombre corto, hiberna los intentos de llamar al Class.forName(..). El parámetro allí se llama strategy. Así que aquí está lo que intente:

  • tratar de establecer la clase nombre completo como cadena en el atributo generator
  • Intente ajustar las FQN clase como cadena en el atributo @GenericGeneratorstrategy (con un nombre arbitrario)

me dejó saber qué (si lo hay) trabajó

+0

He visto lo mismo y me gustaría evitar el archivo xml si es posible. Pero creo que recurriré a esto si no puedo encontrar una alternativa. ¡Gracias! – digiarnie

+0

@digiarnie see updated – Bozho

+0

Agregué la siguiente anotación a mi campo de identificación: @GeneratedValue (generator = "spike.TestIdGenerator") que dio como resultado el siguiente error al iniciar: org.hibernate.AnnotationException: Desconocido Id.generator: spike.TestIdGenerator - Probé esta anotación: @GenericGenerator (name = "foo", strategy = "spike.TestIdGenerator") que dio como resultado esta excepción cuando se intentó guardar: org.hibernate.id.IdentifierGenerationException: los ids para esta clase deben ser manualmente asignado antes de llamar a save() – digiarnie

Cuestiones relacionadas