首次保存后出现 TransientObjectException 错误

TransientObjectException error occurs after first save

每当我进行连续保存时,我都会收到 TransientObjectException;我在第一次保存或刷新 screen/page 时没有收到此错误。这让我很困惑,因为一旦对象被成功保存,它就不应该是暂时的,因为它在我的数据库中有一个表示。

我下面的文件(用户和 UserPasswordInfo)具有一对多关系,其中一个用户可以拥有多个 UserPasswordInfo(或 UPI),它们由我的用户的主键链接: user_id.

我很确定问题存在于以下文件中,而不是其他地方,因为 TOE 错误仅在添加此文件后出现,据我所知,该软件在其生命周期中从未出现过此问题(一些年)。

这是我的休眠文件:

User.hbm.xml

<hibernate-mapping>
    <class name="com.app.common.domain.User" table="users"  >
        <id name="id" type="java.lang.Integer">
            <column name="user_id" />
            <generator class="identity" />
        </id>

       <set name="passwordInfos" table="user_password_info" lazy="false" cascade="all" fetch="select">
            <key>
                <column name="user_id"></column>
            </key>
            <one-to-many class="com.app.common.domain.UserPasswordInfo" />
        </set>        

    </class>
</hibernate-mapping>

UserPasswordInfo.hbm.xml

<hibernate-mapping>
   <class name="com.app.common.domain.UserPasswordInfo" table="user_password_info">
      <id name="passwordInfoId" type="java.lang.Integer">
            <column name="password_info_id" />
            <generator class="identity" />
       </id>

      <property name="password" column="password" type="java.lang.String"/>
      <property name="passwordTS" column="password_ts" type="java.sql.Timestamp"/>
      <property name="pwdChange" column="initial_password_change" type="java.lang.Boolean"/>
      <property name="userId" column="user_id" type="java.lang.Integer"/>

   </class>
</hibernate-mapping>

这是他们的 Java 文件:

User.java

import java.sql.Timestamp;
import java.util.Collections;
import java.util.Comparator;

import java.util.HashSet;
import java.util.Set;

public class User extends DBObject {//DBObject has a getter and setter for the id

    private Set<UserPasswordInfo> passwordInfos = new HashSet<UserPasswordInfo>(0);

    public User() {
        super();
    }

    .
    .
    .

    public Set<UserPasswordInfo> getPasswordInfos() {
        return passwordInfos;
    }

    public void setPasswordInfos(Set<UserPasswordInfo> passwordInfos) {
        this.passwordInfos = passwordInfos;
    }

    public void addUPI(String pwd, Timestamp pwdTS, Boolean pwdChng){
        UserPasswordInfo upi = new UserPasswordInfo();
        upi.setPassword(pwd);
        upi.setPasswordTS(pwdTS);
        upi.setPwdChange(pwdChng);
        if(getId() != null)
            upi.setUserId(getId().intValue());
        passwordInfos.add(upi);
    }

    public String getLatestPassword(){
        return getSortedList().get(0).getPassword();
    }

    public UserPasswordInfo getLatestUPI(){
        ArrayList<UserPasswordInfo> list = getSortedList();
        return (list == null || list.size() == 0) ? null : list.get(0);
    }

    public ArrayList<String> getLastThreePasswords(){

        final ArrayList<UserPasswordInfo> upiList = getSortedList();

        if(upiList == null)
            return null;

        ArrayList<String> list = new ArrayList<String>();
        int i = 0; 
        while(i < 3 && i < list.size()){
            list.add(upiList.get(i++).getPassword());
        }
        return list;
    }

    public ArrayList<UserPasswordInfo> getSortedList(){

        ArrayList<UserPasswordInfo> upi = new ArrayList<UserPasswordInfo>(getPasswordInfos());
        if(upi == null || upi.size() == 0){
            return null;
        }

        Collections.sort(upi, new Comparator<UserPasswordInfo>() {
            public int compare(UserPasswordInfo o1, UserPasswordInfo o2) {
                if (o1.getPasswordTS() == null || o2.getPasswordTS() == null)
                    return 0;
                return o2.getPasswordTS().compareTo(o1.getPasswordTS());
            }
        });
        return upi;
    }
}

