Writing in the front

Spring Data JPA is a large module in the Spring Data family that makes it easy to implement jPA-based repositories. This module provides enhanced support for handling the JPA-based data access layer. It makes it easier to build spring-enabled applications that access data. As we know, Spring has been implementing the data access layer for applications for a long time. Too much boilerplate code must often be written to perform simple queries as well as paging and auditing. Spring Data JPA is designed to significantly improve the implementation of the Data access layer by reducing the amount of work to what is actually needed. As developers, we just need to write the repository interface that includes custom Finder methods, and Spring will automatically provide the implementation. If you need to know more about Spring Data JPA, you can go to the official website for more information. This article mainly explains the use of @manytomany, @onetomany and other annotations.

Meanings of relevant notes

As we all know, Spring Data JPA uses Hibernate as ORM framework by default, and Hibernate as relational database involves joining between different tables. It can be controlled by the business logic relationship between entities. There is nothing wrong with using business logic to control, but let’s think about it. The logic of the code that handles joins between tables is basically the same. Do we need to write the same code for each table that needs to be processed? Of course not. Don’t forget that Spring Data JPA aims to significantly improve the implementation of the Data access layer by reducing the amount of work to what is actually needed, so we can annotate the relationships between entities and load the Data as soon as the entity class is loaded.

Note that the annotations in this article are the Spring Data JPA default Hibernate annotations. If you are using Mybatis, they are not applicable to this article

Note that

Before we explain the usage, let’s take a look at the meaning of the annotations. This will help us understand the code when we explain the usage later. Of course, you can skip this section and look it up for yourself when you need to.

  • @TRANSIENT: @TRANSIENT indicates that this attribute is not a mapping to a field of a database table and will be ignored by the ORM framework. If a property is not a field mapping of a database table, it must be marked as @TRANSIENT, otherwise the ORM framework defaults to annotating it as @BASIC.

  • JsonIgnoreProperties: This annotation is a class annotation that ignores some properties in the Java bean during JSON serialization, affecting both serialization and deserialization.

  • @jsonIgnoreProperties: This annotation applies to a property or method (preferably a property) and does the same as @jsonIgnoreProperties above.

  • @jsonFormat: This annotation is used on properties or methods (preferably properties) to convert the Date type directly to the desired pattern, such as @jsonFormat (pattern = “YYYY-MM-DD hH-MM-SS”).

  • JsonSerialize: This annotation is used on property or getter methods to embed our custom code during serialization, such as limiting the number of two decimal points after a double.

  • Association annotations: Association annotations include @JoinColumn, @oneToOne, @onetomany, @ManyToOne, @ManyTomany, @JoinTable, and @OrDerby.

  • Cascade Relationship: In actual services, the following situations occur: A user and a user’s shipping addresses are one-to-many. When a user is deleted, all the shipping addresses of the user are deleted. The order and the goods in the order are also one-to-many relationships, but when the order is deleted, the goods associated with the order cannot be deleted. In this case, the desired effect can be achieved as long as the cascading relationship is correctly configured. Cascading relationship types:

    • Cascadetype.refresh: Cascade REFRESH. When multiple users operate on an entity at the same time, the REFRESH () method can be called before the data in the entity is used in order for the data to be retrieved by the user in real time
    • Cascadetype. REMOVE: cascading deletes the data associated with OrderItem when the REMOVE () method is called to REMOVE the Order entity
    • Cascadetype. MERGE: A cascading update. When MERGE () is called, the data in OrderItem is updated accordingly if the data in Order changes
    • Cascadetype. ALL: contains ALL the above cascading properties
    • Cascadetype.persist: cascades data when the PERSIST () method is called

The following focuses on OneToMany and ManyToMany, because these two mappings are used more, other mappings will also be slightly discussed.

Understanding and Using

The first thing we need to know is that all relationships in Java and JPA are one-way, unlike relational databases, where you can define and query with a foreign key, so that reverse queries always exist. JPA also defines an OneToMany relation, which is similar to a ManyToMany relation, but the reverse relation (if defined) is a ManyToOne relation. The main difference between OneToMany and ManyToMany relationships in JPA is that ManyToMany always uses an intermediate relation to join tables to store relationships. OneToMany can use the primary key of the source object table in the join table or the foreign key in the target object’s table reference, as shown below.

@OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "OPR_WARE_SYSCONFIG_ID",foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private List<WarehouseVO> warehouse;
Copy the code

