2

给定两个实体Department并形成从到Employee的一对多关系。DepartmentEmployee

由于关系非常直观,因此我省略了实体类。

下面的代码段,只是持久化了一个实体Employee

public void insert() {
    Employee employee = new Employee();
    employee.setEmployeeName("k");

    Department department = entityManager.find(Department.class, 1L);
    employee.setDepartment(department);
    entityManager.persist(employee);
    entityManager.flush();

    List<Employee> employeeList = department.getEmployeeList();
    employeeList.add(employee);
}

以下方法返回与特定部门关联的员工列表。

public List<Employee> getList() {
    return entityManager.find(Department.class, 1L).getEmployeeList();
}

这两种方法都是使用名为 let's say 的 CMT(此处不是 BMT)在无状态 EJB 中编写的 EmployeeService

客户端应用程序按顺序调用这些方法,如下所示,

employeeService.insert();
List<Employee> employeeList = employeeService.getList();

for (Employee e : employeeList) {
    System.out.println(e.getEmployeeId() + " : " + e.getEmployeeName());
}

上面循环中的sout语句显示了一个新添加的实体,其中包含一个,因为该行不存在于第一个代码片段中。foreachEmployeeList<Employee>Departmentnull employeeIdentityManager.flush();


EntityManager#persist(Object entity)不保证生成id。id 只保证在刷新时生成。

发生的情况是,如果entityManager.flush();被删除/注释,则实体Employee将添加到Employees ( List<Employee> employeeList) 列表中,其中包含null标识符(基础数据库表中的主键列)。

维持双向关系的常用方法是什么?EntityManager#flush();每次将实体添加到由关系的反面维护的实体集合以生成与新持久化实体相关联的 id 时,是否总是需要?

此外,在删除实体(使用)时是否总是需要手动删除Employeefrom List<Employee>(由关系的反面维护 - )?DepartmentEmployeeentityManager.remove(employee);


编辑:实体类:

部门 :

@Entity
@Table(catalog = "testdb", schema = "", uniqueConstraints = {
@UniqueConstraint(columnNames = {"department_id"})})
public class Department implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "department_id", nullable = false)
    private Long departmentId;

    @Column(name = "department_name", length = 255)
    private String departmentName;

    @Column(length = 255)
    private String location;
    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
    private List<Employee> employeeList = new ArrayList<Employee>(0);

    private static final long serialVersionUID = 1L;
    // Constructors + getters + setters + hashcode() + equals() + toString().
}

员工 :

@Entity
@Table(catalog = "testdb", schema = "", uniqueConstraints = {
    @UniqueConstraint(columnNames = {"employee_id"})})
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "employee_id", nullable = false)
    private Long employeeId;

    @Column(name = "employee_name", length = 255)
    private String employeeName;
    @JoinColumn(name = "department_id", referencedColumnName = "department_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    private static final long serialVersionUID = 1L;

    // Constructors + getters + setters + hashcode() + equals() + toString().
}
4

2 回答 2

1

持久化的时候Employee,需要设置双方的关联

在你的Department你应该有这个方法:

public void addEmployee(Employee employee) {
    employees.add(employee);
    employee.setDepartment(this);
}

确保将de PERSIST 和 MERGE 事件级联到您的孩子协会:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true)
private List<Employee> children = new ArrayList<>();

并且持久化逻辑变为:

Employee employee = new Employee();
employee.setEmployeeName("k");

Department department = entityManager.find(Department.class, 1L);
department.addEmployee(employee);
于 2015-06-04T06:28:38.487 回答
0

答案在问题下方 JB Nizet 的最后一条评论中:

insert()对和的两次调用getList()是在同一个事务中完成的,这意味着flush()调用时还没有发生getList()

我准备的测试用例有一个巨大的疏忽,作为一个快速肮脏的测试。


