基于Springboot讲解SpringData JPA使用

SpringData框架

1、什么是SpringData

SpringData是Spring的一个子项目,它存在的目的是用于简化持久层(数据库访问层)开发,既支持关系型数据库也支持NoSQL数据库的操作。其主要的目标是数据库的访问变得更加便捷。

SpringData支持的非关系型数据库:

  • MongoDB(文档数据库)

  • Neo4j(图形数据库)

  • Redis(键/值存储)

  • HBase

  • ES(搜索库)

SpringData支持的关系型数据库:MySQL、DB2、Oracle等都支持:

  • JDBC:sun公司推出原生的Java操作数据库的规范(JDBC接口编程)

  • JPA:JPA是sun公司给出持久层(ORM层)一种编程规范(不同的ORM框架对JPA都进行具体的实现:Hibernate)。

SpringData的官网: https://spring.io/projects/spring-data

SpringData JPA:致力于减少数据访问层(DAO、Repository)的开发量,作为开发者主要需要做的唯一的事情,根据SpringData JPA定义的接口来书写相关的DAO层的子接口即可,其他的CRUD操作等全部都交给SpringData JPA完成。

2、SpringData JPA快速入门

2.1、搭建SpringData JPA的开发环境

说明环境:使用SpringBoot构建基础环境,使用Maven作为基础的管理。然后搭建一套完成的web环境(SpringMVC、Spring、SpringData、SpringBoot)、使用开发工具是Idea。

2.1.1、使用maven创建项目

2.1.2、导入pom依赖和编写配置

pom文件

 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 ​
     <groupId>com.qubo.springdata</groupId>
     <artifactId>springdata-demo</artifactId>
     <version>1.0-SNAPSHOT</version>
 ​
     <!-- 引入SpringBoot父工程-->
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.1.6.RELEASE</version>
     </parent>
     <!-- 配置一些jdk、编码等信息 -->
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
         <java.version>1.8</java.version>
     </properties>
     <!--引入需要的依赖-->
     <dependencies>
         <!-- 引入springmvc,spring等依赖 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <!-- 导入springdata jpa -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
         </dependency>
         <!-- mysql驱动 -->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <version>8.0.18</version>
         </dependency>
         <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.8</version>
         </dependency>
     </dependencies>
 </project>

yml配置文件:

 # 服务器的端口号
 server:
   port: 80
 ​
 # springdatajap 和数据源的配置
 spring:
   jpa:  # springdata jpa相关的配置
     generate-ddl: false
     show-sql: true
     hibernate:
       ddl-auto: none
   datasource:
     url: jdbc:mysql://127.0.0.1:3306/springdata
     username: root
     password: root
     driver-class-name: com.mysql.jdbc.Driver
 ​
 # 日志
 logging:
   level:
     root: info
     org.hibernate.*: debug
 ​

编写核心启动类:

 package com.qubo;
 ​
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 ​
 @SpringBootApplication
 public class SpringDataJPAStart {
     public static void main(String[] args) {
         SpringApplication.run(SpringDataJPAStart.class,args);
     }
 }

2.2、SpringData JPA的开发步骤

使用SpringDataJPA进行持久层开发的时候一般有4个步骤:

  1. 配置Spring整合JPA

  2. 在Spring配置文件中需要配置SpringData

  3. 定义持久层的接口,需要继承SpringData中提供的Repository接口(一般继承它的子接口JPARepository)

  4. 如果继承的接口中的方法不够用,可以在自己定义的接口中按照一定的规则去书写方法操作数据库

友情提示:

由于使用SpringBoot构建的项目环境,因此第一步和第二步基本省略了,其实改成注解方式。

2.3、Lombok插件

开发中需要大量书写pojo实体类,虽然IDE能够直接生成get、set、constructor、equals、hashCode、toString等等方法,但是还是比较麻烦,代码比较多,看起来pojo类很臃肿。

使用lombok插件,能够快速的生成get、set、constructor、equals、hashCode、toString等等方法。并且实体类中代码很简洁。

说明:这个插件是帮助我们去在编译的时候,给生成的class文件中添加上述的方法。而不是说真正没有这些方法。源码中没有,但是class文件(字节码)中存在。

注意:需要导入lombok的依赖包的同时,还要给IDE(eclipse、idea)安装插件。

  <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.8</version>
         </dependency>

2.4、快速完成查询操作

创建表:

 CREATE TABLE tb_user(
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50),
    PASSWORD VARCHAR(50),
    age INT ,
    salary DOUBLE
 )

