SpringBoot 全家桶 | JPA实例详解

本文源码:Gitee·点这里

参考

Spring Data JPA

引入包

引入jpa的包,同时引入mysql包和test

<dependencys>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencys>

配置文件

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot-familay?charset=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    hikari:
      maximum-pool-size: 32
      minimum-idle: 8
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
  • spring.jpa.show-sql=true 配置是否打印执行的SQL语句
  • spring.jpa.hibernate.ddl-auto=update 配置程序启动时自动创建数据库的类型(注意:此配置很危险,禁止在生产中使用)
    • create 程序重启时先删除表,再创建表
    • create-drop 程序重启时与create一样;程序结束时删除表
    • update 程序启动时若没有表格会创建,表内有数据不会清空,只会更新
    • validate 程序重启时会校验实体类与数据库中的字段是否相同,不同则报错

实体类

代码省略了get/set方法

@Entity
@Table(name = "AUTH_USER")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 32)
    private String name;

    @Column(length = 32, unique = true)
    private String account;

    @Column(length = 63)
    private String pwd;

    @Column
    private Date createTime;
}
  • @Entity 声明此类为实体类
  • @Table 声明该实体类对应数据库中的表信息
    • name指定表名,若不指定使用类名
  • @Id 声明实体属性为表主键
  • @GeneratedValue 主键生成策略,通过strategy属性指定
    • TABLE 使用数据库表来模拟序列产生主键
    • SEQUENCE 根据底层数据库的序列来生成主键,通过@SequenceGenerator注解指定序列名,条件是数据库支持序列
    • IDENTITY 使用数据库ID自增主键,条件是数据库支持
    • AUTO 默认,JPA根据数据库类型自动选择适合的策略
  • @Column 声明实体属性的表字段定义
    • name 指定字段名,不指定则使用该实体属性名
    • unique 唯一索引,默认false
    • nullable 可为空,默认true
    • length 长度,默认255
    • precision 精度,默认0
    • columnDefinition 该字段的SQL片段,示例:@Column(name = "account",columnDefinition = "varchar(32) not null default'' unique")

以上创建好后,程序启动后会自动在数据库中创建表信息

持久层

定义持久层接口

JPA中实现持久层非常简单,创建一个接口,并继承 org.springframework.data.jpa.repository.JpaRepository<T, ID>T为实体类,ID为实体类主键类型;最后在接口上增加 @Repository 注解

@Repository
public interface UserDao extends JpaRepository<User, Long> {
}

JpaRepository继承了CrudRepository,提供了基本的CRUD功能

public interface CrudRepository<T, ID extends Serializable>
  extends Repository<T, ID> {

  <S extends T> S save(S entity);      //(1)

  Optional<T> findById(ID primaryKey); //(2)

  Iterable<T> findAll();               //(3)

  long count();                        //(4)

  void delete(T entity);               //(5)

  boolean existsById(ID primaryKey);   //(6)

  // … more functionality omitted.
}
  • (1) 保存给定的实体,若已存在则更新。
  • (2) 返回由给定 ID 标识的实体。
  • (3) 返回所有实体。
  • (4) 返回实体数。
  • (5) 删除给定的实体。
  • (6) 指示是否存在具有给定 ID 的实体。

JpaRepository继承了PagingAndSortingRepository,提供分页和排序功能

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

定义查询方法

JPA可以通过方法名称派生查询

  • 该机制从方法中剥离前缀find…Byread…Byquery…Bycount…Byget…By,并解析剩余部分
  • 可以包含其他表达式,如Distinct
  • 可以使用AndOr进行串联

官方示例:

interface PersonRepository extends Repository<User, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

方法名称中支持的关键字

Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname , findByFirstnameIs , findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1(参数附加了%)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1(参数与前缀%绑定)
Containing findByFirstnameContaining … where x.firstname like ?1(参数绑定在%中)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

查询返回类型

