第3章

Spring Data JPA






Spring Data是Spring的一个子项目,致力于简化数据库访问,支持NoSQL(非关系型数据库)和关系型数据库存储方式。Spring Data 项目所支持的NoSQL存储技术包括MongoDB(文档数据库)、Neo4j (图形数据库)、Redis(键/值存储)、HBase(列族数据库)。Spring Data 项目所支持的关系数据存储技术包括JDBC和JPA。Spring Data JPA遵从JPA规范,是依靠Hibernate实现的一种ORM(对象关系模型)框架。

3.1Spring Data JDBC技术

Spring Data JDBC提供了JDBCTemplates类,它是Spring对JDBC的封装,由Spring来完成JDBC底层的工作,并提供大量的API供用户直接使用,实现对数据库的增、删、改、查操作。Spring Boot通过其自动配置机制,自动配置了JDBCTemplate,只需在pom.xml文件中导入springbootstarterjdbc依赖和数据库连接Java的依赖,并在application.properties文件中设置好数据库连接信息,Spring Boot就会自动向容器注入JDBCTemplate实例,这样开发者就可使用@Autowired注解依赖注入JDBCTemplate,再调用JDBCTemplate的API方法对数据库进行增、删、改、查操作。由于JDBCTemplate并非主流,这里只提供一个简单的增、删、改、查的案例供参考。

【例31】使用Spring Boot整合JDBCTemplate操作studentdb中的student表。

(1) 数据库准备。在MySQL数据库studentdb中创建表student,插入若干数据,见图31。



图31创建表student



(2) 创建项目导入依赖。在IDEA新建Spring Boot项目JDBCtemplate1,导入springbootstarterjdbc依赖、mysqlconnectorjava依赖等,pom.xml文件的关键代码如下:  



//第3章/jdbc1/pom.xml

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jdbc</artifactId>

</dependency>

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>8.0.25</version>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

</dependency>





(3) 配置数据库连接信息。在application.properties文件中配置数据库连接信息,代码如下:  



//第3章/jdbc1/application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/studentdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC

spring.datasource.username=root

spring.datasource.password=root

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver





(4) 创建实体类。在com.seehope.entity包下创建实体类Student,关键代码如下:  



//第3章/jdbc1/Student.java

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Student {

private int id;

private String studentname;

private String gender;

private int age;

}





(5) 创建数据访问层。在com.seehope.dao包下创建StudentRepository类,用@Respository进行注解,表示是数据访问层,在类中创建JDBCTemplate 类型的属性JDBCTemplate,并使用@Autowired注解,表示从容器中注入JDBCTemplate实例,在类中创建有关添加、删除、修改、查询学生的若干方法,这些方法在内部分别调用JDBCTemplate提供的相应方法进行增、删、改、查操作,但需要提供相应的SQL语句作为参数,关键代码如下:  



//第3章/jdbc1/StudentRepository.java

@Repository

public class StudentRepository {

@Autowired

private JDBCTemplate JDBCTemplate;



//添加学生

public int addStudent(Student student) {

return JDBCTemplate.update("insert into
student(studentname,gender,age) values(?,?,?)",

student.getStudentname(), student.getGender(), student.getAge());

}

//删除学生

public int deleteStudent(int id) {

return JDBCTemplate.update("delete from student where id=?", id);

}

//修改学生

public int updateStudent(Student student) {

return JDBCTemplate.update("update student set studentname=?,gender=?,age=? where id=?",

student.getStudentname(), student.getGender(), student.getAge(), student.getId());

}

//查询所有学生

public List<Student> findAllStudents() {

return JDBCTemplate.query("select * from student", new BeanPropertyRowMapper<>(Student.class));

}

//根据id号查询单个学生

public Student findStudentById(int id) {

return JDBCTemplate.queryForObject("select * from student where id=?",

new BeanPropertyRowMapper<>(Student.class), id);

}

}





在上述代码中JDBCTemplate的update方法用于执行增、删、改SQL语句,SQL语句中的“?”号表示占位符,有几个占位符update方法就需提供几个实参。query方法用于执行查询语句,返回List<T>泛型集合,其中BeanPropertyRowMapper类型的参数用于将查询结果封装为指定的对象类型,即指定泛型T的实际类型。queryForObject方法用于查询单一返回值的结果,同样封装为对象类型。

(6)  创建业务逻辑层。在com.seehope.service包下创建StudentService类,关键代码如下:  



//第3章/jdbc1/StudentService.java

@Service

public class StudentService {

@Autowired

private StudentRepository studentRepository;









public int addStudent(Student student) {

return studentRepository.addStudent(student);

}



public int deleteStudent(int id) {

return studentRepository.deleteStudent(id);

}



public int updateStudent(Student student) {

return studentRepository.updateStudent(student);

}



public List<Student> findAllStudents() {

return studentRepository.findAllStudents();

}



public Student findStudentById(int id) {

return studentRepository.findStudentById(id);

}

}





(7) 创建控制器类StudentController,代码如下:  



//第3章/jdbc1/StudentController.java

@Controller

public class StudentController {

@Autowired

private StudentService studentService;

@GetMapping("/stus")

public ModelAndView findAllStudents(){

List<Student> stus= studentService.findAllStudents();

ModelAndView mv=new ModelAndView();

mv.addObject("stus",stus);

mv.setViewName("stus");

return mv;

}

@GetMapping("/stu/{id}")

public ModelAndView findStudentById(@PathVariable("id") int id){

Student student=studentService.findStudentById(id);

ModelAndView mv=new ModelAndView();

mv.addObject("student",student);

mv.setViewName("student");

return mv;

}

@PostMapping("/addStudent")

public ModelAndView addStudent(Student student){

studentService.addStudent(student);

ModelAndView mv=new ModelAndView();

//添加成功后,跳转到查找所有学生的控制器

mv.setViewName("redirect:/stus");

return mv;







}

@GetMapping("/addStudent")

public String addStudent(){

return "addStudent";

}

@GetMapping("/deleteStudent/{id}")

public ModelAndView deleteStudent(@PathVariable("id") int id){

studentService.deleteStudent(id);

ModelAndView mv=new ModelAndView();

//删除成功后,跳转到查找所有学生的控制器

mv.setViewName("redirect:/stus");

return mv;

}

@GetMapping("/updateStudent/{id}")

public ModelAndView toUpdateStudent(@PathVariable("id") int id){

Student student=studentService.findStudentById(id);

ModelAndView mv=new ModelAndView();

mv.addObject("student",student);

mv.setViewName("updateStudent");

return mv;

}

@PostMapping("/updateStudent")

public ModelAndView UpdateStudent(Student student){

studentService.updateStudent(student);

System.out.println(student);

ModelAndView mv=new ModelAndView();

//修改成功后,跳转到查找所有学生的控制器

mv.setViewName("redirect:/stus");

return mv;

}

}





