JPA 在保存 OneToMany 关系时不清除 Set 迭代器
JPA not clearing a Set iterator when saving in OneToMany relationship
我有一个基本的 Spring 启动应用程序。使用 Spring 初始化程序、JPA、嵌入式 Tomcat、Thymeleaf 模板引擎,并将其打包为可执行 JAR 文件。
我有这个域名class:
Entity
@DiscriminatorValue("sebloc")
public class SeblocDevice extends Device {
/**
*
*/
private static final long serialVersionUID = 1L;
public SeblocDevice() {
super();
}
public SeblocDevice(String deviceKey, String devicePAC) {
super(deviceKey, devicePAC);
}
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<DeviceDriver> driverDevices = new HashSet<>();
public Set<DeviceDriver> getDriverDevices() {
return driverDevices;
}
public void setDriverDevices(Set<DeviceDriver> driverDevices) {
this.driverDevices = driverDevices;
}
public void clearDriverDevices() {
for (DeviceDriver deviceDriver : deviceDrivers) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
public void removeDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
}
...
}
和另一个域 object
@Entity
@Table(name = "t_device_driver")
public class DeviceDriver implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public DeviceDriver() {
}
public DeviceDriver (SeblocDevice device, Driver driver) {
this.device = device;
this.driver = driver;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "device_id")
private SeblocDevice device;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "driver_id")
private Driver driver;
public SeblocDevice getDevice() {
return device;
}
public void setDevice(SeblocDevice device) {
this.device = device;
}
public Driver getDriver() {
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
和这个 JUnit 测试,在上次测试中我排除了 1 driver 但我得到了 2(清除所有 driver 并添加 1)
@Test
public void testUpdateAuthorizedDriver() {
SeblocDevice seblocDevice = (SeblocDevice) deviceService.findById((long)1);
assertEquals (1,seblocDevice.getDriverDevices().size());
Driver authorizedDriver = (Driver) driverService.findById((long)2);
DeviceDriver dd = new DeviceDriver (seblocDevice, authorizedDriver);
DeviceDriver ddToRemove = seblocDevice.getDeviceDrivers().iterator().next();
seblocDevice.removeDriverDevice(ddToRemove);
seblocDevice.clearDriverDevices()
seblocDevice.getDriverDevices().clear();
seblocDevice.getDriverDevices().add(dd);
deviceService.save(seblocDevice);
assertEquals (1, seblocDevice.getDriverDevices().size());
assertEquals (1, Iterators.size(deviceService.findSeblocDeviceAll().iterator()));
SeblocDevice seblocDeviceRetrieved = deviceService.findSeblocDeviceAll().iterator().next();
assertEquals (1, seblocDeviceRetrieved.getDriverDevices().size());
}
我也试过在服务级别创建一个方法
public interface DeviceDriverRepository extends CrudRepository<DeviceDriver, Long> {
}
@Transactional
public SeblocDevice cleanDrivers (SeblocDevice seblocDevice) {
deviceDriverRepository.delete(seblocDevice.getDeviceDrivers());
seblocDevice.getDeviceDrivers().clear();
seblocDevice.setDeviceDrivers(null);
return seblocDeviceRepository.save (seblocDevice);
}
然后是deviceService.cleanDrivers(seblocDevice);
但是 driver 又出现了
crizzis 是对的,您必须将设备设置为 null。
保持双向关联一致的最佳方法是创建方便的方法,例如:
public void addDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(deviceDriver);
driverDevices.add(deviceDriver);
}
public void removeDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
如果你想全部清除
public void clearDriverDevices() {
for (DeviceDriver deviceDriver : deviceDrivers) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
}
为了让您的代码按预期工作,您需要在 SeblocDevice.driverDevices
属性的 @OneToMany
关系中添加 orphanRemoval=true
参数,如下所示:
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<DeviceDriver> driverDevices = new HashSet<>();
清楚地了解JPA映射。你需要记住,在一段关系中,总是有主人的一面。
例如,在 @OneToMany
与 @ManyToOne
关系中,@ManyToOne
是所有者,因为它引用了另一个实体。
基本上,实体管理器只关心所有者端的更改,即如果您调用 DeviceDriver.setDevice(null)
,将执行删除。但恰恰相反
(SeblocDevice.getDriverDevices().clear()
) 不正确。
为此,有orphanRemoval参数,这是不言自明的。分配此参数后,实体管理器现在将作为所有者控制集合的元素,SeblocDevice.getDriverDevices().clear()
将删除数据库中不在 SeblocDevice.getDriverDevices
集合中的 DeviceDrivers,即使 DeviceDriver.device
不为空。
我有一个基本的 Spring 启动应用程序。使用 Spring 初始化程序、JPA、嵌入式 Tomcat、Thymeleaf 模板引擎,并将其打包为可执行 JAR 文件。
我有这个域名class:
Entity
@DiscriminatorValue("sebloc")
public class SeblocDevice extends Device {
/**
*
*/
private static final long serialVersionUID = 1L;
public SeblocDevice() {
super();
}
public SeblocDevice(String deviceKey, String devicePAC) {
super(deviceKey, devicePAC);
}
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<DeviceDriver> driverDevices = new HashSet<>();
public Set<DeviceDriver> getDriverDevices() {
return driverDevices;
}
public void setDriverDevices(Set<DeviceDriver> driverDevices) {
this.driverDevices = driverDevices;
}
public void clearDriverDevices() {
for (DeviceDriver deviceDriver : deviceDrivers) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
public void removeDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
}
...
}
和另一个域 object
@Entity
@Table(name = "t_device_driver")
public class DeviceDriver implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public DeviceDriver() {
}
public DeviceDriver (SeblocDevice device, Driver driver) {
this.device = device;
this.driver = driver;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "device_id")
private SeblocDevice device;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "driver_id")
private Driver driver;
public SeblocDevice getDevice() {
return device;
}
public void setDevice(SeblocDevice device) {
this.device = device;
}
public Driver getDriver() {
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
和这个 JUnit 测试,在上次测试中我排除了 1 driver 但我得到了 2(清除所有 driver 并添加 1)
@Test
public void testUpdateAuthorizedDriver() {
SeblocDevice seblocDevice = (SeblocDevice) deviceService.findById((long)1);
assertEquals (1,seblocDevice.getDriverDevices().size());
Driver authorizedDriver = (Driver) driverService.findById((long)2);
DeviceDriver dd = new DeviceDriver (seblocDevice, authorizedDriver);
DeviceDriver ddToRemove = seblocDevice.getDeviceDrivers().iterator().next();
seblocDevice.removeDriverDevice(ddToRemove);
seblocDevice.clearDriverDevices()
seblocDevice.getDriverDevices().clear();
seblocDevice.getDriverDevices().add(dd);
deviceService.save(seblocDevice);
assertEquals (1, seblocDevice.getDriverDevices().size());
assertEquals (1, Iterators.size(deviceService.findSeblocDeviceAll().iterator()));
SeblocDevice seblocDeviceRetrieved = deviceService.findSeblocDeviceAll().iterator().next();
assertEquals (1, seblocDeviceRetrieved.getDriverDevices().size());
}
我也试过在服务级别创建一个方法
public interface DeviceDriverRepository extends CrudRepository<DeviceDriver, Long> {
}
@Transactional
public SeblocDevice cleanDrivers (SeblocDevice seblocDevice) {
deviceDriverRepository.delete(seblocDevice.getDeviceDrivers());
seblocDevice.getDeviceDrivers().clear();
seblocDevice.setDeviceDrivers(null);
return seblocDeviceRepository.save (seblocDevice);
}
然后是deviceService.cleanDrivers(seblocDevice);
但是 driver 又出现了
crizzis 是对的,您必须将设备设置为 null。
保持双向关联一致的最佳方法是创建方便的方法,例如:
public void addDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(deviceDriver);
driverDevices.add(deviceDriver);
}
public void removeDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
如果你想全部清除
public void clearDriverDevices() {
for (DeviceDriver deviceDriver : deviceDrivers) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
}
为了让您的代码按预期工作,您需要在 SeblocDevice.driverDevices
属性的 @OneToMany
关系中添加 orphanRemoval=true
参数,如下所示:
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<DeviceDriver> driverDevices = new HashSet<>();
清楚地了解JPA映射。你需要记住,在一段关系中,总是有主人的一面。
例如,在 @OneToMany
与 @ManyToOne
关系中,@ManyToOne
是所有者,因为它引用了另一个实体。
基本上,实体管理器只关心所有者端的更改,即如果您调用 DeviceDriver.setDevice(null)
,将执行删除。但恰恰相反
(SeblocDevice.getDriverDevices().clear()
) 不正确。
为此,有orphanRemoval参数,这是不言自明的。分配此参数后,实体管理器现在将作为所有者控制集合的元素,SeblocDevice.getDriverDevices().clear()
将删除数据库中不在 SeblocDevice.getDriverDevices
集合中的 DeviceDrivers,即使 DeviceDriver.device
不为空。