UserPasswordInfo.java

import java.sql.Timestamp;

public class UserPasswordInfo extends DBObject{

    private int passwordInfoId;
    private String password;
    private Timestamp passwordTS;
    private Boolean pwdChange;
    private int userId;

    public UserPasswordInfo(){}

    public UserPasswordInfo(String pwd, Timestamp pwdTS, Boolean pwdChng){
        password = pwd;
        passwordTS = pwdTS;
        pwdChange = pwdChng;
    }

    public UserPasswordInfo(int userId, String pwd, Timestamp pwdTS, Boolean pwdChng){
        this.userId = userId;
        password = pwd;
        passwordTS = pwdTS;
        pwdChange = pwdChng;
    }

    public int getPasswordInfoId(){
        return passwordInfoId;
    }
    public void setPasswordInfoId(int pid){
        passwordInfoId = pid;
    }
    public String getPassword(){
        return password;
    }
    public void setPassword(String pwd){
        password = pwd;
    }
    public Timestamp getPasswordTS(){
        return passwordTS;
    }
    public void setPasswordTS(Timestamp ts){
        passwordTS = ts;
    }
    public void setPwdChange(Boolean pwdChange) {
        this.pwdChange = pwdChange;
    }
    public Boolean getPwdChange() {
        return pwdChange;
    }
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
}

这是错误信息:

ERROR BeanPopulator - 
propertyName=passwordInfos
readerMethod=public java.util.Set com.app.domain.User.getPasswordInfos()
setterMethod=public void com.app.common.domain.User.setPasswordInfos(java.util.Set)
fromBean=[User someUser, id: 268, role: front desk]
toBean=[User , id: null]

net.sf.gilead.exception.TransientObjectException
    at net.sf.gilead.core.hibernate.HibernateUtil.getId(HibernateUtil.java:316)
    at net.sf.gilead.core.hibernate.HibernateUtil.getId(HibernateUtil.java:224)
    at net.sf.gilead.core.hibernate.HibernateUtil.loadPersistentCollection(HibernateUtil.java:854)
    at net.sf.gilead.core.hibernate.HibernateUtil.createPersistentCollection(HibernateUtil.java:843)
    at net.sf.gilead.core.beanlib.merge.MergeCollectionReplicator.replicateCollection(MergeCollectionReplicator.java:119)
    at net.sf.beanlib.provider.replicator.ReplicatorTemplate.replicate(ReplicatorTemplate.java:112)
    at net.sf.beanlib.provider.BeanTransformer.transform(BeanTransformer.java:231)
    at net.sf.beanlib.provider.BeanPopulator.doit(BeanPopulator.java:201)
    at net.sf.beanlib.provider.BeanPopulator.processSetterMethod(BeanPopulator.java:172)
    at net.sf.beanlib.provider.BeanPopulator.populate(BeanPopulator.java:269)
    at net.sf.gilead.core.LazyKiller.populate(LazyKiller.java:288)
    at net.sf.gilead.core.LazyKiller.attach(LazyKiller.java:237)
    at net.sf.gilead.core.PersistentBeanManager.mergePojo(PersistentBeanManager.java:554)
    at net.sf.gilead.core.PersistentBeanManager.merge(PersistentBeanManager.java:318)
    at net.sf.gilead.core.PersistentBeanManager.mergeCollection(PersistentBeanManager.java:581)
    at net.sf.gilead.core.PersistentBeanManager.merge(PersistentBeanManager.java:290)
    at net.sf.gilead.gwt.GileadRPCHelper.parseInputParameters(GileadRPCHelper.java:94)
    at net.sf.gilead.gwt.GileadRPCHelper.parseInputParameters(GileadRPCHelper.java:137)
    at net.sf.gilead.gwt.PersistentRemoteService.processCall(PersistentRemoteService.java:172)

编辑 以下是一些可能与此错误相关的链接和代码部分:

这是我保存对象时调用的方法(上面的错误是在调用这个方法之前抛出的)

@SuppressWarnings({ "rawtypes" })
    private List saveObjectList(List<? extends DBObject> values) {
        Session session = sessionFactory.getCurrentSession();
        Transaction tx = null;

        try {
            tx = session.beginTransaction();
            for (DBObject value : values) {
                if (value.getIsDirty()) {
                    Integer clientId = value.getClientId();
                    if (value.getId() != null) {//enters here
                        value = (DBObject) session.merge(value);
                        session.persist(value);
                    }
                    else {
                        session.save(value);
                    }
                    value.setClientId(clientId);
                    value.setIsDirty(false);
                }
            }
            tx.commit();//fails here

            return values;
        }
        catch (Exception ex) {
            String msg = "Failed to save values. ";
            if (values == null) {
                msg += " Values to save were null.";
            }
            else {
                msg += " Values to save had length: " + values.size();
            }
            rollbackOrCloseSession(tx, session, "saveObjectList() - " + msg, ex);
            return null;
        }
    }

GileadRPCHelper

/**
     * Parse RPC input parameters.
     * Must be called before GWT service invocation.
     * @param rpcRequest the input GWT RPC request
     * @param beanManager the Hibernate bean manager
     * @param session the HTTP session (for HTTP Pojo store)
     */
    public static void parseInputParameters(Object[] parameters, 
                                            PersistentBeanManager beanManager,
                                            HttpSession session)
    {
    //  Init classloader for proxy mode
    //
        if (beanManager.getClassMapper() instanceof ProxyClassMapper)
        {
            initClassLoader();
        }

    //  Set HTTP session of Pojo store in thread local
    //
        HttpSessionProxyStore.setHttpSession(session);

    //  Merge parameters if needed
    //
        if (parameters != null)
        {
            long start = System.currentTimeMillis();
            for (int index = 0 ; index < parameters.length; index ++)
            {
                if (parameters[index] != null)
                {
                    try
                    {
                        //***ERROR occurs when this is called
                        parameters[index] = beanManager.merge(parameters[index], true);
                    }
                    catch (NotAssignableException ex)
                    {
                        log.debug(parameters[index] + " not assignable");
                    }
                    catch (TransientObjectException ex)
                    {
                        log.info(parameters[index] + " is transient : cannot merge...");
                    }
                }
            }

            if (log.isDebugEnabled())
            {
                log.debug("Merge took " + (System.currentTimeMillis() - start) + " ms.");
            }
        }
    }

PersistentBeanManager

/**
     * Merge the clone POJO to its Hibernate counterpart
     */
    public Object merge(Object object) {
        // Explicit merge
        return merge(object, false);
    }

    /**
     * Merge the clone POJO to its Hibernate counterpart
     */
    @SuppressWarnings("unchecked")
    public Object merge(Object object, boolean assignable) {
        // Precondition checking
        //
        if (object == null) {
            return null;
        }

        if (_persistenceUtil == null) {
            throw new RuntimeException("No Persistence Util set !");
        }

        // Collection handling
        //
        if (object instanceof Collection) {
            return mergeCollection((Collection) object, assignable);
        } else if (object instanceof Map) {
            return mergeMap((Map) object, assignable);
        } else if (object.getClass().isArray()) {
            // Check primitive type
            //
            if (object.getClass().getComponentType().isPrimitive()) {
                return object;
            }

            // Merge as a collection
            //
            Object[] array = (Object[]) object;
            Collection result = mergeCollection(Arrays.asList(array), assignable);

            // Get the result as an array (much more tricky !!!)
            //
            Class<?> componentType = object.getClass().getComponentType();
            Object[] copy = (Object[]) java.lang.reflect.Array.newInstance(componentType, array.length);
            return result.toArray(copy);
        } else {
            return mergePojo(object, assignable);
        }
    }

