Springboot整合JPA以及动态条件查询的实现

前言:

        为了学习JPA技术,我在网上翻阅了几十篇关于Springboot整合JPA的文章,但文章内容由于作者水平良莠不均,非常影响类似我这样的菜鸟的学习效率。同时也是为了巩固并汇总 SpringBoot + JPA 的相关知识,才有了这篇博客。

此篇博客的项目代码已经上传到我的github。

1.Demo展示第一阶段:

首先展示Demo项目最终的目录结构,如下图:

Springboot整合JPA以及动态条件查询的实现_第1张图片

1.1在pom.xml中导入以下依赖

 



	4.0.0
	com.netops
	sprintboot_jpa
	0.0.1-SNAPSHOT
	jar
	sprintboot_jpa
	Demo project for Spring Boot
	
		org.springframework.boot
		spring-boot-starter-parent
		1.5.7.RELEASE
		 
	
	
		UTF-8
		UTF-8
		1.8
	
	
		
			org.springframework.boot
			spring-boot-starter-data-jpa
		
		
			mysql
			mysql-connector-java
			runtime
		
		
			org.projectlombok
			lombok
			true
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		
			org.springframework.boot
			spring-boot-starter-web
		
	
	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	

 

1.2.在application.properties中配置JDBC,JPA

 

#JDBC配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_jpa
spring.datasource.username=root
spring.datasource.password=root
#JPA配置
spring.jpa.database=MySQL
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update

 

1.3.配置实体类

1.3.1学生实体类

 

import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Data
public class Student {
    @Id
    @GeneratedValue
    private Integer id;//主键自增
    private String name;//姓名
    private Integer age;//年龄
    private String sex;//性别
    private Integer classNo;//班级号
    private String phoneNum;//手机号码
    private String address;//住址
}

 

1.3.2老师实体类

 

import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Data
public class Teacher {
    @Id
    @GeneratedValue
    private Integer id;//主键
    private String name;//姓名
    private Integer age;//年龄
    private String sex;//性别
    private String subject;//所授学科名称
    private Integer classNo;//授课班级---注:一个老师可能同时在多个班级授课
    private String phoneNum;//手机号
    private String address;//地址
}

 

1.4配置Repository接口

1.4.1TeacherDao

 

import com.netops.sprintboot_jpa.entity.Teacher;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TeacherRepository extends JpaRepository{
}

 

1.4.3StudentDao

 

import com.netops.sprintboot_jpa.entity.Student;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface StudentRepository extends JpaRepository {
    List findByNameLike(String surname);
    List findAll(Specification mySpec);
}

注:根据application.properties文件中JDBC配置参数,在MySQL中新建数据库(这里我的数据库名为springboot_jpa),然后启动springboot项目,JPA就会自动帮我们创建两张表(student,teacher)。

 

另外还需要说明的是:

1.由于导入了Lombok框架的依赖包,在实体类上加入@Data注解,实现了程序运行时自动导入Getter,Setter方法(其实还有:默认的构造器,equals(),hashCode()和toString()等方法)。如果不加该注解,又不手动添加Getter,Setter方法将会报异常。

2.Repository接口中两个参数如TeacherDao的,第一个参数就是实体类的类型:Teacher,第二个参数是实体类主键的类型:Teacher的属性id的类型的包装类Integer。

2.Demo展示第二阶段:

从第一阶段的Demo展示,通过已经简化得不能再简化的配置,JPA就帮我们在数据库中创建了表。这运用的是ORM技术,即Object Relational Mapping对象关系映射技术,将实体类Teacher与数据库中的表teacher进行关联,实体类Teacher的属性与表teacher的字段进行关联。JPA不仅能够帮我们创建表,更提供了众多的接口,帮助我们进行CRUD(增加Create、读取查询Retrieve、更新Update、删除Delete)操作。

2.1准备数据

2.1.1student.sql文件如下:

 

