Сохранение списка целых чисел с помощью JPA?

У нас есть pojo, которому нужен список целых чисел. Например, я создал Message pojo и хотел бы связать список 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.

ДДЛ:

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

POJO:

@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) для моделирования списка целых чисел — не существует ли более элегантного решения?


person Lightbeard    schedule 23.03.2010    source источник


Ответы (3)


Я действительно думаю, что то, что у вас есть, на самом деле является ассоциацией many-to-many между двумя сущностями (назовем их 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.

person Pascal Thivent    schedule 23.03.2010
comment
К сожалению, я не могу контролировать конечную схему (конечно, это тестовая схема, которую я использую для обучения). Я не могу добавить третью таблицу GROUP. У меня есть только таблицы MESSAGE и GROUP_ASSOC. Так еще можно? - person Lightbeard; 23.03.2010

Это старая тема, но со времен 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 ;
person Whome    schedule 27.03.2013

На основе вашей схемы у вас есть отношение 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 (он нужен только в том случае, если ваш идентификатор содержит несколько столбцов).

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

    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.
}
person Mike    schedule 01.04.2010