JPA

 

OneToOne, OneToMany, ManyToOne 可配置属性

  • CascadeType.cascade=ALL is equivalent to cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}. 
  • orphanRemoval  Whether to apply the remove operation to entities that have been removed from the relationship and to cascade the remove operation to those entities

 

In Spring Data JPA OneToMany What are These Fields (mappedBy, fetch, cascade and orphanRemoval)

参考: https://medium.com/@burakkocakeu/in-spring-data-jpa-onetomany-what-are-these-fields-mappedby-fetch-cascade-and-orphanremoval-2655f4027c4f

   
1.Bidirectional One-to-Many: In a bidirectional one-to-many relationship, both entities have a reference to each other. This means that you can navigate the relationship in either direction, from parent to child or from child to parent. 2. Unidirectional One-to-Many: In a unidirectional one-to-many relationship, only one entity has a reference to the other entity. This means that you can only navigate the relationship in one direction, from parent to child
@Entity
public class Author {
    // ...
    @OneToMany(mappedBy = "author")
    private List<Book> books;
}

@Entity
public class Book {
    @ManyToOne
    @JoinColumn(name = "author_id")
    private Author author;
    // ...
}
@Entity
public class Author {
    @OneToMany
    @JoinColumn(name = "author_id")
    private List<Book> books;
    // ...
}

@Entity
public class Book {
    // No reference to the Author entity
    // ...
}
In this example, the Author entity has a collection of Book entities, and the Book entity has a reference to the Author entity. The mappedBy attribute on the OneToMany annotation specifies that the author property on the Book entity is the owner of the relationship. This means that you can navigate the relationship from the Author entity to its associated Book entities, and from the Book entity to its associated Author entity. In this example, the Author entity has a collection of Book entities, but the Book entity does not have a reference to the Author entity. This means that you can only navigate the relationship from the Author entity to its associated Book entities, but not from the Book entity to its associated Author entity
  • mappedBy = The mappedBy attribute is used in a one-to-many relationship to specify the name of the property in the child entity that represents the inverse side of the relationship. The mappedBy attribute is used to specify the owner of the relationship, which helps to ensure that changes made to the relationship are properly persisted in the database.
  • fetch = specify how the associated entities should be fetched from the database.
    • FetchType.EAGER:  the associated entities will be fetched from the database along with the parent entity, in a single query. This means that the associated entities will always be available when the parent entity is loaded from the database, and you won't need to perform additional queries to fetch the associated entities.
    • FetchType.LAZY: the associated entities will not be fetched from the database when the parent entity is loaded. Instead, the associated entities will be fetched from the database on demand, when you first access the collection of associated entities.
  • cascade = {} specify how changes to the parent entity should be cascaded to the associated entities. The cascade attribute can have one or more values from the CascadeType enumeration, such as CascadeType.PERSISTCascadeType.MERGECascadeType.REMOVE, etc
    • CascadeType.PERSIST:  changes to the parent entity will be cascaded to the associated entities when the parent entity is persisted to the database. For example, if you persist a new parent entity to the database, the associated entities will also be persisted to the database.
    • CascadeType.MERGE:  changes to the parent entity will be cascaded to the associated entities when the parent entity is merged with the persistence context. For example, if you make changes to an Author entity and then merge the changes into the persistence context, the associated Book entities will also be merged into the persistence context. the changes will be persisted to the database in a single transaction
    • CascadeType.REMOVE:  changes to the parent entity will be cascaded to the associated entities when the parent entity is removed from the database. For example, if you remove an Author entity from the database, the associated Book entities will also be removed from the database.
    • CascadeType.REFRESH:  changes to the parent entity will be cascaded to the associated entities when the parent entity is refreshed from the database. 
    • CascadeType.DETACH
  • orphanRemoval attribute is used in a one-to-many relationship to specify whether associated entities should be automatically removed from the database when they are no longer referenced by the parent entity.
@Entity
public class Author {
    @OneToMany(orphanRemoval = true)
    private List<Book> books;
    // ...
}

@Entity
public class Book {
    @ManyToOne
    @JoinColumn(name = "author_id")
    private Author author;
    // ...
}

In this example, the Author entity has a collection of Book entities, and the Book entity has a reference to the Author entity. The orphanRemoval attribute specifies that if a Book entity is no longer referenced by an Author entity, it should be removed from the database.

For example, if you remove a Book entity from the collection of books on an Author entity and then persist the changes to the database, the removed Book entity will be deleted from the database.

So, what is the difference between `CascadeType.REMOVE` and `orphanRemoval = true`?

  • orphanRemoval = true and cascade = CascadeType.REMOVE are similar in that they both control the removal of entities from the database, but they work in different ways. 
  • orphanRemoval is specifically designed to remove entities that are no longer referenced by any other entities,
  • while cascade = CascadeType.REMOVE will remove all associated entities when the parent entity is deleted, regardless of whether they are referenced by other entities.

 

"referencedColumnName" property is the name of the column in the table that you are making reference with the column you are anotating. Or in a short manner: it's the column referenced in the destination table. Imagine something like this: cars and persons. One person can have many cars but one car belongs only to one person (sorry, I don't like anyone else driving my car). -----如果外表的主键只有一列,则不需要referencedColumnName

Table Person
name char(64) primary key
age int

Table Car
car_registration char(32) primary key
car_brand (char 64)
car_model (char64)
owner_name char(64) foreign key references Person(name)

When you implement classes you will have something like

class Person{
   ...
}

