Я в конечном итоге выяснил решение.Я документально подтверждено, что на внутренней странице вики моего проекта, но будет копировать все это здесь:
Проблему
Строители позволяют создавать сложные графы объектов быстро, без указания деталей для всех созданных объектов. Преимущество такого подхода заключается в блоге в другом месте и выходит за рамки этого ответа.
Одним из недостатков этих построенных 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());
}
}
}
Надеюсь, это поможет кому-то еще!
Да, это определенно представляет некоторый риск. Мы используем DBUnit в течение последних двух лет, но обслуживание связанных файлов XML (даже когда мы стараемся хранить их как можно более мелкие и конкретные) для крупного корпоративного приложения становится громоздким. По этой причине мы используем шаблон построителя тестовых данных, который можно найти здесь http://nat.truemesh.com/archives/000714.html, чтобы проверить наши репозитории. Теперь, конечно, каскадирование - это то, что вы, возможно, захотите проверить, но в этих случаях нас больше интересует использование домена и спящего режима для генерации данных. – Leo