Сохранять список целых чисел с JPA?

У нас есть Pojo, который должен иметь список целых чисел. В качестве примера я создал Message и хотел бы связать список groupIds (эти идентификаторы должны быть запрошены и отображены в пользовательском интерфейсе). Поэтому в идеале мы хотели бы иметь возможность сделать что-то вроде этого:

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

У меня сложилось впечатление, что для этого потребуется только одно pojo с JPA, но согласно обсуждению здесь, мне нужно создать второе pojo, потому что JPA работает с точки зрения объектов, а не примитивных типов.

Из этого обсуждения я попробовал следующий пример кода, но я получаю ошибку 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:

СОЗДАТЬ СТОЛ "ПРИЛОЖЕНИЕ". "СООБЩЕНИЕ" (INTEGER "MESSAGE_ID" НЕ ОБНАРУЖЕН ВСЕГДА В КАЧЕСТВЕ ИДЕНТИЧНОСТИ (НАЧИНАЙТЕ С 1, ВКЛЮЧЕНИЕ НА 1),
  "АВТОР" CHAR(20) НЕ NULL);

ALTER TABLE "APP"."MESSAGE" ДОБАВИТЬ КОНСТРАИНТ "MESSAGE_PK" ПЕРВИЧНЫЙ КЛЮЧ ("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" ПЕРВИЧНЫЙ КЛЮЧ ("MESSAGE_ID", "GROUP_ID");

ALTER TABLE "APP"."GROUP_ASSOC" ДОБАВИТЬ КОНСТРАИНТ "GROUP_ASSOC_FK" КЛЮЧ ВНЕШНИХ ВИДОВ ("MESSAGE_ID")
 ССЫЛКИ "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 
}

Тестовый код:

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

Все это кажется смешным - 3 класса (если вы включаете составной класс идентификации GroupKey) для моделирования списка целых чисел - не существует ли более элегантного решения?

3 ответа

Решение

Я действительно думаю, что то, что у вас есть, на самом деле является ассоциацией многих ко многим между двумя сущностями (назовем их Message а также Group).

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

И аннотированные классы:

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

    //...
}

Я не уверен, что вам нужна двунаправленная ассоциация. Но вам определенно нужно начать думать об объекте, если вы хотите использовать JPA (в вашем примере вы все еще устанавливаете идентификаторы, вы должны установить Entities). Или, может быть, JPA не то, что вам нужно.


нет ли более элегантного решения?

Я не уверен, что "элегантный" подходит, но JPA 2.0 определяет ElementCollection отображение (как я уже говорил в моем предыдущем ответе):

Он предназначен для обработки нескольких нестандартных отображений отношений. ElementCollection может использоваться для определения отношения один ко многим с Embeddable объект или Basic значение (например, коллекция строк).

Но это в JPA 2.0. В JPA 1.0 вам нужно будет использовать определенный эквивалент поставщика, если ваш поставщик действительно предлагает такое расширение. Похоже, что OpenJPA делает с @PersistentCollection,

Это старая тема, но с OpenJPA2 все изменилось, теперь вы можете напрямую сохранять примитивные типы или объект String. Используйте аннотацию ElementCollection для использования простых связей "один ко многим", нет необходимости в промежуточных объектах или таблицах ссылок. Так большинство из нас, вероятно, создают схемы 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 ;

На основе вашей схемы у вас есть связь ManyToOne между группой и сообщением. Это означает, что одно Сообщение может принадлежать нескольким группам, но каждая группа может иметь одно сообщение.

Сущности будут выглядеть примерно так.

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

Нет необходимости в IDClass в вашем приложении (он нужен только в том случае, если ваш ID содержит несколько столбцов).

Чтобы получить groupIds для данного сообщения, вы можете написать запрос, подобный этому

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

    List results = q.getResultList();

Или просто переберите Message.getGroups():

Message m = em.find(Message.class, 1);
for(Group g : m.getGroups()) {
    // create a list, process the group whatever fits.
}
Другие вопросы по тегам