(8) 创建视图。创建stus.html、stu.html、addStudent.html、updateStudent.html页面(详见教材配套电子资源)。

(9) 运行代码进行测试。这里仅提供部分测试结果,更多的结果可自行测试。

在浏览器网址栏输入http://localhost:8080/stus,结果如图32所示。

在浏览器网址栏输入http://localhost:8080/stu/1,结果如图33所示。



图32查找所有学生




图33查找一个学生



单击图32中的添加学生超链接,并填写数据,如图34所示。单击“添加”按钮,结果如图35所示。



图34添加学生



图35添加后的结果


3.2Spring Data JPA技术

Spring Data JPA是基于Hibernate的一款持久层框架,它通过注解或XML文件描述Java对象与数据库表之间的映射关系,将内存中的对象持久化到数据库中。Spring Data JPA提供了多种基于JPA规范的接口,内置了大量的常用查询方法,由Spring生成接口的代理类,注入Spring容器中进行管理,开发者在大多数情况下无须自行编写SQL(或JPQL)即可实现对数据库的访问与操作。项目中在使用Spring Data JPA时需引入下述依赖,代码如下:  



<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>





Spring Data JPA提供的接口有Repository、JpaRepository、PagingAndSortingRepository、CrudRepository及QueryByExampleExecutor,其中CrudRepository接口定义了基本的增、删、改、查CRUD操作,PagingAndSortingRepository定义了分页和排序的查询操作,QueryByExampleExecutor接口定义了简单的动态查询操作。

JpaRepository接口继承了PagingAndSortingRepository、CrudRepository和QueryByExampleExecutor接口,继承了它们的所有操作并提供了其他一些常用操作,PagingAndSortingRepository和CrudRepository接口又继承自Repository,Repository只是一个空接口,具有标记作用,各个接口之间的关系如图36所示。



图36接口之间的关系


所以开发者只要自定义一个接口继承JpaRepository接口即可获得上述所有接口的功能。开发者自定义的接口还可以进行方法命名查询,方法名称和参数只要符合JPA的命名规范,Spring Data JPA就会根据方法名字来确定需要实现什么样的逻辑,无须开发者编写SQL语句。自定义的接口中的方法也可配套使用JPQL语言或原生SQL语句进行更加灵活的查询,此外Spring Data JPA还可方便地处理一对一,一对多,以及多对多表关联查询。

3.2.1JpaRepository接口

JpaRepository接口除继承了父接口的方法外还提供了其他方法,见表31。


表31JpaRepository接口方法


方 法 名 称功 能 描 述


List<T> findAll()查询所有对象数据
List<T> findAll(Sort var1)查询所有对象数据并进行排序
List<T> findAllById(Iterable<ID> var1)根据提供的多个ID号,将有关对象都查询出来
<S extends T> List<S> saveAll(Iterable<S> var1)将集合数据保存到数据库
void flush()将缓存中的对象更新到数据库
<S extends T> S saveAndFlush(S var1)保存对象并更新到数据库
void deleteInBatch(Iterable<T> var1)批量删除集合中的对象
void deleteAllInBatch()批量删除所有对象
T getOne(ID var1)根据ID号查询对象
<S extends T> List<S> findAll(Example<S> var1)根据提供的Example实例查询对象
<S extends T> List<S> findAll(Example<S> var1, Sort var2)根据提供的Example实例查询对象并排序


3.2.2PagingAndSortingRepository接口

PagingAndSortingRepository接口主要提供了用于分页查询和排序的方法,见表32。


表32PagingAndSortingRepository接口方法


方 法 名 称功 能 描 述


Iterable<T> findAll(Sort var1)查询所有对象并根据Sort对象中的规则进行排序
Page<T> findAll(Pageable var1)查询所有对象并根据Pageable对象中的规则进行分页处理,封装为一个Page对象。注意:  Pageable中又可包含排序


Page是Spring Data提供的一个封装了分页信息的接口,Pageable是构造分页查询的接口,用于指定页码(从0算起),每页显示记录数,以及是否排序等,具体由PageRequest类的of静态方法进行实现。

【例32】假设用户在项目中已经创建好了继承JpaRepository的Dao层接口StudentRepository,在控制器StudentController中使用Page<T> findAll(Pageable var1)方法进行分页与排序查询,代码如下:  



//第3章/jpa1/StudentController.java

@Autowired

Private StudentRepository studentRepository;

@GetMapping("/findAllPage")

public ModelAndView findAll(@RequestParam(value="start",defaultValue="0") Integer start,

@RequestParam(value="size",defaultValue="3") Integer size) {

ModelAndView mv=new ModelAndView();

//构造Pageable,默认查询第1页,每页显示3条记录,根据属性id进行降序排序

Pageable pageable= PageRequest.of(start,size,Sort.by(Sort.Direction.DESC,"id"));

//也可以不排序

//Pageable pageable= PageRequest.of(start,size);

//根据pageable进行查询,结果封装为Page对象

Page<Student> page=studentRepository.findAll(pageable);

mv.addObject("page",page);

mv.setViewName("stusPage");

return mv;

}





PageRequest.of方法的第3个参数用到了Sort类,用于制定排序规则。可以使用Sort类的带参构造方法创建Sort对象,第1个参数用于指定升序还是降序,值为Sort.Direction.DESC或Sort.Direction.ASC,第2个参数用于指定排序属性(字段)。

【例33】Sort sort=new Sort(Sort.Direction.DESC, "id"); 表示创建一个根据属性id进行降序排序的Sort对象。也可以使用Sort类的静态方法by创建Sort对象,语法可参照上述示例代码。

3.2.3CrudRepository接口

CrudRepository接口提供了常用的增、删、改、查方法,见表33。


