Thursday, December 11, 2008

копирование из одной базы данный в другую [java+hibernate]

долго выискивал комбайн которым буду выдергивать сорняки у себя в горшочке, в конце концов соорудил пинцет из подручных инструментов и понял, что никакой комбайн мне был не нужен)
задача была скопировать все из одной базы данных в другую: юзер нам заливает hsqldb запакованную в tar, наша задача добавить все записи из этой базу в нашу основную postgres.
проблема была в том что там 3 таблицы, связанные ключами.
первая таблица содержит обычную строчку и ссылку на вторую таблицу.
во второй таблице хранится дикий ужас, представляющий файловую систему со всей ее древовидностью.
и иногда она содержит ссылку на третью таблицу.
просто скопировать и вставить ряды не получится: id всех элементов изменяются, а, значит, надо менять и ссылки на них.
просто запустить две сессии hibernate, в одной достать а в другую вставить обьект тоже не получится: тоже нет обновления id'шников для связанных с ним объектов.
выгрести объект, создать другой объект этого же класса, скопировать в него важные данные и вставить в базу тоже проблематично: все, что мы знаем про объект из второй таблицы (файлы) это то, что он является объектом, наследующим абстрактный класс ParsedFile, а таких классов около 10 штук. но нам на помощь приходит метод clone() из класса Object. как говорит документация,  x.clone().getClass() == x.getClass(). т.е. то что нам нужно: не зная каков на самом деле класс нашего объекта, мы можем создать объект того же класса, да еще со всеми полями, скопированными из исходного объекта.
но в нашем классе

class MediaCollectionEntry {
    Long id;
    String name;
    ParsedFile firstChild;
...
}

есть ссылка на дочерний объект, который тоже надо клонировать, для этого переопределяем метод clone() этого класса:

public MediaCollectionEntry clone() throws CloneNotSupportedException {
  MediaCollectionEntry clone = (MediaCollectionEntry) super.clone();
  clone.setId(null);
  clone.setFirstChild(clone.getFirstChild().clone());
  return clone;
}

мы обнуляем id, чтобы при вставке в базу он подставился самостоятельно и заменяем firstChild на его клон, где тоже будет обнулен id.

abstract class ParsedFile implements Cloneable {
  protected Long id;
  protected String fileName;
  protected Set children = new HashSet();
  
  public Set getChildren() {
    return children;
  }
  public void setChildren(Set children) {
    this.children = children;
  }
  
  public void addChild(ParsedFile child) {
    child.setParent(this);
    children.add(child);
  }
  
...
  public ParsedFile clone() throws CloneNotSupportedException {
    ParsedFile clone = (ParsedFile) super.clone();
    clone.setId(null);
    Set cloneChildren = clone.getChildren();
    clone.setChildren(new HashSet());
      
    for(ParsedFile child : cloneChildren) {
        clone.addChild(child.clone());
    }
    return clone;
  }
}

и тут побежала рекурисия по всем деткам и внучкам)

в результате этого копирование данных из одной базы данных в другую свелось к двум строчкам:

MediaCollectionEntry mce1 = session1.load(MediaCollectionEntry.class, mediaCollectionEntryId);
MediaCollectionEntry mce2 = mce1.clone();
session2.save(mce2);

это скопирует не только MediaCollectionEntry но и его firstChild и всех детей к нему привязанных.

я к чему весь этот непонятный бред пишу. у меня по нему два вопроса:
не является ли это решение нерациональным?
как бы вы такое сделали на пхп?

p.s. связь с третьей таблицей есть у некоторых деток ParsedFile'a а именно у наследующих абстрактный класс AudioFile:


abstract class AudioFile extends ParsedFile implements Cloneable {
  AudioInfo audioInfo;
...
  public AudioFile clone() throws CloneNotSupportedException {
// этот вызов пойдет к методу clone() в классе ParsedFile
// скопирует все его поля а потом обработает здесь audioInfo
    AudioFile clone = (AudioFile) super.clone();


    AudioInfo audioInfo = clone.getAudioInfo().clone();
    audioInfo.setFileObject(clone);
    clone.setAudioInfo(audioInfo);
    return clone;
  }
}

0 comments:

Post a Comment