SpringBoot整合JPA使用
整合JPA
SpringBoot整合JPA十分方便,在Pom中添加如下:
1 | <dependency> |
JPA基本配置
在application.properties 或者 application.yaml 文件中配置数据库链接和JPA的基本配置(本例使用yaml文件):
1 | spring: |
使用JPA
在SpringBoot中使用JPA十分简单,只要引用JPA的starter即可
实体类中属性
使用Spring Data JPA,与Mybatis不同,更多的是面向对象查询.所以构造对象实体类就非常重要,如果配置了自动更新表结构,就需要注意如何在实体中使用特定的注解完成对实体的配置.
1. @Entity
使用 @Entity
注解在类上,用来表示该类为一个实体类
2. @Table
使用 @Table 注解标注在类上, 可以指定使用 @Entity 注解标注的类所生成的数据库表信息,如表名称等.
1 | // 指定这个类为实体类 |
3. @Id,@Column
使用 @Id 注解标注在变量或者set方法上,用来标注该变量为主键ID.
使用 @Column 注解同样标注在变量上,能够设置生成表的各种详细信息,如字段名称,字段长度等等…
1 |
|
4. @GeneratedValue
使用 @GeneratedValue 注解标注在 和 @Id 相同的位置,用来表示主键ID的生成策略.该注解有两个属性,第一个是常用到的 strategy 即生成策略,第二个是不常用到的generator指定生成器.
strategy
GenerationType 包含四种策略:
- TABLE,使用特定的表来存储生成的主键,也就是说会自动生成一种记录主键的表,一般会包含两个字段,第一个字段是字段生成策略的名称,第二个是ID的最大序列值.通常会和 @TableGenerator 一起来使用,能够指定特定表来生成主键,如果不指定,会默认生成一个名称为 sequence 的表来记录.
- SEQUENCE,在特定的数据库,如ORACLE,不支持自增主键,但是会提供一种叫做序列的方式生成主键,此时就需要指定 SEQUENCE 为生成主键的策略,和TABLE相似,通常会使用 @SequenceGenerator 一起使用
- IDENTITY ,遇到向MYSQL能够让主键自增的数据库,就可以指定生成策略为IDENTITY,生成表后, Mysql会默认将该字段设置为 ‘auto_increment’,
- AUTO ,使用 @GeneratedValue 默认的生成策略,把具体生成主键的规则交给持久化引擎来实现,也是我使用最多的一种方式.
1 | // 使用 GenerationType.TABLE 指定生成的记录表,其中name必须,其他可选 |
generator
通过上面对策略的描述,也就明白generator的作用,就是指定特定name的Generator
5. @Transient
使用 @Transient 注解,标注在字段上,在JPA生成表字段时能够忽略该字段,让字段不出现在数据库中.
6. @Embedded和@Embeddable
当实体A出现在实体B中,但实体A不需要单独生成一张表的使用,使用@@Embedded和@Embeddable标注在类上,能够防止实体A生成数据库表.
7. @Temporal
使用 @Temporal 注解特殊标记在需要持久化的字段上,并且字段类型为 java.util.Date
或 java.util.Calendar
的时候,可指定 TemporalType 来生成对应数据库中的时间格式(java.sql.xxxx).
8. 一对一,一对多,多对多关系
在使用关系型数据库中,免不了上述关系的生成,下面就来说一下,如何针对一对一关系、一对多关系、多对多关系的通用设置,在分别实验前,先了解一下 @JoinColumn 的作用
我认为的JoinColumn是特殊的@Column,当需要使用到“关系”时,需要使用到JoinColumn来替代普通的Column注解
OneToOne
使用OneToOne注解,指定两方关系是一对一的.比如User和Status,Status是User的一个属性,在User表中只需记录Stauts表中的主键,所以需要在User表(主)中声明一个Status(从)即可:
1 | () |
OneToMany、ManyToOne
使用 @OneToMany 或 @ManyToOne 注解来表示一对多、多对一的关系.典型的如Project之于Task,一个Project包含多个Task,所以Project为One,Task为Many.
如果在Project(主)中声明Task(从)时,可以使用 OneToMany 表示一对多关系
1 | (cascade = CascadeType.ALL) |
也可以在多的一方使用 ManyToOne 注解,表示多对一的关系
1 |
|
以上是单独使用 @OneToMany 或 @ManyToOne 构造单向的关系,但是这样有缺点,注定有一方找不到另外一方(只建立了单向关系).所以稳妥的方式是构建双向关系,在One和Many双方,使用两个注解相互指定
1 | // Project中,其中mappedBy是双向关系中必须要使用的属性 |
ManyToMany
使用 @ManyToMany 注解来标注多对多关系,比如用户和角色之间就可以理解为多对多的关系,双方需要互相拥有多个.多地多关系需要使用中间表来维护两方关系.建立关系表使用@JoinTable注解来指定实现.
1 | // User表中 |
关系使用时的坑
- 双向关系需要双方都进行维护,否则保存不上 :)
1 | User user = one.get(); |
- 使用JSON时,如果出现循环引用导出溢出时,在一方加入设置防止序列化
1 | // 我使用的是FastJosn,Jackson有不同的实现方式 |
使用 Repository 查询
Repository 是 Spring Data JPA 中最重要的接口,使用实体类和实体类中的主键ID作为类型参数.其中CURDRepository为实体类提供了复杂增删改查的函数.
1. CrudRepository接口
1 | public interface CrudRepository<T, ID> extends Repository<T, ID> {} |
同样提供了特定的Repository,比如JPARepository、MongoRepository,这几个都是继承的 CrudRepository 接口,根据不同的技术特性暴露不同的接口方法.一般使用上直接继承CurdRepository就可以完成基本的工作.
2. PagingAndSortingRepository接口
如果使用分页或者排序需要继承 PagingAndSortingRepository 接口,这个接口中包含 Iterable<T> findAll(Sort sort);
和 Page<T> findAll(Pageable pageable);
,这样能够具有查询时分页的特性
3. 接口中的名称推导查询
- 通过命名查询,具体可查看相关文档,写的非常全 create-query
关键字 | 方法命名 | sql where字句 |
---|---|---|
And | findByNameAndPwd | where name= ? and pwd =? |
Or | findByNameOrSex | where name= ? or sex=? |
Is,Equals | findById,findByIdEquals | where id= ? |
Between | findByIdBetween | where id between ? and ? |
LessThan | findByIdLessThan | where id < ? |
LessThanEquals | findByIdLessThanEquals | where id <= ? |
GreaterThan | findByIdGreaterThan | where id > ? |
GreaterThanEquals | findByIdGreaterThanEquals | where id > = ? |
After | findByIdAfter | where id > ? |
Before | findByIdBefore | where id < ? |
IsNull | findByNameIsNull | where name is null |
isNotNull,NotNull | findByNameNotNull | where name is not null |
Like | findByNameLike | where name like ? |
NotLike | findByNameNotLike | where name not like ? |
StartingWith | findByNameStartingWith | where name like ‘?%’ |
EndingWith | findByNameEndingWith | where name like ‘%?’ |
Containing | findByNameContaining | where name like ‘%?%’ |
OrderBy | findByIdOrderByXDesc | where id=? order by x desc |
Not | findByNameNot | where name <> ? |
In | findByIdIn(Collection<?> c) | where id in (?) |
NotIn | findByIdNotIn(Collection<?> c) | where id not in (?) |
True | findByAaaTue | where aaa = true |
False | findByAaaFalse | where aaa = false |
IgnoreCase | findByNameIgnoreCase | where UPPER(name)=UPPER(?) |
4. 使用 Repository 查询实例
- 声明一个接口继承特定的Repository
1 | /** |
- (可选)在接口中定义方法
1 | // 具体名称推导查询可以查看上面的表格 |
- 使用@AutoWire注解inject到特定的service中调用
1 |
|
到这里,增删改查基本都能够实现,复杂的在于分页、多条件复杂查询.
5. 使用 PagingAndSortingRepository 进行分页查询
前面说到 PagingAndSortingRepository 提供了两个方法,一个是返回Iterable的 findAll(Sort) 方法,另一个是返回Page的findAll方法
- 在没有其他查询条件的情况下,直接定义一个Pageable变量,然后使用特定方法即可:
1 |
|
- 在存在多样的查询条件的情况下,还需要接口继承
JpaSpecificationExecutor
1 | public Page<Book> findBookCriteria(Integer page, Integer size, final BookQuery bookQuery) { |
使用@Query语句查询
尽管通过上面的方法能够非常方便快捷的使用查询,但是有时候包含特殊字段的查询或者使用名称组合出来的方法名又长有丑…而且担心效率低,这时候JPA提供了一种类似Hibernate的方法,就是使用@Query进行查询
1 | "select u from User u where u.emailAddress = ?1") ( |
或者使用原生SQL查询,将Query的参数nativeQuery设置为true
1 | true,value = "select u.name from user u where u.id:id limit 1") (nativeQuery = |
使用QueryDSL进行复杂查询
如果上面的查询方式仍然满足不了需求,那么可以尝试使用QueryDSL进行查询 QueryDSL
1. 过程
- 添加MAVEN依赖
1 | <dependency> |
JPAAnnotationProcessor 会查找所有的 Entity 注解标注的实体类在目标路径下生成 Qxxx 的类.第一次需要使用 maven install 生成一下.
- 使用查询
1 | /** |
在配置文件中使用@Bean把JPAQueryFactory注入到容器中,然后使用JpaQueryFactory查询.
1 | private List<User> doGetrolePerson(Integer role,boolean in,List<User> users){ |
以上是使用基本的QueryDSL进行查询的方式,更多的增删改查的方法可以参考上面引用的文档,这里不在过多描述.
2. 结合查询
SpringDataJPA 对 QueryDSL 提供了一个通用的Repository – QuerydslPredicateExecutor
1 | public interface QuerydslPredicateExecutor<T> { |
使用需要让定义的Repository继承这个QuerydslPredicateExecutor,然后调用方法即可:
1 | public Page<User> findUsers(Integer offset, Integer limit, String order, String search, Integer status, Integer sex, Integer role) throws Exception { |