Pergunta

Temos um pojo que precisa ter uma lista de números inteiros. Como exemplo, eu criei um Message Pojo e gostaria de associar uma lista de groupIds (Esses IDs precisam ser consultados e exibidos na interface do usuário). Então, idealmente, gostaríamos de poder fazer algo assim:

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

Eu estava com a impressão de que isso exigiria apenas um pojo com JPA, mas de acordo com o discussão aqui, Preciso criar um segundo pojo porque o JPA funciona em termos de objetos em vez de tipos primitivos.

A partir dessa discussão, tentei o seguinte código de exemplo, mas recebo o erro 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 teste:

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();

Tudo isso parece ridículo - 3 classes (se você incluir a classe de identidade composta do GroupKey) para modelar uma lista de números inteiros - não existe uma solução mais elegante?

Foi útil?

Solução

Eu realmente acho que o que você tem é de fato um muitos para muitos associação entre duas entidades (vamos chamá -las Message e Group).

O DDL para representar isso seria:

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");

E as aulas 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>();

    //...
}

Não tenho certeza se você precisa de uma associação bidirecional. Mas você definitivamente precisa começar a pensar no objeto Se deseja usar o JPA (no seu exemplo, você ainda está definindo IDs, você deve definir entidades). Ou talvez JPA não seja o que você precisa.


Não existe uma solução mais elegante?

Não tenho certeza se "elegante" é apropriado, mas o JPA 2.0 define um ElementCollection mapeamento (Como eu disse na minha resposta anterior):

Destina-se a lidar com vários mapeamentos de relacionamento fora do padrão. Um ElementCollection pode ser usado para definir um relacionamento um para muitos com um Embeddable objeto, ou um Basic valor (como uma coleção de strings).

Mas isso está no JPA 2.0. No JPA 1.0, você precisaria usar um equivalente específico do provedor, se o seu provedor oferecer tal extensão. Parece que o OpenJpa faz com @PersistentCollection.

Outras dicas

Este é um tópico antigo, mas as coisas mudaram desde o OpenJPA2, agora você pode persistir diretamente tipos primitivos ou objeto de string. Use a anotação do ElementCollection para usar links simples de um para muitos, sem necessidade de intermediários ou tabelas de link. É assim que a maioria de nós provavelmente cria 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 ;

Com base no seu esquema, você tem um relacionamento muito entre grupo e mensagem. O que significa que uma única mensagem pode pertencer a vários grupos, mas cada grupo pode ter uma única mensagem.

As entidades pareceriam algo assim.

@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;
}

Não há necessidade de um IDClass no seu aplicativo (você só precisa de um se o seu ID estiver contiver várias colunas).

Para obter os grupos para uma determinada mensagem, você pode escrever uma consulta como esta

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

    List results = q.getResultList();

Ou apenas itera sobre message.getGroups ():

Message m = em.find(Message.class, 1);
for(Group g : m.getGroups()) {
    // create a list, process the group whatever fits.
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top