改在核心启动类:

 @SpringBootApplication
 // 配置持久层接口所在的包
 @EnableJpaRepositories(basePackages = "com.qubo.repository")
 // 配置pojo所在的包
 @EntityScan("com.qubo.pojo")
 public class SpringDataJPAStart {
     public static void main(String[] args) {
         SpringApplication.run(SpringDataJPAStart.class,args);
     }
 }

编写Controller类:

 package com.qubo.controller;
 ​
 import com.qubo.pojo.User;
 import com.qubo.service.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 ​
 import java.util.List;
 ​
 @RestController
 public class UserController {
 ​
     @Autowired
     private UserService userService;
     @RequestMapping("users")
     public List<User> findUsers(){
         return this.userService.findUsers();
     }
 }
 ​

编写servcie类:

 package com.qubo.service;
 ​
 import com.qubo.pojo.User;
 import com.qubo.repository.UserRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 ​
 import java.util.List;
 ​
 @Service
 public class UserService {
     @Autowired
     private UserRepository userRepository;
     public List<User> findUsers() {
         return this.userRepository.findAll();
     }
 }
 ​

编写repository接口:

 package com.qubo.repository;
 ​
 import com.qubo.pojo.User;
 import org.springframework.data.jpa.repository.JpaRepository;
 ​
 /**
  *  在使用SpringDataJPA自定义的接口需要继承Repository接口或者其子接口
  *      Repository : 它只是一个接口中没有提供方法
  *      JpaRepository : 它提供CRUD的方法,以及分页,排序等方法
  *      接口中泛型:
  *          第一个:实体类类型
  *          第二个:主键的类型
  */
 public interface UserRepository extends JpaRepository<User,Integer> {
 }
 ​

测试结果:

 @Data
 @NoArgsConstructor  // 无参构造
 @AllArgsConstructor // 有参数构造
 @ToString
 @EqualsAndHashCode
 @Getter
 @Setter
 public class User {
     private String username;
     private String password;
     private Integer age;
     private Double salary;
 }
 ​

3、关于SpringData JPA接口介绍

3.1、Repository接口

 @Indexed
 public interface Repository<T, ID> {
 ​
 }

Repository接口是SpringData 的一个核心接口,但是它中不提供任何的方法,类似于Java中标记型接口概念。如果我们编程的时候直接继承这个接口,那么就需要自己在自定义的接口中添加CRUD等方法。

SpringData 提供了一些定义方法的规范,这样可以省略书写SQL过程:

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, Null findByAge(Is)Null … 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 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection 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)
 package com.qubo.repository;
 ​
 import com.qubo.pojo.User;
 import org.springframework.data.repository.Repository;
 ​
 import java.util.List;
 ​
 public interface UserRepository2 extends Repository<User, Integer> {
     // select * from tb_user where username = ? and password = ?
     public List<User> findByUsernameAndPassword(String username , String password);
 }
 ​

3.2、CrudRepository接口

CrudRepository:它是Repository接口的子接口,其中主要提供简单的增删改查操作的方法。

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

CrudRepository细节:

在进行根据主键操作的时候,我们需要给pojo指定主键和主键的策略

3.3、PagingAndSortingRepository接口

PagingAndSortingRepository:它主要在CrudRepository基础上提供分页和排序功能。

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {


	Iterable<T> findAll(Sort sort);   //  排序


	Page<T> findAll(Pageable pageable);  // 分页
}

准备分页对象:

@Data
public class PageResult<T> {
    // 设置总页数
    private Integer total;
    private Integer pages;
    private List<T> datas;
}

service中的逻辑代码:

     public PageResult<User> getPage(Integer page, Integer size) {
         //  设置分页参数
         Pageable pageable = PageRequest.of(page , size);
         // 执行查询
         Page<User> users = this.userRepository.findAll(pageable);
         // 需要将数据构建到自定义的分页结果对象中
         PageResult<User> pageResult = new PageResult<>();
         // 封装分页页面所需的数据
         pageResult.setDatas(users.getContent());
         // 封装总页数
         pageResult.setPages(users.getTotalPages());
         // 封装总记录数
         pageResult.setTotal(users.getTotalElements());
         return pageResult;
     }

controller的代码:

 @RequestMapping("page")
 public PageResult<User> getPage(
     @RequestParam(name="page" ,required = true , defaultValue = "0")Integer page,
     @RequestParam(name="size" ,required = true , defaultValue = "2")Integer size   ){
         return this.userService.getPage(page , size);
     }

3.4、JpaRepository接口

