将新持久化的实体添加到关系的另一方持有的实体列表中,以维护双向关系
Adding a newly persisted entity to a list of entities held by the inverse side of a relationship so as to maintaining a bidirectional relationship
给定两个实体 Department
和 Employee
形成从 Department
到 Employee
的一对多关系。
由于关系非常直观,我省略了实体 类。
下面这段代码,简单的持久化了一个实体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);
}
以及以下方法 returns 与特定部门关联的员工列表。
public List<Employee> getList() {
return entityManager.find(Department.class, 1L).getEmployeeList();
}
这两种方法都是使用 CMT(在此不是 BMT)在无状态 EJB 中编写的,命名为 EmployeeService
.
客户端应用程序按顺序调用这些方法,
employeeService.insert();
List<Employee> employeeList = employeeService.getList();
for (Employee e : employeeList) {
System.out.println(e.getEmployeeId() + " : " + e.getEmployeeName());
}
上面 foreach
循环中的 sout
语句显示新添加的 Employee
实体到 Department
中的 List<Employee>
,带有 null
employeeId
其中 鉴于行 entityManager.flush();
不存在于第一个代码片段中 。
EntityManager#persist(Object entity)
不保证生成 id。 id 只保证在刷新时生成。
发生的情况是,如果 entityManager.flush();
是 removed/commented,则实体 Employee
被添加到 Employee
的列表中(List<Employee> employeeList
)其中有一个 null
标识符(基础数据库中的主键列 table)。
维护双向关系的常用方法是什么? EntityManager#flush();
每次将一个实体添加到由关系的反面维护的实体集合时,总是需要 EntityManager#flush();
以生成与新持久化实体关联的 ID 吗?
另外,是否总是需要手动从List<Employee>
中删除一个Employee
(由关系的反面维护- Department
) 在删除 Employee
实体时(使用 entityManager.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().
}
持久化Employee
时,需要设置both sides of the association。
在你的Department
中你应该有这个方法:
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setDepartment(this);
}
确保你 cascade de PERSIST and MERGE events 加入你的 children 协会:
@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);
答案在 JB Nizet 在问题下方的最后评论中:
The two calls to insert()
and getList()
are done in the same
transaction, which means that the flush()
hasn't happened yet when
calling getList()
我准备作为快速肮脏测试的测试用例存在巨大疏忽。
我选择了一个由 @Startup
标记的单例 EJB(仅使用一个 EJB 模块)作为应用程序的客户端以进行快速测试(@LocalBean
没有任何接口用于纯测试目的 - 自 EJB 以来支持3.1/Java EE 6) 以便在部署 EJB 模块后立即调用其 @PostConstruct
方法。
目标 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)
默认。
如果客户端在事务中,则由 TransactionAttributeType.REQUIRED
标记的 EJB 使用相同的事务。但是,如果客户端不启动事务,则标记为 TransactionAttributeType.REQUIRED
的目标 EJB 会创建一个新事务。
因此,所有事情都发生在单个事务中,因为客户端(单例 EJB)已经使用目标 EJB 必须使用的默认事务属性 TransactionAttributeType.REQUIRED
启动事务。
解决方案是要么阻止客户端启动事务,要么使目标 EJB 始终创建新事务,无论客户端是否启动事务使用 TransactionAttributeType.REQUIRES_NEW
。
如果客户端在事务中,标记为 TransactionAttributeType.REQUIRES_NEW
的 EJB 会暂停客户端启动的事务并创建自己的新事务。尽管如此,如果客户端没有启动事务,标记为 TransactionAttributeType.REQUIRES_NEW
的 EJB 也会创建一个新事务。
简而言之,标记为 TransactionAttributeType.REQUIRES_NEW
的 EJB 总是创建一个新事务,无论该事务是否已由其客户端启动。
可以使用 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
标记的方法总是没有数据库事务。
或者保持客户端不变(默认)并用 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
标记目标 EJB
@Startup
@Singleton
public class Entry {
@Inject
private EmployeeService employeeService;
@PostConstruct
private void main() {
//...
}
}
@Stateless
@LocalBean
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class EmployeeService {
//...
}
由 TransactionAttributeType.REQUIRES_NEW
标记的 EJB(或 EJB 中的方法)总是启动一个新事务,无论关联的客户端是否已经启动一个事务,如前所述。
这是我试图进行的快速肮脏测试。毕竟,这不是测试应用程序的好做法(例如,如果目标 EJB (EmployeeService
) 是有状态会话 bean,那么将它注入到单例 EJB 中在概念上是不合适的)。人们应该更喜欢 JUnit 测试用例或其他东西。
为了完整起见,我添加了这个答案。我不想接受这个答案,因为它遇到了不同的问题。
给定两个实体 Department
和 Employee
形成从 Department
到 Employee
的一对多关系。
由于关系非常直观,我省略了实体 类。
下面这段代码,简单的持久化了一个实体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);
}
以及以下方法 returns 与特定部门关联的员工列表。
public List<Employee> getList() {
return entityManager.find(Department.class, 1L).getEmployeeList();
}
这两种方法都是使用 CMT(在此不是 BMT)在无状态 EJB 中编写的,命名为 EmployeeService
.
客户端应用程序按顺序调用这些方法,
employeeService.insert();
List<Employee> employeeList = employeeService.getList();
for (Employee e : employeeList) {
System.out.println(e.getEmployeeId() + " : " + e.getEmployeeName());
}
上面 foreach
循环中的 sout
语句显示新添加的 Employee
实体到 Department
中的 List<Employee>
,带有 null
employeeId
其中 鉴于行 entityManager.flush();
不存在于第一个代码片段中 。
EntityManager#persist(Object entity)
不保证生成 id。 id 只保证在刷新时生成。
发生的情况是,如果 entityManager.flush();
是 removed/commented,则实体 Employee
被添加到 Employee
的列表中(List<Employee> employeeList
)其中有一个 null
标识符(基础数据库中的主键列 table)。
维护双向关系的常用方法是什么? EntityManager#flush();
每次将一个实体添加到由关系的反面维护的实体集合时,总是需要 EntityManager#flush();
以生成与新持久化实体关联的 ID 吗?
另外,是否总是需要手动从List<Employee>
中删除一个Employee
(由关系的反面维护- Department
) 在删除 Employee
实体时(使用 entityManager.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().
}
持久化Employee
时,需要设置both sides of the association。
在你的Department
中你应该有这个方法:
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setDepartment(this);
}
确保你 cascade de PERSIST and MERGE events 加入你的 children 协会:
@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);
答案在 JB Nizet 在问题下方的最后评论中:
The two calls to
insert()
andgetList()
are done in the same transaction, which means that theflush()
hasn't happened yet when callinggetList()
我准备作为快速肮脏测试的测试用例存在巨大疏忽。
我选择了一个由 @Startup
标记的单例 EJB(仅使用一个 EJB 模块)作为应用程序的客户端以进行快速测试(@LocalBean
没有任何接口用于纯测试目的 - 自 EJB 以来支持3.1/Java EE 6) 以便在部署 EJB 模块后立即调用其 @PostConstruct
方法。
目标 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)
默认。
如果客户端在事务中,则由 TransactionAttributeType.REQUIRED
标记的 EJB 使用相同的事务。但是,如果客户端不启动事务,则标记为 TransactionAttributeType.REQUIRED
的目标 EJB 会创建一个新事务。
因此,所有事情都发生在单个事务中,因为客户端(单例 EJB)已经使用目标 EJB 必须使用的默认事务属性 TransactionAttributeType.REQUIRED
启动事务。
解决方案是要么阻止客户端启动事务,要么使目标 EJB 始终创建新事务,无论客户端是否启动事务使用 TransactionAttributeType.REQUIRES_NEW
。
如果客户端在事务中,标记为 TransactionAttributeType.REQUIRES_NEW
的 EJB 会暂停客户端启动的事务并创建自己的新事务。尽管如此,如果客户端没有启动事务,标记为 TransactionAttributeType.REQUIRES_NEW
的 EJB 也会创建一个新事务。
简而言之,标记为 TransactionAttributeType.REQUIRES_NEW
的 EJB 总是创建一个新事务,无论该事务是否已由其客户端启动。
可以使用 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
标记的方法总是没有数据库事务。
或者保持客户端不变(默认)并用 @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 {
//...
}
由 TransactionAttributeType.REQUIRES_NEW
标记的 EJB(或 EJB 中的方法)总是启动一个新事务,无论关联的客户端是否已经启动一个事务,如前所述。
这是我试图进行的快速肮脏测试。毕竟,这不是测试应用程序的好做法(例如,如果目标 EJB (EmployeeService
) 是有状态会话 bean,那么将它注入到单例 EJB 中在概念上是不合适的)。人们应该更喜欢 JUnit 测试用例或其他东西。
为了完整起见,我添加了这个答案。我不想接受这个答案,因为它遇到了不同的问题。