/**
     * Retrieve the Hibernate Pojo and merge the modification from GWT
     * 
     * @param clonePojo the clone pojo
     * @param assignable does the source and target class must be assignable
     * @return the merged Hibernate POJO
     * @exception UnsupportedOperationException if the clone POJO does not implements ILightEntity and the POJO store is
     *                stateless
     * @exception NotAssignableException if source and target class are not assignable
     */
    protected Object mergePojo(Object clonePojo, boolean assignable) {

        // Get Hibernate associated class
        Class<?> cloneClass = clonePojo.getClass();
        Class<?> hibernateClass = null;
        if (_classMapper != null) {
            hibernateClass = _classMapper.getSourceClass(cloneClass);
        }
        if (hibernateClass == null) {
            // Not a clone : take the inner class
            hibernateClass = clonePojo.getClass();
        }

        // Precondition checking : is the pojo managed by Hibernate
        if (_persistenceUtil.isPersistentClass(hibernateClass) == true) {
            // Assignation checking
            if ((assignable == true) && (hibernateClass.isAssignableFrom(cloneClass) == false)) {
                throw new NotAssignableException(hibernateClass, cloneClass);
            }
        }

        // Retrieve the pojo
        try {
            Serializable id = null;
            try {
                id = _persistenceUtil.getId(clonePojo, hibernateClass);
                if (id == null) {
                    _log.info("HibernatePOJO not found : can be transient or deleted data : " + clonePojo);
                }
            } catch (TransientObjectException ex) {
                _log.info("Transient object : " + clonePojo);
            } catch (NotPersistentObjectException ex) {
                if (holdPersistentObject(clonePojo) == false) {
                    // Do not merge not persistent instance, since they do not
                    // necessary
                    // implement the Java bean specification
                    //
                    if (_log.isDebugEnabled()) {
                        _log.debug("Not persistent object, merge is not needed : " + clonePojo);
                    }
                    return clonePojo;
                } else {
                    if (_log.isDebugEnabled()) {
                        _log.debug("Merging wrapper object : " + clonePojo);
                    }
                }
            }

            if (ClassUtils.immutable(hibernateClass)) {
                // Do not clone immutable types
                //
                return clonePojo;
            }

            // Create a new POJO instance
            //
            Object hibernatePojo = null;
            try {
                if (AnnotationsManager.hasGileadAnnotations(hibernateClass)) {
                    if (id != null) {
                        // ServerOnly or ReadOnly annotation : load from DB
                        // needed
                        //
                        hibernatePojo = _persistenceUtil.load(id, hibernateClass);
                    } else {
                        // Transient instance
                        //
                        hibernatePojo = clonePojo;
                    }
                } else {
                    Constructor<?> constructor = hibernateClass.getDeclaredConstructor(new Class<?>[] {});
                    constructor.setAccessible(true);
                    hibernatePojo = constructor.newInstance();
                }
            } catch (Exception e) {
                throw new RuntimeException("Cannot create a fresh new instance of the class " + hibernateClass, e);
            }

            // Merge the modification in the Hibernate Pojo
            //
            _lazyKiller.attach(hibernatePojo, clonePojo);
            return hibernatePojo;
        } finally {
            _persistenceUtil.closeCurrentSession();
            _proxyStore.cleanUp();
        }
    }

BeanPopulator

