Spring Data JPA的@Entity注解

基础注解

@Entity

源码

public @interface Entity {
		// 可选,默认是此实体类的名字,全局唯一
    String name() default "";
}

@Entity定义对象将会成为被JPA管理的实体,将映射到指定的数据库表。

@Table

用于指定数据库表的表名

源码

package javax.persistence;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
  	// 表的名字,可选。如果不填写,系统认为和实体的名字一样为表名
    String name() default "";
  	// 此表的catalog,可选
    String catalog() default "";
  	// 此表的schema,可选
    String schema() default "";
  	// 唯一性约束,只有创建表的时候有用,默认不需要
    UniqueConstraint[] uniqueConstraints() default {};
  	//索引,只有创建表的时候使用,默认不需要
    Index[] indexes() default {};
}

@Id

@Id定义属性为数据库的主键,一个实体里面必须有一个

@GeneratedValue

源码

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratedValue {
  	// Id的生成策略
    GenerationType strategy() default GenerationType.AUTO;
		// 通过Sequence生成Id,常见的是Oracle数据库Id生成规则,需要配合@SequenceGenerator使用
    String generator() default "";
}

GenerationType一共有四个值

public enum GenerationType {
		// 通过表产生主键,框架由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植
    TABLE,
    // 通过序列产生主键,通过@SequenceGenerator注解指定序列名称,MySQL不支持这种方式
    SEQUENCE,
    // 采用数据库ID自增长,一般用于MYSQL数据库
    IDENTITY,
    // JPA自动选择合适的策略,是默认选项。
    AUTO;

    private GenerationType() {
    }
}

@Basic

@Basic表示属性是到数据库表的字段的映射。如果实体的字段没有任何注解,默认即为@Basic

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Basic {
  	// 可选,EAGER为立即加载,LAZY为延迟加载
    FetchType fetch() default FetchType.EAGER;
		// 可选,设置这个字段是否为null,默认为true
    boolean optional() default true;
}

@Transient

@Transient表示该属性并非一个到数据库表字段的映射,表示持久化属性,与@Basic作用相反。JPA映射数据库的时候就会忽略它。

@Column

源码

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
		// 数据库中表的列明。可选,默认为与实体属性名保持一样,如果实体属性名中由大写,它会自动分割
    String name() default "";
		// 是否唯一,默认false,可选
    boolean unique() default false;
		// 数据字段是否允许为空,可选,默认为true
    boolean nullable() default true;
		// 执行insert操作的时候是否包含此字段,可选,默认为true
    boolean insertable() default true;
    // 执行update的时候是否包含此字段,可选,默认为true
    boolean updatable() default true;
		// 表示该字段在数据库中的实际类型
    String columnDefinition() default "";
		// 数据库字段的长度,可选,默认为255
    int length() default 255;
}

@Temporal

@Temporal用来设置Date类型的属性映射到对应精准的字段。

  • @Temporal(TemporalType.DATA)映射为日期 --data
  • @Temporal(TemporalType.TIME)映射为日期 --time
  • @Temporal(TemporalType.TIMESTAMP)映射为日期 --data time

@Enumerated

@Enumerated很好用,直接映射enum枚举类型的字段

源码

public @interface Enumerated {
		// 枚举映射的类型,默认为ORDINAL
    EnumType value() default EnumType.ORDINAL;
}

EnumType可选:

  • ORDINAL:映射枚举的下标
  • SIRING:映射枚举的name

示例:

// 定义一个枚举类
public enum Gender{
	MAIL("男性"),FMAIL("女性");
	
	private String value;
	
	private Gender(String value){
		this.valus = valus;
	}

}

//实体类中的使用
@Entity
@Table(name = "tb_user")
public class User implements Serializable{
	@Enumerated(EnumType.STRING)
	@Column(name = "user_gender")
	private Gender gender;

}

这时插入两条数据,数据库里面的值是MAIL/FMAIL,而不是男性、女性,如果是我们用@Enumerated(EnumType.ORDINAL),那么这时数据库里存储的是0,1。但是实际工作中不建议这样做,因为下标使会发生变化。

@Lob

