JPA and Hibernate Cascade DELETE OneToMany does not work - spring

I've been reading post after post and article after article trying to get cascade deletes to work with JPA/Hibernate in the latest Spring Boot version. I've read that You have to use Hibernate specific cascades and I've read that you don't. I've read that they just don't work but it seems to be a mixed bag. Everything I've tried doesn't work. The relationship is bi-directional.
Doesn't Work:
#Entity
public class Brand {
#OneToMany(mappedBy = "brand", orphanRemoval = true, fetch = FetchType.LAZY)
#Cascade({CascadeType.DELETE})
#JsonManagedReference("brand-tax-rate")
private List<TaxRate> taxRates;
}
Doesn't Work:
#Entity
public class Brand {
#OneToMany(mappedBy = "brand", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
#JsonManagedReference("brand-tax-rate")
private List<TaxRate> taxRates;
}
Does anything work other than deleting the TaxRates prior to deleting the Brand ?
My test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {Application.class, SpringSecurityConfig.class})
#ActiveProfiles("test")
#Transactional
public class CascadeTests {
#Autowired
private BrandService brandService;
#Autowired
private TaxRateLoaderService taxRateLoaderService;
#Autowired
private TaxRateService taxRateService;
#Autowired
private TaxRateRepository taxRateRepository;
#Autowired
private BrandRepository brandRepository;
#Test
public void testCascadeWorks() throws Exception {
taxRateLoaderService.loadData(null, 10);
// if I uncomment this then I'm good
// but shouldn't have to if cascade works
//taxRateService.deleteAll();
brandService.deleteAll();
List<TaxRate> rates = Lists.newArrayList(taxRateRepository.findAll());
List<Brand> brands = Lists.newArrayList(brandRepository.findAll());
Assert.assertEquals(rates.size(), 0);
Assert.assertEquals(brands.size(), 0);
}
}
Error for reference:
Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity
constraint violation: "FKC4BCIKI2WSPO6WVGPO3XLA2Y9: PUBLIC.TAX_RATE
FOREIGN KEY(BRAND_ID) REFERENCES PUBLIC.BRAND(ID) (1)"; SQL statement:
delete from brand where id=? [23503-192]
UPDATE: modified my brandService.deleteAll() method to do the following:
#Override
public void deleteAll() {
Iterable<Brand> iter = this.brandRepository.findAll();
iter.forEach(brand -> this.brandRepository.delete(brand) );
}
Still does not work.
UPDATE 2: It only appears to be a problem via tests. Cascade seems to work okay with the app running.

I think you want to take a look at #OnDelete annotation which generates a DDL-level cascade delete.
This will add an ON DELETE CASCADE to the FOREIGN KEY definition if you're using the automatic schema generation (e.g. hbm2ddl). However, using Flyway is almost always a better choice than hbm2ddl.
Your mapping becomes:
#OneToMany(mappedBy = "brand", orphanRemoval = true, fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonManagedReference("brand-tax-rate")
private List<TaxRate> taxRates;

Related

Spring JPA delete entity not working when iterating

I am trying to delete an entity using its remove method of its repository from another service class, but it is not getting deleted. Below code works when I hard code the Id:
long id = 1234;
Optional<Employee> employeeOptional = employeeRepository.findById(id);
Employee employee = employeeOptional.get();
employeeRepository.delete(employee);
Above code is working fine, but if I try with below code, deletion is not happening.
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employeeRepository.delete(employee);
}
}
I am trying the code from DepartmentServiceImpl class, but above is not working, but same when id is hardcoded it works.
Inside Department I have relationship like below,
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "deal")
private Set<Employee> employees= new HashSet<>();
And inside Employee I have like below,
#ManyToOne
#JoinColumn(name = "department_id", referencedColumnName = "department_id")
private Department department;
How can I fix this issue?
You are attempting to delete Employees, but your entities still have references to each other.
A better way to delete an employee is to use orphan removal and remove the employee from the collection which will trigger a delete.
Also mappedBy = "deal" should be the name of the attribute on the owning side of the relationship so this should be mappedBy = "department"
#OneToMany(
cascade = CascadeType.ALL,
mappedBy = "department",
orphanRemoval = true
)
private Set<Employee> employees= new HashSet<>();
add a method to Department to remove the Employee and keep the bidirectional relationship in sync by also removing Department from Employee
public void removeEmployee(Employee employee) {
employees.removeEmployee(employee);
employee.setDepartment(null);
}
you can then remove the 'Managers' from your Employees collection which should trigger the delete statements
List<Employee> managers = department.getEmployees.stream()
.filter(e -> e.getRole().equals("Manager"))
.collect(Collectors.toList());
for (Employee manager : managers) {
department.removeEmployee(manager);
}
Not tested but should work fine:
Try tweaking your code a little like this:
Set<Employee>employees= new HashSet<>();
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employees.add(employee);
}
}
department.setEmployees(employees);//I assume you have a setter
departmentRepository.save(department); //I hope you understand what departmentRepository means
Here you are reassigning the valid employees list.
You could follow another method, instead of deleting each entity separately, you could call a bulk-save using saveAll(...) method on the valid list.

