Pregunta

Tenemos un POJO que necesite disponer de una lista de números enteros. A modo de ejemplo, he creado un POJO Message y me gustaría asociar una lista de groupIds (estos identificadores deben ser consultados y se muestran en la interfaz de usuario). Así que lo ideal, nos gustaría ser capaz de hacer algo como esto:

Message msg = em.find(Message.class, 101);
List<Integer> groupIds = msg.getGroupIds();

I tenía la impresión de que esto requeriría solamente un POJO con JPA, pero de acuerdo a la discusión aquí , tengo que crear una segunda POJO porque APP funciona en términos de objetos en lugar de tipos primitivos.

A partir de ese debate lo he intentado el siguiente código de ejemplo, pero me sale el error openjpa-1.2.3-SNAPSHOT-r422266:907835 fatal user error: org.apache.openjpa.util.MetaDataException: The type of field "pojo.Group.messageId" isn't supported by declared persistence strategy "ManyToOne". Please choose a different strategy.

DDL:

CREATE TABLE "APP"."MESSAGE" (
  "MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
  "AUTHOR" CHAR(20) NOT NULL
 );

ALTER TABLE "APP"."MESSAGE" ADD CONSTRAINT "MESSAGE_PK" PRIMARY KEY ("MESSAGE_ID");

CREATE TABLE "APP"."GROUP_ASSOC" (
  "GROUP_ID" INTEGER NOT NULL,
  "MESSAGE_ID" INTEGER NOT NULL
 );

ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_PK" PRIMARY KEY ("MESSAGE_ID", "GROUP_ID");

ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_FK" FOREIGN KEY ("MESSAGE_ID")
 REFERENCES "APP"."MESSAGE" ("MESSAGE_ID");

POJOs:

@Entity
@Table(name = "MESSAGE")
public class Message {
    @Id
    @Column(name = "MESSAGE_ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)    
    private Long messageId;

    @OneToMany   
    private List<Group> groups = new ArrayList<Group>();

    @Column(name = "AUTHOR")
    private String author;

    // getters/setters ommitted
}    

@Entity
@IdClass(pojo.Group.GroupKey.class)
@Table(name = "GROUP_ASSOC")
public class Group {

 @Id
 @Column(name = "GROUP_ID")
 private Long groupId;

 @Id
 @Column(name = "MESSAGE_ID")
 @ManyToOne
 private Long messageId;

 public static class GroupKey {
  public Long groupId;
  public Long messageId;

  public boolean equals(Object obj) {
   if(obj == this) return true;
            if(!(obj instanceof Group)) return false;
   Group g = (Group) obj;
   return g.getGroupId() == groupId && g.getMessageId() == messageId; 
  }

  public int hashCode() {
            return ((groupId == null) ? 0 : groupId.hashCode())
                ^ ((messageId == null) ? 0 : messageId.hashCode());
  } 
 }

 // getters/setters ommitted 
}

Código de prueba:

EntityManager em = Persistence.createEntityManagerFactory("JPATest").createEntityManager();
em.getTransaction().begin();

Message msg = new Message();
msg.setAuthor("Paul");
em.persist(msg);
List<Group> groups = new ArrayList<Group>();

Group g1 = new Group();
g1.setMessageId(msg.getMessageId());
Group g2 = new Group();
g2.setMessageId(msg.getMessageId());

msg.setGroups(groups);
em.getTransaction().commit();

Todo esto parece ridículo - 3 clases (si se incluye la clase identidad compuesta GROUPKEY) para modelar una lista de números enteros - no hay una solución más elegante

¿Fue útil?

Solución

Realmente creo que lo que tienes es de hecho un muchos-a-muchos asociación entre dos entidades (llamémosles Message y Group).

El DDL para representar esto sería:

CREATE TABLE "APP"."MESSAGE" (
  "MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
  "AUTHOR" CHAR(20) NOT NULL
 );

ALTER TABLE "APP"."MESSAGE" ADD CONSTRAINT "MESSAGE_PK" PRIMARY KEY ("MESSAGE_ID");

CREATE TABLE "APP"."GROUP" (
  "GROUP_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)
 );

ALTER TABLE "APP"."GROUP" ADD CONSTRAINT "GROUP_PK" PRIMARY KEY ("GROUP_ID");

CREATE TABLE "APP"."MESSAGE_GROUP" (
  "GROUP_ID" INTEGER NOT NULL,
  "MESSAGE_ID" INTEGER NOT NULL
 );

ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_PK" PRIMARY KEY ("MESSAGE_ID", "GROUP_ID");

ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_FK1" FOREIGN KEY ("MESSAGE_ID")
 REFERENCES "APP"."MESSAGE" ("MESSAGE_ID");

ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_FK2" FOREIGN KEY ("GROUP_ID")
 REFERENCES "APP"."MESSAGE" ("GROUP_ID");

Y las clases anotadas:

@Entity
public class Message {
    @Id
    @Column(name = "MESSAGE_ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)    
    private Long messageId;

    @ManyToMany
    @JoinTable(
        name = "MESSAGE_GROUP", 
        joinColumns = @JoinColumn(name = "MESSAGE_ID"), 
        inverseJoinColumns = @JoinColumn(name = "GROUP_ID")
    ) 
    private List<Group> groups = new ArrayList<Group>();

    private String author;

    //...
}    

@Entity
public class Group {    
    @Id
    @GeneratedValue
    @Column(name = "GROUP_ID")
    private Long groupId;

