Delete parent row without affecting child rows in hibernate - mysql

I have a problem with my code in #Manytomany hibernate relationship.
I see above issue is due to "CascadeType.ALL" on #ManyToMany relationship side. But if i remove this CascadeType.ALL in #ManyToMany. If I change this CascadeType.ALL in #ManyToMany with CascadeType.PERSIST and CascadeType.MERGE, I solve the problem. But when I add new an object will get an error. otherwise, if I change it to CascadeType.ALL, when adding new objects it works fine but it doesn't solve the problem I'm discussing. So how can I keep the rows in the Class Tag when deleting rows in Post.
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
public Post() {}
public Post(String title) {
this.title = title;
}
#ManyToMany(cascade = {
CascadeType.ALL
},fetch = FetchType.EAGER)
#JoinTable(name = "post_tag",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<>();
}
//
public class Tag {
#Id
#GeneratedValue
private Long id;
#NaturalId
private String nameTag;
#ManyToMany(mappedBy = "tags",fetch = FetchType.LAZY)
private List<Post> posts = new ArrayList<>();
public Tag() {}
public Tag(String nameTag) {
this.nameTag = nameTag;
}
}
As I said above if it is CascadeType.ALL I will not be able to retain the row in Tag when I delete a row in Post
when I write it is CascadeType.PERSIST and CascadeType.MERGE i get an error:
object references an unsaved transient instance - save the transient instance before flushing.
And this is my input API object:
{
"title": "System",
"tags": [
{
"nameTag": "apt"
},
{
"nameTag":"Worm"
}
]
}
Remember that the api input works fine when I use Casecade.ALL

Related

JPA/Hibernate - How implement soft delete in a #OneToMany relationship

