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

时间:2013-12-16 10:02:34

标签: spring hibernate spring-mvc jpa transactions

除了原帖(2013年12月16日)

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

注入控制器和服务impl的实体管理器仅用于调试目的,并澄清了我的stackoverflow问题。

我遵循了Andrei I的建议,并添加了一种服务方法:

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

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

我重新测试了行为并且仍然是相同的:实体保持连接在服务方法之外,并且对实体的更改将持久保存到数据库。只有当我离开调用服务方法的控制器方法时,实体才会分离。

所以仍然很感激帮助理解这种行为!

原帖

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

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

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步的值(根据epxectation,因为实体状态现已分离)

所以以下情况似乎如此:

  • 在离开服务层方法后,这是事务性的(我可以在日志中看到这会导致提交发生),实体仍然是托管的,这是意料之外的。
  • 离开调用服务层方法的控制器方法后,实体最终变为分离。

一个假设是它是由自动提交引起的。默认情况下,自动提交已打开。我用以下方式将其关闭:

<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 个答案:

答案 0 :(得分:0)

您是否检查该实体是否仍在管理中。你不应该在控制器中注入EM。如果您想这样做,请向您的服务提出另一个请求并在那里进行检查(类似service.isManaged(mySavedEntity))。

答案 1 :(得分:0)

Spring使用EntityManager的代理,在使用时,为当前线程注册EntityManager。 Springs事务管理查看是否已存在绑定EntityManager的线程,由于控制器内部注入,因此在您的情况下就是这种情况。

现在因为已经有一个线程绑定EntityManager,所以没有为事务创建一个新的(正如您可能预期的那样)但是现有的一个被重用。现在因为现有的一个被重用,所以事务不会关闭/销毁EntityManager但是它会离开它,它只会提交事务。

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