6.4 SpringData JPA的使用

引言:该文档是参考尚硅谷的关于springboot教学视屏后整理而来。当然后面还加入了一些自己从网上收集整理而来的案例!

一、SpringData JPA初步使用

1. springdata简介

2. springboot整合springdata jpa(这是一个最简单的集成案例)

pom.xml引入架包依赖:

<?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.atguigu</groupId>
    <artifactId>spring-boot-06-data-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-boot-06-data-jpa</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
View Code

application.yml中配置如下:

spring:
  datasource:
    url: jdbc:mysql://192.168.15.22/jpa
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  jpa:
    hibernate:
#     更新或者创建数据表结构
      ddl-auto: update
#    控制台显示SQL
    show-sql: true
View Code

建表sql:

/*
 Navicat Premium Data Transfer

 Source Server         : 47.98.202.86
 Source Server Type    : MySQL
 Source Server Version : 50721
 Source Host           : 47.98.202.86:3306
 Source Schema         : springboot

 Target Server Type    : MySQL
 Target Server Version : 50721
 File Encoding         : 65001

 Date: 09/04/2019 23:51:27
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tbl_user
-- ----------------------------
DROP TABLE IF EXISTS `tbl_user`;
CREATE TABLE `tbl_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `last_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tbl_user
-- ----------------------------
INSERT INTO `tbl_user` VALUES (1, '刘亦菲', '123@qq.com');
INSERT INTO `tbl_user` VALUES (2, '唐嫣', '123@qq.com');
INSERT INTO `tbl_user` VALUES (3, '??', 'aa');

SET FOREIGN_KEY_CHECKS = 1;
View Code

实体类User:

package com.atguigu.springboot.entity;


import lombok.Data;

import javax.persistence.*;

//使用JPA注解配置映射关系
@Entity //告诉JPA这是一个实体类(和数据表映射的类)
@Table(name = "tbl_user") //@Table来指定和哪个数据表对应;如果省略默认表名就是user;
@Data
public class User {

    @Id //这是一个主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
    private Integer id;

    @Column(name = "last_name",length = 50) //这是和数据表对应的一个列
    private String lastName;
    @Column //省略默认列名就是属性名
    private String email;



}
View Code

数据层接口UserRepository:

package com.atguigu.springboot.repository;

import com.atguigu.springboot.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

//继承JpaRepository来完成对数据库的操作
public interface UserRepository extends JpaRepository<User,Integer> {
}
View Code

Controller层UserController:这里省略了service,主要是为了测试集成springdata jpa

package com.atguigu.springboot.controller;

import com.atguigu.springboot.entity.User;
import com.atguigu.springboot.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    UserRepository userRepository;

    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Integer id){
        User user = userRepository.findOne(id);
        return user;
    }

    @GetMapping("/user")
    public User insertUser(User user){
        User save = userRepository.save(user);
        return save;
    }

}
View Code

测试(这里只是通过get请求简单的测试):

  查询用户id=2的员工,访问网址:http://localhost:8080/user/2 

      返回结果:{"id":2,"lastName":"唐嫣","email":"123@qq.com"}

  增加一个用户,访问网址:http://localhost:8080/user?lastName=王菲&email=aa  

      返回结果为:{"id":3,"lastName":"王菲","email":"aa"}

 

二、springdata jpa 详解

1. springdata jpa 中的接口

 先参考【Spring Data JPA--接口方法】,后期在自己整理!

2. 实现复杂查询  参考:https://www.cnblogs.com/zjfjava/p/8456771.html

(1)基于方法名解析的概念

  JpaRepository支持接口规范方法名查询。意思是如果在接口中定义的查询方法符合它的命名规则,就可以不用写实现。
  spring-data-jpa会根据方法的名字来自动生成sql语句,我们只需要按照方法定义的规则即可;

  例如:findByName(String name): 这个方法表示从数据库中查询Name这个属性等于XXX的所有记录
       类似于SQL语句:select * from xxTable where name=xxx这种形式

这段话有两个重点:方法名需要在接口中设定; 必须符合一定的命名规范;

(2)方法名构造方法

      find+全局修饰+By+实体的属性名称+限定词   + 连接词+ ...(其它实体属性)+OrderBy+排序属性+排序方向 
例如:find Distinct By FirstName      IgnoreCase And         LastName OrderBy      Age      Desc(String firstName,String lastName);
      findDistinctByFirstNameIgnoreCaseAndLastNameOrderByAgeDesc(String firstName,String lastName)
其中:
      Distinct是全局修饰(非必须),
      FirstName和LastName是实体的属性名,
      And是连接词,
      IgnoreCase是限定词,
      Age是排序属性,
      Desc是排序方向,限定词和连接词统称为“关键词”。
spring-data-jpa规定,在属性后面接关键字.

 

(3)目前支持的关键词
  全局修饰:Distinct,Top,First
  排序方向:Asc,Desc

  关键字:

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 (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<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)
View Code

(4)嵌套实体方法命名规则

构词法:主实体中子实体的名称+ _ +子实体的属性名称

例如:
    List<Person> findByAddress_ZipCode(ZipCode zipCode) 
    表示查询所有 Address(地址)的zipCode(邮编)为指定值的所有Person(人员)
    
//查询需求: 从数据库中查询电话号码(phone)以指定字符串开始(例如:136)的,并且地址(address)中包含指定字符串(例如:路)的记录;   提取前两条,降序排列
//select * from user where phone like '136%' and address like '%路%' order by phone desc limit 0,2
List<User> findTop2ByPhoneStartingWithAndAddressContainingOrderByPhoneDesc(String phone,String address);
List<User> findTop2ByPhoneStartingWithAndAddressContaining(String phone,String address,Sort sort);    
//分页要用到Pageable接口
Page<User> findByPhoneStartingWithAndAddressContaining(String phone,String address,Pageable pageable);
View Code

 

 三、Criteria(条件)查询

转载:SpringDataJpa的Specification查询   Specification动态构建多表查询

 

相应的接口是JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的, Specification接口(JPA)中只定义了如下一个方法:

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); 这个方法的参数和返回值都是JPA标准里面定义的对象。

1、Criteria(条件)查询基本概念:

Root接口:代表Criteria查询的根对象,Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似;

  (1) Root实例是类型化的,且定义了查询的FROM子句中能够出现的类型;

  (2) 查询根实例能通过传入一个实体类型给 AbstractQuery.from方法获得;

  (3) Criteria查询,可以有多个查询根;

  (4) AbstractQuery是CriteriaQuery 接口的父类,它提供得到查询根的方法;

CriteriaQuery接口:代表一个specific的顶层查询对象,它包含着查询的各个部分,比如:select 、from、where、group by、order by等;

   注意:CriteriaQuery对象只对实体类型或嵌入式类型的Criteria查询起作用;

CriteriaBuilder接口:用来构建CritiaQuery的构建器对象Predicate:一个简单或复杂的谓词类型,其实就相当于条件或者是条件组合;

 

2. Criteria查询基本对象的构建

(1) 通过EntityManager的getCriteriaBuilder或EntityManagerFactory的getCriteriaBuilder方法可以得到CriteriaBuilder对象;(可以构建查询条件对象)

(2) 通过调用CriteriaBuilder的createQuery或createTupleQuery方法可以得到CriteriaQuery对象;(执行查询条件对象)

(3) 通过调用CriteriaQuery的from方法可以获得Root实例过滤条件;

 

    a. 过滤条件会被应用到SQL语句的FROM子句中。在criteria 查询中,查询条件通过Predicate或Expression实例应用到CriteriaQuery对象上;

    b. 这些条件使用 CriteriaQuery .where 方法应用到CriteriaQuery 对象上;(查询条件怎么应用到顶层查询对象上)

    c. CriteriaBuilder也作为Predicate实例的工厂,通过调用CriteriaBuilder 的条件方( equal,notEqual, gt, ge,lt, le,between,like等)创建Predicate对象

    d. 复合的Predicate 语句可以使用CriteriaBuilder的and, or and not 方法构建。 

        构建简单的Predicate示例:

            Predicate p1=cb.like(root.get(“name”).as(String.class), “%”+uqm.getName()+“%”);

            Predicate p2=cb.equal(root.get("uuid").as(Integer.class), uqm.getUuid());

            Predicate p3=cb.gt(root.get("age").as(Integer.class), uqm.getAge());

        构建组合的Predicate示例:

           Predicate p = cb.and(p3,cb.or(p1,p2)); 

使用实例:

(1)当然也可以形如前面动态拼接查询语句的方式,比如:

/**
     * 测试条件查询
     * @return
     */
    @GetMapping("/user/list")
    public List<User> getList(){

        //假设这是前端传过来的条件
        User user = new User();
        user.setLastName("fei");
        user.setId(1);

        Specification<User> spec = new Specification<User>() {
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //用于暂时存放查询条件的集合
                List<Predicate> list = new ArrayList<Predicate>();
                //------------------- 查询条件示例 -------------------------
                //like示例
                if (user.getLastName() != null && user.getLastName().trim().length() > 0){
//                    Predicate lastNamePredicate = cb.like(root.get("lastName").as(String.class), "%"+user.getLastName()+"%");
                    Predicate lastNamePredicate = cb.like(root.get("lastName"), '%'+user.getLastName()+'%');//和上面一句等效
                    list.add(lastNamePredicate);
                }
                //equal示例
                if(user.getId() >0){
//                    Predicate idPredicate = cb.equal(root.get("id"), user.getId());
                    Predicate idPredicate = cb.equal(root.get("id").as(Integer.class), user.getId());
                    list.add(idPredicate);
                }

                //between示例()  以下注释的部分,自己参照修改
//                if (birthday != null) {
//                    Predicate birthdayPredicate = cb.between(root.get("birthday"), birthday, new Date());
//                    predicatesList.add(birthdayPredicate);
//                }
                //排序示例(先根据学号升序排序,后根据姓名升序排序)
//                query.orderBy(cb.asc(root.get("studentNumber")),cb.asc(root.get("name")));

                //最终将查询条件拼好然后return
                    Predicate[] p = new Predicate[list.size()];
                return cb.and(list.toArray(p));
            }
        };

        List<User> users = userRepository.findAll(spec);
        users.forEach(System.out::println);
        return users;
    }

