执行此操作需要事务(使用事务或扩展持久性上下文)

Posted

技术标签:

【中文标题】执行此操作需要事务(使用事务或扩展持久性上下文)【英文标题】:Transaction is required to perform this operation (either use a transaction or extended persistence context) 【发布时间】:2016-06-18 21:47:42 【问题描述】:

我正在使用 Wildfly 10.0.0 Final、Java EE7、Maven 和 JPA 2.1。当我在数据库中查询记录时,它工作正常并列出了员工,但是当我尝试保留新员工时,它给了我以下异常:

javax.servlet.ServletException: WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context)
javax.faces.webapp.FacesServlet.service(FacesServlet.java:671)
io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
...

我正在尝试使用 JSF 和 CDI bean 来实现它。我有一个 JTA 数据源,已在我的 persistence.xml 文件中配置:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
    <persistence-unit name="MyPersistenceUnit">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>java:/EmployeesDS</jta-data-source>
        <class>com.home.entity.Employee</class>
        <properties>
            <property name="hibernate.archive.autodetection" value="class"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hbm2ddl.auto" value="update"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.mysqlDialect"/>
        </properties>
    </persistence-unit>
</persistence>

CDI bean 如下所示。比较简单,有一种方法可以列出 25 名员工,另一种方法应该持久化特定员工:

@Named
@RequestScoped
public class DataFetchBean 
    @PersistenceContext
    EntityManager em;

    public List getEmployees() 
        Query query = em.createNamedQuery("findEmployees");
        query.setMaxResults(25);
        return query.getResultList();
    

    public String getEmployeeNameById(final int id) 
        addEmployee();

        Query query = em.createNamedQuery("findEmployeeNameById");
        query.setParameter("empno", id);
        Employee employee = (Employee) query.getSingleResult();
        return employee.getFirstName() + " " + employee.getLastName();
    

    public void addEmployee() 
        em.persist(new Employee(500000, new Date(335077446), "Josh", "Carribean", 'm', new Date(335077446)));
    

员工实体类如下:

@NamedQueries(
        @NamedQuery(
                name = "findEmployees",
                query = "select e from Employee e"
        ),           
        @NamedQuery(
                name = "findEmployeeNameById",
                query = "select e from Employee e where e.empNo = :empno"
        )
)
@Table(name = "employees")
public class Employee 
    @Id
    @Column(name = "emp_no")
    private int empNo;
    @Basic
    @Column(name = "birth_date")
    private Date birthDate;
    @Basic
    @Column(name = "first_name")
    private String firstName;
    @Basic
    @Column(name = "last_name")
    private String lastName;
    @Basic
    @Column(name = "gender")
    private char gender;
    @Basic
    @Column(name = "hire_date")
    private Date hireDate;

    public Employee()  

    public int getEmpNo() 
        return empNo;
    

    public void setEmpNo(int empNo) 
        this.empNo = empNo;
    

    public Date getBirthDate() 
        return birthDate;
    

    public void setBirthDate(Date birthDate) 
        this.birthDate = birthDate;
    

    public String getFirstName() 
        return firstName;
    

    public void setFirstName(String firstName) 
        this.firstName = firstName;
    

    public String getLastName() 
        return lastName;
    

    public void setLastName(String lastName) 
        this.lastName = lastName;
    

    public char getGender() 
        return gender;
    

    public void setGender(char gender) 
        this.gender = gender;
    

    public Date getHireDate() 
        return hireDate;
    

    public void setHireDate(Date hireDate) 
        this.hireDate = hireDate;
    

    public Employee(int empNo, Date birthDate, String firstName, String lastName, char gender, Date hireDate) 
        this.empNo = empNo;
        this.birthDate = birthDate;
        this.firstName = firstName;
        this.lastName = lastName;
        this.gender = gender;
        this.hireDate = hireDate;
    

    @Override
    public boolean equals(Object o) 
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Employee employee = (Employee) o;

        if (empNo != employee.empNo) return false;
        if (gender != employee.gender) return false;
        if (birthDate != null ? !birthDate.equals(employee.birthDate) : employee.birthDate != null) return false;
        if (firstName != null ? !firstName.equals(employee.firstName) : employee.firstName != null) return false;
        if (lastName != null ? !lastName.equals(employee.lastName) : employee.lastName != null) return false;
        if (hireDate != null ? !hireDate.equals(employee.hireDate) : employee.hireDate != null) return false;

        return true;
    

    @Override
    public int hashCode() 
        int result = empNo;
        result = 31 * result + (birthDate != null ? birthDate.hashCode() : 0);
        result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
        result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
        result = 31 * result + (int) gender;
        result = 31 * result + (hireDate != null ? hireDate.hashCode() : 0);
        return result;
    