class Car{
    ...
    @ManyToOne
    @JoinColumn([column]name="owner_name", referencedColumnName="name")
    private Person owner;
}

Difference Between @JoinColumn and mappedBy

1. Overview 

 参考: https://www.baeldung.com/jpa-joincolumn-vs-mappedby

JPA Relationships can be either unidirectional or bidirectional. This simply means we can model them as an attribute on exactly one of the associated entities or both.

Defining the direction of the relationship between entities has no impact on the database mapping. It only defines the directions in which we use that relationship in our domain model.

For a bidirectional relationship, we usually define

  • the owning side
  • inverse or the referencing side

The @JoinColumn annotation helps us specify the column we'll use for joining an entity association or element collection. On the other hand, the mappedBy attribute is used to define the referencing side (non-owning side) of the relationship.

The @JoinColumn annotation defines the actual physical mapping on the owning side. On the other hand, the referencing side is defined using the mappedBy attribute of the @OneToMany annotation.

 2. Initial Setup

To follow along with this tutorial, let’s say we have two entities: Employee and Email.

Clearly, an employee can have multiple email addresses. However, a given email address can belong exactly to a single employee.

This means they share a one-to-many association:

Employee - Email

Also in our RDBMS model, we'll have a foreign key employee_id in our Email entity referring to the id attribute of an Employee.

3. @JoinColumn Annotation

In a One-to-Many/Many-to-One relationship, the owning side is usually defined on the many side of the relationship. It's usually the side that owns the foreign key.

The @JoinColumn annotation defines that actual physical mapping on the owning side:

@Entity
@Table(name = "Email")
public class Email {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO) //or GenerationType.IDENTITY
    @Column(name = "ID")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "employee_id")
    // name--- The name of the foreign key column
    //referencedColumnName-- The name of the column referenced by this foreign key column. 
    //    default is the same name as the primary key column of the referenced table.
    private Employee employee;
    // ...
}

 It simply means that our Email entity will have a foreign key column named employee_id referring to the primary attribute id of our Employee entity.

 4. mappedBy Attribute

Once we have defined the owning side of the relationship, Hibernate already has all the information it needs to map that relationship in our database.

To make this association bidirectional, all we'll have to do is to define the referencing side using "the mappedBy attribute of @OneToMany annotation". The inverse or the referencing side simply maps to the owning side.

import jakarta.persistence.*;

@Entity
@Table(name = "employee")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "employee",cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Email> emails;
    @Column(name = "name", nullable = false, length = 256)
    private String name;
    @Enumerated(EnumType.STRING)
    //EnumType.STRING---Persist as a string. ORDINAL--Persist as an integer.
    private RoleEnum role;
    @Column(columnDefinition = "TEXT")
    private String otherInfo;
    // ...
}

Here, the value of mappedBy is the name of the association-mapping attribute on the owning side. With this, we have now established a bidirectional association between our Employee and Email entities.

 

根据如下链接:

https://www.baeldung.com/jpa-strategies-when-set-primary-key

https://www.baeldung.com/jpa-get-auto-generated-id

  • GenerationType.IDENTITY   relies on the database auto-increment column. The database generates the primary key after each insert operation. JPA assigns the primary key value after performing the insert operation or upon transaction commit

  

@Entity
@Table(name = "app_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_name")
    private String name;
    // standard getters and setters
}
 

Here, we verify the id values before and after the transaction commit:

@Test
public void givenIdentityStrategy_whenCommitTransction_thenReturnPrimaryKey() {
    User user = new User();
    user.setName("TestName");        
    entityManager.getTransaction().begin();
    entityManager.persist(user);
    Assert.assertNull(user.getId());
    entityManager.getTransaction().commit();
    Long expectPrimaryKey = 1L;
    Assert.assertEquals(expectPrimaryKey, user.getId());
}

 After the domain class, we’ll create a UserService class. This simple service will have a reference to EntityManager and a method to save User objects to the database:

public class UserService {
    EntityManager entityManager;
 
    public UserService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
 
    @Transactional
    public long saveUser(User user){
        entityManager.persist(user);
        return user.getId();
    }
}

 This setup is a common pitfall that we previously mentioned. We can prove that the return value of the saveUser method is zero with a test:

@Test
public void whenNewUserIsPersisted_thenEntityHasNoId() {
    User user = new User();
    user.setUsername("test");
    user.setPassword(UUID.randomUUID().toString());
 
    long index = service.saveUser(user);
 
    Assert.assertEquals(0L, index);
}

  the managed object gets an id after the transaction commit. Since the saveUser method is still running, the transaction created by the @Transactional annotation isn’t yet committed. Our managed entity gets an id when saveUser finishes execution.---也即对于GenerationType.IDENTITY因为我们将saveUser添加了“@Transactional”,因此只有该方法执行完毕时,才执行提交操作,所以在函数中Id还没有获取,所以函数中为0,执行完毕后才改为1

One possible solution is to call the flush method on EntityManager manually. On the other hand, we can manually control transactions and guarantee that our method returns the id correctly. We can do this with EntityManager:------解决方法是在函数中使用flush方法,或者手动提交事务

@Test
public void whenTransactionIsControlled_thenEntityHasId() {
    User user = new User();
    user.setUsername("test");
    user.setPassword(UUID.randomUUID().toString());
     
    entityManager.getTransaction().begin();
    long index = service.saveUser(user);
    entityManager.getTransaction().commit();
     
    Assert.assertEquals(2L, index);
}

 

 

 

posted @ 2025-02-18 16:25  cyj2024  阅读(22)  评论(0)    收藏  举报