OneToMany Create Fails with InvalidDataAccessApiUsageException

236 views Asked by At

I am fairly new to Hibernate and have been using the manual & online forums, but I am stumped on this issue. I’m using Spring 3.2 with Hibernate 4 & Annotations. I have a parent (PledgeForm) & child (PledgeFormGiftLevel) table that is one-to-many.

Domain/Models: Parent

@Entity
@Table(name="PLEDGE_FORMS")
@SuppressWarnings("serial")
public class PledgeForm implements Serializable {

static final Logger log = Logger.getLogger(PledgeForm.class);

@Id
@GeneratedValue(strategy=GenerationType.AUTO, generator="pledge_form_seq")
@SequenceGenerator(name="pledge_form_seq", sequenceName="PLEDGE_FORM_SEQ")
@Column(name="ID", unique=true, nullable=false)
private Integer id;

….

@OneToMany(mappedBy="pledgeForm", fetch=FetchType.EAGER, cascade=CascadeType.ALL)//********1
private List<PledgeFormGiftLevel> pledgeFormGiftLevels = new ArrayList<PledgeFormGiftLevel>();

….

public List<PledgeFormGiftLevel> getPledgeFormGiftLevels() {
   return this.pledgeFormGiftLevels;
}

public void setPledgeFormGiftLevels(List<PledgeFormGiftLevel> pledgeFormGiftLevels) {
   this.pledgeFormGiftLevels = pledgeFormGiftLevels;
}

//I do not think the following method is needed, but I decided to try it just in case
public void addPledgeFormGiftLevels(PledgeFormGiftLevel pledgeFormGiftLevels) {
   pledgeFormGiftLevels.setPledgeForm(this);
   getPledgeFormGiftLevels().add(pledgeFormGiftLevels);
}   

Child

@Entity
@Table(name="PLEDGE_FORM_GIFT_LEVELS")
@SequenceGenerator(name="pledge_form_gift_level_seq", sequenceName="PLEDGE_FORM_GIFT_LEVEL_SEQ")
@SuppressWarnings("serial")
public class PledgeFormGiftLevel implements Serializable {

static final Logger log = Logger.getLogger(PledgeFormGiftLevel.class);

@Id
@GeneratedValue(strategy=GenerationType.AUTO, generator="pledge_form_gift_level_seq")
@Column(name="ID", unique=true, nullable=false)
private Integer id; 

…

@ManyToOne(fetch=FetchType.EAGER)//yes?
@JoinColumn(name="PLEDGE_FORM_ID", referencedColumnName="ID", insertable=true, updatable=true)//yes?
private PledgeForm pledgeForm = new PledgeForm();

…

public PledgeForm getPledgeForm() {
   return pledgeForm;
}
public void setPledgeForm(PledgeForm pledgeForm) {
   this.pledgeForm = pledgeForm;
}

Controller (there is a graphic, so I have code to pull in the file):

@Controller
@SessionAttributes("pledgeForm")
public class PledgeFormController {
   @Autowired
   org.unctv.service.PledgeFormManager Service;

…

@RequestMapping(value = "/saveJdbcPledgeForm", method = RequestMethod.POST, params="save")
public ModelAndView save(
   @ModelAttribute("pledgeForm")
   @Valid PledgeForm pledgeForm, BindingResult result,
   @RequestParam("logoImg") MultipartFile file,
   @RequestParam(value="removeLogoImg", required=false) String removeLogoImg) throws Exception {

      ModelAndView mav = null;
      mav = new ModelAndView("pledgeFormSearch");//Name of the JSP

      if (removeLogoImg != null) {
         pledgeForm.setLogoFilename(null);

         pledgeForm.setLogoImg(null);
         pledgeForm.setLogoContentType(null);
      } else if (file != null && file.getBytes().length > 0) {
         pledgeForm.setLogoFilename(file.getOriginalFilename());
         pledgeForm.setLogoImg(file.getBytes());
         pledgeForm.setLogoContentType(file.getContentType());
      }

      Service.save(pledgeForm);
      mav.addObject("pledgeForm", pledgeForm);//JSP Form's Command Name (pledgeForm); 
      mav.addObject("cmdName", "pledgeForm");
      mav.addObject("actionType", "Save");
      return mav;
}   

Service:

@Service("simplePledgeFormManager")
@Transactional(readOnly=true)
public class SimplePledgeFormManager implements PledgeFormManager { 
   @Autowired
   private HibernatePledgeFormDao hibernatePledgeFormDao;


…

@Transactional(readOnly=false)
public void save(PledgeForm pledgeForm) throws Exception {
   hibernatePledgeFormDao.save(pledgeForm);
} 

DAO:

@Repository("PledgeFormDAO")
public class HibernatePledgeFormDao implements PledgeFormDao {

static final Logger log = Logger.getLogger(HibernatePledgeFormDao.class);