Return type Description
void 表示没有返回值。
Primitives Java primitives.
Wrapper types Java 包装器类型。
T 唯一实体。期望查询方法最多返回一个结果。如果未找到结果,则返回null。一个以上的结果触发IncorrectResultSizeDataAccessException
Iterator<T> 一个Iterator
Collection<T> A Collection
List<T> A List
Optional<T> Java 8 或 Guava Optional。期望查询方法最多返回一个结果。如果未找到结果,则返回Optional.empty()Optional.absent()。多个结果触发IncorrectResultSizeDataAccessException
Option<T> Scala 或 Javaslang Option类型。语义上与前面描述的 Java 8 的Optional相同。
Stream<T> Java 8 Stream
Future<T> A Future。期望使用@AsyncComments 方法,并且需要启用 Spring 的异步方法执行功能。
CompletableFuture<T> Java 8 CompletableFuture。期望使用@AsyncComments 方法,并且需要启用 Spring 的异步方法执行功能。
ListenableFuture A org.springframework.util.concurrent.ListenableFuture。期望使用@AsyncComments 方法,并且需要启用 Spring 的异步方法执行功能。
Slice 一定大小的数据块,用于指示是否有更多可用数据。需要Pageable方法参数。
Page<T> Slice以及其他信息,例如结果总数。需要Pageable方法参数。
GeoResult<T> 具有附加信息(例如到参考位置的距离)的结果条目。
GeoResults<T> GeoResult<T>列表以及其他信息,例如到参考位置的平均距离。
GeoPage<T> PageGeoResult<T>,例如到参考位置的平均距离。
Mono<T> 使用 Reactive 存储库的 Project Reactor Mono发出零或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty()。多个结果触发IncorrectResultSizeDataAccessException
Flux<T> Project Reactor Flux使用 Reactive 存储库发出零,一个或多个元素。返回Flux的查询也可以发出无限数量的元素。
Single<T> 使用 Reactive 存储库发出单个元素的 RxJava Single。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty()。多个结果触发IncorrectResultSizeDataAccessException
Maybe<T> 使用 Reactive 存储库的 RxJava Maybe发出零或一个元素。期望查询方法最多返回一个结果。如果未找到结果,则返回Mono.empty()。多个结果触发IncorrectResultSizeDataAccessException
Flowable<T> RxJava Flowable使用 Reactive 存储库发出零个,一个或多个元素。返回Flowable的查询也可以发出无限数量的元素。

异步查询结果

我们在这里重点讲一下异步查询结果。
JPA查询结果支持异步,这意味着该方法在调用时立即返回,而实际查询的执行已提交给Spring的TaskExecutor的任务中。
官方示例:

interface PersonRepository extends Repository<User, Long> {
    @Async
    Future<User> findByFirstname(String firstname);               //(1)
    
    @Async
    CompletableFuture<User> findOneByFirstname(String firstname); //(2)
    
    @Async
    ListenableFuture<User> findOneByLastname(String lastname);    //(3)
}
  • (1) 使用java.util.concurrent.Future作为返回类型。
  • (2) 使用 Java 8 java.util.concurrent.CompletableFuture作为返回类型。
  • (3) 使用org.springframework.util.concurrent.ListenableFuture作为返回类型。

使用SQL语句查询

JPA自定义查询语句非常简单,使用@Query注解即可

public interface UserDao extends JpaRepository<User, Long> {

    @Query("select u from User u where u.account = ?1")
    User selectByAccount(String account);

    @Transactional
    @Modifying
    @Query("update User u set u.pwd = ?2 where u.id = ?1")
    int updatePassword(Long id, String password);

}
  • SQL语句中使用的是实体类名和实体属性,而不是表名和表字段
  • 如果使用update/delete,需要加上@Transactional@Modifying

实体关系映射

JPA 实体关系映射:@OneToOne 一对一关系、@OneToMany @ManyToOne 一对多和多对一关系、@ManyToMany 多对多关系。

OneToOne

一对一关系,即两个表中的数据是一一对应关系,这种主要应用到对象的扩展信息中。

用户实体类:

@Entity
@Table(name = "AUTH_USER")
public class User {

    /*
        其余代码省略
     */

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
    private UserDetail userDetail;
}

用户详情实体类:

@Entity
@Table(name = "AUTH_USER_DETAIL")
public class UserDetail {

    /*
        其余代码省略
     */

    @OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    private User user;
}
  • mappedBy加在了User实体为的userDetail属性上,并指向user表示:在UserDetail实体类映射的表中创建关系字段(user_id),并设为外键,指向User实体类映射的表中主键
  • UserDetail实体类的user属性的CascadeTypePERSIST表示:删除用户详情时,不级联删除用户信息

OneToMany/ManyToOne

一对多关系即一个表中的一行数据关联另外一个表中的多行数据,多对一与之相反。

用户实体类:

@Entity
@Table(name = "AUTH_USER")
public class User {

    /*
        其余代码省略
     */

    @ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
    private Company company;
}

公司实体类:

@Entity
@Table(name = "AUTH_COMPANY")
public class Company {

    /*
        其余代码省略
     */

    @OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY, mappedBy = "company")
    private Set<User> users = new HashSet<>();
}
  • @mappedBy只能出现在@OneToMany注解中
  • 会在User实体类对应映射表中创建一个外键(company_id),指向Company实体类对应映射表的主键
  • 两边最好不要做级联删除, cascade设为PERSIST

ManyToMany

多对多关系即其中一个表中的一行,与另一表中的多行关联,比如本示例中用户和角色的关系。

用户实体类:

@Entity
@Table(name = "AUTH_USER")
public class User {

    /*
        其余代码省略
     */

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Role> roles = new HashSet<>();
}

角色实体类:

@Entity
@Table(name = "AUTH_ROLE")
public class Role {

    /*
        其余代码省略
     */

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "roles")
    private Set<User> users = new HashSet<>();
}
  • 会建立一个中间表,包含两个实体类的主键关系
  • mappedBy指向哪一个都可以,中途不要修改

完整代码

springboot-jpa

timg (1).jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352