Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
从Spring mvc控制器方法更新JPA实体以及使用隐藏输入字段存储ID时出现的问题_Jpa_Spring Mvc_Spring Data Jpa - Fatal编程技术网

从Spring mvc控制器方法更新JPA实体以及使用隐藏输入字段存储ID时出现的问题

从Spring mvc控制器方法更新JPA实体以及使用隐藏输入字段存储ID时出现的问题,jpa,spring-mvc,spring-data-jpa,Jpa,Spring Mvc,Spring Data Jpa,假设我有一个具有大量属性的实体/javabean 此外,我有一个html表单(jsp或thymeleaf),用于更新该实体 正如我的应用程序所示,我将继续更新实体: 我在表单中的隐藏html字段中设置实体的JPA ID 在Spring控制器中,我使用该隐藏ID从数据库中检索实体 在控制器方法中,我使用作为参数传递给控制器方法的SpringMVCModelAttribute的字段设置之前检索到的实体的每个字段 然后,我使用entityManager持久化/更新实体 以下是我的控制器方法的示例: @

假设我有一个具有大量属性的实体/javabean

此外,我有一个html表单(jsp或thymeleaf),用于更新该实体

正如我的应用程序所示,我将继续更新实体:

  • 我在表单中的隐藏html字段中设置实体的JPA ID
  • 在Spring控制器中,我使用该隐藏ID从数据库中检索实体
  • 在控制器方法中,我使用作为参数传递给控制器方法的SpringMVCModelAttribute的字段设置之前检索到的实体的每个字段
  • 然后,我使用entityManager持久化/更新实体
  • 以下是我的控制器方法的示例:

    @RequestMapping(value = "/family/advertisement/edit", method = RequestMethod.POST, produces = "text/html")
        public String editFamilyAdvertisement(@ModelAttribute @Validated(value = Validation.AdvertisementCreation.class) FamilyAdvertisement familyAdvertisement,
                BindingResult bindingResult, Model model) {
            FamilyAdvertisement advertisementForUpdate = advertisementService.findFamilyAdvertisement(familyAdvertisement.getId());
            if (bindingResult.hasErrors()) {
                populateModel(model, familyAdvertisement);
                return "family/advertisement/edit";
            }
            advertisementForUpdate.setNeeds(familyAdvertisement.getNeeds());
            advertisementForUpdate.setChildcareTypes(familyAdvertisement.getChildcareTypes());
            advertisementForUpdate.setDayToTimeSlots(familyAdvertisement.getDayToTimeSlots());
            ...
            advertisementService.editFamilyAdvertisement(advertisementForUpdate);
            return "redirect:/some/url";
        }
    
    @RequestMapping(value = "/family/advertisement/edit", method = RequestMethod.POST, produces = "text/html")
        public String editFamilyAdvertisement(@ModelAttribute @Validated(value = Validation.AdvertisementCreation.class) FamilyAdvertisementInfo familyAdvertisementInfo,
                BindingResult bindingResult, Model model) {
            Member member = memberService.retrieveCurrentMember();
            FamilyAdvertisement advertisementForCheck = advertisementService.findFamilyAdvertisement(familyAdvertisementInfo.getFamilyAdvertisement().getId());
            if (!member.getAdvertisements().contains(advertisementForCheck)) {
                throw new IllegalStateException("advertisement does not belong to member");
            }
            if (bindingResult.hasErrors()) {
                populateModel(model, familyAdvertisementInfo);
                return "family/advertisement/edit";
            }
            advertisementService.editFamilyAdvertisement(familyAdvertisementInfo.getFamilyAdvertisement());
            return "redirect:/family/advertisement/edit/" + familyAdvertisementInfo.getFamilyAdvertisement().getId();
        }
    
    我对目前的应用程序有两个问题:

    • 首先,聪明的黑客可以轻易地篡改ID并更新其他人的广告
    • 其次,我必须使用SpringMVCModel属性中的字段手动更新附加实体的每个字段:这既单调又难看
    谁能提出一个更好的模式或解决方案

    编辑1:我遵循了提供的建议

    以下是我修改的控制器方法:

    @RequestMapping(value = "/family/advertisement/edit", method = RequestMethod.POST, produces = "text/html")
        public String editFamilyAdvertisement(@ModelAttribute @Validated(value = Validation.AdvertisementCreation.class) FamilyAdvertisement familyAdvertisement,
                BindingResult bindingResult, Model model) {
            FamilyAdvertisement advertisementForUpdate = advertisementService.findFamilyAdvertisement(familyAdvertisement.getId());
            if (bindingResult.hasErrors()) {
                populateModel(model, familyAdvertisement);
                return "family/advertisement/edit";
            }
            advertisementForUpdate.setNeeds(familyAdvertisement.getNeeds());
            advertisementForUpdate.setChildcareTypes(familyAdvertisement.getChildcareTypes());
            advertisementForUpdate.setDayToTimeSlots(familyAdvertisement.getDayToTimeSlots());
            ...
            advertisementService.editFamilyAdvertisement(advertisementForUpdate);
            return "redirect:/some/url";
        }
    
    @RequestMapping(value = "/family/advertisement/edit", method = RequestMethod.POST, produces = "text/html")
        public String editFamilyAdvertisement(@ModelAttribute @Validated(value = Validation.AdvertisementCreation.class) FamilyAdvertisementInfo familyAdvertisementInfo,
                BindingResult bindingResult, Model model) {
            Member member = memberService.retrieveCurrentMember();
            FamilyAdvertisement advertisementForCheck = advertisementService.findFamilyAdvertisement(familyAdvertisementInfo.getFamilyAdvertisement().getId());
            if (!member.getAdvertisements().contains(advertisementForCheck)) {
                throw new IllegalStateException("advertisement does not belong to member");
            }
            if (bindingResult.hasErrors()) {
                populateModel(model, familyAdvertisementInfo);
                return "family/advertisement/edit";
            }
            advertisementService.editFamilyAdvertisement(familyAdvertisementInfo.getFamilyAdvertisement());
            return "redirect:/family/advertisement/edit/" + familyAdvertisementInfo.getFamilyAdvertisement().getId();
        }
    
    您可以看到,我必须从db中获取家族广告实体,以便检查它是否属于会话中的当前成员。然后,当我尝试按建议保存实体时,我得到一个StaleObjectStateException,如下所示:

    SEVERE: Servlet.service() for servlet [bignibou] in context with path [/bignibou] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaOptimisticLockingFailureException: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.FamilyAdvertisement#1]; nested exception is javax.persistence.OptimisticLockException: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.FamilyAdvertisement#1]] with root cause
    org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.FamilyAdvertisement#1]
        at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
        at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887)
        at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891)
        at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
        at com.sun.proxy.$Proxy120.merge(Unknown Source)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:241)
        at com.sun.proxy.$Proxy119.merge(Unknown Source)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:345)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:601)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocatio
    
    edit2:如果我没有从数据库中获取实体,问题是上面对
    contains
    的调用总是会求值为
    false
    ,因为它在内部使用
    equals
    方法,实体可能已经更改(毕竟这是该方法的目的)

    编辑3

    对于StaleObjectStateException,我仍然有同样的问题,因为我的控制器方法似乎执行了两次保存/事务

    @RequestMapping(value = "/family/advertisement/edit", method = RequestMethod.POST, produces = "text/html")
        public String editFamilyAdvertisement(@ModelAttribute @Validated(value = Validation.AdvertisementCreation.class) FamilyAdvertisementInfo familyAdvertisementInfo,
                BindingResult bindingResult, Model model) {
            Member member = memberService.retrieveCurrentMember();//ONE
            if (!advertisementService.advertisementBelongsToMember(familyAdvertisementInfo.getFamilyAdvertisement(), member)) {
                throw new IllegalStateException("advertisement does not belong to member");
            }
            if (bindingResult.hasErrors()) {
                populateModel(model, familyAdvertisementInfo);
                return "family/advertisement/edit";
            }
            familyAdvertisementInfo.getFamilyAdvertisement().setMember(member);
            advertisementService.editFamilyAdvertisement(familyAdvertisementInfo.getFamilyAdvertisement());//TWO
            return "redirect:/family/advertisement/edit/" + familyAdvertisementInfo.getFamilyAdvertisement().getId();
        }
    
    见例外情况:

    org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.bignibou.domain.FamilyAdvertisement#1]
        org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303)
        org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
        org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
        org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:903)
        org.hibernate.internal.SessionImpl.merge(SessionImpl.java:887)
        org.hibernate.internal.SessionImpl.merge(SessionImpl.java:891)
        org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:879)
        sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        java.lang.reflect.Method.invoke(Method.java:601)
        org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
        com.sun.proxy.$Proxy123.merge(Unknown Source)
        sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        java.lang.reflect.Method.invoke(Method.java:601)
        org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:241)
        com.sun.proxy.$Proxy122.merge(Unknown Source)
        org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:345)
        sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        java.lang.reflect.Method.invoke(Method.java:601)
        org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
        org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
        org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
        org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
        org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:91)
        org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
        org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
        com.sun.proxy.$Proxy129.save(Unknown Source)
        com.bignibou.service.AdvertisementServiceImpl_Roo_Service.ajc$interMethod$com_bignibou_service_AdvertisementServiceImpl_Roo_Service$com_bignibou_service_AdvertisementServiceImpl$updateFamilyAdvertisement(AdvertisementServiceImpl_Roo_Service.aj:58)
        com.bignibou.service.AdvertisementServiceImpl.updateFamilyAdvertisement(AdvertisementServiceImpl.java:1)
        com.bignibou.service.AdvertisementServiceImpl_Roo_Service.ajc$interMethodDispatch1$com_bignibou_service_AdvertisementServiceImpl_Roo_Service$com_bignibou_service_AdvertisementServiceImpl$updateFamilyAdvertisement(AdvertisementServiceImpl_Roo_Service.aj)
        com.bignibou.service.AdvertisementServiceImpl.editFamilyAdvertisement(AdvertisementServiceImpl.java:27)
        com.bignibou.controller.AdvertisementController.editFamilyAdvertisement(AdvertisementController.java:85)
    

    第一个问题:从数据库检索实体时,请指定id和用户。如果找不到具有id和用户的实体,则表示该用户不拥有该实体

    第二个问题:根据您的需求,有几种解决方案

    • 直接公开实体,而不是专用的表单对象
    • 将实体封装在表单中并使用委托方法(IDE可以生成它们)
    • 使用

    你好,威尔克。谢谢你的回复。关于第一个问题的观点很好!现在关于第二个问题,我已经直接公开了实体,但是来自控制器模型属性的对象是分离的,所以我仍然需要更新每个字段,正如我在文章中解释的那样。。。你明白我的意思吗?顺便说一句,Dozer看起来不错,但如果可能的话,我不想引入另一个依赖项。@balteo为什么不重新连接它呢?谢谢你最后的评论。我正在相应地编辑我的帖子。如果您的实体继承自抽象类,并且在抽象类中包含id字段,您可以创建一个方法来包装
    entityManager.merge
    ,该方法首先执行
    Object ref=this.entityManager.find(persistentClass,entity.getId());如果(ref!=null){BeanUtils.copyProperties(entity,ref);}
    您可以通过传入忽略的字段跳过对
    BeanUtils.copyProperties
    中id字段的复制。您是否重写了hashCode()/equals()方法?也许他们应该只使用id。不建议在hashcode/equals中只使用id…为什么不建议?在这种情况下,这似乎是有道理的。但是如果这是一个问题,那么您可以逐个比较ID,而不是调用contains()。请参阅我的编辑#3。
    advertisementBelongsToMember
    方法逐个进行比较……您还可以将id字段添加为路径变量,如so
    @RequestMapping(value=“/family/advision/{id}/edit”,method=RequestMethod.POST,products=“text/html”)公共字符串editFamilyAdvertisement(@PathVariable Integer id、@ModelAttribute@Validated(value=Validation.AdvertisementCreation.class)FamilyAdvertisementInfo FamilyAdvertisementInfo,BindingResult,Model Model)
    。从那里,您可以在控制器中设置实体的id,以保证它们不会攻击另一个id。有很多样板代码,但都可以工作。