表33CrudRepository接口方法



方 法 名 称功 能 描 述


<S extends T> S save(S var1)保存对象,如果对象包括主键ID,则进行更新操作
<S extends T> Iterable<S> saveAll(Iterable
<S> var1)保存对象集合
Optional<T> findById(ID var1)根据主键ID查询对象,结果可以为空
boolean existsById(ID var1)根据主键ID查询对象是否存在
Iterable<T> findAll()查询所有对象
Iterable<T> findAllById(Iterable<ID> var1)根据给定的多个ID号查询对象集合
long count()查询对象的数量
void deleteById(ID var1)根据ID号删除对象,没有返回值,如果要判断是否成功,则要结合try…catch…语句
void delete(T var1)删除对象
void deleteAll(Iterable<? extends T> var1)删除给定的对象
void deleteAll()删除所有对象


用户自定义的接口只要实现JpaRepository接口,调用上述接口提供的方法,无须编写SQL或JPQL语句就可操作数据库。

3.2.4基本增、删、改、查方法

【例34】Spring Data JPA的基本增、删、改、查及分页操作。

(1) 数据库准备。在studentdb数据库中创建表course,主键courseid自增长,添加若干数据,
如图37所示。也可以不创建表,由程序自动创建,见第(3)步中有关update的说明。



图37数据库表course



(2) 创建Spring Boot项目。在IDEA中创建Spring Boot项目,命名为pom.xml并在pom.xml文件中导入JPA相关依赖,关键代码如下:  



//第3章/jpa1/pom.xml

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>







<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

</dependency>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

</dependency>





(3) 配置application.properties。需要配置数据库连接信息和JPA属性,配置如下:  



//第3章/jpa1/application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/studentdb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true

spring.datasource.username=root

spring.datasource.password=root

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver



spring.jpa.properties.hibernate.hbm2ddl.auto=update

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

spring.jpa.show-sql=true





其中spring.jpa.properties.hibernate.hbm2ddl.auto属性用于设置自动创建、更新或验证数据库表结构。一般取以下4个值之一。

create:  每次加载Hibernate时都会删除数据库表,然后根据Model类重新创建新表,这样会导致数据库数据丢失,应谨慎使用。

createdrop:  每次加载Hibernate时都会根据Model类创建新表,一旦SessionFactory关闭就自动删除表。

update:  首次加载Hibernate时若没有Model类对应的数据库表就会根据Model类创建数据表,以后加载时,不会创建新表,若Model类被修改过,则会更新数据库表结构,但数据不会被删除。

validate:  每次加载Hibernate时,会验证数据库的表结构而不会创建新表,但会插入新值。

spring.jpa.properties.hibernate.dialect:  对特定的关系数据库生成优化的SQL。

spring.jpa.showsql=true属性用于设置控制台输出SQL语句。

(4) 创建实体类Course,关联数据库表course,代码如下:  



//第3章/jpa1/Course.java

@Data

@AllArgsConstructor

@NoArgsConstructor

@Entity(name="course")

public class Course {

//课程编号

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)







private int courseid;

//课程名称

@Column(name="coursename",nullable = false,length =30)

private String coursename;

//课程学分

@Column(name="coursescore")

private int coursescore;

//课时

@Column(name="coursetime")

private int coursetime;

//选修或必修课

@Column(name="coursetype")

private String coursetype;

//备注,数据库中无对应的列

@Transient

private String description;

}





@Entity注解表示该实体类是一个与数据库表映射的实体类,用name属性指定该实体类映射的数据库表名,也可以省略name属性,默认数据库表名与类名一致。

@Id注解表示该属性映射为数据库表的主键,并用@GeneratedValue注解来指定主键生成策略,若设置为strategy = GenerationType.IDENTITY,则表示主键由数据库自动增长,若设置为strategy = GenerationType.AUTO,则表示主键由程序生成。

@Column注解用来描述该属性对应的数据库表中的字段的详细定义,name属性表示所映射的数据库表中的字段,默认字段名与属性名一致,nullable属性表示该字段是否允许为空,length属性表示该字段的长度,仅对String类型有效,还可以有unique属性,表示该字段是否是唯一标识。

@Transient注解用于标注数据库表中无对应列的属性,ORM框架将忽略该属性。

(5) 创建数据访问层。创建CourseDao接口,继承JpaRepository接口,关键代码如下:  



//第3章/jpa1/CourseDao.java

public interface CourseDao extends JpaRepository<Course,Integer> {

}





这里暂时不用写任何自定义方法(父类接口已经有很多直接可用的方法)。

(6) 创建业务逻辑层。创建CourseService类,关键代码如下:  



//第3章/jpa1/CourseService.java

@Service

public class CourseService {



@Autowired

private CourseDao courseDao;



//使用JpaRepository的API查询,DAO层无须手动编写SQL或JPQL语句

//查找所有课程,不分页,相当于SQL: select * from course

public List<Course> findAll(){







return courseDao.findAll();

}



//查找所有课程,有排序,相当于SQL: select * from course order by xxx DESC|ASC

public List<Course> findAll(Sort sort){

return courseDao.findAll(sort);

}



//查找所有课程,有分页,相当于SQL: select * from course limit x,y

public Page<Course> findAll(Pageable pageable){

return courseDao.findAll(pageable);

}



//查询某个编号的课程,相当于SQL: select * from course where courseid=?1

//也可以用方法名findByIdIs findByIdEquals

public Course findById(int courseid){

return courseDao.findById(courseid).orElse(new Course());

}



public void updateCourse(Course course) {

//若参数的主键存在,则进行更新操作

courseDao.save(course);

}



public void addCourse(Course course) {

//添加课程,若参数的主键不存在,则进行添加操作

courseDao.save(course);

}



public void deleteCourse(int courseid) {

//删除课程

courseDao.deleteById(courseid);

}

}





(7) 创建控制器。创建CourseController,关键代码如下:  



//第3章/jpa1/CourseController.java

@Controller

