2011-01-31 2 views
2

я задал этот вопрос на форумах Hibernate некоторое время назад, но пока не получили ответа: https://forum.hibernate.org/viewtopic.php?f=1&t=1008487Можете ли вы передать переменные замещения в файлы сопоставления Hibernate?

Я пытаюсь выяснить, есть ли какой-нибудь способ, чтобы задать переменные или свойства при конфигурации Hibernate в чтобы изменить детали моих файлов сопоставления.

Например, скажем, у меня есть следующие:

<hibernate-mapping default-access="field"> 

<id name="${standardIdName}" column="id" type="long"> 
    <generator class="sequence"> 
     <param name="sequence">warning_seq</param> 
    </generator> 
    </id> 

    <version name="version" column="version" /> 

Я хотел бы иметь значение для имени атрибута ID установлен в $ {standardIdName} и затем передайте это $ {standardIdName} во время запуска приложения. Это очень похоже на то, что Spring позволяет вам делать через PropertyConfigurer.

Реальный случай использования вождения - это то, что я хотел бы иметь возможность каскада всех моих отношений на определенных этапах тестирования.

Если я тестирую класс ребенка, и он должен иметь ссылку на класс Parent, я могу использовать тестовый шаблон строитель данных и один спящий режим сохранить называют упорствовать мой график объекта:

public class Parent { 
    ... 
} 

public class Child { 
    private Parent parent; 
    ... 
} 

Мой тест делает что-то вроде:

Child child = new ChildBuilder().build() //Builds child class, a "dummy" Parent class and gives the Child that "Dummy" Parent class. 
childRepository.save(child); 

для того, что второй линии на работу, экономя как ребенка и родителя, мне нужен мой файл отображения, чтобы быть что-то вроде этого, где будет каскадное создание родитель:

<hibernate-mapping> 
    <class name="Child" table="child"> 
     <key column="id" /> 

     <many-to-one name="Parent" column="parent_id" not-null="true" 
      class="parent" cascade="all"/> 
    </class> 
</hibernate-mapping> 

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

<hibernate-mapping> 
    <class name="Child" table="child"> 
     <key column="id" /> 

     <many-to-one name="parent" column="parent_id" not-null="true" 
      class="parent" cascade="${cascadeType}"/> 
    </class> 
</hibernate-mapping> 

Где cascadeType передается во время спящего режима.

Есть ли какие-либо функциональные возможности в Hibernate?

Спасибо!

Лео

ответ

2

Я в конечном итоге выяснил решение.Я документально подтверждено, что на внутренней странице вики моего проекта, но будет копировать все это здесь:

Проблему

Строители позволяют создавать сложные графы объектов быстро, без указания деталей для всех созданных объектов. Преимущество такого подхода заключается в блоге в другом месте и выходит за рамки этого ответа.