/*
Navicat MySQL Data Transfer

Source Server         : cloud
Source Server Version : 50622
Source Host           : localhost:3306
Source Database       : springboot_jpa

Target Server Type    : MYSQL
Target Server Version : 50622
File Encoding         : 65001

Date: 2017-09-25 18:28:37
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `address` varchar(255) DEFAULT NULL COMMENT '地址',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `class_no` int(11) DEFAULT NULL COMMENT '班级号',
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  `phone_num` varchar(255) DEFAULT NULL COMMENT '手机号',
  `sex` varchar(255) DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', '蜀国', '54', '1', '刘备', '12306', '男');
INSERT INTO `student` VALUES ('2', '蜀国', '48', '1', '关羽', '88888', '男');
INSERT INTO `student` VALUES ('3', '蜀国', '46', '1', '张飞', '66666', '男');
INSERT INTO `student` VALUES ('4', '蜀国', '40', '1', '诸葛亮', '12581', '男');
INSERT INTO `student` VALUES ('5', '吴国', '45', '2', '孙权', '34543', '男');
INSERT INTO `student` VALUES ('6', '吴国', '32', '2', '周瑜', '99999', '男');
INSERT INTO `student` VALUES ('7', '吴国', '29', '2', '大乔', '56565', '女');
INSERT INTO `student` VALUES ('8', '吴国', '27', '2', '小乔', '12321', '女');
INSERT INTO `student` VALUES ('9', '魏国', '57', '3', '曹操', '74848', '男');
INSERT INTO `student` VALUES ('10', '魏国', '42', '3', '司马懿', '58581', '男');
INSERT INTO `student` VALUES ('11', '魏国', '35', '3', '曹丕', '69393', '男');
INSERT INTO `student` VALUES ('12', '魏国', '30', '3', '甄姬', '98778', '女');

 

 

 

2.1.2teacher.sql文件如下:

 

/*
Navicat MySQL Data Transfer

Source Server         : cloud
Source Server Version : 50622
Source Host           : localhost:3306
Source Database       : springboot_jpa

Target Server Type    : MYSQL
Target Server Version : 50622
File Encoding         : 65001

Date: 2017-09-25 18:29:25
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `teacher`
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `address` varchar(255) DEFAULT NULL COMMENT '地址',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `class_no` int(11) DEFAULT NULL COMMENT '班级号-一个老师可能有多个班级号',
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  `phone_num` varchar(255) DEFAULT NULL COMMENT '手机号',
  `sex` varchar(255) DEFAULT NULL COMMENT '性别',
  `subject` varchar(255) DEFAULT NULL COMMENT '老师授课学科',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('1', '武汉', '43', '1', '易中天', '65432', '男', '语文');
INSERT INTO `teacher` VALUES ('2', '武汉', '43', '3', '易中天', '65432', '男', '语文');
INSERT INTO `teacher` VALUES ('3', '杭州', '40', '2', '于丹', '88858', '女', '政治');
INSERT INTO `teacher` VALUES ('4', '杭州', '40', '3', '于丹', '88858', '女', '政治');
INSERT INTO `teacher` VALUES ('5', '北京', '35', '1', '华罗庚', '74147', '男', '数学');
INSERT INTO `teacher` VALUES ('6', '北京', '35', '2', '华罗庚', '74147', '男', '数学');

 

2.2编写Controller类如下:

2.2.1StudentController

 

import com.netops.sprintboot_jpa.entity.Student;
import com.netops.sprintboot_jpa.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
@RequestMapping("/springboot_jpa")
public class StudentController {
    @Autowired
    private StudentService studentService;

    //找出所有的学生
    @RequestMapping("/student/findAll")
    public List findAll(){
        return studentService.findAll();
    }

    //找出所有姓 “surname”的学生
    @RequestMapping("/student/findByNameLike")
    public List findByNameLike(@RequestParam("surname") String surname){
        return studentService.findByNameLike( surname );
    }

    //动态查询:
    @RequestMapping("/student/findByCases")
    public List findByDynamicCases(){
        return studentService.findByDynamicCases();
    }
}

 

 

 

2.2.2TeacherController

 

import com.netops.sprintboot_jpa.entity.Teacher;
import com.netops.sprintboot_jpa.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
@RequestMapping("/springboot_jpa")
public class TeacherController {
    @Autowired
    private TeacherService teacherService;

    //查看所有的老师
    @RequestMapping("/teacher/findAll")
    public List findAll(){
        return teacherService.findAll();
    }

    //根据id查找对应的老师
    @RequestMapping("/teacher/findOne/{id}")
    public Teacher findOne(@PathVariable(value = "id")Integer id){
        return teacherService.findOne(id);
    }

}

 

 

 

2.3编写Service类如下:

2.3.1StudentService

 

import com.netops.sprintboot_jpa.domain.StudentRepository;
import com.netops.sprintboot_jpa.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import javax.persistence.criteria.*;
import java.util.List;

@Service
public class StudentService {
    @Autowired
    private StudentRepository studentRepository;

    public List findAll() {
        return studentRepository.findAll();
    }

    public List findByNameLike(String surname) {
        return studentRepository.findByNameLike(surname + "%");//查询条件为 where name like surname%
    }

    /*
    动态查询的条件:
        条件1:性别为 男;
        条件2:年龄在25-35之间;
        条件3:吴国人;
     */
    public List findByDynamicCases() {

        return studentRepository.findAll( new Specification() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
                Predicate predicate1,predicate2,predicate3;

                Path sex = root.get("sex");
                Path age = root.get("age");
                Path address = root.get("address");

                predicate1 = cb.like(sex,"男");
                predicate2 = cb.between(age,25,35);
                predicate3 = cb.equal(address,"吴国");

                query.where(predicate1,predicate2,predicate3);
                return null;
            }
        });
    }


}

 

 

 

