JPA Hibernate Relationships

Why this happens?

package az.etibarli.jpa_relationships.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.proxy.HibernateProxy;

import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

@Data
@RequiredArgsConstructor
@Entity
public class Post {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;

@OneToOne(orphanRemoval = true, cascade = CascadeType.ALL)
@JoinColumn(name = "post_details_id")
private PostDetails postDetails;

@OneToMany(mappedBy = "post", orphanRemoval = true, cascade = CascadeType.ALL)
@ToString.Exclude
private Set<PostComment> postComments = new LinkedHashSet<>();

}


package az.etibarli.jpa_relationships.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.ToString;

@Data
@Entity
public class PostComment {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String comment;

@ToString.Exclude
@ManyToOne
@JoinColumn(name = "post_id")
private Post post;

}


package az.etibarli.jpa_relationships.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.ToString;

@Data
@Entity
public class PostDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String createdBy;

@ToString.Exclude
@OneToOne(mappedBy = "postDetails", orphanRemoval = true)
private Post post;

}


package az.etibarli.jpa_relationships;

import az.etibarli.jpa_relationships.entity.Post;
import az.etibarli.jpa_relationships.entity.PostComment;
import az.etibarli.jpa_relationships.entity.PostDetails;
import az.etibarli.jpa_relationships.repository.PostDetailsRepository;
import az.etibarli.jpa_relationships.repository.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Set;

@SpringBootApplication
@RequiredArgsConstructor
public class JpaRelationshipsApplication implements CommandLineRunner {

private final PostRepository postRepository;
private final PostDetailsRepository postDetailsRepository;

public static void main(String[] args) {
SpringApplication.run(JpaRelationshipsApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
PostDetails postDetails = new PostDetails();
postDetails.setCreatedBy("Parvin");

PostComment comment1 = new PostComment();
comment1.setComment("pis");
PostComment comment2 = new PostComment();
comment2.setComment("yaxwi");

Post post = new Post();
post.setTitle("News");
post.setPostDetails(postDetails);
post.setPostComments(Set.of(comment1, comment2));
comment1.setPost(post);
comment2.setPost(post);

System.out.println(post);

postRepository.save(post);
System.out.println(post);
}
}






1. What is TRANSIENT?

In JPA (Java Persistence API) with Hibernate, the transient keyword is used to indicate that a field should not be persisted (saved) to the database. When a field is marked as transient, Hibernate will ignore it when performing entity persistence operations like saving or updating. 


import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Transient;


@Entity

public class Product {

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    

    private String name;

    

    @Transient

    private String temporaryData;

    

    // getters and setters

}


In this example, the temporaryData field is marked as transient, so it will not be saved to the database when an instance of Product is persisted.


2. open-in-view:


spring: jpa: open-in-view: true


In the context of Spring Boot and JPA/Hibernate, open-in-view is a configuration option that controls whether the Hibernate Session should be kept open after a transaction is completed. This setting is typically used to manage lazy-loading of entity relationships in a web application.

Setting open-in-view to true means that the Session will remain open until the view rendering is complete. This allows lazy-loaded entity relationships to be accessed in the view layer, even though the transaction that loaded the entity might have already been committed or closed.

Setting open-in-view to false means that the Session will be closed after the transaction is committed or rolled back. This can improve performance by releasing database connections and resources promptly, but it requires that all data needed for rendering the view be eagerly loaded before the transaction completes.

In most cases, it's recommended to set open-in-view to true for web applications using Spring Boot and JPA/Hibernate, as it helps avoid LazyInitializationExceptions and simplifies the management of entity relationships in the view layer. However, setting it to true can lead to potential performance issues if not used carefully, as it keeps the Session open longer than necessary.


3. 

package az.etibarli.jpa_relationships.controller;

import az.etibarli.jpa_relationships.entity.Product;
import az.etibarli.jpa_relationships.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class ProductController {

private final ProductService service;

@PostMapping
public void save(@RequestBody Product product) {
System.out.println("Before : " + product);
service.save(product);
System.out.println("After : " + product);
}

}


The behavior you're observing, where the id field is null before saving but is set to a non-null value after saving, is a result of how JPA and Hibernate manage entity lifecycle and database interactions.

  1. Transient State: When you create a new entity instance (Product in this case) and set its properties, the entity is in a transient state. In this state, the entity is not associated with any persistence context (like a database session), and its id field is typically null or a default value.

  2. Managed State: When you call the save method (which typically involves persisting the entity using JPA), the entity becomes managed by the persistence context. Hibernate then generates an identifier (e.g., id) for the entity and assigns it to the id field. This process is known as identity assignment.

  3. Flush and Commit: The changes made to managed entities are not immediately written to the database. Instead, they are queued in the persistence context. When the transaction is committed or when a flush operation is triggered (which can happen at various points, like before a query or at the end of a transaction), the changes are synchronized with the database. This is when the id field is set in the database, and Hibernate updates the id field in the managed entity.

So, in your case, the id field is null before saving because the entity is in a transient state without an assigned identifier. After saving, Hibernate assigns an identifier to the entity, and the id field is updated accordingly.


4. org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist:

The InvalidDataAccessApiUsageException you're encountering typically occurs in JPA when you attempt to persist or merge an entity that is in a detached state. A detached entity is an object that is no longer associated with a persistence context (i.e., it was previously managed but is not currently managed).

To resolve this issue, you have a few options:

  1. Reattach the Entity: If the entity was previously fetched from the database and you still have its ID, you can reattach it to the current persistence context before persisting or merging it. You can do this by using the EntityManager's merge method:

    java
    entity = entityManager.merge(entity); entityManager.persist(entity);
  2. Fetch the Entity: If you don't have the entity in a managed state, you can fetch it from the database using its ID and then persist or merge the fetched entity:

    java
    entity = entityManager.find(EntityClass.class, entityId); entityManager.persist(entity);
  3. Check Entity State: Ensure that the entity is not detached before persisting or merging it. You can check the entity's state using the EntityManager's contains method:

    java
    if (!entityManager.contains(entity)) { // Entity is detached, handle accordingly }
  4. Cascade Persist: If the entity has relationships that should also be persisted, ensure that the cascade type is set appropriately in the entity's mapping. For example, if ChildEntity should be persisted when ParentEntity is persisted, you can set the cascade type in the ParentEntity mapping:

    java
    @OneToMany(mappedBy = "parentEntity", cascade = CascadeType.ALL) private List<ChildEntity> childEntities;

By addressing the detached entity issue using one of these approaches, you should be able to resolve the InvalidDataAccessApiUsageException.




































Комментарии

Популярные сообщения из этого блога

IoC:ApplicationContext, BeanFactory. Bean

Lesson1: JDK, JVM, JRE

Lesson_2: Operations in Java