阅读本文之前,为了更好的理解,你应该先搭建一个Spring Data JPA的helloword的例子
概念
在说Spring Data JPA(后面简称SDJ)之前,先了解下什么是JPA(java persistence api)。
- JPA是一种ORM(对象关系映射,Object Relational Mapping,描述对象与数据库映射之间的元数据)规范,JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问 API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
- hibernate 这种框架实现了这种规范(不仅这一个框架),SDJ 对hibernate做了一层封装。SDJ 和Mybatis理念上最大的区别就是前者是面向对象的,mybatis是面向关系的。
何为JPA
- JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
- JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
JPA的规范
JPA规范的对象以及如何规范。通过一些列的注解来标注普通实体对象,以及对象的之间的关系,使指成为Domain Class(域对象)。在此对象封装一层操作API。
- 实体注解
- @Entity 注解在实体类上,也是JPA的操作对象。
- @Table 指定实体类对应的表名。
- @Id 指定主键。
- @GeneratedValue 主键的生成策略。可选的类型有
TABLE:使用一个特定的数据库表格来保存主键。一般配合@TableGenerator使用
SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列,比如oracle。 一般配合@SequenceGenerator使用
IDENTITY:主键由数据库自动生成(主要是自动增长型)
AUTO:主键由程序控制。 - @Column 可以对实体类的属性起别名,对应表中的字段,以及对该字段添加一些非空等约束。
- 关系注解
在介绍关系的注解之前,先了解一个概念,关系拥有者(owning side,后面简称ow)和非拥有者(non-owning sise,后面简称now)。下面的例子中,User对象称之为关系拥有者,Heart对象称之为非拥有者。
例子1 User和Heart对象
public class User{
//主键
private Long id;
//心脏
//@OneToOne
//@ManyToOne
private Hear heart;
//...
}
public class Heart{
//主键
private Long id;
//重量
private Double weight;
//...
}
@OneToOne 表示实体类与嵌入式类之间一对一的映射关系。这个注解用在一个实体类包含一个嵌入式的类。比如在在例子1里,这个注解放在User类的Heart属性上。当SDJ生成表的时候,会在User表上生成一个字段名为heart_id的外键。该注解有个级联cascade的属性,可选(ALL,PERSIST,MERGE,REMOVE,REFRESH,DETACH),拿PERSIST说明,当调用Spring Data JPA中的Reponsitory中的save方法保存User对象的时候,如果User对象中含有Heart对象的实例时候,会同时将Heart对象也保存到数据库中。
@ManyToOne、@OneToMany、@ManyToMany 用法都差不多。具体细节。后面会再次说明
- @JoinColumn 此注解一般会配合上面的6、7两点中的关系注解一起使用。表示关系拥有者的外键相关。
- 当此注解和@OneToOne或者@ManyToOne注解一起使用的时候,那么@Join注解 的属性值就表示外键的名字,并且此外键会在该注解所在实体类对应的表中。在例子2中,利用SDJ自生成表,会在User表中生成一个heart_id字段的外键,该外键对应的是Heart表中的主键。除非你想指定外键的名字,否者这种情况下@JoinColumn可以完全去掉
例子2
public class User{
//主键
private Long id;
//心脏
@OneToOne
@ManyToOne(表示多个人对应一个心脏,比如连体人,例子不是那么恰当,大致是这个意思)
@JoinColumn(name = "heartId",referencedColumnName = "id")
private Hear heart;
//...
}
public class Heart{
//主键
private Long id;
//重量
private Double weight;
//...
}
- @JoinColumn和@OneToMany使用的时候,并且是单向关系(关于单向/双向的解释,随后举例)的时候,那么@JoinColumn注解中的name就表示ow类对应表的外键名,referencedColumnName表示外键关联ow类对应的表。
例子3
public class Country{
//主键
private Long id;
@OneToMany
@JoinColumn(name = "countryId",referencedColumnName = "id")
private List<People> peoples;
//...
}
public class People{
//主键
private Long id;
//重量
private String name;
//...
}
在这个例子里,people对应的表会有个外键,对应着country实体的表的id。
到目前为止,说的都是实体类的单向关系(unidirectional),比如拿上面的例子来说,通过country类 我们知道有哪些people,但是从people类,我并不知道people对应的country。此时若想满足这个需求,就得靠注解形成双向关系(bidirectional)。请看下面的例子。
例子4
public class Country{
//主键
private Long id;
@OneToMany(mappedBy="country")
private List<People> peoples;
//...
}
public class People{
//主键
private Long id;
//
private String name;
@ManyToOne
private Country country;
//...
}
可以看出在双向关系中,需要在相互关系的类中配置对应的注解。此时表结构与例子3的表结构一样,在例子4中,@OneToMany的mappedBy注解是只能配置在双向关系中。
- @ManyToMany 多对多映射,这种关系肯定会设计到第三张表来存储实体之间的关系。
例子5
public class User{
private Long id;
private String name;
@ManyToMany
private List<Role> roleList;
}
public class Role{
private Long id;
private String name;
}
此时你如果用SDJ自动生成表的话,你会在数据库里面看到一张除了User和Role之外的表。如下
create table if not exists jpa.user_role_list
(
user_id bigint not null,
role_id bigint not null,
constraint FK8hyj0c7rglkeyinrekdeul39
foreign key (user_id) references jpa.user (id),
constraint FKofayuingjc8gnn4bkdek0s8sv
foreign key (role_id) references jpa.role (id)
);
你可以指定生成的表名以及里面的列名,对User修改如下
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false,length = 12)
private String name;
private Integer sex;
@ManyToMany
@JoinTable(name = "t_user_role",joinColumns = {@JoinColumn(name = "userId",referencedColumnName = "id")}
,inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
private List<Role> roleList;
此时生成的关联表结构如下
create table if not exists jpa.t_user_role
(
user_id bigint not null,
role_id bigint not null,
constraint FKjwwimwt7mcivvbosl7rms6q4y
foreign key (role_id) references jpa.role (id),
constraint FKm61t2kt35dkh35mbd8t0wjisl
foreign key (user_id) references jpa.user (id)
);
小结
到目前为止,了解了JPA中这些主要的注解,主要是通过注解来标注实体类(domain class)与表的映射关系以及实体类之间的关系,当然实际也是表之间的关系。回过头看,如果我们的系统中 ORM框架切换到mybatis这种,可能在项目的前期工作转备好之后,我们先设计表,甚至现在都不会建立外键这种约束了,而是会完全由业务代码来维护。而JPA主要让使用者只关心domain class,表之间的关系也不需要使用者去在意,所谓的面向OO,而mybatis这种是面向关系的。听上去OO编程显得更高大上一点,毕竟java就是OO的语言。
下一节 说说Spring Data JPA的使用。