public class CourseController {



@Autowired

private CourseService courseService;

//获取旧数据并传递到视图进行展示以便修改

@GetMapping("/updateCourse/{id}")

public ModelAndView toupdateCourse(@PathVariable("id") int courseid){

ModelAndView mv=new ModelAndView();

Course course=courseService.findById(courseid);

mv.addObject("course",course);

mv.setViewName("updateCourse");

return mv;

}







//获取新数据,修改数据库

@PostMapping("/updateCourse")

public ModelAndView updateCourse(Course course){

ModelAndView mv=new ModelAndView();

courseService.updateCourse(course);

//更新完毕后转发到查询所有课程的控制器

mv.setViewName("redirect:findAll1");

return mv;

}

//将课程添加到数据库

@PostMapping("/addCourse")

public ModelAndView addCourse(Course course){

ModelAndView mv=new ModelAndView();

courseService.addCourse(course);

mv.setViewName("redirect:findAll1");

return mv;

}

//用于超链接,呈现添加课程的视图

@GetMapping("/addCourse")

public String addCourse(){

return "addCourse";

}

//删除课程

@GetMapping("/deleteCourse/{id}")

public ModelAndView deleteCourse(@PathVariable("id") int courseid){

ModelAndView mv=new ModelAndView();

courseService.deleteCourse(courseid);

mv.setViewName("redirect:findAll1");

return mv;

}

//查询所有课程,无排序

@GetMapping("/findAll1")

public ModelAndView findAll(){

ModelAndView mv=new ModelAndView();

List<Course> courselist=courseService.findAll();

mv.addObject("courses",courselist);

mv.setViewName("courses");

return mv;

}

//查找所有课程,有排序

@GetMapping("/findAll2")

public ModelAndView findAll2(){

ModelAndView mv=new ModelAndView();

//按课程学分降序排序

List<Course> courselist=courseService.findAll(Sort.by(Sort.Direction.DESC,"coursescore"));

mv.addObject("courses",courselist);

mv.setViewName("courses");

return mv;

}

//查找所有课程,有分页与排序

@GetMapping("/findAll3")

public ModelAndView findAll(@RequestParam(value="start",defaultValue="0") Integer start,







@RequestParam(value="size",defaultValue="3") Integer size) {

ModelAndView mv=new ModelAndView();

//默认查询第1页,1页显示3条记录

Pageable pageable= PageRequest.of(start,size,Sort.by(Sort.Direction.DESC,"courseid"));

//也可以不排序

//Pageable pageable= PageRequest.of(start,size);

Page<Course> page=courseService.findAll(pageable);

mv.addObject("page",page);

mv.setViewName("coursePage");

return mv;

}



//查询某个编号的课程

@GetMapping("/findById/{id}")

public ModelAndView findById(@PathVariable("id") int courseid){

ModelAndView mv=new ModelAndView();

Course course=courseService.findById(courseid);

mv.addObject("course",course);

mv.setViewName("course");

return mv;

}

}





(8) 创建视图。在resource/templates目录下创建courses.html,用于以列表的形式展示所有课程,course.html用于展示课程详情,addCourse.html用于添加课程,coursePage.html用于分页展示所有课程。具体代码参见随书配置电子资源,其中分页展示是重点,关键代码如下:  



//第3章/jpa1/coursePage.html

<body>

<a th:href="@{/addCourse}">添加学生</a><br/>

<table border="1" align="center">

<tr>

<td>课程编号</td>

<td>课程名称</td>

<td>课程学分</td>

<td>课程学时</td>

<td>课程类型</td>

<td>修改</td>

<td>删除</td>

</tr>

<tr th:each="course:${page.getContent()}">

<td th:text="${course.courseid}"></td>

<td th:text="${course.coursename}"></td>

<td th:text="${course.coursescore}"></td>

<td th:text="${course.coursetime}"></td>

<td th:text="${course.coursetype}"></td>

<td><a th:href="@{/updateCourse/}+${course.courseid}">修改</a></td>

<td><a th:href="@{/deleteCourse/}+${course.courseid}">删除</a></td>

</tr>







</table>

<div>

<a th:if="${not page.isFirst()}" th:href="@{/findAll3(start=${page.number-1})}">上一页</a>

|总页数: <span th:text="${page.getTotalPages()}"></span>&nbsp;

|当前页: <span th:text="${page.getNumber()+1}"></span>

|总记录数: <span th:text="${page.getTotalElements()}"></span>&nbsp;\

|当前页记录数: <span th:text="${page.getNumberOfElements()}"></span>

<a th:if="${not page.isLast()}" th:href="@{/findAll3(start=${page.number+1})}">下一页</a>

</div>

</body>





(9) 运行代码进行测试。这里仅提供查询全部和分页查询的结果,其余结果可自行下载源码进行测试。在浏览器地址栏输入http://localhost:8080/findAll1,结果如图38所示。



图38查询所有图书列表展示


在浏览器输入http://localhost:8080/findAll3,结果如图39所示。



图39分页查询结果


3.2.5方法命名查询

在用户自定义的继承了JpaRepository的接口中,用户可以自定义一些方法,只要这些方法符合JPA的约定命名规范,Spring Data JPA就能分析出开发者的意图,自动调用SQL实现特定的查询,而无须手动编写SQL语句。以自定义方法名称为findByName(String username)为例,这里的findBy可以认为是关键字,而Name是可变的,可以是任意一个实体类的名称,框架会将该方法解释为查询条件where name=?,如果方法的返回值类型为List<User>,则完整的SQL语句为 select*from user where name=?。如果有多个条件,则方法名称中还可以出现And或 Or进行连接。符合命名规范的方法名见表34。


表34符合命名规范的方法名示例


关键词方法命名示例对应的SQL


AndfindByGenderAndAgewhere gender=? And age=?
OrfindByGenderOrAgewhere gender=? And age=?

=
findByName
findByNameIs
findByNameEqualswhere name=?

BetweenfindByAgeBetweenwhere age between ? and ?
<findByAgeLessThanwhere age <?
<=findByAgeLessThanEqualwhere age <=?
>findByAgeGreaterThanwhere age >?
>=findByAgeGreaterThanEqualwhere age>=?
<>findByAgeNotwhere age <> ?
IsNullfindByNameIsNullwhere name is null
NotNullfindByNameNotNullwhere name is not null
InfindByAgeIn(Collection<Age> ages)where age in (?)
NotInfindByAgeNotIn(Collection<Age> ages)where age not in(?)
likefindByNameLikewhere name like ?
NotLikefindByNameNotLikewhere name not like ?
StartingWithfindByNameStartingWithwhere name like ‘?%’
EndingWithfindByNameEndingWithwhere name like ‘%?’
ContainingWithfindByNameContainingWithwhere name like ‘%?%’
OrderByfindByGenderOrderByAgeDescWhere gender=? Order by age desc
TruefindByGenderTruewhere gender=true
FalsefindByGenderFalsewhere gender=false


