Hibernate - Can't delete child if parent has cascade set - spring

I try to delete an entity which is a child of another entity (one-to-many).
The problem is: If the parent has set a cascade type, I am not able to delete a child directly. The delete command is ignored (using JpaRepository). Only if I remove the cascade setting I am able to delete child.
Is there a way to do this without a native SQL statement?
Parent Entity:
#Entity
public class ExplorerItem {
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "explorerItem")
private Set<UserACL> userAcls = new HashSet<>();
...
}
Child Entity:
#Entity
public class UserACL {
...
#ManyToOne
private ExplorerItem explorerItem;
...
}
I'm using JpaRepositories created by Spring Boot:
public interface UserACLRepository extends JpaRepository<UserACL, Long> {
void deleteByUser(User user);
}

You can set orphanRemoval="true" in your #OneToMany annotation. Setting orphanRemoval to true automatically removes disconnected references of entities. On the other hand, if we specify only CascadeType.Remove, no action is taken as it will only disconnect from the association, which is not equivalent of deleting an object.
Eg.
#Entity
public class ExplorerItem {
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval="true", mappedBy = "explorerItem")
private Set<UserACL> userAcls = new HashSet<>();
...
}

Related

Spring Boot Jpa Update- Avoid unnecessary updates of columns & child entities

I have a jpa entity with parent-child relationship(1:n).
Any sort of update on the entity results in jpa trying to update each and every column, even if there is no change in the value. I'm confused about the update behaviour for child enitities, I guess same would be happening with those also.
I went through #DynamicUpdate over entities but I do not know how they behave in case of child-entities.
public class Tutorial extends Auditable<String> implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private string description;
#OneToMany(
fetch = FetchType.LAZY,
mappedBy = "tutorial",
cascade = CascadeType.ALL,
orphanRemoval = true)
private Collection<Modules> Modules = new HashSet<>();
// Getter/Setters
}
How to come up with an approach so that only necessary fields/columns and child entities and their columns are update on each save.

spring one-to-may annotation exception

I'm using spring-boot.
I have a class named Datum and a class named User. A user can have many datum. So the relation is one-to-many from user's perspective. Now I want to design those classes.
Here is what I have tried :
public class Datum{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private User user;
...
}
And :
public class User{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Set<Datum> data = new HashSet<>();//i have tried list-arrayList too
...
}
But this gives me org.hibernate.AnnotationException: Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements: error.
What are wrong here?
public class Datum{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private User user;
...
}
This is not a OneToMany relationship. This is a ManyToOne, by looking at your User class.
This will do the job:
public class Datum {
...
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private User user;
...
}
As a user can have many datum, so in user class relation will be like
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Datum> datums = new ArrayList<>()
And in Datum class no relation is needed.
The One to Many relationship is a Many to One on the other side. You should map it in this way:
public class Datum{
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
...
}
And:
public class User{
...
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<Datum> data = new HashSet<>();
...
}
As you can see, the relation is a Many to One on the Datum side. Furthermore it should have a FetchType.LAZY to increase performance and, if can't be a Datum without user, you should also ad "orphanRemoval = true".

Using Entity with OneToMany and HATEOAS RessourceAssembler leads to infinite recursion

I'm using two JPA entities annotated with #OneToMany (parent) <-> #ManyToOne (child) and I also wrote a RessourceAssembler to turn the entities into resources in the controller of a Springboot application (see below for code samples).
Without the relationship #OneToMany in the parent entity, Ressource assembling and serialisation works just fine.
As soon as I add the OneToMany relation on the parent the serialisation breaks with this:
"Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.hateoas.Resource[\"content\"]->com.marcelser.app.entities.Storage[\"storageStock\"])"
As you can see the infinite loop comes from the hateoas Resource, not the entities themselves.
I already tried to add #JsonManagedReference and #JsonBackReference on the entities or #JsonIgnore on the child but nothing really helps. The Hateoas RessourceAssembler always ends up in a infinite loop as soon as the child entity is embedded. It seems that shose #Json.... annotations help with the JSON serialisation of the entity itself but they don't solve problems with the RessourceAssembler
I have these 2 entities (Storage & Stock)
#Entity
#Table(name = "storage")
#Data
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;
}
#Entity
#Table(name = "stock")
#Data
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
and I'm using a RessourceAssemlber like follows for the parent entity 'Storage':
#Component
public class StorageResourceAssembler implements ResourceAssembler<Storage, Resource<Storage>> {
#Override
public Resource<Storage> toResource(Storage storage) {
return new Resource<>(storage,
linkTo(methodOn(StorageController.class).one(storage.getId())).withSelfRel(),
linkTo(methodOn(StorageController.class).all()).withRel("storages"));
}
}
and in the controller I have 2 get classes to list all or just a single Storage with its childs
public class StorageController {
private final StorageRepository repository;
private final StorageResourceAssembler assembler;
#GetMapping
ResponseEntity<?> all() {
List<Resource<Storage>> storages = repository.findAll().stream()
.map(assembler::toResource)
.collect(Collectors.toList());
Resources<Resource<Storage>> resources = new Resources<>(storages,
linkTo(methodOn(StorageController.class).all()).withSelfRel());
return ResponseEntity.ok(resources);
}
private static final Logger log = LoggerFactory.getLogger(StorageController.class);
StorageController(StorageRepository repository, StorageResourceAssembler assembler) {
this.repository = repository;
this.assembler = assembler;
}
#GetMapping("/{id}")
ResponseEntity<?> one(#PathVariable Long id) {
try {
Storage storage = repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(id));
Resource<Storage> resource = assembler.toResource(storage);
return ResponseEntity.ok(resource);
}
catch (EntityNotFoundException e) {
log.info(e.getLocalizedMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body (new VndErrors.VndError("Storage not found", "could not find storage with id " + id ));
}
}
... omitted Put/Post/Delete
}
Can anyone enlighten me how I can solve this infinite loop in HateOAS. What I want is that the embedded child entries just either don't link back to the parent (so no links to parent are created) or they contain the link for the one level but no further processing is done.
To handle the problem related to the serialization of the model using Jackson API when the model attributes have a lazy loading defined, we have to tell the serializer to ignore the chain or helpful garbage that Hibernate adds to classes, so it can manage lazy loading of data by declaring #JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) annotation.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {...
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {...
or you can just declare unilaterally mapping commenting the Storage entity declaration and changing the private Storage storage; to fetch EAGER #ManyToOne(fetch = FetchType.EAGER) in Stock class.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
/*#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;*/
}
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
Maybe a little late, but I've had this problem or very similar and I've only found one solution. The same error 500 gave me the clue on how to solve it:
Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.hateoas.PagedModel["_embedded"]->java.util.Collections$UnmodifiableMap["usuarios"]->java.util.ArrayList[0]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Usuario["rol"]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Rol$HibernateProxy$QFcQnzTB["hibernateLazyInitializer"])
So I only had to add in the application.properties:
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false

