Можете ли вы передать переменные подстановки в файлы отображения Hibernate? - PullRequest
2 голосов
/ 31 января 2011

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

Я пытаюсь выяснить, есть ли способ указать переменные или свойства во время конфигурации 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" />

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

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

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

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.

Есть ли в Hibernate подобная функциональность?

Спасибо!

Leo

Ответы [ 2 ]

2 голосов
/ 19 июля 2012

В итоге я нашел решение. Я задокументировал это на внутренней вики-странице моего проекта, но скопирую все это сюда:

Проблема

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

Один из недостатков этих классов, созданных Builder, заключается в том, что мы создаем большие, «отсоединенные» ( см. Определение Hibernate ) графы объектов, которые необходимо повторно присоединить к сеансу 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 голосов
/ 31 января 2011

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

...