此外约定方法名还可用Top或First关键词来限制查询结果的数量,如findTop 10ByGender,指显示查询结果的前10条,相当于SQL语句:  where gender=? limit 0,10。findFirst20ByAgeGreaterThan,指显示查询结果的前20条,相当于SQL语句:  where age>? limit 0,20。

【例35】根据约定命名规范查询。

(1) 创建数据访问层。接着上述案例项目jpa1,在CourseDao中创建方法,代码如下:  



//第3章/jpa1/CourseDao.java

//使用JpaRepository的约定命名规范查询,DAO层无须手动编写SQL或JPQL语句

//查询学分为xx的选修(或必修)课,按照命名规范,无须手动编写SQL语句

//相当于SQL: select * from course where //coursescore=?1 and coursetype=?2

public List<Course> findByCoursescoreAndCoursetype(int coursescore,String 

//coursetype);



//查询学分为xx或者选修(或必修)的课程,按照命名规范,无须手动编写SQL语句

//相当于SQL: select * from course where coursescore=?1 or coursetype=?2

public List<Course> findByCoursescoreOrCoursetype(int coursescore,String coursetype);



//查询课时在xx到xx的课程

public List<Course> findByCoursetimeBetween(int start,int end);



//查询课时小于xx的课程

public List<Course> findByCoursetimeLessThan(int coursetime);



//查询课程名称包含xx字的课程

public List<Course> findByCoursenameContains(String coursename);





(2) 创建业务逻辑层。在CourseService中添加方法,代码如下:  



//第3章/jpa1/CourseService.java

//查询学分为xx的选修(或必修)课

public  List<Course> findByCoursescoreAndCoursetype(int coursescore,String coursetype){

return
courseDao.findByCoursescoreAndCoursetype(coursescore,coursetype);

}



//查询学分为xx或者选修(或必修)的课程

public  List<Course> findByCoursescoreOrCoursetype(int coursescore,String coursetype){

return
courseDao.findByCoursescoreOrCoursetype(coursescore,coursetype);

}



//查询课时在xx到xx的课程

public List<Course> findByCoursetimeBetween(int start,int end){

return courseDao.findByCoursetimeBetween(start,end);

}



//查询课时小于xx的课程

public List<Course> findByCoursetimeLessThan(int coursetime){

return courseDao.findByCoursetimeLessThan(coursetime);

}



//查询课程名称包含xx字的课程

public List<Course> findByCoursenameContains(String coursename){

return courseDao.findByCoursenameContains(coursename);

}





(3) 创建控制器。在CourseController中添加的代码如下:  



//第3章/jpa1/CourseController.java

//查询学分为xx的选修(或必修)课

@GetMapping("/findByCoursescoreAndCoursetype/{coursescore}/{coursetype}")

public  ModelAndView findByCoursescoreAndCoursetype(@PathVariable("coursescore") int coursescore,@PathVariable("coursetype") String coursetype){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.findByCoursescoreAndCoursetype(coursescore,coursetype);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}



//查询学分为xx或者选修(或必修)的课程

@GetMapping("/findByCoursescoreOrCoursetype/{coursescore}/{coursetype}")

public  ModelAndView findByCoursescoreOrCoursetype(@PathVariable("coursescore") int coursescore,@PathVariable("coursetype") String coursetype){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.findByCoursescoreOrCoursetype(coursescore,coursetype);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}



//查询课时在xx到xx的课程

@GetMapping("/findByCoursetimeBetween/{start}/{end}")

public ModelAndView findByCoursetimeBetween(@PathVariable("start") int start,@PathVariable("end") int end){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.findByCoursetimeBetween(start,end);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}



//查询课时小于xx的课程

@GetMapping("/findByCoursetimeLessThan/{coursetime}")

public ModelAndView findByCoursetimeLessThan(@PathVariable("coursetime") int coursetime){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.findByCoursetimeLessThan(coursetime);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}



//查询课程名称包含xx字的课程

@GetMapping("/findByCoursenameContains/{coursename}")

public ModelAndView findByCoursenameContains(@PathVariable("coursename") String coursename){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.findByCoursenameContains(coursename);







mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}





(4) 运行代码进行测试。在浏览器中输入:  http://localhost:8080/findByCoursescoreAndCoursetype/3/选修,结果如图310所示。其他结果可自行进行测试。



图310查询结果


3.2.6使用JPQL或原生SQL查询

除了可使用JPA接口或约定命名规范进行查询,还可自定义方法,使用JPQL或原生SQL进行更加灵活的查询。

JPQL全称为Java Persistence Query Language,是一种功能类似SQL,但不是结构化而是对象化的查询语言,查询的是对象,而不是表,查询时使用的不是字段而是对象的属性,它是一种适用不同关系数据库的通用查询语言,但最终会被编译成不同底层数据库的SQL语句。要使用JPQL,需要在DAO层接口的方法上添加@Query注解。

JPQL不支持INSERT,此外在执行UPDATE和DELETE操作时需要添加@Transactional和@Modifying注解。由于JPQL语法类似SQL,只是用对象取代数据库表,用属性取代字段,无须特意去学习JPQL语法,只需通过下面的一些案例熟悉一下JPQL的风格。

【例36】SQL与JPQL的对比。



//第3章/jpa1/CourseDao.java

//SQL语句,查询所有课程的所有列

select  *  from course c

//JPQL语句,查询所有课程的所有属性

select c from course c

//JPQL语句查询所有属性的简化格式

from course c



//SQL查询部分列

select c.coursename from course c

//JPQL查询部分属性

select c.coursename from course c





在DAO层方法上的@Query注解中添加一个nativeQuery参数,将值设置为true即可使用原生SQL语句进行查询。如果不指明nativeQuery参数,则默认为JPQL查询。

在JPQL或原生SQL中,若用到参数,语法如下:  ?+参数的序号,如?1表示第1个参数,?2表示第2个参数。也可用命名参数,语法是“:+参数名称”,在这种情况下方法的参数要用@Param注解进行定义,并且名称要相同,具体用法见下面的案例步骤(1)。@Query注解中若进行增、删、改操作,则需要再加上@Transaction注解和@Modifying注解。

【例37】使用JPQL及原生SQL查询。

(1) 数据访问层添加方法。接着上述案例项目jpa1,在CourseDao中添加方法,代码如下:  



//第3章/jpa1/CourseDao.java

//自定义查询,DAO层需手动编写SQL或JPQL语句

//使用JPQL,默认

@Query("select c from course c where c.coursescore>?1")

public List<Course> selectCourses1(int coursescore);

//使用JPQL,默认,多个参数

@Query("select c from course c where c.coursescore>?1 and c.coursetime>?2")

public List<Course> selectCourses2(int coursescore,int coursetime);

//使用原始SQL

@Query(value="select * from course where coursescore>?1",nativeQuery=true)

public List<Course> selectCourses3(int coursescore);

//使用命名参数

@Query("select c from course c where c.coursescore>:coursescore")

public List<Course> selectCourses4(@Param("coursescore")int coursescore);

//模糊查询

@Query("select c from course c where c.coursename like %?1%")

public List<Course> selectCourses5(String coursename);

//自定义的增、删、改、查还要加上@Transactional和@Modifying注解

@Transactional

@Modifying

@Query("update course set coursename=?1,coursescore=?2,coursetime=?3,coursetype=?4 where courseid=?5")

public void updateCourse(String coursename,int coursescore,int coursetime,String coursetype,int courseid);





(2) 在业务逻辑层添加方法。在业务逻辑层CourseService中添加上述Dao层方法对应的方法。由于代码几乎一样,这里不再提供。

(3) 控制器添加方法。在CourseController中添加如下方法,代码如下:  



//第3章/jpa1/CourseController.java

//自定义查询,DAO层需手动编写SQL或JPQL语句

//查询学分大于某个值的课程信息,DAO层使用JPQL

@GetMapping("/selectCourses1/{coursescore}")

public ModelAndView selectCourses1(@PathVariable("coursescore") int coursescore) {

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.selectCourses1(coursescore);

mv.addObject("courses",courses);

mv.setViewName("courses");







return mv;

}



//查询学分大于某个值,并且课时大于某个值的课程,DAO层使用JPQL,多个参数

@GetMapping("/selectCourses2/{coursescore}/{coursetime}")

public ModelAndView selectCourses2(@PathVariable("coursescore") int coursescore,@PathVariable("coursetime") int coursetime){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.selectCourses2(coursescore,coursetime);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}



//查询学分大于某个值的课程信息,DAO层使用原生SQL

@GetMapping("/selectCourses3/{coursescore}")

public ModelAndView selectCourses3(@PathVariable("coursescore") int coursescore) {

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.selectCourses3(coursescore);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}



//查询学分大于某个值的课程信息,使用命名参数

@GetMapping("/selectCourses4/{coursescore}")

public ModelAndView selectCourses4(@PathVariable("coursescore") int coursescore){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.selectCourses4(coursescore);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}



//模糊查询

@GetMapping("/selectCourses5/{coursename}")

public ModelAndView selectCourses5(@PathVariable("coursename") String coursename){

ModelAndView mv=new ModelAndView();

List<Course> courses=courseService.selectCourses5(coursename);

mv.addObject("courses",courses);

mv.setViewName("courses");

return mv;

}





(4) 运行代码进行测试。这里仅测试控制器的第1种方法,其余的方法读者可自行测试。在浏览器输入http://localhost:8080/selectCourses1/3,结果如图311所示。



图311查询结果


3.2.7一对一关联查询

对象关系映射模型ORM中有一对一、一对多、多对多等关联映射。所谓一对一映射是指每个实体有唯一的另一个实体与之关联,如人与身份证,每个人只能对应唯一的一张身份证,又如学生与借书证,每个学生只能对应唯一的一张借书证。

ORM的映射方向分为两种,一种是单向关联,另一种是双向关联。单向关联是指在其中一个实体中设置了另一个实体类型的属性,称为关联属性,从而可以引用(导航到)另一个实体,但另一个实体中并没有设置该实体类型的关联属性。双向关联是指实体双方都设置了对方类型的关联属性,从而任意一个实体均可引用(导航到)对方实体。一对一映射通常只做单向关联即可,一对多映射可以只做单向关联,但如果同时要多对一映射,则需要做双向关联,多对多映射则都需要双向关联。

在Spring Data JPA中,在一个实体上设置代表另一个实体的关联属性,在该属性上面添加@OneToOne注解和@JoinColumn注解关联到另一个实体的主键,并在注解中将外键设置为唯一约束即可实现一对一映射。这样查询一个实体的结果将包含另一个实体的数据。

【例38】实现学生Student与借书证LibraryCard之间的一对一关联。

(1) 数据库准备。在studentdb数据库中创建表librarycard,主键为id,自增长,另一个字段为creditnum,int类型,表示可借本数,填入若干测试数据。在student表中添加一个字段librarycardid,并添加若干数据,需参照librarycard表中的id值。

(2) 创建实体类LibraryCard,关键代码如下:  



//第3章/jpa1/LibraryCard.java

@Data

@AllArgsConstructor

@NoArgsConstructor

@Entity(name="librarycard")

public class LibraryCard {

//借书证号

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private int id;

//可借本数

@Column(name="creditnum")

private int creditNum;

}





(3) 创建实体类Student,通过注解实现一对一关联,关键代码如下:  



//第3章/jpa1/Student.java

@Data

@AllArgsConstructor

@NoArgsConstructor

@Entity(name="student")

public class Student {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private int id;



@Column(name="studentname")

private String studentname;



@Column(name="gender")

private String gender;



@Column(name="age")

private int age;

//一对一关联

//表示一对一关联

@OneToOne(

//级联操作

cascade = CascadeType.ALL,

//延迟加载

fetch = FetchType.LAZY,

//关联的另一方实体


targetEntity = LibraryCard.class

)

@JoinColumn(

//外键列

name="librarycardid",

//将外键列设置为唯一约束

unique = true, 

//主表的主键

referencedColumnName = "id"

)

//关联属性

private LibraryCard libraryCard;



public Student(String studentname, String gender, int age) {

this.studentname = studentname;

this.gender = gender;

this.age = age;

}

}





(4) 创建数据访问层。创建StudentDao接口,继承JpaRepository接口,代码如下:  



//第3章/jpa1/StudentDao.java

public interface StudentDao extends JpaRepository<Student,Integer> {



}





(5) 创建业务逻辑层。创建StudentService,关键代码如下:  



//第3章/jpa1/StudentService.java

@Service

public class  StudentService {

@Autowired

private StudentDao studentDao;

public List<Student> findAllStudents(){

return studentDao.findAll();

}

public Student findStudentById(int id){

return studentDao.findById(id).orElse(new Student());

}

}





(6) 创建控制器。创建StudentController类,关键代码如下:  



//第3章/jpa1/StudentController.java

@Controller

public class StudentController {

@Autowired

private StudentService studentService;



@GetMapping("/findStudentById/{id}")

public ModelAndView findStudentById(@PathVariable("id") int id){

ModelAndView mv=new ModelAndView();

Student student=studentService.findStudentById(id);

mv.addObject("student",student);

mv.setViewName("student");

return mv;

}

}





(7) 创建视图。创建student.html,关键代码如下:  



//第3章/jpa1/student.html

<body>

<h1>学生详情</h1>

<table border="1" align="center">

<tr><td>学号</td><td th:text="${student.id}"></td></tr>

<tr><td>姓名</td><td th:text="${student.studentname}"></td></tr>

<tr><td>性别</td><td th:text="${student.gender}"></td></tr>

<tr><td>年龄</td><td th:text="${student.age}"></td></tr>

<tr><td>借书证信息</td><td>

<table>

<tr><td>借书证号</td><td th:text="${student.libraryCard.id}"></td></tr>

<tr><td>可借本数</td><td th:text="${student.libraryCard.creditNum}"></td></tr>

</table>

</td></tr>

</table>

</body>





(8) 运行代码进行测试。在浏览器中输入:  http://localhost:8080/findStudentById/1,结果如图312所示。



图312查询结果 


3.2.8一对多与多对一关联查询

对象关系映射模型ORM中的一对多映射是指一个实体有若干个其他实体与之对应,如一个部门有若干名员工,部门为“一方”,员工为“多方”,一个班级有若干名学生,班级为“一方”,学生为“多方”。多对一则与一对多正好相反。在Spring Data JPA中,在代表“一方”的实体类上设置代表“多方”实体集合的关联属性,在该属性上面添加@OneToMany注解,在代表“多方”的实体类上设置代表“一方”的关联属性,在该属性上面添加@ManyToOne注解和@JointColumn注解即可同时实现一对多和多对一映射。这样查询“一方”实体的结果将包含“多方”实体集合的数据,查询“多方”实体的结果将包含“一方”实体的数据。

【例39】实现班级Classes对象与学生Student的一对多与多对一映射。

(1) 数据库准备。创建表classes,主键id自增长,添加若干数据,如图313所示。修改student表,添加classes_id列,添加若干数据,如图314所示。



图313班级表




图314学生表



(2) 创建实体类Classes作为“一方”。Classes类的关键代码如下:  



//第3章/jpa1/Classes.java

@Data

@AllArgsConstructor

@NoArgsConstructor

@Entity(name="classes")

public class Classes {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private int id;



@Column(name="classname")

private String classname;



@Column(name="address")

//教室

private String address;



@OneToMany(

//指定关联的另一方实体

targetEntity = Student.class,

//级联操作

cascade = CascadeType.ALL,

//延迟加载

fetch = FetchType.LAZY

)

//指定多方表的外键

@JoinColumn(name="classes_id")

//关联属性,多方的集合

private List<Student> students;

}





(3) 修改实体类Student。添加的关键属性及注解如下:  



@ManyToOne

//多对一关联

private Classes classes;  





这表示多对一关联映射,如果只想做一对多,不做多对一,则Student实体类可以不要classes属性。

(4) 创建Dao层。创建ClassDao,代码如下:  



public interface ClassesDao extends JpaRepository<Classes,Integer> {

}





(5) 创建业务层。在包com.seehope.service下创建ClassesService类,关键代码如下:  



//第3章/jpa1/ClassesService.java

@Service

public class ClassesService {

@Autowired







private ClassesDao classesDao;

public List<Classes> findAllClasses(){

return classesDao.findAll();

}

}





(6) 创建控制器。在com.seehope.controller包下创建ClassesController类,关键代码如下:  



//第3章/jpa1/ClassesController.java

@Controller

public class ClassesController {

@Autowired

private ClassesService classService;



@GetMapping("/findAllClasses")

public ModelAndView findAllClasses(){

ModelAndView mv=new ModelAndView();

List<Classes> classesList=classService.findAllClasses();

mv.addObject("classesList",classesList);

mv.setViewName("classesList");

return mv;

}

}





在StudentController中添加方法,代码如下:  



//第3章/jpa1/StudentController.java

@GetMapping("/findStudentClasses/{id}")

public ModelAndView findStudentClasses(@PathVariable("id") int id){

ModelAndView mv=new ModelAndView();

Student student=studentService.findStudentById(id);

mv.addObject("student",student);

mv.setViewName("student2");

return mv;

}





(7) 创建视图。创建 classesList.html,复制student.html并保存为student2.html,适当修改,以便可以显示班级信息。详细代码见随书配套资源。

(8) 运行代码进行测试。在浏览器中输入http://localhost:8080/findAllClasses,结果如图315所示,体现了一对多(每个班级对应多名学生)。在浏览器中输入 http://localhost:8080/findStudentClasses/1,结果如图316所示,体现了多对一(每名学生对应一个班级)。

3.2.9多对多关联查询

多对多是指双方的一个实体有多个对方类型的实体与之对应,如学生与课程,一名学生可以选多门课程,一门课程可以被多选。多对多关联只能通过中间表进行映射。在Spring Data JPA中,通过在实体类中进行双向关联,设置对方类型的集合属性,并在该属性上面添加@ManyToMany注解,以及使用@JointTable指定中间表及外键即可实现多对多关联。



图315一对多查询 




图316多对一查询



【例310】实现学生Student与课程Course之间的多对多映射。

(1) 数据库准备。创建一个中间表student_course,主键自增长。另有两个字段student_id和course_id,分别作为student表和course表的外键。

(2) 修改实体类Student,添加的关键代码如下:  



//第3章/jpa1/Student.java

@ManyToMany(fetch = FetchType.LAZY)

@JoinTable(name="student_course",joinColumns={@JoinColumn(name="student_id")},inverseJoinColumns={@JoinColumn(name="course_id")})

//该学生选修的课程集合,多对多关联

private List<Course> courseList; 





这里@JointTable用于指定中间表,joinColumns属性用于指定外键列,inverseJoinColumns用于指定关联方的外键列。

(3) 修改实体类Course,添加的关键代码如下:  



//第3章/jpa1/Course.java

@ManyToMany(fetch = FetchType.LAZY)

@JoinTable(name="student_course",joinColumns={@JoinColumn(name="course_id")},

inverseJoinColumns={@JoinColumn(name="student_id")})

//选修该课程的学生集合,多对多关联

private List<Student> studentList; 





(4) 创建测试类,关键代码如下:  



//第3章/jpa1/Test1.java

@Spring BootTest

public class Test1 {

@Autowired

private StudentDao studentDao;



@Autowired

private CourseDao courseDao;



@Test

public void addStudentAndCourse(){

List<Student> students=new ArrayList<>();

List<Course> courses=new ArrayList<>();

Course course1=new Course("数据结构", 2, 40, "选修", "aaa") ;

Course course2=new Course("操作系统", 4, 40, "必修", "aaa") ;

courses.add(course1);

courses.add(course2);

courseDao.save(course1);

courseDao.save(course2);

Student student1=new Student("Smith","男",20);

Student student2=new Student("Alice","女",19);

students.add(student1);

students.add(student2);

course1.setStudentList(students);//一门课程关联多名学生

course2.setStudentList(students);//一门课程关联多名学生

student1.setCourseList(courses);//一名学生关联多门课程

student2.setCourseList(courses);//一名学生关联多门课程

studentDao.save(student1);

studentDao.save(student2);

System.out.println("保存成功!");

}

}





运行测试类,查看数据库表student_course可发现多了若干记录。

(5) 创建控制器。在CourseController中添加的方法如下:  



//第3章/jpa1/CourseController.java

@GetMapping("/findCourseStudent")

//查询某个编号的课程

public ModelAndView findCourseStudent(int id){

ModelAndView mv=new ModelAndView();

Course course=courseService.findById(id);

mv.addObject("course",course);

mv.setViewName("course2");

return mv;

}





在StudentController中添加的方法如下:  



//第3章/jpa1/StudentController.java

@GetMapping("/findStudentCourse")

//查询某个编号的学生







public ModelAndView findStudentCourse(int id){

ModelAndView mv=new ModelAndView();

Student student=studentService.findStudentById(id);

mv.addObject("student",student);

mv.setViewName("student3");

return mv;

}





(6) 创建视图。创建course2.html和student3.html页面,详见配套资源。

(7) 运行代码进行测试。在浏览器输入http://localhost:8080/findStudentCourse?id=8,结果如图317所示。

在浏览器输入http://localhost:8080/findCourseStudent?id=12,结果如图318所示。



图317一名学生对应多门课程




图318一门课程对应多名学生



3.2.10多条件动态查询

对于查询条件不确定的情况,要根据用户的不同选择生成动态的SQL语句。JPA提供了JpaSpecificationExecutor接口,该接口提供了List<T> findAll(@Nullable Specification<T> spec)等带有Specification对象的方法,这个Specification接口的toPredicate方法专门用于实现多条件动态查询。详情参见下面的案例。

【例311】在上面项目的基础上,根据学生姓名和性别等条件动态查询学生信息。

(1) Dao接口继承JpaSpecificationExecutor接口。在原有继承的基础上,增加继承JpaSpecificationExecutor接口,代码如下:  



public interface StudentDao extends JpaRepository<Student,Integer>, JpaSpecificationExecutor {



}





(2) 在业务层添加方法。在业务层StudentService添加的代码如下:  



//第3章/jpa1/StudentService.java

//查找学生







public List<Student> searchStudents(Student student){

//封装查询对象Specification,这是个自带的动态条件查询

Specification<Student> spec = new Specification<Student>() {

@Override

public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {

//定义集合来肯定Predicate[] 的长度,由于CriteriaBuilder的or方法需要传入的是断言数组

List<Predicate> predicates = new ArrayList<Predicate>();

//对客户端查询条件进行判断并封装Predicate断言对象

if(null != student.getStudentname()&&""!=student.getStudentname()){

predicates.add(criteriaBuilder.like(root.get("studentname").as(String.class), "%"+student.getStudentname()+"%"));

}

if(null != student.getGender()&&""!=student.getGender()){

predicates.add(criteriaBuilder.equal(root.get("gender").as(String.class), student.getGender()));

}

return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));

}

};