Relational Mapping (ORM) the databases we normally operate are Relational databases (Relational: there are relationships between tables, for example, [department table] can be checked [employee table]). Terms: ORM, which maps the table structure of a relational database to objects. Next, let’s clarify some concepts. There are two broad categories: database and entity. Here’s an example:

  • Database: table + field
├ ─ ─ department table │ ├ ─ ─ department ID (field) │ └ ─ ─ department name (field) └ ─ ─ the staff table ├ ─ ─ the employee ID (field) └ ─ ─ employee name (field)Copy the code
  • Entities: class + attributes
├ ─ ─ department class │ ├ ─ ─ department ID (attributes) │ └ ─ ─ department name (attributes) └ ─ ─ the employee class ├ ─ ─ the employee ID (attributes) └ ─ ─ employee name (property)Copy the code

In a database, the association between tables is done by foreign keys, which we won’t talk about, but we’ll look at databases and SQL. Associations between classes within entities can be implemented through annotations, which we will discuss in detail in this article.

Annotations were introduced in JDK 1.5 and are based on reflection. They are very easy to use and are used extensively in Spring. There are two ways to develop entity associations using annotations:

  • @onetoone, @onetomany, @manytoone, @manytomanyThese four notes are of the same kind. In addition to@ManyToOneIn addition to this annotation, the other three annotations have the mappedBy attribute, which is used to associate entities.
  • @JoinColumnAnnotations, used to associate entities. (But I’ll add it anyway@OneToOneThe two annotations are used together.)

It should be noted that these two methods of filling in attributes are different

  • MappedBy: at the class level, all associations are the attribute names in the entity class.
// departmentId: Attribute name in the class
@OneToOne(mappedBy = "departmentId")
Copy the code
  • @joincolumn: JoinColumn level, all columns associated with [JoinColumn]
// department_id: field name in the table
@JoinColumn(name = "department_id")
Copy the code

By the way, @table, @column, @JoinTable, @JoinColumn, @JoinColumns, these are all the same, and all the columns in the database are the names of the columns (underlined ones).

Before we get into annotations, let’s create two entity classes, Department and Employee, each with id and name attributes, as follows.

@Data
@Entity
@Table(name = "pz_department")
public class Department implements Serializable { / / department

    @Id
    @Column(name = "department_id")
    private String departmentId;

    @Column(name = "department_name")
    private String departmentName;

}
Copy the code
@Data
@Entity
@Table(name = "pz_employee")
public class Employee implements Serializable { / / employee

    @Id
    @Column(name = "employee_id")
    private String employeeId;

    @Column(name = "employee_name")
    private String employeeName;

}
Copy the code

The following code will skip over the common code above.

OneToOne one-to-one mapping

Scenario: There is only one employee in a department. Similarly, one employee belongs to only one department.

@JoinColumn

public class Department implements Serializable { // Department entities
    / /... (omitted)
    @OneToOne
    @JoinColumn(name = "own_employee_name", referencedColumnName = "employee_name")
    private Employee ownEmployee;
}
Copy the code

Attributes in annotations:

  • Name: entity’s database field
  • ReferencedColumnName: database field of the entity (omitted if primary key)

@ OneToOne (mappedBy = “…” )

public class Department implements Serializable { // Department entities
    / /... (omitted)
    @OneToOne
    @JoinColumn(name = "own_employee_id")
    private Employee ownEmployee;
}

public class Employee implements Serializable { // Employee entity
    / /... (omitted)
    @OneToOne(mappedBy = "ownEmployee")
    private Department belongDepartment;
}
Copy the code

For the @onetoone annotation, mappedBy has only one way to use it, which is for the other person to associate with him first and for him to associate back. (Therefore, one-to-one one-way association cannot be realized through mappedBy. If mappedBy is used for one-to-one relationship, it must be a two-way association.) The above code does something like this: The Department class first associates the Employee class (via the @JoinColumn annotation) with the employee as one of its attributes. [Employee class] is inversely associated with [department Class] through mappedBy, where the value pointed to by mappedBy is the attribute of the employee class already associated with the department class. In other words, the one-to-one relationship is created and maintained by [department class], and mappedBy itself is not related, it just follows the existing single-layer relationship, and then reversely relates back.

@onetomany one-to-many mapping

Scenario: There are multiple employees in a department.

@JoinColumn