"could not initialize proxy - no Session" For Multiple ManyToMany relationships in the parent

I have a Parent User Class that has multiple ManyToMany Relationships.
#Table(name = "user")
public class User {
..
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "user_address",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "address_id")}
)
#JsonIgnore
private final List<Address> addresses = new ArrayList<Address>();
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "reports",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "reports_id")}
)
#JsonIgnore
private final List<Reports> reports = new ArrayList<Reports>();
}
When I access the FIRST ManyToMany property, everything works fine. However, immediately after
accessing the first, when I try to access the SECOND ManyToMany Property I get the "could not initialize proxy - no Session" exception:
#Component
public class Combiner {
public void combineData() {
...
List<Address> addresses = user.getAddress(); // This works
List<Reports> reports = user.getReports(); // Get the error here
..
}
}
The Address and Reports classes have the inverse relationship as many ManyToMany back to the User Entity Above.
public class Address {
#ManyToMany(mappedBy = "addresses", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
public class Reports {
#ManyToMany(mappedBy = "reports", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
I tried searching SO for the same error where there are MULTIPLE relationships like mine and the first passes but second fails, but could'nt find a post (or google couldn't understand the search terms, if anyone knows a pre-existing one - please let me know).
Could someone assess what else Im missing?
I've tried these so far to no avail:
Added #Transactional to the parent Service class that calls Combiner above
Made the second failing relationship EAGER. (as i understand it you cant make BOTH EAGER since i get a multiple bags error probably because of Cartesian join)
AM Using SpringBoot (2.2.4) with Hibernate Core {5.4.10.Final}
Approach one:
Make #ManyToMany uni-directional. The exception clearly says it can not initialize the collection of role you have in User class.
As you asked in the comment section Why can't this use case be Bi Directional - You can make this bi-directional as well.
Approach two: make collection of role EAGER or use Hibernate.initialize() to initialize the collection.
Bonus: you can make both collection EAGER by using Set not List.

How to delete a #ManyToOne #NotNull reference?

Assume I have an Entity0
#Entity
public class Entity0 implements Serializable {
#Id
private Long id;
#ManyToOne
#NotNull
private Entity1 entity1;
public Entity0() {
}
public Entity0(Long id,
Entity1 entity1) {
this.id = id;
this.entity1 = entity1;
}
[getter and setter for id and entity1]
}
which references Entity1
#Entity
public class Entity1 implements Serializable {
#Id
private Long id;
#Basic
private String property0;
public Entity1() {
}
public Entity1(Long id,
String property0) {
this.id = id;
this.property0 = property0;
}
[getter and setter for id and property0]
}
The #NotNull annotation is useful to assert that Entity0.entity1 is set during persist and merge. However, it disallows to delete the reference to Entity1 (set it to null and merge the instance into the persistence context) which is necessary to delete instance of Entity1 which are referenced. I'm wondering whether there's any way to have a #ManyToOne #NotNull property in an entity at all. The following illustrates the problems which arise from the different approaches I took to make it possible to delete Entity1 instances from persistence:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("de.richtercloud_jpa-not-nulll-many-to-one-removal_jar_1.0-SNAPSHOTPU");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Entity1 entity1 = new Entity1(2l, "abc");
Entity0 entity0 = new Entity0(1l, entity1);
entityManager.getTransaction().begin();
entityManager.persist(entity1);
entityManager.persist(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entity1 = entityManager.merge(entity1);
entity0 = entityManager.merge(entity0);
//1: fails due to `ERROR 23503: DELETE on table 'ENTITY1' caused a violation of foreign key constraint 'FK5CQRG47R3H3KQG834IH36DUB' for key (2). The statement has been rolled back.`
//entityManager.remove(entity1);
//entityManager.flush();
//entityManager.merge(entity0);
//entityManager.flush();
//2:
entity0.setEntity1(null);
entityManager.remove(entity1);
entityManager.flush();
entityManager.merge(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManagerFactory.close();
The code contains so many flushs because I'm reproducing this for a JTA-environment where a flush can occur between the parts of a transaction.
I'm aware that I can drop the #NotNull annotation to work around the problem. My question is whether there's any solution to have both annotations and be able to delete. In case that's no possible, is it more common to set the reference temporarily to a bogus Entity1 instance or to give up on #NotNull?
Cascading might be a valid way, if not the way, however I've given up on it because I found it to be hiding issues and behaving in an unwanted fashion - not that it's not possible to master, it's just easier to handle for me personally.
If cascading is a way you could add the following to your entity1
#OneToMany(cascade=CascadeType.REMOVE)
private List<Entity0> entity0s;
This way the referenced entity0s will be deleted when entity1 is deleted.
Cascading can be dangerous and you should always think twice about it whether it is a good idea to always delete the referenced entities. Sometimes it is better to delete the references first and then remove the "parent" entity.
I'm not sure I see what the problem is, why do you think you have to set #NotNull on a #ManyToOne foreign key declaration? #NotNull is not a JPA annotation. If you want to make the database column set to not null you should use:
#ManyToOne
#JoinColumn(name = "entity1_id", nullable=false)
private Entity1 entity1;
So if you want to have nullable=false and delete them both then delete the child first then the parent.
tx.begin();
entity1 = em.find(Entity1.class, 1L);
entity0 = em.find(Entity0.class, 2L);
em.remove(entity0);
em.remove(entity1);
tx.commit();

Spring JPA nested Exception: detached entity passed to persist

I tried almost all what I could find here in SO, and another sites tutorials about creating an One to One Relationship with Hibernate.
So, I have two models, here are the last modifications, like for example the #MapsId annotation I also removed in previous test.
Usuario:
#Entity
#Table(name="usuarios")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Usuario {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="usuarios_id_seq")
#SequenceGenerator(name="usuarios_id_seq", sequenceName="usuarios_id_seq", allocationSize=1)
#Column(name="id")
private Long id;
#ManyToOne
#JoinTable(name="roles_usuarios", joinColumns={#JoinColumn(name="usuarios_id", referencedColumnName="id")}, inverseJoinColumns={#JoinColumn(name="roles_id", referencedColumnName="id")})
private Rol rol;
#OneToOne(cascade = CascadeType.ALL, mappedBy="usuario")
private Cliente cliente;
Cliente:
#Entity
#Table(name="clientes")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Cliente {
#Id
//#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="clientes_id_seq")
//#SequenceGenerator(name="clientes_id_seq", sequenceName="clientes_id_seq", allocationSize=1)
//#Column(name="id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="usuario_id", referencedColumnName="id")
#MapsId
private Usuario usuario;
Cliente Controller:
#PostMapping("/")
public ResponseEntity<Void> postCliente(#RequestBody Cliente cliente, UriComponentsBuilder ucBuilder) {
if( clienteService.isClienteExist(cliente) ){
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
clienteService.save(cliente);
HttpHeaders headers = new HttpHeaders();
headers.setLocation( ucBuilder.path("/{id}").buildAndExpand(cliente.getId()).toUri() );
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
Cliente Service:
#Override
public Cliente save(Cliente cliente) {
Cliente clt = new Cliente();
clt.setUsuario(cliente.getUsuario());
clt.setRazonSocial(cliente.getRazonSocial());
clt.setRfc(cliente.getRfc());
clt.setDireccion(cliente.getDireccion());
clt.setEmail(cliente.getEmail());
clt.setTelefono(cliente.getTelefono());
clt.setContacto(cliente.getContacto());
clt.setTelContacto(cliente.getTelContacto());
clt.setEmailContacto(cliente.getEmailContacto());
return clienteRepository.save(clt);
}
If you notice I also have a many to one relationship with a Rol table which works fine, but when I pass information in the OneToOne which I pass it as a JSON it produces: detached entity passed to persist: com.swargos.entities.Usuario
IDK if I'm missing some annotations, or is that the database is created when running the spring application.
I'm providing a somewhat qualified guess, since you didn't include code that shows how you call persist.
The error means that the Usuario instance you are passing to persist() already has a primary key, but it is not a managed entity of that persistence context, see here for Entity Object lifecycle
My guess is that the Usuario instance was loaded by another EntityManager, then json-serialized to the front-end, and then posted back to the backend, and you need to set it on a Cliente (Since you have cascade in both directions it may also be the Cliente being set on the Usuario). Every time an entity has been loaded in one Persistence Context, and you want to save it in another you must either call em.merge() or you must call em.find() to load it into it (and then set the changes).
JPA is not magic, the life-cycle of the Entities and the Persistence Context which manage them is well defined, and unless the developer understands how these mechanisms work, a lot of time will be wasted trying to work against the framework.
Also #MapsId should only be used if Cliente used an #EmbeddedId for it primary key, which does not seem to be the case.

Hibernate 2 records being inserted for Single JAVA Object

I am using Hibernate Implementation of JPA with Spring.
Class Country{
#OneToMany(mappedBy="Country", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
List<State> stateList;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "Current_State_ID")
State currnetState;
}
Class State{
#ManyToOne
#JoinColumn(name="Country_ID")
private Country country;
}
State stateObj = new State();
country.getStateList().add(stateObj);
country.setCurrnetState(stateObj);
countryRepository.saveAndFlush(country);
countryRepository is a JPA Repository Implemenntation.
This creates 2 entries for in State Table, which messes up my logic. Can someone please point me what I am doing worng.
I am not sure why but changing my code to following works for me.
List<State> stateList = new ArrayList<State>();
stateList.add(state);
country.setStateList(stateList);
Creating a New List instance and setting it to country.

Resources