4

I am getting a TransientObjectException whenever I make a consecutive save; I do not get this error on the first save or if I refresh the screen/page. This is pretty confusing to me since once the object has been saved sucessfully it should not be transient, since it has a representation in my DB.

My files below (User and UserPasswordInfo) have a one-to-many relationship, where a User can have more than one UserPasswordInfo (or UPI) and they are linked by my User's primary key: user_id.

I am pretty sure the problem exists from the following files, and not elsewhere, since the TOE error only showed up after adding this and, from my knowledge, the software has never had this problem in its lifetime (a few years).

Here are my hibernate files:

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>

Here are their Java files:

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;
    }
}

Here is error message:

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)

EDIT Here are some links and sections of code that may be related to this error:

This is the method call that's made when I save the objects (the above error is thrown before this method is called)

@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

mr nooby noob
  • 1,860
  • 5
  • 33
  • 56
  • Your TansientObjectException is related with a third Library (BeanPopulator) you use . Could you paste code in relatation with your beanpopulator and also code of method where you call save. – Abass A Jun 16 '17 at 08:38
  • The library is gilead, what is this ? – Abass A Jun 16 '17 at 08:46
  • @AbassA I have added some relative links and code. "Gilead permits you to use your Persistent POJO (and especially the partially loaded ones) outside the JVM (GWT) without pain" – mr nooby noob Jun 16 '17 at 18:28
  • I see thanks, Could you provide if possible source code of the method where you instantiate and use your entities (User and UserPasswordInfo) before passing them to saveObjectList. Also i think it will be more clear if you just leave links to gilead ressources. – Abass A Jun 16 '17 at 19:18
  • @AbassA I have added another gilead link for HibernateUtil class. As for the source code, I am using the same User entities the entire time, which is not null and gets pass around from method to method. The UserPasswordInfo is simply just used like so, for this case: `user.addUPI(txtPassword.getText(), new Timestamp(currentTime), false);` – mr nooby noob Jun 16 '17 at 23:50
  • 1
    I've read some links about gilead framework so now the topic is more clear. here are some tips: It seems that all hibernate proxies are replaced with null by gilead framework.So are you sure that Your user object with id = 268 is not an hibernate proxy? Try to ensure that it is not an proxy,this could be the cause of your issue.And also try to remove the Collection userAndPasswordInfo from you User entity and check if it works without it? Hope that helps. – Abass A Jun 19 '17 at 10:02
  • For better answers i suggest you to tag your question with GWT,hibernate and there is also a gilead tag. – Abass A Jun 19 '17 at 10:03
  • @Thanks for the feedback. That's actually a pretty interesting find, but I don't think that my user object is not a proxy since the code for that has been the same since day one and didn't have this problem before. As for the collection for UserPasswordInfo (UPI) I will see what will happen if I remove the set, although I do need this set for the functionality. – mr nooby noob Jun 19 '17 at 18:58

3 Answers3

1

I think problem is with the method which is cloning your fromBean object to toBean.

As you can see from the logs toBean=[User , id: null]

And from 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;
        }

Hence the exception you are getting.

In short, the method cloning fromObject seems to be the problem. Try to debug just after the toBean is cloned.

Gaurav
  • 559
  • 4
  • 8
1

Potentially this might be caused by the following code:

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);
}

If you invoke this method and then store your User object, I would assume the exception to be thrown. The thing is that you need to attach UserPasswordInfo to the context first, and only then you can add it to the User object.

So the correct sequence is to store UserPasswordInfo first:

entityManager.persist(upi);

And then add it to passwords and store the user object:

user.getPasswordInfos().add(upi);
entityManager.save(user);
Danylo Zatorsky
  • 5,856
  • 2
  • 25
  • 49
  • Hmm that's a good point that you brought up, however, in my particular case I might have a small problem since I only save the value if the user actually hits the save button and from my understanding, when you persist an object it gets saved in the DB, right? – mr nooby noob Jun 21 '17 at 17:24
  • Can't you then just save `UserPasswordInfo` and add it to the User object when the user clicks save button? This is a pretty common approach btw. – Danylo Zatorsky Jun 21 '17 at 17:37
  • Yes you are right, but I think I was confused a little bit about the definition of a persistent object. I will persist my UserPasswordInfo object once the user hits SAVE and then I will add it to the user object and see what happens. – mr nooby noob Jun 21 '17 at 18:01
  • I actually have a question though: how come this doesn't happen the first time the user loads the screen and creates a new UPI object? This only happens if user loads the screen, creates a new UPI, saves, and tries creating a new UPI again. – mr nooby noob Jun 21 '17 at 18:07
  • 1
    I think that after the user has loaded the screen the transaction is ended. Hibernate Session is ended as well as it's bounded to the transaction. All the objects that you loaded the first time are not attached to any transaction now. So when the user presses 'save' button the new transaction comes in. This transaction doesn't know anything about your objects from the previous transaction, that's why it works when you read the first time but doesn't work when you store afterwards. – Danylo Zatorsky Jun 21 '17 at 18:20
0

Regarding to your 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);

The second time you save, the current Hibernate session has no knowledge about the persistency. So I think moving

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

out of the if check will fix your issue.

An Do
  • 309
  • 1
  • 8