//查询出所有数据源列表

return studentDao.findAll(spec);

}





多条件动态查询的关键技术就是上述代码。

(3) 在控制器添加方法,代码如下:  



//第3章/jpa1/StudentController.java

@GetMapping("/searchStudents")

public ModelAndView searchStudents(Student student){

ModelAndView mv=new ModelAndView();

List<Student> students=studentService.searchStudents(student);

mv.addObject("students",students);

mv.setViewName("students");

return mv;

}





(4) 前端页面。在students.html页面添加的代码如下:  



//第3章/jpa1/students.html

<div>

<form action="/searchStudents" method="get">

姓名: <input type="text" name="studentname" /><br/>

性别: <input type="text" name="gender"/><br/>

<input type="submit" value="搜索"/>

</form>

</div>

<br/>





(5) 运行代码进行测试。通过浏览器访问http://localhost:8080/findAllStudents,结果如图319所示。在搜索框中输入李,结果如图320所示。在两个搜索框分别输入李和男,结果如图321所示。可见不同条件都可以动态地查出来。



图319全部学生信息




图320姓李的学生



图321姓李的男生


本章小结

本章学习了Spring Boot JDBC操作数据库的技术,Spring Data JPA操作数据库技术包括简单查询、命名查询、JPQL或原生SQL查询、一对多、多对一、多对多等复杂查询与动态查询。