public class Department implements Serializable { // Department entities
    / /... (omitted)
    @OneToMany
    @JoinColumn(name = "employee_name", referencedColumnName = "own_employee_id")
    private List<Employee> ownEmployeeList;
}
Copy the code

Attributes in annotations:

  • Name: database field of the entity
  • ReferencedColumnName: database field of the entity (omitted if primary key)

    (See, it’s the opposite of a one-to-one relationship.)



    That’s an interesting thing, why is this reversed? We will discuss this later in the analysis.

The @onetomany (mappedBy = “…” )

In a one-to-many situation, mappedBy can be used in two ways.

  • The value of mappedBy is the name of its attribute in the other class. (In this case, bidirectional association is required)
public class Department implements Serializable {
    / /... (omitted)
    @OneToMany(mappedBy = "employeeName") // Match your own entity attributes on the other side
    private List<Employee> ownEmployeeList;
}
Copy the code
  • Unassociate the foreign key attribute of the other party without the association of the other party. (In this case, mappedBy is used, but the association is still one-way.)
public class Department implements Serializable {
    / /... (omitted)
    @OneToMany(mappedBy = "departmentId") // Match each other's foreign keys
    private List<Employee> ownEmployeeList;
}
Copy the code

However, there is a precondition for such a one-way association: when the foreign key of the other party is associated with itself, it must be associated with its own primary key. It’s a little bit easier. I’m not going to draw it.

@manytoOne Many-to-one mapping

Scenario: Multiple employees belong to the same department.

@JoinColumn

public class Employee implements Serializable { // Employee entity
    / /... (omitted)
    @ManyToOne
    @JoinColumn(name = "belong_department_name", referencedColumnName = "department_name")
    private Department belongDepartment;
}
Copy the code

Attributes in annotations:

  • Name: entity’s database field
  • ReferencedColumnName: database field of the entity (omitted if primary key)

Many-to-one associations (ManyToOne) and OneToOne associations (OneToOne) are exactly the same when @joinColumn is used. In other words, one-to-one, many-to-one association and one-to-many association are opposite in name and referencedColumnName, which will be analyzed later.

mappedBy

@manyToOne Does not have the mappedBy attribute. The principle of mappedBy is to delegate the task of association to the opposite party. There are N employees and only one department, and the employee asks the department to maintain the association. A department cannot associate with N employees at the same time, so there is no mappedBy attribute.

@manytomany Many-to-many mapping

Scenario: There are multiple employees in a department, but at the same time, an employee can belong to multiple departments.

@JoinTable

public class Department implements Serializable { // Department entities
    / /... (omitted)
    @ManyToMany
    @JoinTable(name = "pz_ref", joinColumns = {@JoinColumn(name = "ref_department_id")}, inverseJoinColumns = {@JoinColumn(name = "ref_employee_id")})
    @JSONField(serialize = false)
    private List<Employee> employeeList;
}
Copy the code

Many-to-many associations require you to create your own intermediate table. If you think about it a little bit, many-to-many, and both sides are many, you can’t have one side associated with the other with a foreign key, so you have to have an intermediate table. (You don’t need to create an entity class for this intermediate table, though). In addition to a new table, even the annotations have been changed. It used to be @joinColumn, join to the field, now it’s @joinTable, join to the table.

Attributes in annotations:

  • Name: [intermediate table name]
  • JoinColumns: [own table] join [middle table] (as @onetomany)
  • InverseJoinColumns: @onetomany inverseJoinColumns: @onetomany

@ ManyToMany (mappedBy = “…” )

public class Employee implements Serializable { // Employee entity
    / /... (omitted)
    @ManyToMany(mappedBy = "employeeList")
    private List<Department> departmentList;
}
Copy the code

There is only one way to use it, which is the same as OneToOne and OneToMany: the mappedBy attribute value is the name of its own attribute in the opposite entity class, which must be bidirectional.

A concrete analysis

The four types of relationships: one-to-one, one-to-many, many-to-one and many-to-many, have all been gone through. Now let’s analyze the two annotations (@jointable won’t bother to mention them).

@JoinColumn

The @JoinColumn that I understand, which is essentially @column, is essentially not doing an association, it’s doing a mapping, it’s mapping the database to the entity, and you can do that with annotations like this: a field in the database corresponds to a field in the entity class. So what it’s doing, instead of associating departments with entities, is mapping tables with entities (but at the same time, associating two entities).

@OneToOne
@joinColumn (name = "own ", referencedColumnName =" other ")

