@getatonheim

Как правильно сделать полную копию объекта, со всеми потомками в hibernate?

Здравствуйте! Система построена на основе Jboss Seam версии 2.3.1. В системе используется Java версии 6, а так же Apache Tomcat выступающий в роли сервера. Вместо EJB используются Spring Bean, для того, чтобы запускать приложение в сервлет контейнере (Tomcat) вместо сервера приложения (TomEE, GlassFish, Weblogic) и тд. Зачем это сделано не суть.
Версии используемых компонентов:
Jboss Seam 2.3.1
Spring Web 3.2.2
Spring Bean 3.2.2
Spring Remote
Hibernate 4.1.4

Проблема вот в чем. В метод сохранения сущности (назовем его saveEntity()) приходит persistent объект. Мне необходимо в этом методе, сделать копию этого объекта. У объекта есть потомки, то есть необходимо также сделать deep copy, чтобы все ссылки были сохраненны. Грубо говоря, мне необходимо сохранить такой же объект, но с другим ID. После нескольких дней чтения ответов в гугле, и на StackOverflow в частности, использовал несколько способов.

1) Использовать следующий метод, как советуют здесь:
public static <T> T clone(Class<T> clazz, T dtls) { 
        T clonedObject = (T) SerializationHelper.clone((Serializable) dtls); 
        return clonedObject; 
  }

2) Использовать самописный метод, суть которого в распроксировании объекта (unproxy) и последующим копированием:
public static <T> T initializeAndUnproxy(T entity) {

        if (entity == null) {

            return null;
        }

        try {
            Hibernate.initialize(entity);

        }
        catch (Exception e) {

            logger.error("Exception occured:", e);
        }

        if (entity instanceof HibernateProxy) {
            entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
        }

        return entity;
    }

public static Object clone(Object obj) {
        
        try {
            
            ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            byte buf[] = baos.toByteArray();
            oos.close();

            ByteArrayInputStream bais = new ByteArrayInputStream(buf);
            ObjectInputStream ois = new ObjectInputStream(bais);

            Object newObject = ois.readObject();
            ois.close();

            return newObject;
        }

У объекта entity (который необходимо скопировать), есть поле Author, с типом, который имплементит UserDetails (Spring Security). Поле соответствует пользователю системы, который сохраняет запись. Проблема в следующем, при использовании любого из вышеперечисленных способов, кидается следующий exception:
xxx.yyy.zzz.User cannot be cast to org.jboss.seam.intercept.Proxy

При использовании самописного метода, падает конкретно вот здесь:
ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(obj); <--- тут валится


Я не эксперт в Spring Security, но подозреваю, что это он не дает мне сделать копию, но не уверен. В документации JBoss, ничего нет по интерфейсу org.jboss.seam.intercept.Proxy. Гугл тоже молчит по этому поводу. Тип сущности имплеменитит Serializable. Заранее спасибо за любую помощь.

P.S С помощью грязного хака удается сохранить сущность под другим id, но это попродило другие проблемы, поэтому временно от него отказался
this.getSessionFactory().getCurrentSession().evict(entity);
((AEntity) entity).setEntryKey(null);
this.getSessionFactory().getCurrentSession().merge(entity);
  • Вопрос задан
  • 1460 просмотров
Пригласить эксперта
Ответы на вопрос 1
@bobzer
Java EE Developer
Точного ответа не дам, но есть что сказать по этому поводу:
  1. Проблема с Proxy обычно возникает на дочерних коллекциях, особенно когда они помечены как Lazy. Hibernate подставляет в коллекцию свой прокси-объект и при обращении к коллекции осуществляет чтение из БД.
  2. Судя по всему, Hibernate.initialize(entity) в вашем случае не делает ровным счётом ничего. Этот метод следует применять не к самой сущности, а ко всем Lazy-коллекциям сущности, по отдельности к каждой. Если сущность не является detached (не "оторвана" от сессии, в которой её считывали из БД), то Hibernate.initialize и вовсе не требуется, прокси-объекты коллекций (из п.п. 1) сами считают всё из БД при обращении к списку в коде.
  3. Ваш хак - первое что пришло мне в голову еще в начале чтения вопроса (если EntryKey - это первичный ключ сущности). Как только сущность "теряет" первичный ключ, она становится кандидатом на insert, если же первичный ключ есть - тогда Hibernate сделает update. Если аккуратно "обыграть" такие манипуляции с учетом этого правила и на дочерних объектах, то почему бы и нет?
  4. Вопросы клонирования объектов - это отдельная большая и непростая тема, даже без привязки её к сущностям БД (что ещё больше эту проблему усугубляет). Два основных варианта осуществления клонирования: 1 - использовать специфические библиотеки; 2 - писать всё руками (100500 set(get())-ов). Самый надежный и производительный вариант - 2, но он порождает много лишнего кода, который еще и поддерживать надо. Тем не менее, такой подход "живее всех живых", особенно если кол-во сущностей для клонирования можно пересчитать по пальцам.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы