当前位置: 首页 > 知识库问答 >
问题:

JPA2实体在完成事务服务层方法后意外地保持管理状态

尚河
2023-03-14

到目前为止,感谢您的评论!

在控制器和服务impl中注入的实体管理器只是为了调试目的和澄清我的stackoverflow问题而添加的。

我按照Andrei I的建议,添加了一种服务方式:

@Transactional(readOnly=true)
public boolean isManaged(MyEntity myEntity) {
    return em.contains(myEntity);
}

在控制器中,我使用这个新的isManaged()方法,而不是注入EntityManager。

我重新测试了行为,这仍然是一样的:实体仍然附加在服务方法之外,对实体的更改将持久化到数据库。只有当我离开调用服务方法的控制器方法时,实体才会脱离。

因此,帮助理解这种行为仍然是值得赞赏的!

我有一个非常标准的SpringMVCWeb应用程序,使用JSP视图技术、控制器、服务层方法、实体、SpringCrudepository和JPA2/hibernate作为ORM。服务层方法使用@Transactional注释在事务中运行每个方法。我没有显式配置PersistenceContextType,这意味着我使用默认的PersistenceContextType,即事务。

我的问题是关于实体的托管状态。我的理解是,实体在事务提交后会分离,但事实似乎并非如此!我观察到的行为如下(为了清楚起见,省略了一些代码):

public class DiningTableController {
    @RequestMapping(value = "/diningTables/{diningTableId}/menuItems", method = RequestMethod.POST)
    public String addMenuItem() {
        1. invoke a service layer method which creates an entity object and invokes save() on it. The service method returns the saved entity
        2. check whether the saved entity is managed. Result: true
        3. change the value of a field of the entity (do not invoke save())
        4. invoke an arbitrary service method in which the entity is not referred to in any way
        5. manually verify in the database that the entity field value is indeed updated as a result of step 3.
        6. check whether the saved entity is managed. Result: true
        7. change the value of a field of the entity to a different value (do not invoke save())
        8. manually verify in the database that the entity field value is NOT updated as a result of step 3.
    }

离开控制器方法后,测试实体是否受管理返回false。如果在调用addMenuItem()方法之后,我调用了另一个同样调用任意服务方法的控制器方法,那么我仍然看不到步骤7的值。在数据库中(根据eXectation,实体状态现在已分离)。

因此,以下情况似乎是这样的:

  • 离开一个服务层方法后,它是事务性的(我可以在日志中看到这会导致提交),实体仍然被管理,这是意想不到的。
  • 在离开调用服务层方法的控制器方法后,实体最终变得分离。

一个假设是,它是由自动提交引起的。默认情况下,“自动提交”处于启用状态。我使用以下方法将其关闭:

<prop key="hibernate.connection.autocommit">false</prop>

这会给出警告"WARN:org.hibernate.ejb.Ejb3Configuration-HHH000144:hibernate.connection.autocommit=false破坏了EJB3规范"。日志在其行为中显示自动提交确实被关闭。这不会改变行为。所以我的结论是观察到的行为不是由自动提交引起的。

下面我展示了实际的代码。

@RequestMapping("/")
@Controller
public class DiningTableController {

    private MyEntity savedMyEntity;

    @PersistenceContext
    private EntityManager em;       

    @Autowired
    private MyEntityService myEntityService;
@Autowired
private DiningTableService diningTableService;

    @RequestMapping(value = "/diningTables/{diningTableId}/menuItems", method = RequestMethod.POST)
    public String addMenuItem(
            @PathVariable("diningTableId") String diningTableId,
            @RequestParam String menuItemName, Model uiModel) {

        logger.info("*************BeforeServiceCall (attached=" + em.contains(savedMyEntity) + ")");
        savedMyEntity = myEntityService.doMyEntity();
        savedMyEntity.setSomeValue("ValueChangedOutsideTransaction");
        logger.info("*************ValueChangedOutsideTransaction (attached=" + em.contains(savedMyEntity) + ")");

        DiningTable diningTable = diningTableService.fetchWarmedUp(Long.valueOf(diningTableId));
        uiModel.addAttribute("diningTable", diningTable);

        savedMyEntity.setSomeValue("ValueChangedOutsideTransactionButAfterOtherTransaction");
        logger.info("*************ValueChangedOutsideTransactionButAfterOtherTransaction (attached=" + em.contains(savedMyEntity) + ")");

        return "redirect:/diningTables/" + diningTableId;
    }
}



@Service("diningTableService")
@Repository
@Transactional(rollbackFor = StateException.class)
public class DiningTableServiceImpl implements DiningTableService {
    final Logger logger = LoggerFactory.getLogger(DiningTableServiceImpl.class);

    @Autowired
    private MyEntityRepository myEntityRepository;

    public MyEntity doMyEntity() {
        MyEntity myEntity = new MyEntity(5L, "ValueInitially");
        MyEntity savedMyEntity = myEntityRepository.save(myEntity);
        logger.info("*************ValueInitially");
        savedMyEntity.setSomeValue("ValueChangedInsideTransaction");
        logger.info("*************ValueChangedInsideTransaction");
        return savedMyEntity;
    }

}




public interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, Long> {

}



@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@id") 
@Getter @Setter
public class MyEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    private Long id;

    private String someValue;

    public MyEntity() {
    }

    public MyEntity(Long id, String someValue) {
        this.id = id;
        this.someValue = someValue;
    }

}

共有2个答案

顾乐池
2023-03-14

Spring为EntityManager使用代理,该代理在使用时为当前线程注册EntityManager。Springs事务管理查看是否已经有一个线程绑定的EntityManager,在您的情况下是由于控制器内部的注入。

现在,由于已经存在线程绑定的alreadyaEntityManager,因此不会为事务创建一个新的线程绑定(正如您所期望的那样),但会重用现有的线程绑定。现在,由于重用了现有事务,因此事务不会关闭/销毁EntityManager,而是将其保留,它只会提交事务。

要修复它并获得真正的事务范围的持久性上下文行为,不要将EntityManager注入到控制器中,而是在服务上创建一个方法来检查对象是否被管理。正如@Andreii在他的回答/评论中建议的那样。这将为每个事务创建一个新的EntityManager,并且不会重用已经绑定线程的EntityManager

丁晋
2023-03-14

您检查实体是否仍处于管理状态时出错。你不应该把它们注射到你的控制器里。如果您想这样做,请向您的服务发出另一个请求并将其签入(类似于service.isManaged(mySavedEntity))。

 类似资料:
  • 我有以下问题: 我正在使用Spring MVC 4.0.5和Hibernate 4.3.5,我正在尝试创建一个Restfull Web应用程序。问题是,我想排除某些字段,使其无法序列化为 JSON,具体取决于使用方面在控制器中调用的方法。 我现在的问题是Hibrate不会在事务从方法返回后立即提交事务,而只是在序列化之前提交。 Controller.java Service.java 方面的建议执

  • 本文向大家介绍django session完成状态保持的方法,包括了django session完成状态保持的方法的使用技巧和注意事项,需要的朋友参考一下 本例使用登录页面演示,session的状态保持功能。 说明:因为http是无状态的,客户端请求一次页面后,就结束了,当再次访问时,服务器端并不知道浏览器此访问过什么。所以这样就需要状态保持功能,状态保存有两种方式:session和cookie都

  • 在我的实际应用程序中,我有一个业务层,它根据一些业务规则使用JPA来持久化数据,问题是camel JPA事务没有与业务层事务共享。我需要业务类中的EntityManager与Camel事务范围集成,我该怎么做? 下面是一个简单的例子,但这反映了实际设计中的问题。 项目实例 服务级别 骆驼路线 骆驼背景。xml

  • 一些背景知识,我在我的web应用程序中使用JPA/Hibernate/Spring,我也使用org。springframework。orm。jpa。支持OpenEntityManagerViewFilter和用于处理实体的扩展持久性上下文。 问题: > 使用find方法从数据库加载实体,实体存储在会话中 用户更改实体并点击保存 用户更改反映在会话中存储的实体上(在控制器中) 实体被发送到服务类中的

  • 服务方法管理器 在上面介绍上下文对象时,我们提到在服务端上下文对象(ServiceContext)包含有一个服务方法(method)属性。该属性对应服务端发布的服务方法,该属性的类型在不同语言中的定义会有所不同,它其中包含了关于服务方法的一些必要信息,比如发布名称(区别于方法定义的名称),方法本体(可以反射调用或直接调用的方法对象),方法所属对象,参数类型,是否是通用方法,参数中是否包含有上下文对

  • 我正在使用Spring的事务支持和JPA(Hibernate)来持久化我的实体。一切正常,但我在处理一个请求中的部分更新时陷入困境: 对于每个用户(HTTP)请求,我必须将一个日志条目写入数据库表,即使“主”业务实体的更新失败(例如,由于验证错误)。因此,我的第一个/主要事务get被回滚,但第二个(写日志)应该提交。这似乎可以使用正确的传播级别来写入日志条目: 然而,我的问题是,我在第二个事务中注