SpringBoot 整合 JPA 轻松搞定数据表增删改查!
01、背景介绍
在之前的文章中,我们介绍了通过JdbcTemplate
来实现数据库的访问和读写操作。当有一定的开发经验之后,你会发现所有涉及到数据库操作的代码,除了表名和字段不同外,操作的语句基本都类似,功能上可以统一归纳为“增、删、改、查”,编写大量这种类似的代码,对于开发者来说,其实非常枯燥。
为了解决重复的编写数据操作语句,开发社区诞生了许多优秀的 ORM 框架,比如 Hibernate、OpenJPA、TopLink 等,其中 Hibernate 相对较为知名,在 Hibernate 框架的帮助下,开发者可以轻松的以操作 Java 实体的方式来完成对数据表的“增删改查”操作,能极大的简化代码编写的工作量。
国内外有不少的项目基于 Spring Boot JPA 来完成对数据库的操作访问,那么 Spring Boot JPA 和 ORM 框架之间有着怎样的关系呢?
简单的说,Spring Boot JPA 是 Spring 在 ORM 框架的基础上封装的一套 JPA 应用框架,具体的数据访问和操作实现还是依赖于 ORM 框架来完成,Spring Boot JPA 只是完成了接口操作的标准封装,包括增删改查等在内的常用功能,可以帮助开发者降低学习成本,同时极大的提升开发效率。
值得一提的是:JPA (Java Persistence API) 是 Sun 官方提出的一套 Java 数据持久化操作的规范,不是一套产品,像上文说的 Hibernate,OpenJPA,TopLink 等,可以理解成 JPA 的具体产品实现。
以 Spring Boot 的2.0
版本为例,Spring Boot JPA 的底层依赖于 Hibernate 框架来完成数据库的访问和操作,如果你熟悉 Hibernate 的框架使用,那么可以轻松的上手并使用它。
下面我们一起来看看 Spring Boot JPA 的具体使用姿势。
02、应用实践
2.1、工程配置
首先在pom.xml
文件中添加相关的依赖包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
然后在application.properties
文件中配置相关的数据源访问地址,以及相关hibernate
属性配置。
# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# hibernate信息配置
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
参数是hibernate
的一个配置属性,主要作用有:自动创建、更新、验证数据库表结构。
相关的可选参数如下:
create
:每次加载 hibernate 相关实体表时会删除上一次生成的表,然后按照最新的 model 类生成新表,会造成数据库表数据丢失;create-drop
:每次加载 hibernate 时会根据 model 类生成新表,当服务关闭时,表自动删除,通常用于测试;update
:常用属性,第一次加载 hibernate 时会根据 model 类自动建表,以后加载 hibernate 时根据 model 类自动更新表结构,但是不会删除表中的数据;validate
:每次加载 hibernate 时会验证数据库表的结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值;
其次,spring.jpa.properties.hibernate.dialect
参数主要用于指定生成表名时的存储引擎为 InnoDBD
,如果不配置,默认是MylSAM
;spring.jpa.show-sql
参数用于打印出自动生成的 SQL,方便调试。
2.2、基本增删改查操作
下面我们一起来体验一下 JPA 中常用的增删改查操作。
2.2.1、创建实体
创建一个Student
实体,包含三个属性,表名为tb_student
,当加载 hibernate 的时候会自动创建到数据库中,代码如下:
@Entity
@Table(name = "tb_student")
public class Student {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, unique = true)
private String name;
@Column(nullable = false)
private Integer age;
// set、get方法等...
}
@Entity
注解用于标识Student
类是一个持久化的实体类;@Table
注解用于标识Student
类映射到数据库中的表名称;@Id
注解用于标识Student
映射到数据库的主键字段@GeneratedValue
注解用于标识Student
映射到数据库的主键为自增类型@Column
注解用于标识Student
映射到数据库的字段相关信息
2.2.2、创建数据访问接口
针对Student
实体类,创建一个对应的JpaRepository
接口,用于实现对实体的数据访问和操作,代码如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
}
其中JpaRepository
接口已经封装好了常用的增删改查方法逻辑,使用者只需要调用相关的方法接口实现对数据库表的操作。
JpaRepository
接口封装的部分方法,源码如下图!
#### 2.2.3、单元测试
完成以上的实体创建和数据访问接口的编写之后,下面我们编写对应的单元测试类来验证一下编写的内容是否正确,代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentJPATest {
@Autowired
private StudentRepository studentRepository;
@Test
public void test(){
// 插入3条数据
studentRepository.save(new Student("张三", 20));
studentRepository.save(new Student("李四", 21));
studentRepository.save(new Student("王五", 22));
// 查询全部数据
List<Student> dbList = studentRepository.findAll();
System.out.println("第一次全量查询结果:" + dbList.toString());
System.out.println("------------------------");
// 修改数据
studentRepository.save(new Student(dbList.get(0).getId(),"赵六", 20));
// 查询指定数据
Optional<Student> findResult = studentRepository.findById(dbList.get(0).getId());
System.out.println("查询第一条数据结果:" + findResult.toString());
System.out.println("-----------------");
// 删除数据
studentRepository.deleteById(dbList.get(0).getId());
// 查询全部数据
List<Student> result = studentRepository.findAll();
System.out.println("第二次全量查询结果:" + result.toString());
}
}
运行单元测试,输出结果如下!
第一次全量查询结果:[Student{id=1, name='张三', age=20}, Student{id=2, name='李四', age=21}, Student{id=3, name='王五', age=22}]
------------------------
查询第一条数据结果:Optional[Student{id=1, name='赵六', age=20}]
------------------------
第二次全量查询结果:[Student{id=2, name='李四', age=21}, Student{id=3, name='王五', age=22}]
2.3、自定义简单查询操作
Spring Boot JPA 不仅为开发者封装了常用的模板方法,还支持根据方法名来动态生成 SQL 语句,比如findByXX
,countByXX
,getByXX
后面跟属性名称,当调用方法的时候会自动生成响应的 SQL 语句,具体示例如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
/**
* 自定义简单查询,通过姓名进行搜索
* @param name
* @return
*/
Student findByName(String name);
/**
* 自定义简单查询,通过姓名和年龄进行统计
* @param name
* @return
*/
Integer countByNameAndAge(String name, Integer age);
}
编写单元测试验证内容的正确性。
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentJPATest {
@Autowired
private StudentRepository studentRepository;
@Test
public void simpleTest(){
Student result1 = studentRepository.findByName("李四");
System.out.println("第一次查询结果:" + result1.toString());
System.out.println("-----------------");
Integer result2 = studentRepository.countByNameAndAge("王五", 22);
System.out.println("第二次查询结果:" + result2);
}
}
输出结果如下!
第一次查询结果:Student{id=2, name='李四', age=21}
-----------------
第二次查询结果:1
方法上支持 SQL 语句中的关键字,比如And
、Or
、Like
、OrderBy
等。
具体关键字上的使用和生成的 SQL 对应的关系如下:
更多的关键字使用可以参阅官方文档。
2.4、复杂查询操作
在实际的开发过程中,由于业务的需要,我们经常需要编写复杂的 SQL 语句,比如链表查询,分页查询等,这个时候就需要用到自定义 SQL 语句的操作了。
2.4.1、自定义 SQL 查询
其实大部分的 SQL 语句都可以通过方法来动态生成,如果想自定义 SQL 查询,Spring Boot JPA 也是支持的,操作上很简单。
在接口方法上,添加@Query
注解,即可实现自定义 SQL 语句;如果涉及到新增、修改和删除操作,需要再加上@Modifying
注解,同时也需要添加@Transactional
注解事务支持。
具体示例如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
/**
* 自定义SQL语句,单条查询
* @param studentName
* @return
*/
@Query(value = "select s from Student s where s.name = ?1")
Student findByStudentName(String studentName);
/**
* 自定义SQL语句,修改数据
* @param name
* @param age
* @return
*/
@Transactional
@Modifying
@Query(value = "update Student s set s.age = ?2 where s.name = ?1")
int updateAgeByName(String name, Integer age);
/**
* 自定义SQL语句,删除数据
* @param name
*/
@Transactional
@Modifying
@Query(value = "delete from Student s where s.name = ?1")
int deleteByName(String name);
}
编写单元测试验证内容的正确性。
@Test
public void sqlTest(){
// 新增
studentRepository.save(new Student("王五", 22));
// 查询
Student result1 = studentRepository.findByStudentName("王五");
System.out.println("第一次查询结果:" + result1.toString());
System.out.println("-----------------");
// 修改
studentRepository.updateAgeByName("王五", 30);
Student result2 = studentRepository.findByStudentName("王五");
System.out.println("第二次查询结果:" + result2.toString());
System.out.println("-----------------");
// 删除
studentRepository.deleteByName("王五");
Student result3 = studentRepository.findByStudentName("王五");
System.out.println("第三次查询结果:" + result3);
}
输出结果如下!
第一次查询结果:Student{id=4, name='王五', age=22}
-----------------
第二次查询结果:Student{id=4, name='王五', age=30}
-----------------
第三次查询结果:null
值得注意的是:这里自定义的 SQL 语句并非数据库中的 SQL 语句,而是 hibernate 所支持 SQL 语句,简称 hsql,例如表名要采用 Java 实体而非数据库中真实的表名,否则可能会报错。
2.4.2、分页查询
分页查询,在实际的业务开发中非常常见,其实 Spring Boot JPA 已经帮助开发者封装了分页查询的方法逻辑,在查询的时候传入Pageable
参数即可。
示例如下:
@Test
public void pageTest(){
// 构建分页参数
int page=0,size=10;
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
// 构建单条件查询参数
Student param = new Student();
param.setAge(21);
Example<Student> example = Example.of(param);
// 发起分页查询
Page<Student> result = studentRepository.findAll(example, pageable);
System.out.println("查询结果,总行数:" + result.getTotalElements());
System.out.println("查询结果,明细:" + result.getContent());
}
输出结果如下:
查询结果,总行数:1
查询结果,明细:[Student{id=2, name='李四', age=21}]
当然我们也可以根据自定义简单查询来实现分页查询,在 JPA 的帮助下动态生成 SQL 语句,示例如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
/**
* 自定义简单查询,通过年龄进行分页搜索
* @param age
* @param pageable
* @return
*/
Page<Student> findByAge(Integer age, Pageable pageable);
}
值得注意的是:自定义的方法不会自动进行count
语句汇总查询,推荐采用模板方法来进行分页查询。
2.4.3、多表查询
多表查询,也是实际开发中经常会碰到的场景,Spring Boot JPA 提供了两种实现方式,第一种是利用 Hibernate 的级联查询来实现,第二种是自定义 SQL 语句来实现。
第一种就不多说了,主要通过@OneToOne
、@OneToMany
、@ManyToOne
、@ManyToMany
和@JoinTable
注解来完成多表的级联查询,不过这种方式需要在数据库层面建立外键关联,通过外键来完成级联查询,不推荐采用。
下面我们来介绍一下自定义 SQL 语句来实现,实现起来也很简单,示例如下:
public interface TeacherRepository extends JpaRepository<Teacher,Long> {
/**
* 自定义链表查询
* @return
*/
@Query(value = "select s.id, s.name, t.teacher_id as teacherId, t.teacher_name as teacherName from tb_student s left join tb_teacher t on s.teacher_id = t.teacher_id ", nativeQuery = true)
List<Map<String,Object>> findCustomer();
}
编写单元测试,验证代码的正确性。
@RunWith(SpringRunner.class)
@SpringBootTest
public class TeacherJPATest {
@Autowired
private TeacherRepository teacherRepository;
@Test
public void test2(){
// 查询全部数据
List<Map<String,Object>> dbList = teacherRepository.findCustomer();
System.out.println("查询结果:" + JSONObject.toJSONString(dbList));
}
}
输出结果如下:
查询结果:[{"id":9,"teacherId":1,"name":"李1","teacherName":"张老师"},{"name":"李2","id":10,"teacherId":1,"teacherName":"张老师"},{"id":11,"name":"李3","teacherId":1,"teacherName":"张老师"}]
直接编写 sql 语句,非常简单灵活。
2.5、属性映射屏蔽操作
如果某个实体类中的属性,不想被映射到数据库,可以添加@Transient
注解来实现,示例如下。
@Transient
private String name;
03、小结
本文主要围绕利用 Spring Boot JPA 对数据库的访问和操作,做了一次知识内容的整理和总结,如果有描述不对的地方,欢迎留言指出!
04、参考
1、http://www.ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html