2011-01-31 99 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" /> 

我想有设置为$ {standardIdName}和ID的name属性的值然后在应用程序启动过程中传入$ {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中有这样的功能吗?

谢谢!

利奥

回答

2

我最终找出了一个解决方案。我证明,我的项目的内部Wiki页面上,但将它拷贝到这里一切:

的问题

建设者让我们能够快速创建复杂的对象图,而不指定用于创建的所有对象的细节。这种方法的好处是在别处讨论并超出了这个答案的范围。

这些Builder构建的类的缺点之一是我们正在创建大型的“分离”(see Hibernate's definition)对象图,在我们告诉存储库保存时需要重新附加到Hibernate会话。

如果我们正确地模仿我们的关系,最多对一的关系不会自动持久和保存与多对一依赖实体将为您呈现以下错误消息:

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

我可能是错的,但据我记得休眠没有像Spring的PropertyConfigurer占位符的支持。我想我明白你在这里想要完成什么,但是如果你为了测试的目的而改变你的HBM文件,你如何确保你所做的改变反映了生产方案?如果你在测试中错误地设置你的级联,那么你基本上是在编写测​​试用例来测试错误的东西。我相信你有一个合理的理由这样做,但从你在这里解释的,这听起来对我来说是一个风险。当我编写测试用例时,他们测试实际的生产代码。如果您想重置数据库或将其用于测试目的,我建议您使用DBUnit或其他类似的东西,而不是用HBM文件加以解决。

+0

是的,它肯定会带来一些风险。过去两年来,我们一直在使用DBUnit,但对于大型企业应用程序来说,维护相关的XML文件(即使我们正在努力保持它们尽可能小而特殊)变得笨拙。出于这个原因,我们使用测试数据生成器模式(http://nat.truemesh.com/archives/000714.html)来测试我们的存储库。当然,级联是您可能想要测试的,但在这些情况下,我们更感兴趣的是使用域和休眠来生成数据。 – Leo 2011-02-01 13:17:44

相关问题