说明:以下是上面sql的打印结果,打印的应该是预编译的sql,至于为啥主键id的值被替换了?以后再做探讨,如果有大神了解,补充上!

Hibernate: select user0_.id as id1_2_, user0_.email as email2_2_, user0_.last_name as last_nam3_2_ from tbl_user user0_ where (user0_.last_name like ?) and user0_.id=1;
User(id=1, lastName=liuyifei, email=123@qq.com);

注意:此处记录下我遇到的坑,由于我只是做简单的动态条件查询测试,所以就没有考虑编码的问题,原来我在数据库中存的是中文的用户名,但是这就导致我在以上测试的时候,

     尽快代码没有问题,但是死活查不出结果,我一直在纠结我预编译的那个sql,还以为是我代码或者是yml中jpa配置的有问题,导致花了很长时间,后来我将数据库中的用户 名改为英文的就可以了!(编码问题是所有中国程序员都要考虑的问题)

(2)使用CriteriaQuery来得到最后的Predicate

/**
     * 测试条件查询
     * @return
     */
    @GetMapping("/user/list1")
    public List<User> getList1(){
        User user = new User();
        user.setId(1);
        user.setLastName("fei");
        user.setEmail("123@qq.com");

        Specification<User> spec = new Specification<User>() {
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

                Predicate p1 = cb.like(root.get("lastName").as(String.class), "%"+user.getLastName()+"%");
                Predicate p2 = cb.equal(root.get("email").as(String.class), user.getEmail());
                Predicate p3 = cb.gt(root.get("id").as(Integer.class), user.getId());
                //把Predicate应用到CriteriaQuery中去,因为还可以给CriteriaQuery添加其他的功能,比如排序、分组啥的  
                query.where(cb.and(p3,cb.or(p1,p2)));
                //添加排序的功能  
                query.orderBy(cb.desc(root.get("id").as(Integer.class)));

                return query.getRestriction();
            }
        };

        List<User> users = userRepository.findAll(spec);
        users.forEach(System.out::println);
        return users;
        
//   sql: select user0_.id as id1_2_, user0_.email as email2_2_, user0_.last_name as last_nam3_2_ from tbl_user user0_ 
//        where user0_.id>1 and (user0_.last_name like ? or user0_.email=?) order by user0_.id desc
//        User(id=2, lastName=tanying, email=123@qq.com)
    }

综上所述,我们可以用Specification的toPredicate方法构建更多更复杂的查询方法。

  

 

posted @ 2019-04-09 22:40  一帘幽梦&nn  阅读(358)  评论(0编辑  收藏  举报
点击查看具体代码内容