@OneToMany
@joincolumn (name = "referencedColumnName ", referencedColumnName =" own ")

@ManyToOne
@joinColumn (name = "own ", referencedColumnName =" other ")
Copy the code

Just now, we found that the use methods of [1-1] and [n-1] are the same, but [1-n] is just reversed. This is why. That’s because @JoinColumn doesn’t care what entity class it’s in, its name property always points to a foreign key. Because the foreign key is always on the many side (one to one defaults to many), the name attribute value is the foreign key of the many side. I am not clear about @joinColumn automatic table creation.

MappedBy = “…”

MappedBy is usually used for bidirectional associations, and for @onetoone and @manytoone, mappedBy can only do bidirectional associations. As we pointed out at the beginning of this article, mappedBy operates on the entity class and its value is the attribute name of this class in the other class. Let’s explain it again. It should wait for the other party to associate with itself, and then follow the layer [established connection] back.

The idea is that A is related to B, and B should not be able to re-associate A (you can do that if you want to), but should automatically find its way back based on the relationship between A and B. This is called:

This class abandons the control of the association, and the association is controlled by the other party.

One strange thing is that there is a common way to use the three annotations that can use the mappedBy attribute: @onetoone, @onetomany, and @manytoone. But there is a second use for @onetomany, which seems to point to the foreign key property of the other class using mappedBy without having to establish the connection.

The principle of doing this is to still let the other party maintain the association, but the other party’s foreign key must be associated with its own primary key. (If @joinColumn is used, the other party’s foreign key can be associated with its own key.)

That is, in a one-to-many relationship, [one party] wants to associate [many parties], but does not want to maintain the association itself (because for one-to-many, the code automatically creates a new table to maintain the association), so [one party] uses mappedBy to let the opposite party handle the association. So how do we associate the opposite side? We associate it with a foreign key and a primary key.

Problems encountered in use

Automatic conversion of hump and underline nomenclature

I’m not sure if it’s Spring or Hibernate, but the framework now automatically maps underlined naming from the database to hump naming from entity classes. For example, attributes in an entity class would normally be mapped via @column.

@Table(name = "pz_department")
public class Department implements Serializable {

    // This is annotated by @column
    // Map department_name from department table to departmentName from department class
    @Column(name = "department_name")
    private String departmentName;

}
Copy the code

But in fact, even without the @Column annotation, the frame can map automatically.

@Table(name = "pz_department")
public class Department implements Serializable {

    Department_name = departmentName = departmentName = departmentName
    // The framework can automatically convert the underline name to the camel name
    private String departmentName;

}
Copy the code

But! Don’t do it! If the @Column annotation is not added to the database, the fields in the new table will not be the names of the fields in the original table (underlined), but will be the names of the attributes in the entity class (camel name). In this case, an error will be reported when the association is performed.

Caused by: org.hibernate.MappingException: Unable to find column with logical name: employee_name in org.hibernate.mapping.Table(pz_employee) and its related supertables and secondary tables
Copy the code

Table employee_name not found in pz_EMPLOYEE table Error cause: Field name is [employeeName] in other related tables (created automatically).

Print circularly for bidirectional association

Departments associate with employees, employees associate with departments, and departments associate with employees… The program itself runs fine, but if it is printed, it creates an endless loop of associations until it overflows. To solve this problem, add jSON-related annotations to the property in one of the classes so that the property is not serialized. For example, via the @jsonField (serialize = false) annotation in Fastjson, or @jsonIgnore.

@JSONField(serialize = false)
private Department department;
Copy the code

@ JoinColumn (name = “…” Property mappings cannot be duplicated

As analyzed above, the @JoinColumn annotation is essentially a mapping between a database and an entity class. If a field in a database is already mapped to a property, and the name property in @JoinColumn is mapped again, the question arises: Which one is mapped to?

@Column(name = "belong_department_id")
private String belongDepartmentId;

@ManyToOne
@JoinColumn(name = "belong_department_id", referencedColumnName = "department_id")
private Department belongDepartment;
Copy the code

For example, this code will return an error:

Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: com.app.ykym.modules.test.entityAndRepository.Employee column: belong_department_id (should be mapped with insert="false" update="false")
Copy the code

Insert = “false” update= “false” (in the annotation), which means that one of the attributes has no update and insert privileges to the database. (But @onetomany, @joinColumn (name = “… ) is repeatable.