Lob将属性映射为数据库支持的大对象类型,支持以下两种数据库类型的字段

  • Clob类型是长字符串类型,java.sql.Clob、Character[]、char[]和String将被映射为Clobl类型
  • Blob类型是字节类型,java.sql.Blob、Byte[]、byte[]和实现了Serializable接口的类型将被映射为Blob类型
  • Clob、Blob占用内存空间较大,一般配合@Basic(fatch=FechType.LAZY)将其设置为延迟加载

关联关系注解

@JoinColumn

定义外键关联的字段名称

public @interface JoinColumn {
		// 目标表的字段名,必填
    String name() default "";
		// 本实体的字段名,非必填,默认为本表ID
    String referencedColumnName() default "";
		// 外键字段是否唯一,默认为false
    boolean unique() default false;
		// 外键字段是否允许为空,默认为允许
    boolean nullable() default true;
		// 是否跟随一起新增
    boolean insertable() default true;
		// 是否跟随一起修改
    boolean updatable() default true;
}

用法:主要配合@OneToOne、@ManyToOne、@OneToMany一起使用,但是使用没有意义。

@OneToOne

源码

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OneToOne {
		// 关系目标实体,非必填,默认为该字段的类型
    Class targetEntity() default void.class;
		// cascade 级联操作策略
    	// PERSIST 级联新建
    	// REMOVE 级联删除
    	// REFRESH 级联刷新
    	// MERGE 级联更新
    	// ALL 四项权限
    CascadeType[] cascade() default {};
		// 数据获取方式,立即加载和延迟加载
    FetchType fetch() default FetchType.EAGER;
		// 是否允许为空
    boolean optional() default true;
		// 关联关系被谁维护,非必填,一般不需要特别指定
    String mappedBy() default "";
		// 是否级联删除,和CascadeType.REMOVE的效果一样,只要配置了两种的一种就会自动级联删除
    boolean orphanRemoval() default false;
}

用法:@OneToOne需要配合@JoinCloumn一起使用。注意:可以双相关联,也可以只配置一方,需要视需求而定。

示例:假设一个部门只有一个员工

@OneToOne
@JoinColumn(name="employee_id",referencedColumnName = "id")
private Employee employeeAttribute = new Employee();

employee_id指的是Department里面的字段,而referencedColumnName="id"指的是Employee表里面的字段

如果需要双相关联,Employee的内容如下

@OneToOne(mappedBy = "employeeAttribute")
private Department department;

当然不使用mappedBy,和下面效果是一样的

@OneToOne
@JoinColumn(name = "id", referencedColumnName = "employee_id")
private Employee employeeAttribute = new Employee();

@OneToMany与@ManyToOne

@OneToMany与@ManyToOne可以相对存在,也可以只存在一方

@Entity
@Table(name = "user")
public class User {
    @Id
    private Long id;

    @OneToMany(
            cascade = CascadeType.ALL,
            fetch = FetchType.LAZY,
            mappedBy = "user"
    )
    private Set<Role> setRole;

}

@Entity
@Table(name = "role")
public class Role {
    @Id
    private Long id;

    @ManyToOne(
            cascade = CascadeType.ALL,
            fetch = FetchType.EAGER
    )
    @JoinColumn(name = "user_id")
    private User user;
}

@OrderBy关联查询时排序

public @interface OrderBy {
		// 要排序的字段,格式如下
		//  orderby_list::=orderby_item[,orderby_item]*
		//  orderby_item::=[property_or_field_name][ASC|DESC]
    String value() default "";
}

用法

@Entity
@Table(name = "user")
public class User {
    @Id
    private Long id;

    @OneToMany(
            cascade = CascadeType.ALL,
            fetch = FetchType.LAZY,
            mappedBy = "user"
    )
    @OrderBy("roleName DESC ")
    private Set<Role> setRole;

}

OrderBy中的字段对应的是实体中的名称

@JoinTable

如果对象与对象之间有一个关联关联表的时候,就会用到@JoinTable,一般和@ManyToMany一起使用。

源码:

public @interface JoinTable {
		// 中间关联关系表名
    String name() default "";
		// 表的catelog
    String catalog() default "";
		// 表的schema
    String schema() default "";
		// 主链接表的字段
    JoinColumn[] joinColumns() default {};
		// 被联机的表外键字段
    JoinColumn[] inverseJoinColumns() default {};
}

示例:

假设Blog和Tag是多对多的关系,有一个关联关系表blog_tag_relation,表中有两个属性blog_id和tag_id,那么Blog实体里面的写法如下:

@Entity
public class Blog {
    @Id
    private Long id;
    @ManyToMany
    @JoinTable(
            name = "blog_tag_relation",
            joinColumns = @JoinColumn(name = "blog_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")

    )
    private List<Tag> tags = new ArrayList<>();
}

@ManyToMany

源码

public @interface ManyToMany {
    Class targetEntity() default void.class;

    CascadeType[] cascade() default {};

    FetchType fetch() default FetchType.LAZY;

    String mappedBy() default "";
}

@ManyToMany表示多对多,和@OneToOne、@ManyToOne一样也有单向、双向之分。单向双向和注解没有关系,只看实体类之间是否相互引用。

示例:一个文章可以有多个标签,一个标签也可以在多个文章上,Blog和Tag就是多对多的关系

// 单向多对多
@Entity
public class Blog {
    @Id
    private Long id;
    @ManyToMany
    @JoinTable(
            name = "blog_tag_relation",
            joinColumns = @JoinColumn(name = "blog_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")

    )
    private List<Tag> tags = new ArrayList<>();
}


@Entity
public class BlogTagRelation {
    @Column(name = "blog_id")
    private Integer blogId;

    @Column(name = "tag_id")
    private Integer tag_id;

}

// 双向多对多
@Entity
public class Blog {
    @Id
    private Long id;
    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(
            name = "blog_tag_relation",
            joinColumns = @JoinColumn(name = "blog_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id")

    )
    private List<Tag> tags = new ArrayList<>();
}
@Entity
public class Tag {
    @Id
    private String id;

    @ManyToMany(mappedBy = "BlogTagRelation")
    private List<Blog> blog = new ArrayList<>();
}

@EntityGraph

JPA2.1推出的@EnttiyGraph、@NamedEntityGraph用来提高查询效率,很好地解决了N+1条SQL的问题。两者配合起来使用,缺一不可。

1、现在Entity里面定义@NamedEntityGraph,其他不变,其中,@NamedAttributeNode可以有多个,也可以有一个

@NamedEntityGraph(
        name = "UserInfoEntity.addressEntityList",
        attributeNodes = {
                @NamedAttributeNode("addressEntityList"),
                @NamedAttributeNode("userBlogEntityList")
        })
@Entity(name = "UserInfoEntity")
@Table(name = "user_info", schema = "test")
public class UserInfoEntity implements Serializable {
    @Id
    @Column(name = "id", nullable = true)
    private Integer id;

    @OneToOne(optional = true)
    @JoinColumn(
            referencedColumnName = "id",
            name = "address_id",
            nullable = false
    )
    private UserReceivingAddressEntity addressEntityList;

    @OneToMany
    @JoinColumn(name = "create_user_id", referencedColumnName = "id")
    private List<UserBlogEntity> userBlogEntityList;
}

2、只需要在查询方法上加@EntityGraph注解即可,其中value就是@NamedEntityGraph中的name。

public interface UserRepository extends JpaRepository<User,Long> {
    @Override
    @EntityGraph(value = "UserInfoEntity.addressEntityList")
    List<User> findAll();
}

关于关系查询的一些坑

1、所有的注解要么配置在字段上,要么配置在get方法上,不要混用

2、所有的关联都是支持单向关联和双相关联的。JSON序列化的时候双向注解会产生死循环,需要人为手动转化一次,或者使用@JsonIgnore

3、在所有的关联查询中,表一般是不需要建立外键索引

4、级联删除比较危险,建议考虑清楚,或者完全掌握

5、不同的关联关系的设置,@JoinCloum里面的name、referencedCloumName代表的意思是不一样的,很容易混淆,可以根据打印出来的sql调整

6、当配置这些关联关系的时候建议直接在表上面,把外键建好,然后通过工具生成,这样可以减少调试的时间