2.3.2TeacherService

 

import com.netops.sprintboot_jpa.domain.TeacherRepository;
import com.netops.sprintboot_jpa.entity.Teacher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class TeacherService {
    @Autowired
    private TeacherRepository teacherRepository;

    public List findAll() {
        return teacherRepository.findAll();
    }

    public Teacher findOne(Integer id) {
        return teacherRepository.findOne(id);
    }
}

 

 

 

3.Demo测试、展示以及学习总结:

注:这里再介绍一款火狐浏览器的插件HttpRequester,主要帮助开发人员用来模拟http请求。可以在在火狐浏览器菜单栏的“附加组件”中进行下载。(HttpRequester插件已经被新版本的火狐禁用,这里推荐使用Postman插件来模拟http请求。该插件可以在postman官网进行下载)

 

3.1teacher表的相关操作:

Springboot整合JPA以及动态条件查询的实现_第2张图片

Url栏目内容:http://localhost:8080/springboot_jpa/teacher/findAll

点击Sumbit按钮可以获取json格式的结果集,效果如上图。

Springboot整合JPA以及动态条件查询的实现_第3张图片

Url栏目内容:http://localhost:8080/springboot_jpa/teacher/findOne/2

由于id属性是用@PathVariable注解进行注释的,所以直接将参数值在Url后拼接就可以进行查询,效果如上图。

3.2student表的相关操作

Springboot整合JPA以及动态条件查询的实现_第4张图片

Url栏目内容:http://localhost:8080/springboot_jpa/student/findAll

效果如上图,获取到所有的学生的Json格式。

 

Springboot整合JPA以及动态条件查询的实现_第5张图片

Url栏目内容:http://localhost:8080/springboot_jpa/student/findByNameLike;然后在Parameters输入(name:surname,value:曹)

根据上图可知,由于surname属性是用@RequestParam注解注释,所以需要再设定一个surname的参数,这里设定的surname="曹",也就是查询所有姓曹的Student;

 

Springboot整合JPA以及动态条件查询的实现_第6张图片

Url栏目内容:http://localhost:8080/springboot_jpa/student/findByCases

