Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Маппинг generic коллекций (Hibernate)


Автор: INlHELL 14.1.2010, 14:07
Всем доброго времени суток!
Столкнулся со следующей проблемой, в упрощённом виде выглядет так: есть класс First и есть класс Second никак не связанный с First.
Необходимо создать "универсалный контейнер" для хранения наборов этих объектов. Т.е. generic класс с коллекцией, который принимает объекты обоих типов,
помещает их в коллекцию и сохраняет в БД.

Ниже классы тестового приложения:
Код

@Entity
@Table(name = "first")
public class First {
  private String uuid = UUID.randomUUID().toString();
  private String name;
  
  public First() {
    super();
    this.name = "First " + this.uuid;
  }

  @Id
  @Column(name = "firstId", nullable = false)
  public String getUuid() {
    return uuid;
  }

  public void setUuid(String uuid) {
    this.uuid = uuid;
  }

  @Column(name = "firstName")
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
  
  public void putInContainer(Container<First> container) {
    Collection<First> collection = new ArrayList<First>();
    collection.add(this);
    container.setCollectionOfElements(collection);
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    First other = (First) obj;
    if (name == null) {
      if (other.name != null)
        return false;
    } else if (!name.equals(other.name))
      return false;
    if (uuid == null) {
      if (other.uuid != null)
        return false;
    } else if (!uuid.equals(other.uuid))
      return false;
    return true;
  }

  @Override
  public String toString() {
    return "First [name=" + name + ", uuid=" + uuid + "]";
  }
  
}


Код

@Entity
@Table(name = "second")
public class Second {
  private String uuid = UUID.randomUUID().toString();
  private String name;
  private Color color;
  
  public Second() {
    super();
    this.name = "First " + this.uuid;
    this.color = Color.YELLOW;
  }
  
  @Id
  @Column(name = "secondId", nullable = false)
  public String getUuid() {
    return uuid;
  }
  public void setUuid(String uuid) {
    this.uuid = uuid;
  }
  
  @Column(name = "secondName")
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  
  @Column(name = "secondColor")
  public Color getColor() {
    return color;
  }
  public void setColor(Color color) {
    this.color = color;
  }
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((color == null) ? 0 : color.hashCode());
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
    return result;
  }
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Second other = (Second) obj;
    if (color == null) {
      if (other.color != null)
        return false;
    } else if (!color.equals(other.color))
      return false;
    if (name == null) {
      if (other.name != null)
        return false;
    } else if (!name.equals(other.name))
      return false;
    if (uuid == null) {
      if (other.uuid != null)
        return false;
    } else if (!uuid.equals(other.uuid))
      return false;
    return true;
  }
  @Override
  public String toString() {
    return "Second [color=" + color + ", name=" + name + ", uuid=" + uuid + "]";
  }
  
}


Самое интересное, контейнер:
Код

@Entity
@Table(name = "container")
public class Container<E> {

  private String uuid = UUID.randomUUID().toString();

  private Collection<E> collectionOfElements = new ArrayList<E>();

  public Container() {
    super();
  }
  
  @Id
  @Column(name = "containerId", nullable = false)
  public String getUuid() {
    return uuid;
  }

  public void setUuid(String uuid) {
    this.uuid = uuid;
  }
  
  
  @CollectionOfElements
  @OneToMany(targetEntity = First.class)
  public Collection<E> getCollectionOfElements() {
    return collectionOfElements;
  }

  public void setCollectionOfElements(Collection<E> collectionOfElements) {
    this.collectionOfElements = collectionOfElements;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime
        * result
        + ((collectionOfElements == null) ? 0 : collectionOfElements.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Container other = (Container) obj;
    if (collectionOfElements == null) {
      if (other.collectionOfElements != null)
        return false;
    } else if (!collectionOfElements.equals(other.collectionOfElements))
      return false;
    return true;
  }

  @Override
  public String toString() {
    return "Container [collectionOfElements=" + collectionOfElements + "]";
  }
  
}


Как можно обойти следующие анотации:  
 @CollectionOfElements
 @OneToMany(targetEntity = First.class)


В таком виде пример зарускается и данные вносятся в таблицы, но коллекция только для класса First и при попытке добавления экземпляра класса Second,
появляются эксепшены.

Активное "гугление" не помогло, есть мысль использовать супер класс и в качестве targetEntity указывать его, но для этого необзодима маппить и его и данное решение мне кажется несовсем верным.

Буду очень признателен за любую помощь!

Автор: afon 14.1.2010, 17:48
Накатал большую телегу по мапингу с наследованием и связями, но передумал. 
1) На сколько сильно НИКАК НЕ СВЯЗАНЫ First и Second?
2) Сохраняется ли объект Container в базу?
3) имеет ли Container какую-то связь с First и Second?

Если 1) Сильно 2) нет, 3) нет - то храни их как хочешь, при любых раскладах будет щастье, выдергиваешь нужный List<First> или List<Second> запросом и суешь в свой Контейнер. 

Если 1) Есть общие поля 2) да 3) да - то выложу свою телегу smile

Автор: LSD 14.1.2010, 18:25
А как ты это у тебя в базе представлено? У тебя одна колонка содержит айдишники из двух разных таблиц?

Автор: INlHELL 14.1.2010, 18:26
Цитата

1) На сколько сильно НИКАК НЕ СВЯЗАНЫ First и Second?

