В процессе работы с Hibernate столкнулся с весьма неординарной проблемой. Вкратце на проекте нужно было организовать редактирование данных в древовидной модели. Т.е. в таблице есть поле которое на которое ссылаются прочие записи в этой таблице (пример XML документ или файловая система). Пример маппинга до решения проблемы:
Моя же основная задача свелась к смене родителя у коллекции детей при добавлении промежуточного узла. Сделать стандартными средствами Hibernate это оказалось невозможным - При создании Set'a из новой коллекции и добавления туда child'ов выскакивал Exception при сохранении (org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations)) При работе с существующей коллекцией (клонирование элементов, очистка Set'a, вставка промежуточного nod'а и заполнение его клонами) также не дало результата - NonUniqueObjectInSession. Дело в том что Hibernate считает операцию удаления приоритетнее, т.е. сначала выполняет каскадирование помещает объект в сессию для удаления его, а потом находит в сессии точно такой же присоединенный объект к другому узлу дерева - в результате Exception. Вылечить это как оказалось можно только использованием своей специфической Usertype-collection. Маппнг Хмл притерпел следующие изменения:<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.full.package.name.Node" table="calulation_tree_nodes"> <id name="id" column="calulation_tree_node_id" type="integer"> <generator class="native"/> </id>... <many-to-one name="parent" column="parent" class="com.full.package.name.Node"/> <many-to-one name="dataNode" column="data_id" class="com.full.package.name.Node"/>... <set name="children" inverse="true" cascade="all,delete-orphan" lazy="true" fetch="join" order-by="node_name desc" batch-size="20"> <key column="parent"/> <one-to-many class="com.full.package.name.Node"/> </set> </class> </hibernate-mapping>
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.full.package.name.Node" table="calulation_tree_nodes"> <id name="id" column="calulation_tree_node_id" type="integer"> <generator class="native"/> </id>... <many-to-one name="parent" column="parent" class="com.full.package.name.Node"/> <many-to-one name="dataNode" column="data_id" class="com.full.package.name.Node"/>... <set name="children" collection-type="com.full.package.name.ChangeParentsCollection" inverse="false" cascade="all,delete-orphan" lazy="true" fetch="join" order-by="node_name desc" batch-size="20"> <key column="parent"/> <one-to-many class="com.full.package.name.Node"/> </set> </class> </hibernate-mapping>
Ну и для того чтобы Код Java работал и Hibernate увидел нашу пользовательскую коллекцию мы должны унаследовать интерфейс UserCollectionType В Итоге пользовательский клас получился таким
public class ChangeParentsCollection extends
PersistentSet implements UserCollectionType {
/**
*
*/
private static final long serialVersionUID = -5852154001164009614L;
public ChangeParentsCollection() {
super();
}
public ChangeParentsCollection(SessionImplementor session) {
super(session);
}
public ChangeParentsCollection(SessionImplementor session,
Set set) {
super(session, set);
}
@Override
public Collection getOrphans(Serializable snapshot,
String entityName) throws HibernateException {
final Collection orphans = super.getOrphans(snapshot, entityName);
if(orphans != null){
final Iterator iterator = orphans.iterator();
while (iterator.hasNext()) {
final Object item = iterator.next();
if (item != null && item instanceof Node) {
final Node node = (Node) item;
if(node.getParent() == null){
iterator.remove();
}
}
}
}
return orphans;
}
// standard wrapper for collection type.
@Override
public PersistentCollection wrap(SessionImplementor session,
Object collection) {
return new ChangeParentsCollection(session, (Set)collection);
}
@Override
public boolean contains(Object collection, Object entity) {
Set set = (Set)collection;
return set.contains(entity);
}
// could be common for all collection implementations.
@Override
public Iterator getElementsIterator(Object collection) {
return ((Set)collection).iterator();
}
// common for list-like collections.
@Override
public Object indexOf(Object collection, Object entity) {
return null;
}
// factory method for certain collection type.
@Override
public Object instantiate(int anticipatedSize) {
return new HashSet(); }
@Override
public PersistentCollection instantiate(SessionImplementor session,
CollectionPersister persister) throws HibernateException {
return new ChangeParentsCollection(session);
}
// could be common implementation for all collection implementations
@Override
public Object replaceElements(Object original, Object target,
CollectionPersister persister, Object owner,
Map copyCache, SessionImplementor session)
throws HibernateException {
Set result = (Set) target;
result.clear();
if(original != null && original instanceof Collection){
Iterator it = ((Collection)original).iterator();
while(it.hasNext()){
final Object item = it.next();
if(item != null){
result.add(item);
}
}
}
return result;
}
}
Inverse был изменен для того, чтобы отношением управлял родительский объект, а не наоборот. Снижения производительности это не вызвало, так как inverse больше используется при вставке элементов. Ссылочка на документацию об inverse В коде при очистке коллекции дочерних элементов ссылка на Родителя устанавливается в null, чтобы в последствии этот элемент был найден и удален из сессии вручную. Тесты показали, что все замечательно работает.Ах, да, совсем забыл это решение проблемки описаной в hibernate а именно
Persistence through cascading should have higher precedence than orphan-delete
moving child between parents in tree-like structure
Там про это можно почитать подробнее...
Комментариев нет:
Отправить комментарий