I'm trying implement soft delete in a #OneToMany relationship in an academic project. I need to mark as "deleted" a child entity in the following scenarios:
When the parent entity is deleted (soft deleted as well).
When the child entity is no longer referenced by his parent.
I've achieved this requirement using the #SqlDelete annotation, and the CascadeType.all / orphanRemoval = true options on the #OneToMany side as follow:
Parent
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql = "update Discount set deleted = true where id = ?")
#Where(clause = "deleted = false")
public class Discount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
/// More properties ...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "discountId", nullable = false)
private List<PetrolStationDiscount> petrolStationDiscounts = new ArrayList<>();
public Discount() {}
// Getters and setters...
public List<PetrolStationDiscount> getPetrolStationDiscounts() {
return petrolStationDiscounts;
}
public void setPetrolStationDiscounts(List<PetrolStationDiscount> petrolStationDiscounts) {
this.petrolStationDiscounts = petrolStationDiscounts;
}
// HashCode & Equals methods
Child Entity
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql="UPDATE PetrolStationDiscount SET deleted = true WHERE id = ?")
public class PetrolStationDiscount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
#ManyToOne
#JoinColumn(name = "petrolStationId")
private PetrolStation petrolStation;
#Column(insertable = false, updatable = false)
private String discountId;
// Getters & Setters -- HashCode & Equals methods
BaseDAO
#MappedSuperclass
public abstract class BaseDao {
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date createdAt;
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date updatedAt;
#Column(name = "deleted", nullable = false)
private boolean deleted = false;
// Getters and Setters
}
This works fine, but I'm not comfortable mixing the sql statement straight into the entities, so I've tried the approach I found in the following thread:
How to soft delete parent and child together (cascade) using spring boot with jpa
Following the thread, I've created a BaseRepository overriding the CrudRepository methods "delete" and "deleteById", but it doesn't work:
When the parent entity is deleted, only the parent deleted field is saved as true.
When a child entity is no longer referenced by his parent, if orphanRemoval is set to true, the child entity is hard deleted, and if false, nothing happened.
#NoRepositoryBean
public interface BaseRepository<T extends BaseDao> extends CrudRepository<T, Serializable> {
#Override
#Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
#Transactional
#Modifying
void deleteById(Serializable id);
#Override
#Transactional
default void delete(T entity) {
deleteById(entity.getId());
}
¿Someone could help me?
A lot of thanks!

Hibernate & MySQL: When a record is created, a second record is added and is completely null

I'm fairly new to spring and hibernate and I'm creating a simple application that has two entity classes that are linked by #OneToMany and #ManyToOne relationships.
#Entity
public class ActiveIngredient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="name")
private String name;
#Column(name="active")
private String active;
#Column(name="potency")
private double potency;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
#JoinColumn(name="manufacturer")
private Manufacturer manufacturer;
and
#Entity
public class Manufacturer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int manId;
#Column(name="name")
private String manName;
#Column(name="country")
private String country;
#Column(name="city")
private String city;
#Column(name="email")
private String email;
#Column(name="phone")
private String phone;
#OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH}, mappedBy = "manufacturer")
private List<ActiveIngredient> activeIngredients;
#OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH}, mappedBy = "manufacturer")
private List<ExcipientIngredient>excipientIngredients;
On the front end, a thymeleaf template is used where users can submit the data to create an ingredient and manufacturer. Below is the controller:
#Controller
#RequestMapping("/excipients")
public class ExcipientIngredientController {
#Autowired
private ExcipientIngredientService excipientIngredientService;
#Autowired
private ManufacturerService manufacturerService;
#GetMapping("/listExc")
public String listExcipients(Model model) {
List<ExcipientIngredient> theExcipient = excipientIngredientService.findAll();
//add list to the model.
model.addAttribute("excipient", theExcipient);
//return the thymeleaf page with this path.
return "list-excipients";
}
#GetMapping("/addExcipientForm")
public String addForm(Model model){
//to add a new excipient ingredient, need to create a new object
ExcipientIngredient excipientIngredient = new ExcipientIngredient();
Manufacturer manufacturer = new Manufacturer();
//add to the model
model.addAttribute("excipientIngredient", excipientIngredient);
model.addAttribute("manufacturer", manufacturer);
return "add-excipient-form";
}
#PostMapping("/saveExc")
public String saveExcipients(#ModelAttribute("excipientIngredient") ExcipientIngredient excipientIngredient, #ModelAttribute("manufacturer") Manufacturer manufacturer) {
// save the Ingredient
excipientIngredientService.saveOrUpdate(excipientIngredient);
manufacturerService.saveOrUpdate(manufacturer);
// use a redirect to prevent duplicate submissions
return "redirect:/excipients/listExc";
}
}
and this is the implementation for the save/update method.
#Override
public void saveOrUpdate(ExcipientIngredient excipientIngredient) {
Session currentSession = entityManager.unwrap(Session.class);
currentSession.saveOrUpdate(excipientIngredient);
}
Everything works fine when creating an ingredient and being updated into MySQL database, however, when the manufacturer is added to the database, an extra record is created that is completely null:
MySQL entry
I've been trying for a few hours to resolve this issue but have had no luck. Any suggestions or pointing me in the right direction would be appreciated.
In the Manufacturer class, you can try removing the cascade parameter from the #OneToMany annotation.
Here's a good article that you might find useful: https://www.baeldung.com/hibernate-one-to-many

failed to lazily initialize a collection of role: <Relation Class>could not initialize proxy - no Session

I am getting the exception while updating the Parent entity with a new Child entity,
Here is my Sample Table
public class NavigationNode implements Serializable,Auditable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "NodeID")
private Long id;
#Embedded
private AuditSection auditSection = new AuditSection();
#Column(name = "Code", nullable = false, unique = true)
private String navCode;
#Column(name = "NodeTitle")
private String title;
#OneToMany(fetch=FetchType.LAZY, cascade = {CascadeType.PERSIST})
#JoinTable(name = "node2linkRel", joinColumns = { #JoinColumn(name = "NavigationNodeID") }, inverseJoinColumns = { #JoinColumn(name = "LinkID") })
private Collection<Link> links;
#ElementCollection
#OneToMany(fetch=FetchType.LAZY, orphanRemoval = true, cascade = {CascadeType.PERSIST} )
#JoinTable(name = "node2nodeRel", joinColumns = { #JoinColumn(name = "ParentID") }, inverseJoinColumns = { #JoinColumn(name = "ChildID") })
private Collection<NavigationNode> children;
#Column(name = "Visible")
private Boolean visible;
}
There is another Table called Link
public class Link implements Serializable,Auditable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "LinkID")
private Long id;
#Embedded
private AuditSection auditSection = new AuditSection();
#Column(name = "LinkUrl")
private String url;
#Column(name = "linkName")
private String linkName;
#Column(name = "VisibleInMenu")
private Boolean visibleInMenu;
#ManyToOne(cascade = {CascadeType.PERSIST})
#JoinTable(name = "node2linkRel", joinColumns = { #JoinColumn(name = "LinkID") }, inverseJoinColumns = { #JoinColumn(name = "NavigationNodeID") })
private NavigationNode node;
}
While Saving without the Links I am getting the above error.
failed to lazily initialize a collection of role: Navigation.links
could not initialize proxy
My requirements are
I can created a Navigaiton with optional children and links, Update an Child/parent with optional children and links.
If I delete Navigation Node then the related children should be deleted but I want to keep only the Link and delete the relation between Navigation and Link.
If I try to delete the LInk record, only the relation between Navigation and link should be deleted.
How Can I achieve that and is this the correct Table configuration.
Also While deleting the Link or Node I am getting another exception.
Please suggest.
Thanks
This is because I was trying to Assign a new Object List to the Links in navigation Table after getting the Object from the DataBase.
I was trying to break a Relation between a Link and Navigation.
For achieving this I just remove the Link from the Links list in the Navigaition Table and save, so that the Relation between those entity breaks.
My requirement is not to delete the Link if I try to unlink the relation between Navigation and Link.

