2015-07-23 2 views
2

Я работаю в проекте с Hibernate Entity Manager, и все было хорошо, пока я не начал реализовывать оптимистичную блокировку в наших объектах.Hibernate throw TransientObjectException, когда я передаю переходный объект в качестве параметра для запроса после реализации блокировки оптимиста

Например, мы имеем следующий именованный запрос, который возвращает адреса человека:

@NamedQuery(name = "Address.findByPerson", query = "SELECT a FROM Address a WHERE a.person = :person") 

Для того, чтобы передать объект человека на этот запрос, мы обычно делали что-то вроде этого:

Person nPerson = new Person(); 
nPerson.setId(1); 

TypedQuery<Address> query = manager.createNamedQuery("Address.findByPerson", Address.class); 
query.setParameter("person", nPerson); 
query.getResultList(); 

Это было нормально, пока мы не представим @Version внутри объекта Person. После этого, исключение последующих начинают происходит:

java.lang.reflect.InvocationTargetException 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:497) 
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389) 
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:497) 
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767) 
Caused by: java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.sandboxmaven.entity.Person 
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1760) 
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677) 
    at org.hibernate.jpa.internal.QueryImpl.getResultList(QueryImpl.java:458) 
    at com.sandboxmaven.main.Sandbox.main(Sandbox.java:52) 
    ... 11 more 
Caused by: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.sandboxmaven.entity.Person 
    at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:294) 
    at org.hibernate.type.EntityType.getIdentifier(EntityType.java:510) 
    at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:174) 
    at org.hibernate.param.NamedParameterSpecification.bind(NamedParameterSpecification.java:67) 
    at org.hibernate.loader.hql.QueryLoader.bindParameterValues(QueryLoader.java:613) 
    at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1900) 
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1861) 
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1838) 
    at org.hibernate.loader.Loader.doQuery(Loader.java:909) 
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) 
    at org.hibernate.loader.Loader.doList(Loader.java:2551) 
    at org.hibernate.loader.Loader.doList(Loader.java:2537) 
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2367) 
    at org.hibernate.loader.Loader.list(Loader.java:2362) 
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:496) 
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:387) 
    at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:229) 
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1260) 
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:103) 
    at org.hibernate.jpa.internal.QueryImpl.list(QueryImpl.java:573) 
    at org.hibernate.jpa.internal.QueryImpl.getResultList(QueryImpl.java:449) 
    ... 12 more 

Я не смог найти каких-либо объяснений в документации такого поведения. Несомненно, это простая задача решить передачу только идентификатора Person или управляемого объекта в качестве параметра. Но я хотел бы понять, почему это началось. Кто-нибудь знает почему?

Спасибо!

ответ

3

Hibernate должен убедиться, что объект не является временным, в противном случае нет смысла использовать его в запросе.

Как Hibernate проверяет, временно ли объект? Я не смог найти официальную документацию по этому вопросу, но я посмотрел исходный код. Часть метода AbstractEntityPersister.isTransient выглядит следующим образом:

// check the version unsaved-value, if appropriate 
final Object version = getVersion(entity); 
if (isVersioned()) { 
    // let this take precedence if defined, since it works for 
    // assigned identifiers 
    Boolean result = entityMetamodel.getVersionProperty() 
      .getUnsavedValue().isUnsaved(version); 
    if (result != null) { 
     return result; 
    } 
} 

Как вы можете видеть, Hibernate будет проверять значение поля version (среди прочего), чтобы попытаться проверить, является ли переходный объект.

Поскольку version в своем прототипе Person является null (или другое неспасенное значение, на основе стратегии, которая определяет, что неспасенное значение), Hibernate знает экземпляр преходящ и бросает исключение. Без контроля версий, Hibernate проверяет личность идентификатор, является ли null (или другое неспасенное значение, на основе стратегии), чтобы сделать различие между переходными и непреходящими случаями:

// check the id unsaved-value 
Boolean result = entityMetamodel.getIdentifierProperty() 
     .getUnsavedValue().isUnsaved(id); 
if (result != null) { 
    return result; 
} 

так он работал, прежде чем ввел управление версиями, потому что идентификатор не равен null.

Чтобы преодолеть это, лучше всего передать идентификатор в запрос, как вы уже сказали. Или вы можете установить version в nPerson прототипа вы используете в запросе:

Person nPerson = new Person(); 
nPerson.setId(1); 
nPerson.setVersion(1); 

Здесь мы можем установить любое значение версии, так как он не используется в любом случае; Hibernate увидит, что это не null и не будет рассматривать объект как переходный.