I have a basic SpringBoot app. using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
I have this domain 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);
}
}
...
}
and this other domain 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;
}
}
and this JUnit test, where in the last test I was excepting 1 driver but I got 2 (clear all the drivers, and add 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());
}
I also tried to create a method in the service level
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);
}
and then deviceService.cleanDrivers(seblocDevice);
but the drivers appears again
crizzis is right you have to set device to null.
The best way to keep a bidirectional association consistent is to create convenience methods like:
public void addDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(deviceDriver);
driverDevices.add(deviceDriver);
}
public void removeDriverDevice(DeviceDriver deviceDriver) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
And if you want to clear all
public void clearDriverDevices() {
for (DeviceDriver deviceDriver : deviceDrivers) {
deviceDriver.setDriver(null);
driverDevices.remove(deviceDriver);
}
}
For your code to work as you expect, you need to add the orphanRemoval=true
parameter in @OneToMany
relationship in SeblocDevice.driverDevices
attribute as shown below:
@OneToMany(mappedBy = "device", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<DeviceDriver> driverDevices = new HashSet<>();
To clearly understand the JPA mapping. You need to keep in mind that in a relationship, there is always the owner side. For example, in an @OneToMany
vs. @ManyToOne
relationship, @ManyToOne
is the owner because it has the reference to the other entity.
Basically the Entity Manager only cares for changes on the owner side, ie if you invoke DeviceDriver.setDevice(null)
, the removal will be performed. But the opposite ( SeblocDevice.getDriverDevices().clear()
) is not true.
For this reason, there is the orphanRemoval parameter, which is self explanatory. When this parameter is assigned, the Entity Manager will now control the elements of the collection as an owner, and a SeblocDevice.getDriverDevices().clear()
will remove the DeviceDrivers in database that are not in the SeblocDevice.getDriverDevices
collection, even though DeviceDriver.device
is not null.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.