Одним из недостатков этих построенных Builder классов является то, что мы создаем большие, «отдельные» (see Hibernate's definition) графы объектов, которые необходимо повторно подключить к сеансу Hibernate, когда мы скажем сохранить хранилище.

Если мы смоделировали наши отношения правильно, большинство ManyToOne отношений не будут автоматически сохраняются и сохранение объекта с зависимостями ManyToOne представят вам следующее сообщение об ошибке:

org.hibernate.TransientObjectException: объект ссылается на несохраненный переходный экземпляре - сохранить переходный экземпляр перед промывкой

введения ManyToOneDependencySaver помогает решить эти временные проблемы экземпляра:

public interface ManyToOneDependencySaver<E, T extends GenericRepository<?, ?>> { 

    T saveDependencies(E entity); 

    ManyToOneDependencySaver<E, T> withRepository(T repository); 

} 

Допустим, мы имеем следующую объектную модель:

http://s15.postimage.org/3s1oxboor/Object_Model.png

И позволяет сказать, что мы используем строитель в тесте, чтобы создать экземпляр SomeSimpleEntity, что в конечном счете необходимо сохранить (интеграционный тест):

@Test 
public void shouldSaveEntityAndAllManyToOnesWhenPropertiesAreDummiedUp() { 
    SomeSimpleEntity someSimpleEntity = new SomeSimpleEntityBuilder() 
     .withFieldsDummiedUp() 
      .build(); 

    this.idObjectRepository.saveOrUpdate(someSimpleEntity); 

    Assert.assertNotNull("SimpleEntity's ID should not be null: ", 
      someSimpleEntity.getId()); 
    Assert.assertNotNull(
      "SimpleEntity.RequiredFirstManyToManyEntity's ID should not be null: ", 
      someSimpleEntity.getRequiredFirstManyToManyEntity().getId()); 
    Assert.assertNotNull(
      "SimpleEntity.RequiredSecondManyToManyEntity's ID should not be null: ", 
      someSimpleEntity.getRequiredSecondManyToManyEntity().getId()); 
    Assert.assertNotNull(
      "SimpleEntity.RequiredFirstManyToManyEntity.RequiredSecondManyToManyEntity's ID should not be null: ", 
       someSimpleEntity.getRequiredFirstManyToManyEntity() 
         .getRequiredSecondManyToManyEntity().getId()); 

} 

Это происходит сбой на this.idObjectRepository.saveOrUpdate (someSimpleEntity); как SomeSimpleEntity имеет обязательное поле типа FirstManyToManyEntity и экземпляр, созданный создателем для FirstManyToManyEntity, еще не является сохраняемым объектом. Наши каскады не готовы подниматься вверх.

Ранее пройти эту проблему мы должны были бы сделать это:

@Test 
public void shouldSaveEntityAndAllManyToOnesWhenPropertiesAreDummiedUp() { 
     FirstManyToOneEntity firstManyToOneEntity = new FirstManyToOneEntity() 
     .withFieldsDummiedUp() 
     .build(); 

    this.idObjectRepository.saveOrUpdate(firstManyToOneEntity); //Save object SomeSimpleEntity depends on 

    SomeSimpleEntity someSimpleEntity = new SomeSimpleEntityBuilder() 
     .withFieldsDummiedUp() 
     .withRequiredFirstManyToOneEntity(firstManyToOneEntity) 
     .build(); 

    this.idObjectRepository.saveOrUpdate(someSimpleEntity); //Now save the SomeSimpleEntity 

    Assert.assertNotNull("SimpleEntity's ID should not be null: ", 
      someSimpleEntity.getId()); 
    Assert.assertNotNull(
      "SimpleEntity.RequiredFirstManyToOneEntity's ID should not be null: ", 
      someSimpleEntity.getRequiredFirstManyToManyEntity().getId()); 

} 

Это работает, но наш хороший свободно интерфейс сломалась и мы с указанием объектов, которые не являются интересные части теста. Этот не обязательно связывает этот тест SomeSimpleEntity с FirstManyToOneEntity.

Используя ManyToOneDependencySaver, мы можем избежать этого:

public class ManyToOneDependencySaver_saveDependenciesTests 
     extends 
     BaseRepositoryTest { 

    @Autowired 
    private IDObjectRepository idObjectRepository; 
    @Autowired 
    private ManyToOneDependencySaver<IDObject, IDObjectRepository> manyToOneDependencySaver; 

    @Test 
    public void shouldSaveEntityAndAllManyToOnesWhenPropertiesAreDummiedUp() { 
     SomeSimpleEntity someSimpleEntity = new SomeSimpleEntityBuilder() 
       .withFieldsDummiedUp() 
       .build(); 

     this.manyToOneDependencySaver.withRepository(this.idObjectRepository) 
       .saveDependencies(someSimpleEntity) 
       .saveOrUpdate(someSimpleEntity); 

     Assert.assertNotNull("SomeSimpleEntity's ID should not be null: ", 
       someSimpleEntity.getId()); 
     Assert.assertNotNull(
       "SomeSimpleEntity.RequiredFirstManyToOneEntity's ID should not be null: ", 
       someSimpleEntity.getRequiredFirstManyToManyEntity().getId()); 

    } 
} 

Это может показаться, что нет ничего особенного в этом примере, но некоторые из наших объектов имеют глубокие графики, которые могут создавать тонны зависимостей. Использование ManyToOneDependencySaver значительно уменьшает размер вашего теста и повышает его читаемость.

Реализация ManyToOneDependencySaver?

@Repository 
public class HibernateManyToOneDependencySaver<E, T extends GenericRepository<E, ?>> 
     implements ManyToOneDependencySaver<E, T> { 

    private static final Log LOG = LogFactory 
      .getLog(HibernateManyToOneDependencySaver.class); 

    protected T repository; 
    protected HibernateTemplate hibernateTemplate; 

    @Autowired 
    public HibernateManyToOneDependencySaver(
      final HibernateTemplate hibernateTemplate) { 
     super(); 
     this.hibernateTemplate = hibernateTemplate; 
    } 

    @Override 
    public T saveDependencies(final E entity) { 
     HibernateManyToOneDependencySaver.LOG.info(String.format(
       "Gathering and saving Many-to-One dependencies for entity: %s", 
       entity)); 

     this.saveManyToOneRelationshipsInDependencyOrderBeforeSavingThisEntity(entity); 

     return this.repository; 
    } 

    @Override 
    public ManyToOneDependencySaver<E, T> withRepository(final T aRepository) { 
     this.repository = aRepository; 

     return this; 
    } 

    private void saveManyToOneRelationshipsInDependencyOrderBeforeSavingThisEntity(
      final E entity) { 
     SessionFactory sessionFactory = this.hibernateTemplate 
       .getSessionFactory(); 

     @SuppressWarnings("unchecked") 
     Map<String, ClassMetadata> classMetaData = sessionFactory 
       .getAllClassMetadata(); 

     HibernateManyToOneDependencySaver.LOG 
       .debug("Gathering dependencies..."); 

     Stack<?> entities = ManyToOneGatherer.gather(
       classMetaData, entity); 

     while (!entities.empty()) { 
      @SuppressWarnings("unchecked") 
      E manyToOneEntity = (E) entities.pop(); 

      HibernateManyToOneDependencySaver.LOG.debug(String.format(
        "Saving Many-to-One dependency: %s", 
        manyToOneEntity)); 

      this.repository.saveOrUpdate(manyToOneEntity); 
      this.repository.flush(); 

     } 

    } 

} 

public class ManyToOneGatherer { 

    private static final Log LOG = LogFactory 
      .getLog(HibernateManyToOneDependencySaver.class); 

    public static <T> Stack<T> gather(
      final Map<String, ClassMetadata> classMetaData, 
      final T entity) { 
     ManyToOneGatherer.LOG.info(String.format(
       "Gathering ManyToOne entities for entity: %s...", entity)); 

     Stack<T> gatheredManyToOneEntities = new Stack<T>(); 

     ClassMetadata metaData = classMetaData.get(entity 
       .getClass().getName()); 

     EntityMetamodel entityMetaModel = ManyToOneGatherer 
       .getEntityMetaModel(metaData); 
     StandardProperty[] properties = entityMetaModel.getProperties(); 

     for (StandardProperty standardProperty : properties) { 
      Type type = standardProperty.getType(); 

      ManyToOneGatherer.LOG.trace(String.format(
        "Examining property %s...", standardProperty.getName())); 

      if (type instanceof ManyToOneType) { 
       ManyToOneGatherer.LOG.debug(String.format(
         "Property %s IS a ManyToOne", 
         standardProperty.getName())); 

       DirectPropertyAccessor propertyAccessor = new DirectPropertyAccessor(); 

       @SuppressWarnings("unchecked") 
       T manyToOneEntity = (T) propertyAccessor.getGetter(
         entity.getClass(), standardProperty.getName()).get(
         entity); 

       ManyToOneGatherer.LOG.debug(String.format(
         "Pushing ManyToOne property '%s' of value %s", 
         standardProperty.getName(), manyToOneEntity)); 
       gatheredManyToOneEntities.push(manyToOneEntity); 

       ManyToOneGatherer.LOG.debug(String.format(
         "Gathering and adding ManyToOnes for property %s...", 
         standardProperty.getName())); 
       ManyToOneGatherer.pushAll(ManyToOneGatherer.gather(
         classMetaData, manyToOneEntity), 
         gatheredManyToOneEntities); 
      } 
      else { 
       ManyToOneGatherer.LOG.trace(String.format(
         "Property %s IS NOT a ManyToOne", 
         standardProperty.getName())); 
      } 
     } 
     return gatheredManyToOneEntities; 
    } 

    private static EntityMetamodel getEntityMetaModel(
      final ClassMetadata metaData) { 

     EntityMetamodel entityMetaModel = null; 
     if (metaData instanceof JoinedSubclassEntityPersister) { 
      JoinedSubclassEntityPersister joinedSubclassEntityPersister = (JoinedSubclassEntityPersister) metaData; 
      entityMetaModel = joinedSubclassEntityPersister 
        .getEntityMetamodel(); 
     } 

     if (metaData instanceof SingleTableEntityPersister) { 
      SingleTableEntityPersister singleTableEntityPersister = (SingleTableEntityPersister) metaData; 
      entityMetaModel = singleTableEntityPersister 
        .getEntityMetamodel(); 
     } 
     return entityMetaModel; 

    } 

    private static <T> void pushAll(final Stack<T> itemsToPush, 
      final Stack<T> stackToPushOnto) { 
     while (!itemsToPush.empty()) { 
      stackToPushOnto.push(itemsToPush.pop()); 
     } 
    } 
} 

Надеюсь, это поможет кому-то еще!

2

Я могу ошибаться, но, насколько я помню, Hibernate не поддерживает заполнитель, как Spring, PropertyConfigurer. Я думаю, что понимаю, что вы пытаетесь сделать здесь, но если вы меняете свои HBM-файлы ради целей тестирования, как вы можете обеспечить, что вы изменили, отражает сценарий производства? Если вы неправильно настроили свой каскад при тестировании, вы, по сути, записываете тестовые тесты, чтобы проверить неправильную вещь. Я уверен, что у вас есть веские основания для этого, но из того, что вы здесь объяснили, это звучит как риск для меня. Когда я пишу testcases, они проверяют фактический производственный код. Если вы хотите сбросить базу данных или манипулировать ею для целей тестирования, я бы рекомендовал вам использовать DBUnit или что-то подобное, а не futzing с вашими файлами HBM.

+0

Да, это определенно представляет некоторый риск. Мы используем DBUnit в течение последних двух лет, но обслуживание связанных файлов XML (даже когда мы стараемся хранить их как можно более мелкие и конкретные) для крупного корпоративного приложения становится громоздким. По этой причине мы используем шаблон построителя тестовых данных, который можно найти здесь http://nat.truemesh.com/archives/000714.html, чтобы проверить наши репозитории. Теперь, конечно, каскадирование - это то, что вы, возможно, захотите проверить, но в этих случаях нас больше интересует использование домена и спящего режима для генерации данных. – Leo