@Override
    public <T> T populate() {
        if (getBeanTransformerSpi() != null) {
            getBeanTransformerSpi().getClonedMap().put(fromBean, toBean);
        }
        // invoking all declaring setter methods of toBean from all matching getter methods of fromBean
        for (Method m : baseConfig.getSetterMethodCollector().collect(toBean)) {
            processSetterMethod(m);
        }
        @SuppressWarnings("unchecked")
        T ret = (T) toBean;
        return ret;
    }

 private void processSetterMethod(Method setterMethod) {
        String methodName = setterMethod.getName();
        final String propertyString = methodName.substring(baseConfig.getSetterMethodCollector().getMethodPrefix().length());

        if (baseConfig.isDebug()) {
            if (log.isInfoEnabled()) {
                log.info(new StringBuilder("processSetterMethod: processing propertyString=").append(propertyString).append("")
                        .append(", fromClass=").append(fromBean.getClass()).append(", toClass=").append(toBean.getClass()).toString());
            }
        }
        Method readerMethod = baseConfig.getReaderMethodFinder().find(propertyString, fromBean);

        if (readerMethod == null) {
            return;
        }
        // Reader method of fromBean found
        Class<?> paramType = setterMethod.getParameterTypes()[0];
        String propertyName = Introspector.decapitalize(propertyString);
        try {
            doit(setterMethod, readerMethod, paramType, propertyName);
        } catch (Exception ex) {
            baseConfig.getBeanPopulationExceptionHandler().initFromBean(fromBean).initToBean(toBean).initPropertyName(propertyName)
                    .initReaderMethod(readerMethod).initSetterMethod(setterMethod).handleException(ex, log);
        }
    }

    private <T> void doit(Method setterMethod, Method readerMethod, Class<T> paramType, final String propertyName) {
        if (baseConfig.getDetailedPropertyFilter() != null) {
            if (!baseConfig.getDetailedPropertyFilter().propagate(propertyName, fromBean, readerMethod, toBean, setterMethod)) {
                return;
            }
        }
        if (baseConfig.getPropertyFilter() != null) {
            if (!baseConfig.getPropertyFilter().propagate(propertyName, readerMethod)) {
                return;
            }
        }
        Object propertyValue = this.invokeMethodAsPrivileged(fromBean, readerMethod, null);

        if (baseConfig.getBeanSourceHandler() != null) {
            baseConfig.getBeanSourceHandler().handleBeanSource(fromBean, readerMethod, propertyValue);
        }

        if (transformer != null) {
            PropertyInfo propertyInfo = new PropertyInfo(propertyName, fromBean, toBean);
            propertyValue = transformer.transform(propertyValue, paramType, propertyInfo);
        }

        if (baseConfig.isDebug()) {
            if (log.isInfoEnabled()) {
                log.info("processSetterMethod: setting propertyName=" + propertyName);
            }
        }
        // Invoke setter method
        Object[] args = { propertyValue };
        this.invokeMethodAsPrivileged(toBean, setterMethod, args);
        return;
    }

Gilead HibernateUtil class

我认为问题在于将您的 fromBean 对象克隆到 toBean 的方法。

从日志中可以看出toBean=[User , id: null]

来自 HibernateUtils class -

   @Override
        public Serializable getId(Object pojo, Class<?> hibernateClass) {
    ...
        if (isUnsavedValue(pojo, id, hibernateClass)) {
            throw new TransientObjectException(pojo);
        }
}

 private boolean isUnsavedValue(Object pojo, Serializable id, Class<?> persistentClass) {
        // Precondition checking
        //
        if (id == null) {
            return true;
        }

因此你得到了异常。

简而言之,克隆 fromObject 的方法似乎是问题所在。尝试在克隆 toBean 之后立即进行调试。

这可能是由以下代码引起的:

public void addUPI(String pwd, Timestamp pwdTS, Boolean pwdChng){
    UserPasswordInfo upi = new UserPasswordInfo();
    upi.setPassword(pwd);
    upi.setPasswordTS(pwdTS);
    upi.setPwdChange(pwdChng);
    if(getId() != null)
        upi.setUserId(getId().intValue());
    passwordInfos.add(upi);
}

如果您调用此方法然后存储您的 User 对象,我认为会抛出异常。问题是您需要先将 UserPasswordInfo 附加到上下文,然后才能将其添加到 User 对象。

所以正确的顺序是先存储UserPasswordInfo:

entityManager.persist(upi);

然后将其添加到密码并存储用户对象:

user.getPasswordInfos().add(upi);
entityManager.save(user);

关于您的 saveObjectList

if (value.getId() != null) {//enters here
    value = (DBObject) session.merge(value);
    session.persist(value);
}
else {
    session.save(value);
}
value.setClientId(clientId);
value.setIsDirty(false);

第二次保存时,当前Hibernate会话不知道持久化。所以我觉得搬家

value = (DBObject) session.merge(value);

if 检查将解决您的问题。