我最终找出了一个解决方案。我证明,我的项目的内部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());
}
}
}
希望能帮助别人!
是的,它肯定会带来一些风险。过去两年来,我们一直在使用DBUnit,但对于大型企业应用程序来说,维护相关的XML文件(即使我们正在努力保持它们尽可能小而特殊)变得笨拙。出于这个原因,我们使用测试数据生成器模式(http://nat.truemesh.com/archives/000714.html)来测试我们的存储库。当然,级联是您可能想要测试的,但在这些情况下,我们更感兴趣的是使用域和休眠来生成数据。 – Leo 2011-02-01 13:17:44