写点什么

Spring Boot「21」JPA 中的 Entity

作者:Samson
  • 2022-11-01
    上海
  • 本文字数:3374 字

    阅读完需:约 11 分钟

JPA(Java Persistence API)是一个规范,通过提供 ORM 功能,使开发者能够利用 Java Domain Model 控制关系数据库。JPA 仅是一个规范,目前业界有几种不同的实现,例如 Hibernate、EclipseLink、TopLink、Open JPA 等。Spring Boot2 默认使用 Hibernate 作为底层实现。


JPA 的主要关注点在 ORM 层。ORM(Object-Relational Mapping)是指从 Java object 转换到数据库表的过程。


Entity(实体)就是简单的 POJO 对象,例如表示学生的 Student 类、表示国家的 Country 类等。JPA 中定义了@Entity来标识某个类是实体类。被标记的实体类必须满足:

  • 包含无参构造器

  • 必须指定标识符,即@Id标注的,用来定义表中的主键

  • 必须不能被 final 修饰,因为 JPA 实现通常会尝试继承实体类来扩展其功能

01-主键标识符

Hibernate 中主键标识符用来指明实体类中的属性作为对应数据库表中的主键。主键标识符一般可分为四种类型:简单标识符、生成标识符(Generated Identifiers)、复合标识符和派生标识符。


  1. 简单标识符。最简单的一种,指使用@Id标注标识在实体类的属性来声明的标识符。支持的属性类型包括三类:基本类型、基本类型的包装类型和 String、Date、BigDecimal、BigInteger。

  2. 生成标识符。指通过@Id@GeneratedValue注解标注的,在持久化时有 JPA 实现自动生成的标识符。此类标识符根据生成策略不同,划分为五类:

  • GenerationType.AUTO,不显示指定策略时,默认使用此策略。对数字类型的属性值,使用基于 sequence 或 table 的策略,对于 UUID 类型的使用 UUIDGenerator(需 Hibernate 5 及以上版本)。

  • GenerationType.IDENTITY,基于 IdentityGenerator,要求目标数据库表中存在 identify 列。例如,MySQL 中可以在建表时声明某个列为 auto_increment,当 JPA 实现插入数据时会自动递增。

  • GenerationType.SEQUENCE,基于 SequenceStyleGenerator,用于支持 Oracle 等这种可以定义 Sequence 的数据库。例如:

@Id@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence-generator")@GenericGenerator(  name = "sequence-generator",  strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",  parameters = {    @Parameter(name = "sequence_name", value = "country_seq"),    @Parameter(name = "initial_value", value = "1"),    @Parameter(name = "increment_size", value = "1")  })private Long id;
复制代码

其中,parameters 中定义的参数,是要传入 SequenceStyleGenerator 的值。

  • GenerationType.TABLE,基于 TableGenerator,用数据库中某个表中的数据作为主键值。例如:

       @Id       @GeneratedValue(strategy = GenerationType.TABLE, generator = "table-generator")       @TableGenerator(name = "table-generator",       table = "COUNTRY_NAME",       pkColumnName = "seq_id",       valueColumnName = "seq_value"       )       private String name;
复制代码


  • 自定义类型。通过实现 IdentifierGenerator + Configurable 接口来自定义 Generator。例如:public class MyGenerator implements IdentifierGenerator, Configurable { /**...*/ }。在使用时,类似于之前的 SequenceStyleGenerator。

      @GenericGenerator(              name = "my-generator",              strategy = "self.samson.example.MyGenerator",              parameters = { /**...*/ }      )      private Long some;
复制代码

可以通过 parameters 向 MyGenerator 传递必要的参数。

3. 复合标识符(Composite Identifier)。定义复合标识符的方式有两种:

  • @Embeddable标注某个主键类,然后在实体类中通过@EmbeddedId使用。例如:

      @Embeddable      public class CountryPK implements Serializable { /** .. */ }      public class Country {          @EmbeddedId          private CountryPK entryId;      }
复制代码
  • @Embeddable + @IdClass例如:

      @IdClass(CountryPK.class)      public class Country {          @Id private Long id;          @Id private String name;      }
复制代码


  1. 派生标识符(Derived Identifier),指从与其他实体的关系中派生出自己的标识符。例如:

@Entitypublic class UserProfile {
@Id private long profileId; @OneToOne @MapsId private User user;}
复制代码

UserProfile 实体从与 User 实体的一对一关系中派生出自己的标识符。