Собственно вообще никак не связаны, т.е. это две независимые сущности.
Цитата

2) Сохраняется ли объект Container в базу?

Сохраняется, ниже приведу код  с тестовым приложением для этих классов.
Цитата

3) имеет ли Container какую-то связь с First и Second?

Не имеет и имеет не должен, в идеале (в конченом приложении) существует множество объектов не связанных иерархией наследования и другими связями (имею ввиду экземпляр одного объекта не содержит ссылок на экземпляр другого). И есть объект (Container) в который можно передать набор (в примере Collection) этих объектов. Главное, что Container должен принимать коллекцию любого типа (Firtst и Second в данном примере) и ничего "не знает" о них.

Код использующий эти классы (не догадался, надо было сразу выложить):
Код

public class TestPlatform {

  public static void main(String[] args) {
    AnnotationConfiguration myConfiguration;

    myConfiguration = new AnnotationConfiguration();
    myConfiguration.setProperty("hibernate.connection.url",
        "jdbc:mysql://localhost:3306/generic_collection");
    myConfiguration.setProperty("hibernate.connection.username", "version");
    myConfiguration.setProperty("hibernate.connection.password", "");
    myConfiguration.setProperty("hibernate.connection.writedelay", "0");
    myConfiguration.setProperty("hibernate.connection.driver_class",
        "com.mysql.jdbc.Driver");
    myConfiguration.setProperty("hibernate.connection.autocommit", "false");
    myConfiguration.setProperty("hibernate.dialect",
        "org.hibernate.dialect.MySQLDialect");
    myConfiguration.setProperty("hibernate.show_sql", "true");
    myConfiguration.setProperty("hibernate.hbm2ddl.auto", "create");
    myConfiguration.setProperty("hibernate.transaction.factory_class",
        "org.hibernate.transaction.JDBCTransactionFactory");
    myConfiguration.setProperty("hibernate.cache.provider_class",
        "org.hibernate.cache.HashtableCacheProvider");
    myConfiguration.setProperty("hibernate.current_session_context_class",
        "thread");
    myConfiguration.setProperty("hibernate.connection.charSet", "utf-8");
    
    myConfiguration.addAnnotatedClass(First.class);
    myConfiguration.addAnnotatedClass(Second.class);
    myConfiguration.addAnnotatedClass(Container.class);
    
    First f1 = new First();
    First f2 = new First();
    Second s1 = new Second();
    Second s2 = new Second();
    Second s3 = new Second();
    
    Collection<First> setFirstCollection = new ArrayList<First>();
    setFirstCollection.add(f1);
    setFirstCollection.add(f2);
    Collection<Second> setSecondCollection = new ArrayList<Second>();
    setSecondCollection.add(s1);
    setSecondCollection.add(s2);
    setSecondCollection.add(s3);

    
    Container<First> containerForFirst = new Container<First>();
    containerForFirst.setCollectionOfElements(setFirstCollection);
    
    Container<Second> containerForSecond = new Container<Second>();
    containerForSecond.setCollectionOfElements(setSecondCollection);
    
    SessionFactory sessionFactory1;

    sessionFactory1 = myConfiguration.buildSessionFactory();

    Session session1 = null;

    session1 = sessionFactory1.openSession();
    session1.beginTransaction();
    session1.save(f1);
    session1.save(f2);
    session1.save(s1);
    session1.save(s2);    
    session1.save(s3);
    session1.save(setSecondCollection);
    session1.save(setFirstCollection);
    session1.getTransaction().commit();

    session1.flush();
    session1.close();


Прошу прощения, что сразу не уточнил и за неказистость кода тоже, это исключительно тестовое приложение.

Буду благодарен за любой совет или мысль, как это можно реализовать.

Автор: INlHELL 14.1.2010, 18:43
В предыдущем блоке кода скопировал две строки не те:
Код

...
session1.save(setSecondCollection);
session1.save(setFirstCollection);
...


Правильно конечно так:

Код

...
session1.save(containerForSecond);
session1.save(containerForFirst);
...


http://floomby.com/content/l61SvMJu1U/ вот такая.

Автор: LSD 14.1.2010, 18:50
Цитата(INlHELL @  14.1.2010,  18:43 Найти цитируемый пост)
Структура БД вот такая.

Прикрепи картинку к посту. У меня этот сайт заблокирован.

Автор: afon 14.1.2010, 19:44
В общем, похоже, что решение с collectionOfElements очень даже удачное для коллекций любых никак не связанных объектов. 
Меня только смущает, что 
Цитата

Собственно вообще никак не связаны, т.е. это две независимые сущности.

и в тоже время оба First и Second имеют одинаковые поля uuis и name. Но это наверное для простоты и просто совпадение. 

В любом случае, твой варинат работает, но возможны и другие. 
Например, можно отэкстендить каждый элемент от какого-нибудь пустого интерфейса, допустим, interface MyStorableCollectionItem, в свой Container прописать Collection<? extends MyStorableCollectionItem> и всех делов. Когда будешь поднимать конкретный Container из базы, всегда можно будет взять instanceof объектов, которые будут в листе. Тут вроде просто. 

Другие варианты могут базироваться на http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e829, но они дадут свои ограничения. Например, расшаренные строчные id у всех реализаторов MyStorableCollectionItem, или общий пул числовых id. 

Мне кажется, что изначальный вариант с collectionOfElements, который реализует данную ситуацию всего в одну таблицу, самый подходящий.



Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)