JpaRepository : 它是我们书写代码的时候真正需要继承的持久层的接口。JpaRepository接口将SpringData JPA提供的所有的常用的功能都继承到了

3.5、JpaSpecificationExecutor接口

JpaSpecificationExecutor : 它主要通过编码的方式完成条件查询。

public interface JpaSpecificationExecutor<T> {
	Optional<T> findOne(@Nullable Specification<T> spec);

	List<T> findAll(@Nullable Specification<T> spec);

	Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

	List<T> findAll(@Nullable Specification<T> spec, Sort sort);

	long count(@Nullable Specification<T> spec);
}

Specification:使用Specification来封装查询的条件。由于Specification又是一个接口,因此经常配合内部类或者使用Lambda表达式完成条件的封装

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
    public List<User> findNames(String name) {
 ​
         //  构建查询的条件 , 使用匿名内部类
         Specification spec = new Specification(){
             /*
                 在使用Specification接口构建查询条件的时候,需要复写toPredicate方法
                 但是toPredicate方法上有三个参数:
                     Root root:与数据库表对应的实体类对象,就是pojo
                     CriteriaQuery query:获取Root中的属性名,添加额外的一些查询条件
                     CriteriaBuilder criteriaBuilder:用来构建出查询条件对象
              */
             public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
                 // SELECT * FROM tb_user WHERE username LIKE '%唐%'
                 Path username = root.get("username"); // 获取到属性名,对应将来就是列名
                 return criteriaBuilder.like(username , "%"+name+"%");
             }
         };
         // List<User> users = this.userRepository.findAll(spec);
         // 上的写法完成可以使用Lambda表达式代替
         List<User> users = this.userRepository.findAll( (root , query  ,criteriaBuilder )-> criteriaBuilder.like(root.get("username"),"%"+name+"%") );
         return users;
     }

3.5、总结

使用SpringData JPA的时候,我们需要在持久层书写接口,然后去继承JPARepository接口。这时我们的接口就已经具备了对数据库表的基本的操作了。

比如:增删改查,分页,排序等等。

但是不具备编写一些复杂的条件查询,于是一般建议自定义的接口同时还要去继承JpaSpecificationExecutor 。

 public interface UserRepository extends JpaRepository<User,Integer> , JpaSpecificationExecutor<User> 

3.6、@Query注解

有时使用SpringData JPA的时候,已经提供的哪些方法无法满足我们的需求,只能自己手写sql来完成查询操作。这时需要就需要在自定义的方法上使用@Query注解来书写sql语句。

3.6.1、数字占位符

     //  自定义的方法,不要和接口中已经存在的方法重名,也不要和已经提供的方法命名规范重合
     //@Query("select * from tb_user where id = ?")
     @Query("select u from User u where id = ?1")
     public User findUserById(Integer id);
 ​

说明:

  1. 在接口中自定义的方法上使用@Query注解来实现自定义sql语句,在sql语句中参数使用?数字,数字从1往后排。要求数字和方法上的参数一一对应

  2. SQL中表名的位置不能书写表名,需要书写的是表对应的POJO的名字。并且需要给起别名

  3. select后面千万不要使用 * 号,使用别名代替

3.6.2、使用参数占位符

上面使用的数字占位符,参数多了非常容易搞错。可以使用参数占位符代替。但是需要在方法上使用@Param注解声明参数占位符名称。

    @Query("select u from User u where u.username = :username and u.password = :password")
    public User findUserByNameAndPwd(String username , String password);

注意:

参数占位符需要还是用:号,后面跟着参数的名称。

有时我们希望真正书写的是SQL语句,而不是上面这种写法。其实严格意义上来讲不算合法的SQL。

    @Query(value="select * from tb_user where id = ?1 and age = ?2" , nativeQuery = true)
    public User findUserByIdAndAge(Integer id , Integer age);

3.7、@Modifying注解

使用SpringData JPA的时候对数据库进行查询之外,还有增删改的操作。一般对数据库的增删改的操作,都会配合事务一起操作。

如果需要自定义SQL,还是增删改的操作,需要两个注解一起使用。

    @Modifying   
    @Query("update User set username=:name where id =:id")
    public Integer updateUserName(String name , Integer id);

说明:@Modifying 注解来说明是对数据库进行非查询操作

    public Integer updateUserName(String username , Integer id){
        return this.userRepository.updateUserName(username , id);
    }

执行程序发生下面的异常:

上面的异常是因为SpringData JPA规定,在对数据库进行增删改的时候,必须放在事务中。

    @Transactional
    public Integer updateUserName(String username , Integer id){
        return this.userRepository.updateUserName(username , id);
    }

