Сохранять список целых чисел с JPA? - PullRequest
0 голосов
/ 23 марта 2010

У нас есть 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.

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 
}

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

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 ]

2 голосов
/ 27 марта 2013

Это старая тема, но с 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 ;
2 голосов
/ 23 марта 2010

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

0 голосов
/ 01 апреля 2010

В зависимости от вашей схемы у вас есть связь 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.
}
...