    @ManyToMany(mappedBy = "groups")
    private List<Message> messages = new ArrayList<Message>();

    //...
}

No estoy seguro de que necesita una asociación bidireccional sin embargo. Pero definitivamente hay que empezar a pensar objeto si desea utilizar la APP (en el que eres ejemplo, todavía se está configurando las identificaciones, se debe configurar Entidades). O tal vez la APP no es lo que necesita.


  

no es una solución más elegante?

No estoy seguro "elegante" es adecuada, pero JPA 2.0 define un ElementCollection mapeo (como dije en mi respuesta anterior):

  

Está destinado a manejar varias asignaciones de relaciones no estándar. Un ElementCollection se puede utilizar para definir una relación de uno a muchos a un objeto Embeddable, o un valor Basic (tales como una colección de cadenas).

Pero eso es en JPA 2.0. En JPA 1.0, que tendría que utilizar un proveedor específico equivalente, si su proveedor no ofrece dicha extensión. Parece que OpenJPA hace con @PersistentCollection .

Otros consejos

Este es un tema viejo, pero las cosas han cambiado desde OpenJPA2, ahora puede persistir directamente o tipos primitivos objeto String. Uso ElementCollection anotación a utilizar la vinculación sencilla de uno a muchos, no hay necesidad de tablas de objetos o de enlaces intermedios. Así es como la mayoría de nosotros probablemente crear esquemas SQL.

@Entity @Table(name="user") @Access(AccessType.FIELD)
public class User {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;    // primary key (autogen surrogate)
    private String name;

    // ElementCollection provides simple OneToMany linking.
    // joinColumn.name=foreign key in child table. Column.name=value in child table
    @ElementCollection(fetch=FetchType.LAZY)
    @CollectionTable(name="user_role", joinColumns={@JoinColumn(name="user_id")})
    @Column(name="role")
    private List<String> roles;

    public long getId() { return id; }
    public void setId(long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name=name; }

    public List<String> getRoles() { return roles; }
    public void setRoles(List<String> roles) { this.roles=roles; }

}
- - -
CREATE TABLE user (
  id bigint NOT NULL auto_increment,
  name varchar(64) NOT NULL default '',
  PRIMARY KEY (id),
  UNIQUE KEY USERNAME (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

CREATE TABLE user_role (
  user_id bigint NOT NULL,
  role varchar(64) NOT NULL default '',
  PRIMARY KEY (user_id, role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

Sobre la base de su esquema que tiene una relación entre el Grupo y ManyToOne mensaje. Lo que significa que un solo mensaje puede pertenecer a varios grupos, pero cada grupo puede tener un solo mensaje.

Las entidades sería algo como esto.

@Entity
@Table(name = "GROUP_ASSOC")
public class Group {
    @Id
    @Column(name="GROUP_ID")
    private int id;

    @ManyToOne
    @Column(name="MESSAGE_ID")
    @ForeignKey
    private Message message;

    // . . . 
}

@Entity
public class Message {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "MESSAGE_ID")
    private int id;

    @Column(length=20)
    private String author;

    @OneToMany(mappedBy="message")  
    private Collection<Group> groups;
}

No hay necesidad de un IDClass en su aplicación (sólo se necesita uno si su identificación es contiene varias columnas).

Para obtener los groupIds para un mensaje dado se podría escribir una consulta como éste

    Query q =  em.createQuery("Select g.id from Group g where g.message.id = :messageId");
    q.setParameter("messageId", 1);

    List results = q.getResultList();

O simplemente iterar sobre Message.getGroups ():

Message m = em.find(Message.class, 1);
for(Group g : m.getGroups()) {
    // create a list, process the group whatever fits.
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top