【问题标题】:OneToMany Create Fails with InvalidDataAccessApiUsageExceptionOneToMany 创建失败并出现 InvalidDataAccessApiUsageException
【发布时间】:2013-12-06 16:19:29
【问题描述】:

我对 Hibernate 还很陌生,一直在使用手册和在线论坛,但我对这个问题感到困惑。我正在使用带有 Hibernate 4 和注释的 Spring 3.2。我有一个一对多的父 (PledgeForm) 和子 (PledgeFormGiftLevel) 表。

领域/型号: 家长

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

儿童

@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
@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("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);
} 

使用上面的代码,可以很好地选择和更新父/子记录。当我显示来自休眠的“跟踪”消息时,更新确实有这个关于孩子的跟踪消息:

[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]

如果有子记录,create总是会报这个错误:

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 

当我查看休眠日志时,我看到它根据瞬态对象更新父级和子级。然后它会尝试刷新并找到孩子的持久副本,因此它会回滚所有内容。

[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 

Hibernate 文档显示这比我的代码更简单,但我必须添加 fetch 和 cascade 值。我已经尝试过更改 fetch & cascade 值 & 放置(从 Hibernate 文档开始,然后添加),但我尝试的其他所有操作仍然会导致创建失败并且通常也会导致更新失败。

我发现的许多论坛帖子都显示了 flush() 或 evict()。我不确定我正在使用的是 Hibernate 4 还是注释(我认为是 @Transactional),但我在我的代码中没有看到它的位置。从 Hibernate 跟踪日志中,我可以看到在 saveOrUpdate() 方法中自动进行了刷新。

我还尝试删除表格和序列并重新开始。

感谢任何关于让创建工作的建议。如果您能指出我错过的特定文档,我也很感激。

谢谢, 邦妮

【问题讨论】:

  • 只是一个简短的说明。 Java 变量名是小写的。所以org.unctv.service.PledgeFormManager Service; 应该是org.unctv.service.PledgeFormManager service; 如果有一个实际的Service 类具有可能导致问题的静态方法。我怀疑这就是您使用完全限定名称的原因。还有其他Service 类吗?
  • PledgeForm 是否从 UI 中返回并设置了 PledgeFormGiftLevel
  • @KevinBowersox,感谢您的提示。我在学习 Spring 时使用了这个教程——javabeginnerstutorial.com/spring-framework-tutorial/…——我从中获得了资本服务。实际上,我认为将其大写是一种特殊的 Spring 事物。我可以做出这样的改变。我的项目中没有名为 Service 的静态类。
  • @KevinBowersox 更新时,PledgeForm 会返回 PledgeFormGift 级别集(并正确更新)。在创建时,由于回滚,两者都不会保存到数据库中。休眠日志确实显示 PledgeForm 被保存,然后它找到 PlegdeFormGiftLevel 并保存它。保存 PledgeForm 后,我看到了这条跟踪消息,然后它继续进行保存: Wrapped collection in role:org.unctv.domainmodel.PledgeForm.pledgeFormGiftLevels 这就是你要问的吗?
  • 我担心的是PledgeFormList&lt;PledgeFormGiftLevel&gt; 中的PledgeFormGiftLevels 的PledgeForm 字段设置为PledgeForm。在双向关系中,您负责管理实体的双方。因此,如果您创建一个PledgeFormGiftLevel 并将其添加到PledgeForm 的列表中,您需要确保PledgeFormGiftLevel 的每个实例都有其PledgeForm 属性集。

标签: spring hibernate annotations transient hibernate-onetomany


【解决方案1】:

我注意到 equalshashcode 在实体中没有被覆盖。这些方法用于比较对象以确定它们的相等性。如果不覆盖这些方法,Hibernate 可能无法确定实体的现有实例是否存在。尝试为hashcodeequals 提供实现。

如果您使用 Eclipse,请按 CTRL + SHIFT + SH 以调出用于创建 hashcodeequals 方法的对话框。选择包含相对不变的值的字段,然后生成方法。

还要确保您正在管理实体的双方,如上述 cmets 中所述:

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

【讨论】:

  • 您上面列出的“管理实体的双方”代码使其工作。太感谢了。我觉得我需要在父数组中显式设置子值,但我不知道从哪里获取它们。我从来没有想过要把它们从父母身上拉出来。我仍在努力找出哈希码和等号,但我会继续并接受您的回答。我非常感谢您提供的非常明确的帮助以及其他建议。
  • @BonnieSmyre 太棒了!我相信 hashcode 和 equals 方法会在执行更新实体时发挥作用。您当前的案例是插入物吗?
  • 实际上,更新已经在工作并且仍然有效。是插入给我带来了问题。
猜你喜欢
  • 2014-08-29
  • 1970-01-01
  • 2015-06-01
  • 1970-01-01
  • 2023-01-03
  • 1970-01-01
  • 1970-01-01
  • 2020-05-07
  • 2021-11-11
相关资源
最近更新 更多