   @Autowired
   private SessionFactory sessionFactory;


...

@Override
public void save(PledgeForm pledgeForm) throws Exception {
   sessionFactory.getCurrentSession().saveOrUpdate(pledgeForm);
} 

Using the code above, parent/child records can be selected and updated fine. When I display the “trace” messages from hibernate, the update does have this trace message about the child, though:

[2013-12-06 10:31:24,648] TRACE Persistent instance of: org.unctv.domainmodel.PledgeFormGiftLevel
[2013-12-06 10:31:24,649] TRACE Ignoring persistent instance
[2013-12-06 10:31:24,649] TRACE Object already associated with session: [org.unctv.domainmodel.PledgeFormGiftLevel#1]

The create always gives this error if there is a child record:

object references an unsaved transient instance - save the transient instance before flushing: org.unctv.domainmodel.PledgeForm; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: org.unctv.domainmodel.PledgeForm 

When I look at the hibernate logs, I see that it updates the parent & the child based on transient objects. Then it tries to flush & finds a persistent copy of the child, so it rolls back everything.

[2013-12-06 10:34:13,615] TRACE Automatically flushing session
[2013-12-06 10:34:13,615] TRACE Flushing session
[2013-12-06 10:34:13,615] DEBUG Processing flush-time cascades
[2013-12-06 10:34:13,615] TRACE Processing cascade ACTION_SAVE_UPDATE for: org.unctv.domainmodel.PledgeForm
[2013-12-06 10:34:13,615] TRACE Cascade ACTION_SAVE_UPDATE for collection: org.unctv.domainmodel.PledgeForm.pledgeFormGiftLevels
[2013-12-06 10:34:13,615] TRACE Cascading to save or update: org.unctv.domainmodel.PledgeFormGiftLevel
[2013-12-06 10:34:13,616] TRACE Persistent instance of: org.unctv.domainmodel.PledgeFormGiftLevel
[2013-12-06 10:34:13,616] TRACE Ignoring persistent instance
[2013-12-06 10:34:13,616] TRACE Object already associated with session: [org.unctv.domainmodel.PledgeFormGiftLevel#51]
[2013-12-06 10:34:13,616] TRACE Done cascade ACTION_SAVE_UPDATE for collection: org.unctv.domainmodel.PledgeForm.pledgeFormGiftLevels
[2013-12-06 10:34:13,616] TRACE Done processing cascade ACTION_SAVE_UPDATE for: org.unctv.domainmodel.PledgeForm
[2013-12-06 10:34:13,617] DEBUG Dirty checking collections
[2013-12-06 10:34:13,617] TRACE Flushing entities and processing referenced collections
[2013-12-06 10:34:13,617] DEBUG Collection found: [org.unctv.domainmodel.PledgeForm.pledgeFormGiftLevels#51], was: [<unreferenced>] (initialized)
[2013-12-06 10:34:13,618] DEBUG rolling back
[2013-12-06 10:34:13,618] DEBUG rolled JDBC Connection 

The Hibernate documentation shows this as even simpler than I my code is, but I had to add the fetch & cascade values. I’ve played with changing the fetch & cascade values & placement (starting with the Hibernate documentation & then adding on), but everything else I try still causes the create to fail & often causes the update to fail too.

Many forum posts that I find show flush() or evict(). I am not certain if it is Hibernate 4 or annotations (@Transactional, I think) I’m using, but I do not see a place for that in my code. From the Hibernate trace logs, I can see that flushing is occurring automatically with in the saveOrUpdate() method.

I also tried dropping the tables & sequences & starting fresh.

Any advice about getting the create to work is appreciated. If you can point me to specific documentation that I missed, that is appreciated as well.

Thanks, Bonnie

1

There are 1 answers

3
Kevin Bowersox On BEST ANSWER

I noticed that equals and hashcode have not been overridden in the entities. These methods are used to compare objects to determine their equality. Hibernate may not be able to determine if an existing instance of the entity exists without these methods being overridden. Try providing implementations for hashcode and equals.

If your using Eclipse, press CTRL + SHIFT + S, H to bring up the dialog for creating the hashcode and equals methods. Pick fields that contain values that are relatively unchanged and then generate the methods.

Also be sure that you are managing both sides of the entity as discussed in the above comments:

public ModelAndView save(
   @ModelAttribute("pledgeForm")
   @Valid PledgeForm pledgeForm, BindingResult result,
   @RequestParam("logoImg") MultipartFile file,
   @RequestParam(value="removeLogoImg", required=false) String removeLogoImg) throws Exception {

      ModelAndView mav = null;
      mav = new ModelAndView("pledgeFormSearch");//Name of the JSP

      //Manage both sides of the entity
      List<PledgeFormGiftLevel> levels = pledgeForm.getPledgeFormGiftLevels();

      for(PledgeFormGiftLevel level: levels){
          level.setPledgeForm(pledgeForm);
      }

      if (removeLogoImg != null) {
         pledgeForm.setLogoFilename(null);

         pledgeForm.setLogoImg(null);
         pledgeForm.setLogoContentType(null);
      } else if (file != null && file.getBytes().length > 0) {
         pledgeForm.setLogoFilename(file.getOriginalFilename());
         pledgeForm.setLogoImg(file.getBytes());
         pledgeForm.setLogoContentType(file.getContentType());
      }

      Service.save(pledgeForm);
      mav.addObject("pledgeForm", pledgeForm);//JSP Form's Command Name (pledgeForm); 
      mav.addObject("cmdName", "pledgeForm");
      mav.addObject("actionType", "Save");
      return mav;
}