02-类型转换

  1. @Basic,表示实体类中属性可以直接映射到数据库表的对应列中。它包含两个属性:

  • optional=true|false,表示当前属性是否可为 null 值,默认为 true;

  • fetch=FetchType.LAZY|FetchType.EAGER,表示相关属性是懒加载、或立即加载,默认为 EAGER。


如何理解两种类型呢?下面举例说明。

@Entity@Table(name = "USER")public class User implements Serializable {    @Id    @Column("USER_ID")    private Long id;    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")    private Set<Order> order = new HashSet();}@Entity@Table(name = "USER_ORDER")public class Order implements Serializable {    @Id    private Long orderId;    @ManyToOne(fetch = FetchType.LAZY)    @JoinColumn(name="USER_ID")    private User user;}
List<User> users = userRepository.findAll(); // 1 User user = users.get(2); // 2user.getOrder(); // 3
复制代码


使用 LAZY 和 EAGER 的区别:

  • 如果使用 LAZY 方式,执行第 1 行代码,并不会从数据库中加载 Order 信息,到第 3 句才会从数据库加载;

  • 如果使用 EAGER 方式,执行第 1 行代码,就会从数据库中加载 Order 信息。


从日志的方向对比下不同:

  • 使用 LAZY 时,日志中包含:select user0_.user_id as user_id1_0_, user0_.user_name as user_nam2_0_ from user user0_

  • 使用 EAGER 时,日志中包含额外的语句:select orders0_.user_id as user_id2_1_0_, orders0_.order_id as order_id1_1_0_, orders0_.order_id as order_id1_1_1_, orders0_.user_id as user_id2_1_1_ from user_order orders0_ where orders0_.user_id=?


  1. @Enumerated可以控制是将枚举的序号,还是枚举值的名称持久化到数据库。例如,@Enumerated(EnumType.STRING)表示持久化枚举值的名称,默认情况或@Enumerated(EnumType.ORDINAL)表示持久化枚举值的序号。

  2. 与时间相关的类型

  • java.sql 包中定义的 Date、Time、Timestamp 可以直接映射到 SQL 标准中定义的 DATE、TIME、TIMESTAMP

  • java.util 包中也定义了 Date、Time、Timestamp,不过不可以直接转换为 SQL 标准中定义的 DATE、TIME、TIMESTAMP,需要通过@Temporal配置额外信息。例如:@Temporal(TemporalType.DATE)/@Temporal(TemporalType.TIME)/@Temporal(TemporalType.TIMESTAMP)。需要注意的是,SQL 中的 DATE 精度到纳秒,java.util.Date 精度到毫秒。

  • java.util.Calendar 可以通过@Temporal(TemporalType.DATE)/@Temporal(TemporalType.TIMESTAMP)转换到 SQL DATE、TIMESTAMP。需要注意的是,不支持到 SQL TIME 类型的转换。

  • java.time 包下的 LocalDate、LocalTime/OffsetTime、Instant/LocalDateTime/OffsetDateTime/ZonedDateTime 可以直接转换到 SQL 标准中定义的 DATE、TIME、TIMESTAMP

  1. 设置 time_zone

  • 在 url 中设置,例如 jdbc:mysql://localhost:3306/test?connectionTimeZone=UTC

  • 或者通过 hibernate 属性 hibernate.jdbc.time_zone 设置

  • 或者 Hibernate Session Factory 创建 Session 时设置,例如:HibernateUtil.getSessionFactory().withOptions().jdbcTimeZone(TimeZone.getTimeZone("UTC")).openSession();

03-其他注解

  1. @Table(name = "TABLE_NAME", schema = "SCHEMA_NAME"),若不指定任何属性,则表名与 entity 名相同。

  2. @Column(name = "COLUMN_NAME"),另外开指定列配置,例如:长度、是否可空、唯一性等;若不指定,entity 中的属性默认映射到与属性名相同的列。注意与@Basic的区别,总得来说,@Basic上的配置是针对 Entity 对象上的,而@Column是针对数据库表的。

  3. @Transient可以指定持久化到数据库时要忽略的实体类属性,例如@Transient private Long age;表示持久化时忽略 age 属性。

发布于: 刚刚阅读数: 3
用户头像

Samson

关注

还未添加个人签名 2019-07-22 加入

InfoQ签约作者 | 阿里云社区签约作者

评论

发布
暂无评论
Spring Boot「21」JPA 中的 Entity_Java_Samson_InfoQ写作社区