第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文件中导入springbootstarterjdbc依赖和数据库连接Java的依赖,并在application.properties文件中设置好数据库连接信息,Spring Boot就会自动向容器注入JDBCTemplate实例,这样开发者就可使用@Autowired注解依赖注入JDBCTemplate,再调用JDBCTemplate的API方法对数据库进行增、删、改、查操作。由于JDBCTemplate并非主流,这里只提供一个简单的增、删、改、查的案例供参考。 【例31】使用Spring Boot整合JDBCTemplate操作studentdb中的student表。 (1) 数据库准备。在MySQL数据库studentdb中创建表student,插入若干数据,见图31。 图31创建表student (2) 创建项目导入依赖。在IDEA新建Spring Boot项目JDBCtemplate1,导入springbootstarterjdbc依赖、mysqlconnectorjava依赖等,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,结果如图32所示。 在浏览器网址栏输入http://localhost:8080/stu/1,结果如图33所示。 图32查找所有学生 图33查找一个学生 单击图32中的添加学生超链接,并填写数据,如图34所示。单击“添加”按钮,结果如图35所示。 图34添加学生 图35添加后的结果 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只是一个空接口,具有标记作用,各个接口之间的关系如图36所示。 图36接口之间的关系 所以开发者只要自定义一个接口继承JpaRepository接口即可获得上述所有接口的功能。开发者自定义的接口还可以进行方法命名查询,方法名称和参数只要符合JPA的命名规范,Spring Data JPA就会根据方法名字来确定需要实现什么样的逻辑,无须开发者编写SQL语句。自定义的接口中的方法也可配套使用JPQL语言或原生SQL语句进行更加灵活的查询,此外Spring Data JPA还可方便地处理一对一,一对多,以及多对多表关联查询。 3.2.1JpaRepository接口 JpaRepository接口除继承了父接口的方法外还提供了其他方法,见表31。 表31JpaRepository接口方法 方 法 名 称功 能 描 述 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接口主要提供了用于分页查询和排序的方法,见表32。 表32PagingAndSortingRepository接口方法 方 法 名 称功 能 描 述 Iterable<T> findAll(Sort var1)查询所有对象并根据Sort对象中的规则进行排序 Page<T> findAll(Pageable var1)查询所有对象并根据Pageable对象中的规则进行分页处理,封装为一个Page对象。注意: Pageable中又可包含排序 Page是Spring Data提供的一个封装了分页信息的接口,Pageable是构造分页查询的接口,用于指定页码(从0算起),每页显示记录数,以及是否排序等,具体由PageRequest类的of静态方法进行实现。 【例32】假设用户在项目中已经创建好了继承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个参数用于指定排序属性(字段)。 【例33】Sort sort=new Sort(Sort.Direction.DESC, "id"); 表示创建一个根据属性id进行降序排序的Sort对象。也可以使用Sort类的静态方法by创建Sort对象,语法可参照上述示例代码。 3.2.3CrudRepository接口 CrudRepository接口提供了常用的增、删、改、查方法,见表33。 表33CrudRepository接口方法 方 法 名 称功 能 描 述 <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基本增、删、改、查方法 【例34】Spring Data JPA的基本增、删、改、查及分页操作。 (1) 数据库准备。在studentdb数据库中创建表course,主键courseid自增长,添加若干数据, 如图37所示。也可以不创建表,由程序自动创建,见第(3)步中有关update的说明。 图37数据库表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类重新创建新表,这样会导致数据库数据丢失,应谨慎使用。 createdrop: 每次加载Hibernate时都会根据Model类创建新表,一旦SessionFactory关闭就自动删除表。 update: 首次加载Hibernate时若没有Model类对应的数据库表就会根据Model类创建数据表,以后加载时,不会创建新表,若Model类被修改过,则会更新数据库表结构,但数据不会被删除。 validate: 每次加载Hibernate时,会验证数据库的表结构而不会创建新表,但会插入新值。 spring.jpa.properties.hibernate.dialect: 对特定的关系数据库生成优化的SQL。 spring.jpa.showsql=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> |当前页: <span th:text="${page.getNumber()+1}"></span> |总记录数: <span th:text="${page.getTotalElements()}"></span> \ |当前页记录数: <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,结果如图38所示。 图38查询所有图书列表展示 在浏览器输入http://localhost:8080/findAll3,结果如图39所示。 图39分页查询结果 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进行连接。符合命名规范的方法名见表34。 表34符合命名规范的方法名示例 关键词方法命名示例对应的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。 【例35】根据约定命名规范查询。 (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/findByCoursescoreAndCoursetype/3/选修,结果如图310所示。其他结果可自行进行测试。 图310查询结果 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的风格。 【例36】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注解。 【例37】使用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,结果如图311所示。 图311查询结果 3.2.7一对一关联查询 对象关系映射模型ORM中有一对一、一对多、多对多等关联映射。所谓一对一映射是指每个实体有唯一的另一个实体与之关联,如人与身份证,每个人只能对应唯一的一张身份证,又如学生与借书证,每个学生只能对应唯一的一张借书证。 ORM的映射方向分为两种,一种是单向关联,另一种是双向关联。单向关联是指在其中一个实体中设置了另一个实体类型的属性,称为关联属性,从而可以引用(导航到)另一个实体,但另一个实体中并没有设置该实体类型的关联属性。双向关联是指实体双方都设置了对方类型的关联属性,从而任意一个实体均可引用(导航到)对方实体。一对一映射通常只做单向关联即可,一对多映射可以只做单向关联,但如果同时要多对一映射,则需要做双向关联,多对多映射则都需要双向关联。 在Spring Data JPA中,在一个实体上设置代表另一个实体的关联属性,在该属性上面添加@OneToOne注解和@JoinColumn注解关联到另一个实体的主键,并在注解中将外键设置为唯一约束即可实现一对一映射。这样查询一个实体的结果将包含另一个实体的数据。 【例38】实现学生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,结果如图312所示。 图312查询结果 3.2.8一对多与多对一关联查询 对象关系映射模型ORM中的一对多映射是指一个实体有若干个其他实体与之对应,如一个部门有若干名员工,部门为“一方”,员工为“多方”,一个班级有若干名学生,班级为“一方”,学生为“多方”。多对一则与一对多正好相反。在Spring Data JPA中,在代表“一方”的实体类上设置代表“多方”实体集合的关联属性,在该属性上面添加@OneToMany注解,在代表“多方”的实体类上设置代表“一方”的关联属性,在该属性上面添加@ManyToOne注解和@JointColumn注解即可同时实现一对多和多对一映射。这样查询“一方”实体的结果将包含“多方”实体集合的数据,查询“多方”实体的结果将包含“一方”实体的数据。 【例39】实现班级Classes对象与学生Student的一对多与多对一映射。 (1) 数据库准备。创建表classes,主键id自增长,添加若干数据,如图313所示。修改student表,添加classes_id列,添加若干数据,如图314所示。 图313班级表 图314学生表 (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,结果如图315所示,体现了一对多(每个班级对应多名学生)。在浏览器中输入 http://localhost:8080/findStudentClasses/1,结果如图316所示,体现了多对一(每名学生对应一个班级)。 3.2.9多对多关联查询 多对多是指双方的一个实体有多个对方类型的实体与之对应,如学生与课程,一名学生可以选多门课程,一门课程可以被多选。多对多关联只能通过中间表进行映射。在Spring Data JPA中,通过在实体类中进行双向关联,设置对方类型的集合属性,并在该属性上面添加@ManyToMany注解,以及使用@JointTable指定中间表及外键即可实现多对多关联。 图315一对多查询 图316多对一查询 【例310】实现学生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,结果如图317所示。 在浏览器输入http://localhost:8080/findCourseStudent?id=12,结果如图318所示。 图317一名学生对应多门课程 图318一门课程对应多名学生 3.2.10多条件动态查询 对于查询条件不确定的情况,要根据用户的不同选择生成动态的SQL语句。JPA提供了JpaSpecificationExecutor接口,该接口提供了List<T> findAll(@Nullable Specification<T> spec)等带有Specification对象的方法,这个Specification接口的toPredicate方法专门用于实现多条件动态查询。详情参见下面的案例。 【例311】在上面项目的基础上,根据学生姓名和性别等条件动态查询学生信息。 (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,结果如图319所示。在搜索框中输入李,结果如图320所示。在两个搜索框分别输入李和男,结果如图321所示。可见不同条件都可以动态地查出来。 图319全部学生信息 图320姓李的学生 图321姓李的男生 本章小结 本章学习了Spring Boot JDBC操作数据库的技术,Spring Data JPA操作数据库技术包括简单查询、命名查询、JPQL或原生SQL查询、一对多、多对一、多对多等复杂查询与动态查询。