我选择了一个单例 EJB(仅使用一个 EJB 模块)@Startup作为应用程序的客户端进行快速测试(@LocalBean没有任何接口用于纯测试目​​的 - 自 EJB 3.1/Java EE 6 起支持),以便@PostConstruct可以尽快调用其方法随着 EJB 模块的部署。

目标 EJB。

@Stateless
@LocalBean
public class EmployeeService {

    public void insert() {
        // Business logic is excluded for brevity.
    }

    public List<Employee> getList() {
        // Business logic is excluded for brevity.
    }    
}

这些方法由@Startup(客户端)标记的单例 EJB 调用。

@Startup
@Singleton
public class Entry {

    @Inject
    private EmployeeService employeeService;

    @PostConstruct
    private void main() {
        employeeService.insert();
        List<Employee> employeeList = employeeService.getList();

        for (Employee e : employeeList) {
            System.out.println(e.getEmployeeId() + " : " + e.getEmployeeName());
        }
    }
}

因此,客户端(单例 EJB)已经处于目标 EJB ( EmployeeService) 必须使用的事务(使用容器管理事务 (CMT))中,因为两个 EJB 都使用,

@TransactionAttribute(TransactionAttributeType.REQUIRED)
@TransactionManagement(TransactionManagementType.CONTAINER)

默认。

如果客户端在事务中,则标记为 的 EJBTransactionAttributeType.REQUIRED使用相同的事务。但是,如果客户端没有启动事务,则由 标记的目标 EJBTransactionAttributeType.REQUIRED创建一个新事务。

因此,一切都发生在单个事务中,因为客户端(单例 EJB)已经TransactionAttributeType.REQUIRED使用目标 EJB 必须使用的默认事务属性启动了一个事务。


解决方案是阻止客户端启动事务或使目标 EJB 始终创建新事务,无论客户端是否启动事务TransactionAttributeType.REQUIRES_NEW

如果客户端处于事务中,则标记为 的 EJB 会TransactionAttributeType.REQUIRES_NEW挂起客户端启动的事务并创建自己的新事务。但是,如果客户端没有启动事务,则标记为的 EJBTransactionAttributeType.REQUIRES_NEW也会创建一个新事务。

简而言之,标记为 的 EJBTransactionAttributeType.REQUIRES_NEW总是会创建一个新事务,无论该事务是否已由其客户端启动。


可以使用 Bean Managed Transaction (BMT) 阻止客户端启动事务 - 通过 @TransactionManagement(TransactionManagementType.BEAN)诸如标记客户端(单例 EJB)。

@Startup
@Singleton
@TransactionManagement(TransactionManagementType.BEAN)
public class Entry {
    // ...
}

这需要使用接口显式启动和提交事务javax.transaction.UserTransaction(可以通过@javax.annotation.Resource注释注入)。否则,所有方法都将没有活动的数据库事务。

或者使用@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)之类的。

@Startup
@Singleton
public class Entry {
    @PostConstruct
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    private void main() {
        //...
    }
}

标记为的方法TransactionAttributeType.NOT_SUPPORTED总是没有数据库事务。


或者保持客户端不变(默认)并标记目标 EJB@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

@Startup
@Singleton
public class Entry {

    @Inject
    private EmployeeService employeeService;

    @PostConstruct
    private void main() {
        //...    
    }
}

@Stateless
@LocalBean
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class EmployeeService {
    //...
}

标记为 的 EJB(或 EJB 中的方法)TransactionAttributeType.REQUIRES_NEW总是启动一个新事务,无论关联的客户端是否已经如前所述启动了一个事务。


这是我试图做的一个快速肮脏的测试。毕竟,这不是测试应用程序的好习惯(EmployeeService例如,如果目标 EJB ( ) 是一个有状态会话 bean,那么将它注入到单例 EJB 中在概念上是不合适的)。人们应该更喜欢 JUnit 测试用例或其他东西。

为了完整起见,我添加了这个答案。我不想接受这个答案,因为它遇到了不同的问题。

于 2015-06-05T06:30:12.330 回答