4、多表的操作

多表的操作:一对一、一对多、多对多

4.1、一对一查询操作

假设一对一的场景:用户和住址,一般一个用户只能拥有一个地址(户籍归属地)。

CREATE TABLE tb_addr(
   id INT PRIMARY KEY AUTO_INCREMENT,
   address VARCHAR(100),
   uid INT 
)

SpringData JPA的一对一查询:提供@oneToOne注解,标注一对一的关系。同时还需要使用@JoinColumn注解声明关联关系。

 package com.qubo.pojo;
 ​
 import lombok.Data;
 ​
 import javax.persistence.*;
 ​
 @Data
 @Entity
 @Table(name="tb_addr")
 public class Address {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String address;
     //private Integer uid;
     //  在一对一中,需要在某个pojo中添加另外一个实体的引用
     @OneToOne
     @JoinColumn(name="uid" , referencedColumnName = "id")
     private User user;
 }

说明:

  • @OneToOne : 来指定当前pojo中的属性(实体引用)和另外类之间是一个一对一关系

  • @JoinColumn(name="uid" , referencedColumnName = "id") : 一对一中的主外键关系。name属性书写的是外键列名,referencedColumnName属性的是外键引用的主键的列名。

还需要编写关于tb_addr 表查询的Repository接口:

 public interface AddressRepository extends JpaRepository<Address , Integer> {
 ​
 }

关于service类:

 @Service
 public class AddressService {
 ​
     @Autowired
     private AddressRepository addressRepository;
 ​
     public Address findAddrById(Integer id){
         Address address = this.addressRepository.findById(id).get();
         return address;
     }
 }

4.2、一对多的查询操作

假设一对多业务场景:一个用户可以编著多本书(作者和读书)。通过User对应的多个Book。

 CREATE TABLE tb_book(
   id INT PRIMARY KEY AUTO_INCREMENT,
   NAME VARCHAR(50),
   price DOUBLE,
   pages INT,
   uid INT
 )

最终的查询目标:通过用户的id查询用户的数据,同时还要找到对应的著作信息

一对多:需要在一的一方pojo中添加集合,关联多的一方的数据。

 // 多的一方的实体类
 @Data
 @Entity
 @Table(name = "tb_book")
 public class Book {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String name;
     private Double price;
     private Integer uid;
 }
 @Data
 @Entity
 @Table(name="tb_user")
 public class User {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Integer id;
     private String username;
     private String password;
     private Integer age;
     private Double salary;
     // 添加对应的多的一方的引用
     @OneToMany
     @JoinColumn(name="uid",referencedColumnName = "id")
     private List<Book> books;
 }

编写Repository接口

 public interface BookRepository extends JpaRepository<Book , Integer> {
     // 需要提供一个方法,可以通过外键查询book数据
     public Book findByUid(Integer uid);
 }

4.3、多对多查询

假设业务场景:订单和商品关系,一个订单中可以拥有多个商品,某类商品可以被添加到不同的订单中。

 CREATE TABLE tb_order(
   id INT PRIMARY KEY AUTO_INCREMENT,
   uid INT,
   order_number VARCHAR(100)
 );
 ​
 CREATE TABLE tb_item(
   id INT PRIMARY KEY AUTO_INCREMENT,
   item_name VARCHAR(100),
   item_price DOUBLE,
   item_detail VARCHAR(200)
 )
 ​
 CREATE TABLE tb_order_detail(
   id INT PRIMARY KEY AUTO_INCREMENT,
   order_id INT,
   item_id INT,
   total_price DOUBLE
 )
 @Entity
 @Data
 @Table(name="tb_order")
 public class Order {
     private Integer id;
     private Integer uid;
     private String orderNumber;
 ​
     @ManyToMany // 声明是多对多关系
     @JoinTable(
             name="tb_order_detail" ,  // 多对多的中间表的名称
             joinColumns = { @JoinColumn( name="order_id" ,referencedColumnName = "id")},
             inverseJoinColumns = { @JoinColumn( name="item_id" , referencedColumnName = "id") } )
     private List<Item> items;
 }
 ​
 @Entity
 @Table(name = "tb_item")
 public class Item {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Getter@Setter
     private Integer id;
     @Getter@Setter
     private String itemName;
     @Getter@Setter
     private Double itemPrice;
     @Getter@Setter
     private String itemDetail;
     @ManyToMany(targetEntity = Order.class , mappedBy = "items")
     private List<Order> orders;
 }

猜你喜欢

转载自blog.csdn.net/baozi7263/article/details/103727947
今日推荐