Spring Data, JPA #OneToMany Lazy fetch not working in Spring Boot

I have #OneToMany relationship between FabricRoll and FabricDefect.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "fabric_roll_id", referencedColumnName = "fabric_roll_id")
private Set<FabricDefect> fabricDefects = new HashSet<>();
The problem is when I get FabricRoll by JpaRepository function
findAll()
the associate FabricDefect is also loaded.
I want to load only FabricRoll and FabricDefect should load when calling the function getFabricDefect()
FabricRollServiceImpl class
#Component
public class FabricRollServiceImpl implements IFabricRollService{
#Autowired
FabricRollRepository fabricRollRepository;
#Transactional(propagation = Propagation.REQUIRED)
#Override
public List<FabricRoll> getAllFabricRoll() {
FabricRoll fabricRoll1 = new FabricRoll();
fabricRoll1.setBatchNo("34344");
fabricRoll1.setLotNo("425");
fabricRoll1.setPoNo("42");
fabricRoll1.setRollLength(2343);
fabricRoll1.setRollNo("356");
fabricRoll1.setRollWidth(60);
fabricRoll1.setStyleNo("354");
FabricDefect fabricDefect = new FabricDefect();
fabricDefect.setDefectNote("note");
fabricDefect.setDefectPoint(3);
fabricDefect.setSegment(3);
fabricDefect.setYard(42);
Set<FabricDefect> fabricDefects = new HashSet<>();
fabricDefects.add(fabricDefect);
fabricRoll1.setFabricDefects(fabricDefects);
addFabricRoll(fabricRoll1);
FabricRoll fabricRoll = null;
return fabricRollRepository.findAll();
}
#Override
public void addFabricRoll(FabricRoll fabricRoll) {
fabricRollRepository.save(fabricRoll);
}
}
Break point:
Console:
It seems to be a debugging artifact.
At debugging time, because the transaction is still open, the watched lazy loaded entity properties will be loaded at the breakpoint evaluation time.
To check the "production" behavior you should insert a em.detach statement just before the breakpoint or use logging (as suggested by Manza) and check em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(fabricRoll1.fabricDefects()) returns false on the detached entity.
(remember to inject EntityManager for example by declaring #PersistenceContext private EntityManager em;)
You don't need to use #JoinColumn, and you don't need to instantiate fabricDefects
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<FabricDefect> fabricDefects ;
See more in this question.
FabricDefect class:
#ManyToOne
#JoinColumn(name = "fabric_roll_id")
private FabricRoll roll;
FabricRoll class:
#OneToMany(mappedBy = "roll")
private Set<FabricDefect> fabricDefects;
Collections are by default loaded lazily, JPA will query the db only when the method getFabricDefects will be called.
You can see it by yourself enabling logging.
I found solution in this tutorial.
You have to modify FabricRoll OneToMany map as below:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "fabricRoll")
private Set<FabricDefect> fabricDefects;
FabricDefect ManyToOne as below (remember to remove fabric_roll_id field if you included it in your entity):
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fabric_roll_id")
private FabricRoll fabricRoll;
And you don't need to add #Transactional(propagation = Propagation.REQUIRED) before getAllFabricRoll() function.

Delete records in ManyToMany relationship spring data jpa

I have Two Entities. A and B. Relationship between A and B is #ManyToMany. So I have introduced Third entity C for #ManyToMany relationship as it needed for project.
My Entity classes are look like following.
#Entity
class A
{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "a")
List<C> cList;
}
#Entity
class B
{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "b")
List<C> cList;
}
#Entity
class C
{
#ManyToOne
#JoinColumn(name = "ref_a")
A a;
#ManyToOne
#JoinColumn(name = "ref_b")
B b;
}
Now, I want to delete record of entity A or B then it should delete respective record from C.
But when I delete record of A or B it shows
Cannot delete or update a parent row: a foreign key constraint fails
What other configuration it need to delete record from A or B and it will also delete respective record from C?
You don't have to create an entity to map the Many To Many table. The ManyToMany JPA annotation is there. Here is a sample of how to do it.
#Entity
public class Team {
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, mappedBy="teams")
private List<Match> matches;
}
#Entity
public class Match {
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST })
#JoinTable(
name="MATCH_TEAM",
joinColumns={#JoinColumn(name="MATCH_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="TEAM_ID", referencedColumnName="ID")})
private List<Team> teams;
}

Resources