你不会还搞不清楚Spring Data JPA的关联关系注解如何使用吧?
Posted c.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你不会还搞不清楚Spring Data JPA的关联关系注解如何使用吧?相关的知识,希望对你有一定的参考价值。
文章目录
- 你不会还搞不清楚Spring Data JPA的关联关系注解如何使用吧?
你不会还搞不清楚Spring Data JPA的关联关系注解如何使用吧?
应该不止我一个人搞不清楚Spring Data JPA的关联关系注解吧?就是平时我们是用的@OneToOne
,@OneToMany
还有@ManyToOne
还有相关的@JoinColumn
注解。参考:Multiplicity in Entity Relationships
可能平时只是会用,但是具体怎么设置以及注解上每个属性的作用可能还不是了解的特别清楚,以及关联关系怎么去维护等等。所以这篇文章就是带你深入了解Spring Data JPA的关联关系注解的使用。文章主要关注在@OneToOne
,@OneToMany
还有@ManyToOne
还有相关的@JoinColumn
注解的使用。@ManyToMany
平时工作基本用的比较少(反正在我工作中目前还没有怎么使用过),所以本篇博文就不会关注@ManyToMany
的使用。
我们先来简单了解一下@OneToOne
,@OneToMany
还有@ManyToOne
还有相关的@JoinColumn
注解。
@JoinColumn
注解的作用:用来指定与所操作实体或实体集合相关联的数据库表中的列字段。@JoinColumn
主要配合@OneToOne
、@ManyToOne
、@OneToMany
一起使用,单独使用没有意义。
由于 @OneToOne
(一对一)、@OneToMany
(一对多)、@ManyToOne
(多对一)、@ManyToMany
(多对多) 等注解只能确定实体之间几对几的关联关系,它们并不能指定与实体相对应的数据库表中的关联字段,因此,需要与@JoinColumn
注解来配合使用。
我们先来介绍一下@JoinColumn
注解,之后我们再来介绍一下@JoinColumn
注解配合其他@OneToOne
(一对一)、@OneToMany
(一对多)、@ManyToOne
(多对一)的使用
@JoinColumn
package javax.persistence;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static javax.persistence.ConstraintMode.PROVIDER_DEFAULT;
/**
* Specifies a column for joining an entity association or element
* collection. If the <code>JoinColumn</code> annotation itself is
* defaulted, a single join column is assumed and the default values
* apply.
*
* <pre>
* Example:
*
* @ManyToOne
* @JoinColumn(name="ADDR_ID")
* public Address getAddress() return address;
*
*
* Example: unidirectional one-to-many association using a foreign key mapping
*
* // In Customer class
* @OneToMany
* @JoinColumn(name="CUST_ID") // join column is in table for Order
* public Set<Order> getOrders() return orders;
* </pre>
*
* @see ManyToOne
* @see OneToMany
* @see OneToOne
* @see JoinTable
* @see CollectionTable
* @see ForeignKey
*
* @since 1.0
*/
@Repeatable(JoinColumns.class)
@Target(METHOD, FIELD)
@Retention(RUNTIME)
public @interface JoinColumn
/**
* (Optional) The name of the foreign key column.
* The table in which it is found depends upon the
* context.
* <ul>
* <li>If the join is for a OneToOne or ManyToOne
* mapping using a foreign key mapping strategy,
* the foreign key column is in the table of the
* source entity or embeddable.
* <li> If the join is for a unidirectional OneToMany mapping
* using a foreign key mapping strategy, the foreign key is in the
* table of the target entity.
* <li> If the join is for a ManyToMany mapping or for a OneToOne
* or bidirectional ManyToOne/OneToMany mapping using a join
* table, the foreign key is in a join table.
* <li> If the join is for an element collection, the foreign
* key is in a collection table.
*</ul>
*
* <p> Default (only applies if a single join column is used):
* The concatenation of the following: the name of the
* referencing relationship property or field of the referencing
* entity or embeddable class; "_"; the name of the referenced
* primary key column.
* If there is no such referencing relationship property or
* field in the entity, or if the join is for an element collection,
* the join column name is formed as the
* concatenation of the following: the name of the entity; "_";
* the name of the referenced primary key column.
*/
String name() default "";
/**
* (Optional) The name of the column referenced by this foreign
* key column.
* <ul>
* <li> When used with entity relationship mappings other
* than the cases described here, the referenced column is in the
* table of the target entity.
* <li> When used with a unidirectional OneToMany foreign key
* mapping, the referenced column is in the table of the source
* entity.
* <li> When used inside a <code>JoinTable</code> annotation,
* the referenced key column is in the entity table of the owning
* entity, or inverse entity if the join is part of the inverse
* join definition.
* <li> When used in a <code>CollectionTable</code> mapping, the
* referenced column is in the table of the entity containing the
* collection.
* </ul>
*
* <p> Default (only applies if single join column is being
* used): The same name as the primary key column of the
* referenced table.
*/
String referencedColumnName() default "";
/**
* (Optional) Whether the property is a unique key. This is a
* shortcut for the <code>UniqueConstraint</code> annotation at
* the table level and is useful for when the unique key
* constraint is only a single field. It is not necessary to
* explicitly specify this for a join column that corresponds to a
* primary key that is part of a foreign key.
*/
boolean unique() default false;
/** (Optional) Whether the foreign key column is nullable. */
boolean nullable() default true;
/**
* (Optional) Whether the column is included in
* SQL INSERT statements generated by the persistence
* provider.
*/
boolean insertable() default true;
/**
* (Optional) Whether the column is included in
* SQL UPDATE statements generated by the persistence
* provider.
*/
boolean updatable() default true;
/**
* (Optional) The SQL fragment that is used when
* generating the DDL for the column.
* <p> Defaults to the generated SQL for the column.
*/
String columnDefinition() default "";
/**
* (Optional) The name of the table that contains
* the column. If a table is not specified, the column
* is assumed to be in the primary table of the
* applicable entity.
*
* <p> Default:
* <ul>
* <li> If the join is for a OneToOne or ManyToOne mapping
* using a foreign key mapping strategy, the name of the table of
* the source entity or embeddable.
* <li> If the join is for a unidirectional OneToMany mapping
* using a foreign key mapping strategy, the name of the table of
* the target entity.
* <li> If the join is for a ManyToMany mapping or
* for a OneToOne or bidirectional ManyToOne/OneToMany mapping
* using a join table, the name of the join table.
* <li> If the join is for an element collection, the name of the collection table.
* </ul>
*/
String table() default "";
/**
* (Optional) Used to specify or control the generation of a
* foreign key constraint when table generation is in effect. If
* this element is not specified, the persistence provider's
* default foreign key strategy will apply.
*
* @since 2.1
*/
ForeignKey foreignKey() default @ForeignKey(PROVIDER_DEFAULT);
我们来看看@JoinColumn
注解中的每个属性的作用吧
-
name: 外键列的名称(数据库中的列名称)。外键在哪个表取决于使用
@OneToOne
或者@ManyToOne
还是@OneToMany
,具体的我们在后面跟其他注解一起配合讲解。- If the join is for a OneToOne or ManyToOne mapping using a foreign key mapping strategy, the foreign key column is in the table of the source entity or embeddable.
- If the join is for a unidirectional OneToMany mapping using a foreign key mapping strategy, the foreign key is in the table of the target entity.
- If the join is for a ManyToMany mapping or for a OneToOne or bidirectional ManyToOne/OneToMany mapping using a join table, the foreign key is in a join table.
- If the join is for an element collection, the foreign key is in a collection table.
如果我们不配置name属性(默认为空字符串),则会帮我们生成一个外键列的名称(一般推荐自己命名,不使用自动生成),生成的逻辑如下:
Default (only applies if a single join column is used): The concatenation of the following: the name of the referencing relationship property or field of the referencing entity or embeddable class;
"_"
; the name of the referenced primary key column. If there is no such referencing relationship property or field in the entity, or if the join is for an element collection, the join column name is formed as the concatenation of the following: the name of the entity;"_"
; the name of the referenced primary key column. -
referencedColumnName: 此外键列引用的列的名称(就是这个外键是关联到哪个表的列,也是数据库中的列名),那如果不设置这个属性(默认为空字符串),则会使用被关联表的主键列名
Default (only applies if single join column is being used): The same name as the primary key column of the referenced table.
-
unique: 外键列是否为唯一键,默认值为false
(Optional) Whether the property is a unique key. This is a shortcut for the UniqueConstraint annotation at the table level and is useful for when the unique key constraint is only a single field. It is not necessary to explicitly specify this for a join column that corresponds to a primary key that is part of a foreign key
-
nullable: 外键列是否可以为空值。默认值为true
(Optional) Whether the foreign key column is nullable.
-
insertable:是否跟随一起新增
(Optional) Whether the column is included in SQL INSERT statements generated by the persistence provider.
-
updatable:是否跟随一起新增
(Optional) Whether the column is included in SQL UPDATE statements generated by the persistence provider.
-
columnDefinition:指定SQL片段来创建外键列
(Optional) The SQL fragment that is used when generating the DDL for the column.
-
table
(Optional) The name of the table that contains the column. If a table is not specified, the column is assumed to be in the primary table of the applicable entity.
Default:- If the join is for a OneToOne or ManyToOne mapping using a foreign key mapping strategy, the name of the table of the source entity or embeddable.
- If the join is for a unidirectional OneToMany mapping using a foreign key mapping strategy, the name of the table of the target entity.
- If the join is for a ManyToMany mapping or for a OneToOne or bidirectional ManyToOne/OneToMany mapping using a join table, the name of the join table.
- If the join is for an element collection, the name of the collection table.
-
foreignKey : 用于表生成时指定外键约束
(Optional) Used to specify or control the generation of a foreign key constraint when table generation is in effect. If this element is not specified, the persistence provider’s default foreign key strategy will apply.
一般我们是用@JoinColumn
注解的时候其实大多数只会用到name
和referencedColumnName
两个属性,有时候还可能会用到foreignKey
属性,因为我们自动创建表的时候不想使用到外键约束。
参考: https://docs.oracle.com/javaee/7/api/javax/persistence/JoinColumn.html
@OneToOne
和 @JoinColumn
我们先来看看@OneToOne
注解
/**
* Specifies a single-valued association to another entity that has
* one-to-one multiplicity. It is not normally necessary to specify
* the associated target entity explicitly since it can usually be
* inferred from the type of the object being referenced. If the relationship is
* bidirectional, the non-owning side must use the <code>mappedBy</code> element of
* the <code>OneToOne</code> annotation to specify the relationship field or
* property of the owning side.
*
* <p> The <code>OneToOne</code> annotation may be used within an
* embeddable class to specify a relationship from the embeddable
* class to an entity class. If the relationship is bidirectional and
* the entity containing the embeddable class is on the owning side of
* the relationship, the non-owning side must use the
* <code>mappedBy</code> element of the <code>OneToOne</code>
* annotation to specify the relationship field or property of the
* embeddable class. The dot (".") notation syntax must be used in the
* <code>mappedBy</code> element to indicate the relationship attribute within the
* embedded attribute. The value of each identifier used with the dot
* notation is the name of the respective embedded field or property.
*
* <pre>
* Example 1: One-to-one association that maps a foreign key column
*
* // On Customer class:
*
* @OneToOne(optional=false)
* @JoinColumn(
* name="CUSTREC_ID", unique=true, nullable=false, updatable=false)
* public CustomerRecord getCustomerRecord() return customerRecord;
*
* // On CustomerRecord class:
*
* @OneToOne(optional=false, mappedBy="customerRecord")
* public Customer getCustomer() return customer;
*
*
* Example 2: One-to-one association that assumes both the source and target share the same primary key values.
*
* // On Employee class:
*
* @Entity
* public class Employee
* @Id Integer id;
*
* @OneToOne @MapsId
* EmployeeInfo info;
* ...
*
*
* // On EmployeeInfo class:
*
* @Entity
* public class EmployeeInfo
* @Id Integer id;
* ...
*
*
*
* Example 3: One-to-one association from an embeddable class to another entity.
*
* @Entity
* public class Employee
* @Id int id;
* @Embedded LocationDetails location;
* ...
*
*
* @Embeddable
* public class LocationDetails
* int officeNumber;
* @OneToOne ParkingSpot parkingSpot;
* ...
*
*
* @Entity
* public class ParkingSpot
* @Id int id;
* String garage;
* @OneToOne(mappedBy="location.parkingSpot") Employee assignedTo;
* ...
*
*
* </pre>
*
* @since 1.0
*/
@Target(METHOD, FIELD)
@Retention(RUNTIME)
public @interface OneToOne
/**
* (Optional) The entity class that is the target of
* the association.
*
* <p> Defaults to the type of the field or property
* that stores the association.
*/
Class targetEntity() default void.class;
/**
* (Optional) The operations that must be cascaded to
* the target of the association.
*
* <p> By default no operations are cascaded.
*/
CascadeType[] cascade() default ;
/**
* (Optional) Whether the association should be lazily
* loaded or must be eagerly fetched. The EAGER
* strategy is a requirement on the persistence provider runtime that
* the associated entity must be eagerly fetched. The LAZY
* strategy is a hint to the persistence provider runtime.
*/
FetchType fetch() default EAGER;
/**
* (Optional) Whether the association is optional. If set
* to false then a non-null relationship must always exist.
*/
boolean optional() default true;
/** (Optional) The field that owns the relationship. This
* element is only specified on the inverse (non-owning)
* side of the association.
*/
String mappedBy() default "";
/**
* (Optional) Whether to apply the remove operation to entities that have
* been removed from the relationship and to cascade the remove operation to
* those entities.
* @since 2.0
*/
boolean orphanRemoval() default false;
我们来看看注解上面属性的作用吧
- targetEntity:指定关联目标的实体类,一般不使用,因为JPA可以通过使用了@OneToOne的字段引用类型推断出来。
(Optional) The entity class that is the target of the association.
Defaults to the type of the field or property that stores the association. - cascade:与关联实体的级联方式,默认没有任何级联操作默认值为:
(Optional) The operations that must be cascaded to the target of the association.
By default no operations are cascaded. - fetch:关联实体的加载方式。是立即加载还是使用懒加载,默认为
FetchType.EAGER
立即加载(Optional) Whether the association should be lazily loaded or must be eagerly fetched. The EAGER strategy is a requirement on the persistence provider runtime that the associated entity must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime.
- optional:是否允许为空,默认为
true
(Optional) Whether the association is optional. If set to false then a non-null relationship must always exist.
- mappedBy:关联关系被谁维护,默认为
""
(Optional) The field that owns the relationship. This element is only specified on the inverse (non-owning) side of the association.
- orphanRemoval: 是否级联删除,默认值为
false
(Optional) Whether to apply the remove operation to entities that have been removed from the relationship and to cascade the remove operation to those entities.
一般我们是用@OneToOne
最常使用的就是cascade
,fetch
(不过在一对一的情况下,一般都是用FetchType.EAGER
立即加载),还有mappedBy
,orphanRemoval
参考:https://docs.oracle.com/javaee/7/api/javax/persistence/OneToOne.html
现在我们来从代码来看看,创建了一个一对一的关系,employee和employee info是一对一的关系。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee")
public class Employee
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
private EmployeeInfo employeeInfo;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee_info")
public class EmployeeInfo
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String departmentName;
这个时候我们自动建表的语句如下:
Hibernate:
create table employee (
id bigint not null auto_increment,
name varchar(255),
employee_info_id bigint,
primary key (id)
) engine=InnoDB
Hibernate:
create table employee_info (
id bigint not null auto_increment,
department_name varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
alter table employee
add constraint FKmn4awjrpongc0bglj2co4ji1x
foreign key (employee_info_id)
references employee_info (id)
可以看出来,当我们在employee实体中加入@OneToOne
关联employee info的时候,就会在employee
表中加入一个column employee_info_id
来关联employee_info
表。并且会生成外键约束。
前面说到了@JoinColumn
不能单独使用,要跟其他关联注解一起使用。这个时候我们就可以通过@JoinColumn
注解和@OneToOne
来设置这些。前面说到了@JoinColumn
注解的name属性是来设置外键列的名称的。在employee
表中会有一个指向employee_info
表主键的字段employee_info_id
,所以主控方或者叫做owning side.(指能够主动改变关联关系的一方)一定是employee
,因为只要改变employee
表的employee_info_id
就改变了employee
与employee_info
之间的关联关系,所以@JoinColumn
要写在员工实体类Employee
上,自然而然地,EmployeeInfo
就是被控方或者叫做non-owning side。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee")
public class Employee
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "info_id") //加上 @JoinColumn指定外键列的字段名
private EmployeeInfo employeeInfo;
从创建语句可以看出,生成的外键字段名称就从默认的employee_info_id
(字段默认的命名规则:被控方类名_被控方主键,参考上面的@JoinColumn
的name属性的default规则)变成了info_id
create table employee (
id bigint not null auto_increment,
name varchar(255),
info_id bigint,
primary key (id)
) engine=InnoDB
Hibernate:
create table employee_info (
id bigint not null auto_increment,
department_name varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
alter table employee
add constraint FK8yw6mmam5rw2fphbh1g985guh
foreign key (info_id)
references employee_info (id)
还可以使用referencedColumnName
属性来指定外键是关联到哪个表的列,不设置默认是关联表的主键。同时我们还可以使用foreignKey
属性来控制外键约束,比如不设置外键约束,因为我们比较少用外键约束,一般从代码层面控制。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee")
public class Employee
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "info_id",referencedColumnName = "employee_info_id",foreignKey = @ForeignKey(NO_CONSTRAINT))
private EmployeeInfo employeeInfo;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee_info")
public class EmployeeInfo
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String departmentName;
@Column(name = "employee_info_id")
private Long employeeInfoId;
建表语句如下:
Hibernate:
create table employee (
id bigint not null auto_increment,
name varchar(255),
info_id bigint,
primary key (id)
) engine=InnoDB
Hibernate:
create table employee_info (
id bigint not null auto_increment,
department_name varchar(255),
employee_info_id bigint,
primary key (id)
) engine=InnoDB
Hibernate:
alter table employee_info
add constraint UK_f9aet7061wab7k5b1s3wr1t8n unique (employee_info_id)
如果你把foreignKey
属性去掉,使用外键约束就可以看到建表语句会加上这么一句外键约束。说明现在employee
的info_id
字段关联的是employee_info
表的employee_info_id
字段。
alter table employee
add constraint FK8yw6mmam5rw2fphbh1g985guh
foreign key (info_id)
references employee_info (employee_info_id)
@OneToOne
的级联操作
接下来来看看@OneToOne
的级联保存,首先我们来看看实体类的内容如下:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee")
public class Employee
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
@JoinColumn(name = "info_id")
private EmployeeInfo employeeInfo;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee_info")
public class EmployeeInfo
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String departmentName;
然后我们看看级联保存的操作如下:
EmployeeInfo department = EmployeeInfo.builder()
.departmentName("test department").build();
Employee employee = Employee.builder()
.name("test employee")
.employeeInfo(department).build();
employeeRepository.save(employee);
结果发现保存的时候报错如下:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : org.example.entity.Employee.employeeInfo -> org.example.entity.EmployeeInfo; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : org.example.entity.Employee.employeeInfo -> org.example.entity.EmployeeInfo
这个原因是因为级联保存没有开启。我们只需要加上@OneToOne(cascade = CascadeType.PERSIST)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "employee")
public class Employee
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "info_id")
private EmployeeInfo employeeInfo;
之后我们保存就能成功了,而且employee
和employee_info
表中都保存了数据,并且外键info_id
也指定好了
接下来试试级联更新,首先是插入了数据,然后我们把两个实体的数据都一并修改了。
Employee employee = employeeService.saveEmployee(); // 跟上面保存一样插入了两个表的数据
employee.setName("test employee 2");
employee.getEmployeeInfo().setDepartmentName("test department 2");
employeeRepository.save(employee);
结果保存完发现,只是更新了Employee
的数据,但是EmployeeInfo
的数据并没有改变,这个原因就是因为没有开启级联更新
@OneToOne(cascade = CascadeType.PERSIST,CascadeType.MERGE)
@JoinColumn(name = "info_id")
private EmployeeInfo employeeInfo;
当我们开启了CascadeType.MERGE
就可以发现能够级联更新了。
接下来看看级联删除
Employee employee = employeeService.saveEmployee();
employeeRepository.deleteById(1L);
运行之后发现只有Employee
被删除了,但是EmployeeInfo
还保留着,如果你想在Employee
删除的同事也想把EmployeeInfo
删除,就需要开启级联删除CascadeType.REMOVE
,开启之后你就能够发现能够正常级联删除了。
以上是关于你不会还搞不清楚Spring Data JPA的关联关系注解如何使用吧?的主要内容,如果未能解决你的问题,请参考以下文章
我是新人,我还搞不清楚定义一个整型integer和string的区别
还搞不懂STL的type_traits?从源码来带你一起分析!!!