提前致谢!

【问题讨论】:

【参考方案1】:

基本上一个是存在容器管理的 JTA 感知持久性上下文和 bean 管理事务 (BMT)。

因此,除了您的EntityManager,您还应该将UserTransaction 注入到您的DataFetchBean 中,以便开始、提交或回滚事务。

@Named
@RequestScoped
public class DataFetchBean 
    @PersistenceContext
    EntityManager em;

    @Resource
    private UserTransaction userTransaction;

    ...

然后,在您的 addEmployee 方法中,您必须开始并提交您的事务,这样您对员工实体的更改才能传播到数据库。

public void addEmployee() throws Exception 
    Employee employee = new Employee(500000, new Date(335077446), "Josh", "Carribean", 'm', new Date(335077446));

    userTransaction.begin();
    em.persist(employee);
    userTransaction.commit();

尽管如此,您应该考虑将数据库操作迁移到 EJB 中,将其注入您的 JSF bean,因此将管理事务的责任委托给容器,即使用 CMT,而不是手动处理它们.

【讨论】:

这给了我“javax.resource.ResourceException: IJ000460: 错误检查事务”“javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: 无法获取 JDBC 连接” @StefanWendelmann,我建议您提出一个新问题,因为仅提供信息很难理解发生了什么。 我发现我们在应用程序中使用了错误的方法,我们使用 MangedBeans 和来自 FacesContext 的手动触发 EL,因此无法正确注入 UserTransaction。我必须想出一个更大的解决方案。 您需要使用BMT吗?否则,您可以将事务管理的责任委托给容器并使用 CMT。另一种选择,看看@JohnAment 的回答。 如果您使用注释@TransactionManagement(TransactionManagementType.BEAN) 将事务管理委托给bean 本身,这也适用于EJB(会话或消息驱动的bean)。【参考方案2】:

另一种处理方法是在DataFetchBean 的方法addEmployee 上使用注释@Transactional。那么你就不需要UserTransaction,可以使用AOP来管理事务了。

这是 JTA 1.2 中添加的新功能。

【讨论】:

【参考方案3】:

您可以查看以下有关处理事务的文档:Container-Managed Transactions JEE6

根据您的应用使用Transaction Attributes

【讨论】:

【参考方案4】:

在您的方法上添加@Transactional 注释,它将使其“事务性”

【讨论】:

【参考方案5】:

一个简单的方法,在方法中添加@Transactional(Transactional.TxType.REQUIRED)

【讨论】:

【参考方案6】:

我遇到了同样的问题,解决办法是

@Transactional(rollbackOn = Exception.class)

添加到 Bean 调用的方法

【讨论】:

这个答案实际上可能会有所帮助。在我的例子中,我在同一个事务中调用了一个服务,它引发了一个异常,我在我的代码中捕获了它。不幸的是,该异常导致了整个事务的回滚。因此,它是无效的,并且实体管理器抛出了问题中提到的错误。【参考方案7】:

与此示例不完全相关,但我遇到了另一个问题。我的项目已经在使用容器管理事务 (CMT) 并收到此错误。

我的问题是我忘记在我的 EJB 中包含 @Stateless 注释。添加该注解(或向类添加其他注解)使其实际上由容器管理并提供所需的事务。

【讨论】:

以上是关于执行此操作需要事务(使用事务或扩展持久性上下文)的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 事务

Java DAO 对象事务

托管对象上下文是事务日志?

undolog实现事务原子性,redolog实现事务的持久性

用EF操作数据库 对多张表操作是不是需要事务

事务管理