这是一个动态查询的案例,组合多个查询条件,查询结果如上图。

4.总结

1.由于StudentRepository和TeacherRepository都继承(extends)了JpaRepository。而JpaRepository接口中自定义了如下方法:

 

@NoRepositoryBean
public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor {
    List findAll();
    List findAll(Sort var1);
    List findAll(Iterable var1);
     List save(Iterable var1);
    void flush();
     S saveAndFlush(S var1);
    void deleteInBatch(Iterable var1);
    void deleteAllInBatch();
    T getOne(ID var1);
     List findAll(Example var1);
     List findAll(Example var1, Sort var2);
}

 

接口JpaRepository又继承了PagingAndSortingRepository和QueryByExampleExecutor,PagingAndSortingRepository继承了CrudRepository。见名知意,其子接口StudentRepository和TeacherRepository不用自定义任何方法,就具备CRUD(增删改查),Page(分页),Sort(排序)等功能。所以可以直接调用findAll(),findOne(ID var1)等方法。

里面的findOne(ID varl)方法,只能根据主键ID查找具体的对象,如果要其他属性查找,需要自定义方法。方法命名规则详见第二点总结。

 

2.仅仅在StudentRepository接口中定义抽象方法  List findByNameLike(String surname);  JPA会根据我们自定义的方法名进行解析和拆分,得知该方法的功能。JPA对自定义方法名的解析规则如下:Springboot整合JPA以及动态条件查询的实现_第7张图片

根据JPA中的方法名命名规则, 我们可以根据业务需求定义相应的抽象方法,而不需要该方法的实现,就可以实现业务功能。

3.从第一点和第二点的总结,我们已经感觉到JPA的强大。从此我们只需要关注业务逻辑,至于如何操作数据,我们就不要关心,一切都交给了JPA。当然,如果JPA只提供了这些功能,JPA也不会如此流行。更重要的是,JPA也提供了如何组装动态条件的查询。具体实现可以看StudentService中的findByDynamicCases()方法的实现。仅仅只是为了演示动态查询的效果,我这里的作法如下:

1)在StudentRepository中定义方法List findAll(Speciation mySpec);其实,更加通用的作法是:直接让StudentRepository继承JpaSpecificationExcutor接口,我们可以看该接口的源码如下:

 

public interface JpaSpecificationExecutor {
    T findOne(Specification var1);

    List findAll(Specification var1);

    Page findAll(Specification var1, Pageable var2);

    List findAll(Specification var1, Sort var2);

    long count(Specification var1);
}

我们可以看到里面定义了很多方法。继承了JpaSpecificationExcutor接口,我们不用自定义方法,直接在StudentService中实现进行具体方法的实现,就可以实现动态查询。

 

2)在StudentService中findByDynamicCases()方法中具体的实现。这里先解释这几个对象是什么意思。

Specification:规则、标准。该对象主要是告诉JPA查询的过滤规则是什么。

Predicate:谓语、断言。该对象主要是定义具体的判断条件。如predicate1 = cb.like(sex,"男");即判断条件为性别为男性。

Root: Root root就是定义引用root指向Student的包装对象。Path sex = root.get("sex");即通过root来获取Student的具体属性。

CriteriaQuery:查询条件的组装。query.where(predicate1,predicate2,predicate3);表示按条件predicate1 and predicate2 and predicate3进行组合条件查询。

CriteriaBuilder:用来构建CritiaQuery的构建器对象;如:predicate2 = cb.between(age,25,35);表示判断条件为Student.age between 25 and 35;

我这里的实现是为了演示基于JPA动态查询(性别为男,年龄在25-25之间,吴国人)我们具体该如何实现。很明显的看到这段代码把查询条件写死了,不易于扩展。通常情况下是把Specification定义为工具类,每一个判断条件Predicate定义为Spefication的一个方法,查询时再将不同的Predicate进行组装。有兴趣的可以自己实现。

你可能感兴趣的:(Java基础,JPA规范,SpringBoot)