In Spring Data how to read entity by foreign key value instead of join?

I am using Spring Data and I am in doubt why even after declaring foreign entities as Lazy loaded they are getting eagerly loaded for this method:
findByReportingManager_IdAndAndLevel(Long reporterManagerId, Integer level)
On logs, I can see the query as:
select userhierar0_.id as id1_28_,
userhierar0_.LEVEL as LEVEL5_28_,
userhierar0_.REPORTING_MANAGER_ID as REPORTIN9_28_,
userhierar0_.USER_ID as USER_ID10_28_
from USER_HIERARCHY userhierar0_
left outer join
USER_V3 user1_ on userhierar0_.REPORTING_MANAGER_ID=user1_.id
where user1_.id=? and userhierar0_.CUSTOMER_ID=? and userhierar0_.LEVEL=?
why extra join even if I am passing reporting manager id ?
UserHierarchy Class:
#Entity
#Table(name = "USER_HIERARCHY")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserHierarchy {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY) // LAZY LOADING
#JoinColumn(name = "USER_ID",referencedColumnName = "ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY) //LAZY LOADING
#JoinColumn(name = "REPORTING_MANAGER_ID", referencedColumnName = "ID")
private User reportingManager;
#Column(name = "LEVEL")
private Integer level;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public User getReportingManager() {
return reportingManager;
}
public void setReportingManager(User reportingManager) {
this.reportingManager = reportingManager;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
#Override
public String toString() {
return ReflectionToStringBuilder.toStringExclude(this, "user", "reportingManager");
}
User Entity class
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
private Long id;
#Column(name = "EMAIL")
private String email;
#Column(name = "CUSTOMER_ID")
private Long customerId;
#Column(name = "STATUS")
private String status;
// Getter and Setter
As per Spring's doc:
At query creation time you already make sure that the parsed property
is a property of the managed domain class.
So does that mean in order to make User object in "managed state" it uses join or I am wrong in the implementation ?
I stumbled across the same problem recently and it seems that there is no solution for this at the moment in Spring Data.
However I've created a ticket for it.
If you go with Criteria API or JPQL for this particular query, then it will work properly.

JPA many to many relation not inserting into generated table

I have a many-to-many relation in my project and although I'm able to write in my two Entities table, the relational table does not get anything written.
Here's how I'm declaring this using JPA annotations:
Professor.java
#Entity
#Table(name = "Professor")
public class Professor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "idProfessor", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"),
inverseJoinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"))
private List<Aluno> alunoList;
// getters and setters
}
Aluno.java
#Entity
#Table(name = "Aluno")
public class Aluno implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "idAluno", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToMany(mappedBy = "alunoList", fetch = FetchType.EAGER)
private List<Professor> professorList;
// getters and setters
}
And here is the service layer to insert into database:
#Autowired
private AlunoDao alunoDao;
#Autowired
private ProfessorDao professorDao;
#RequestMapping(value = RestUriConstants.SUBMETER, method = RequestMethod.POST)
public #ResponseBody JsonResponse submeter(#RequestBody final Aluno aluno) {
Professor professor = professorDao.find(1);
aluno.setProfessorList(Arrays.asList(professor));
alunoDao.persist(aluno);
...
}
In this case, please consider that I already have an entry with id "1" for Professor.
As I said, it does write on Aluno and Professor table but does NOT write anything into ALUNO_PROFESSOR table.
I've already taken a look at these three kind of similiar questions but none of them could help me:
Hibernate and Spring: value of many-to-many not inserted into generated table
JPA many-to-many persist to join table
How to persist #ManyToMany relation - duplicate entry or detached entity
EDIT - Adding more code snippets
JpaAlunoDao.java
#Repository
public class JpaAlunoDao implements AlunoDao {
#PersistenceContext
private EntityManager em;
#Transactional
public void persist(Aluno aluno) {
em.persist(aluno);
}
}
JpaExercicioDao.java
#Repository
public class JpaExercicioDao implements ExercicioDao {
#PersistenceContext
private EntityManager em;
#Transactional
public void persist(Exercicio exercicio) {
em.persist(exercicio);
}
}
Try this:
public class Professor {
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"),
inverseJoinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"))
private List<Aluno> alunoList;
}
public class Aluno {
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idAluno", referencedColumnName = "idAluno"),
inverseJoinColumns = #JoinColumn(name = "idProfessor", referencedColumnName = "idProfessor"))
private List<Professor> professorList;
}
This will ensure that the metadata for the many-to-many relationship is available on both the entities and that operations on either side of the relationship are cascaded to the other side.
I also suggest replacing FetchType.EAGER with FetchType.LAZY for better performance because this has the potential of loading a very large dataset.
I have the same issue. I swapped where the full mapping declare to the class that we will use save() function on.
In your case:
public class Aluno {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "ALUNO_PROFESSOR",
joinColumns = #JoinColumn(name = "idAluno"),
inverseJoinColumns = #JoinColumn(name = "idProfessor")
private List<Professor> professorList;
}
public class Professor {
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "professorList",)
private List<Aluno> alunoList;
}
and It worked fine.
...
Normally, Hibernate holds the persistable state in memory. The process of synchronizing this state to the underlying DB is called flushing.
When we use the save() method, the data associated with the save operation will not be flushed to the DB unless and until an explicit call to flush() or commit() method is made.
If we use JPA implementations like Hibernate, then that specific implementation will be managing the flush and commit operations.
One thing we have to keep in mind here is that, if we decide to flush the data by ourselves without committing it, then the changes won't be visible to the outside transaction unless a commit call is made in this transaction or the isolation level of the outside transaction is READ_UNCOMMITTED.
...
From Difference Between save() and saveAndFlush() in Spring Data JPA by baeldung:
https://www.baeldung.com/spring-data-jpa-save-saveandflush
employeeRepository.saveAndFlush(new Employee(2L, "Alice"));
or
employeeRepository.save(new Employee(2L, "Alice"));
employeeRepository.flush();
Its not necessary to set many-to-many relationship on both entities.
Just remove session.setFlushMode(FlushMode.MANUAL);
By default HibernateTemplate inside of Spring set FlushMode.MANUAL
this is source code from HibernateTemplate.
if (session == null) {
session = this.sessionFactory.openSession();
session.setFlushMode(FlushMode.MANUAL);
isNew = true;
}
Some times the problem is in the way that you insert the values. I explain with an example.
User user = userFacade.find(1);
Post post = new Post("PRUEBA");
user.addPostCollection(post);
post.addUserCollection(user);
postFacade.create(post);
You have to add the post in postCollection and the user in userCollection. You have two add the correspond entity in the collections of the two entities.
Class USER
public void addPostCollection(Post post) {
if(postCollection == null){
postCollection = new ArrayList<Post>();
}
postCollection.add(post);
}
#ManyToMany(mappedBy = "userCollection")
private Collection<Post> postCollection;
Class Post
public void addUserCollection(User user){
if(userCollection == null){
userCollection = new ArrayList<User>();
}
userCollection.add(user);
}
#JoinTable(name = "USER_POST_R", joinColumns = {
#JoinColumn(name = "POSTID", referencedColumnName = "ID")}, inverseJoinColumns = {
#JoinColumn(name = "USERID", referencedColumnName = "ID")})
#ManyToMany
private Collection<User> userCollection;
Also, it is important to instance the list, for example userCollection = new ArrayList(). If you do not, the value won´t insert.