SpringBoot重点详解--使用JPA操作数据库

目录

JPA & Spring Data JPA

配置Maven依赖

配置数据源和JPA

创建POJO实体

数据持久化

使用Spring Data JPA接口(方式一)

CrudRepository

PagingAndSortingRepository

JpaRepository

QueryByExampleExecutor

自定义查询方法(方式二)

JUnit测试


使用数据库是开发基本应用的基础,借助于开发框架,我们已经不用编写原始的访问数据库的代码,也不用调用JDBC(Java Data Base Connectivity)或者连接池等诸如此类的被称作底层的代码,我们将从更高的层次上访问数据库,这在Springboot中更是如此,本章我们将详细介绍在Springboot中使用 Spring Data JPA 来实现对数据库的操作。  

JPA & Spring Data JPA

JPA是Java Persistence API的简称,中文名Java持久层API,是Sun官方提出的Java持久化规范,其设计目标主要是为了简化现有的持久化开发工作和整合ORM技术。JPA使用XML文件或注解(JDK 5.0或更高版本)来描述对象-关联表的映射关系,能够将运行期的实体对象持久化到数据库,它为Java开发人员提供了一种ORM工具来管理Java应用中的关系数据。 简单地说,JPA就是为POJO(Plain Ordinary Java Object)提供持久化的标准规范,即将Java的普通对象通过对象关系映射(Object-Relational Mapping,ORM)持久化到数据库中。由于JPA是在充分吸收了现有Hibernate,TopLink,JDO等ORM框架的基础上发展而来的,因而具有易于使用、伸缩性强等优点。

Spring Data JPA 是 Spring 基于 Spring Data 框架、在JPA 规范的基础上开发的一个框架,使用 Spring Data JPA 可以极大地简化JPA 的写法,可以在几乎不用写实现的情况下实现对数据库的访问和操作,除了CRUD外,还包括分页和排序等一些常用的功能。  

配置Maven依赖

以MySQL数据库为例,为了使用JPA和MySQL,首先在工程中引入它们的Maven依赖。 


 
 
  1. <parent>
  2. <groupId>org.springframework.boot </groupId>
  3. <artifactId>spring-boot-starter-parent </artifactId>
  4. <version>1.5.1.RELEASE </version>
  5. </parent>
  6. <dependencies>
  7. <dependency>
  8. <groupId>mysql </groupId>
  9. <artifactId>mysql-connector-java </artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot </groupId>
  13. <artifactId>spring-boot-starter-data-jpa </artifactId>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework </groupId>
  17. <artifactId>spring-test </artifactId>
  18. </dependency>
  19. </dependencies>

配置数据源和JPA

在Springboot核心配置文件 application.properties 中配置MySQL数据源和JPA。 


 
 
  1. ########################################################
  2. ### Spring datasource -- Datasource configuration.
  3. ########################################################
  4. spring.datasource.url = jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8
  5. spring.datasource.username = root
  6. spring.datasource.password = 123456
  7. spring.datasource.driverClassName = com.mysql.jdbc.Driver
  8. spring.datasource.type = org.apache.tomcat.jdbc.pool.DataSource
  9. ########################################################
  10. ### Java Persistence Api -- Spring jpa configuration.
  11. ########################################################
  12. # Specify the DBMS
  13. spring.jpa.database = MYSQL
  14. # Show or not log for each sql query
  15. spring.jpa.show- sql = true
  16. # Hibernate ddl auto ( create, create- drop, update, validate, none)
  17. spring.jpa.hibernate.ddl- auto = update
  18. # Naming strategy
  19. #[org.hibernate.cfg.ImprovedNamingStrategy #org.hibernate.cfg.DefaultNamingStrategy]
  20. spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
  21. # stripped before adding them to the entity manager)
  22. spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

其中,spring.jpa.hibernate.ddl-auto 参数用来配置是否开启自动更新数据库表结构,可取create、create-drop、update、validate、none五个值。 

  • create 每次加载hibernate时,先删除已存在的数据库表结构再重新生成;
  • create-drop 每次加载hibernate时,先删除已存在的数据库表结构再重新生成,并且当 sessionFactory关闭时自动删除生成的数据库表结构;
  • update 只在第一次加载hibernate时自动生成数据库表结构,以后再次加载hibernate时根据model类自动更新表结构;
  • validate 每次加载hibernate时,验证数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
  • none 关闭自动更新 

创建POJO实体

首先创建一些普通对象,用来与数据库的表建立映射关系,在此我们只定义了员工和部门两个实体来进行示例。 


 
 
  1. @Entity
  2. @Table(name = "tbl_employee") // 指定关联的数据库的表名
  3. public class Employee implements Serializable {
  4. private static final long serialVersionUID = 1L;
  5. @Id
  6. @GeneratedValue(strategy = GenerationType.IDENTITY)
  7. private Long id; // 主键ID
  8. private String name; // 姓名
  9. @ManyToOne
  10. @JoinColumn(name = "department_id")
  11. private Department department; // 部门
  12. public Long getId() {
  13. return id;
  14. }
  15. public void setId(Long id) {
  16. this.id = id;
  17. }
  18. public String getName() {
  19. return name;
  20. }
  21. public void setName(String name) {
  22. this.name = name;
  23. }
  24. public Department getDepartment() {
  25. return department;
  26. }
  27. public void setDepartment(Department department) {
  28. this.department = department;
  29. }
  30. }

 
 
  1. @Entity
  2. @Table(name = "tbl_department")
  3. public class Department implements Serializable{
  4. private static final long serialVersionUID = 1L;
  5. @Id
  6. @GeneratedValue(strategy = GenerationType.IDENTITY)
  7. private Long id; // 主键ID
  8. private String name; // 部门名称
  9. public Long getId() {
  10. return id;
  11. }
  12. public void setId(Long id) {
  13. this.id = id;
  14. }
  15. public String getName() {
  16. return name;
  17. }
  18. public void setName(String name) {
  19. this.name = name;
  20. }
  21. }

其中,注解@Entity用来标记该类是一个JPA实体类,并使用了注解@Table指定关联的数据库的表名;注解@Id用来定义记录的唯一标识,并结合注解@GeneratedValue将其设置为自动生成。  

数据持久化

使用 JPA 进行数据持久化有两种实现方式。

  • 方式一:使用Spring Data JPA 提供的接口默认实现,
  • 方式二:自定义符合Spring Data JPA规则的查询方法,由框架将其自动解析为SQL。

使用Spring Data JPA接口(方式一)

Spring Data JPA提供了一些实现了基本的数据库操作的接口类,如下图所示。 

CrudRepository

CrudRepository提供了一些简单的增删查改功能,接口定义如下。 


 
 
  1. package org.springframework.data.repository;
  2. import java.io.Serializable;
  3. @NoRepositoryBean
  4. public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
  5. <S extends T> S save(S entity); // 保存并返回(修改后的)实体
  6. <S extends T> Iterable<S> save(Iterable<S> entities); // 保存并返回(修改后的)实体集合
  7. T findOne(ID id); // 根据ID获取实体
  8. boolean exists(ID id); // 判断指定ID的实体是否存在
  9. Iterable<T> findAll(); // 查询所有实体
  10. Iterable<T> findAll(Iterable<ID> ids); // 根据ID集合查询实体
  11. long count(); // 获取实体的数量
  12. void delete(ID id); // 删除指定ID的实体
  13. void delete(T entity); // 删除实体
  14. void delete(Iterable<? extends T> entities); // 删除实体集合
  15. void deleteAll(); // 删除所有实体
  16. }

PagingAndSortingRepository

PagingAndSortingRepository继承于CrudRepository,除了具有CrudRepository接口的能力外,还新增了分页和排序的功能,接口定义如下。 


 
 
  1. package org.springframework.data.repository;
  2. import java.io.Serializable;
  3. import org.springframework.data.domain.Page;
  4. import org.springframework.data.domain.Pageable;
  5. import org.springframework.data.domain.Sort;
  6. @NoRepositoryBean
  7. public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
  8. Iterable<T> findAll(Sort sort); // 查询所有实体并排序
  9. Page<T> findAll(Pageable pageable); // 分页查询实体
  10. }

JpaRepository

JpaRepository继承于PagingAndSortingRepository,所以它传递性地拥有了以上接口的所有方法,同时,它还继承了另外一个QueryByExampleExecutor接口,拥有了该接口匹配指定样例的能力,JpaRepository接口定义如下。 


 
 
  1. package org.springframework.data.jpa.repository;
  2. import java.io.Serializable;
  3. import java.util.List;
  4. import javax.persistence.EntityManager;
  5. import org.springframework.data.domain.Example;
  6. import org.springframework.data.domain.Sort;
  7. import org.springframework.data.repository.NoRepositoryBean;
  8. import org.springframework.data.repository.PagingAndSortingRepository;
  9. import org.springframework.data.repository.query.QueryByExampleExecutor;
  10. @NoRepositoryBean
  11. public interface JpaRepository<T, ID extends Serializable>
  12. extends PagingAndSortingRepository< T, ID>, QueryByExampleExecutor< T> {
  13. List<T> findAll(); // 查询所有实体
  14. List<T> findAll(Sort sort); // 查询所有实体并排序
  15. List<T> findAll(Iterable<ID> ids); // 根据ID集合查询实体
  16. <S extends T> List<S> save(Iterable<S> entities); // 保存并返回(修改后的)实体集合
  17. void flush(); // 提交事务
  18. <S extends T> S saveAndFlush(S entity); // 保存实体并立即提交事务
  19. void deleteInBatch(Iterable<T> entities); // 批量删除实体集合
  20. void deleteAllInBatch(); // 批量删除所有实体
  21. T getOne(ID id); // 根据ID查询实体
  22. @Override
  23. <S extends T> List<S> findAll(Example<S> example); // 查询与指定Example匹配的所有实体
  24. @Override
  25. <S extends T> List<S> findAll(Example<S> example, Sort sort); // 查询与指定Example匹配的所有实体并排序
  26. }

QueryByExampleExecutor

QueryByExampleExecutor接口允许开发者根据给定的样例执行查询操作,接口定义如下。 


 
 
  1. package org.springframework.data.repository.query;
  2. import org.springframework.data.domain.Example;
  3. import org.springframework.data.domain.Page;
  4. import org.springframework.data.domain.Pageable;
  5. import org.springframework.data.domain.Sort;
  6. public interface QueryByExampleExecutor<T> {
  7. <S extends T> S findOne(Example<S> example); // 查询与指定Example匹配的唯一实体
  8. <S extends T> Iterable<S> findAll(Example<S> example); // 查询与指定Example匹配的所有实体
  9. <S extends T> Iterable<S> findAll(Example<S> example, Sort sort); // 查询与指定Example匹配的所有实体并排序
  10. <S extends T> Page<S> findAll(Example<S> example, Pageable pageable); // 分页查询与指定Example匹配的所有实体
  11. <S extends T> long count(Example<S> example); // 查询与指定Example匹配的实体数量
  12. <S extends T> boolean exists(Example<S> example); // 判断与指定Example匹配的实体是否存在
  13. }

以部门实体资源库接口DepartmentRepository为例,只需继承CrudRepository接口便会自动拥有基础的增删查改功能,无须编写一条SQL。  


 
 
  1. @Repository
  2. public interface DepartmentRepository extends CrudRepository<Department, Long> {
  3. }

自定义查询方法(方式二)

除了可以直接使用Spring Data JPA接口提供的基础功能外,Spring Data JPA还允许开发者自定义查询方法,对于符合以下命名规则的方法,Spring Data JPA能够根据其方法名为其自动生成SQL,除了使用示例中的 find 关键字,还支持的关键字有:query、get、read、count、delete等。 

另外,Spring Data JPA 还提供了对分页查询、自定义SQL、查询指定N条记录、联表查询等功能的支持,以员工实体资源库接口EmployeeRepository为例,功能代码示意如下。 


 
 
  1. @Repository
  2. public interface EmployeeRepository extends JpaRepository<Employee, Long> {
  3. /**
  4. * 根据部门ID获取员工数量
  5. */
  6. int countByDepartmentId(Long departmentId);
  7. /**
  8. * 根据部门ID分页查询
  9. */
  10. Page<Employee> queryByDepartmentId(Long departmentId, Pageable pageable);
  11. /**
  12. * 根据员工ID升序查询前10条
  13. */
  14. List<Employee> readTop10ByOrderById();
  15. /**
  16. * 根据员工姓名取第一条记录
  17. */
  18. Employee getFirstByName(String name, Sort sort);
  19. /**
  20. * 联表查询
  21. */
  22. @Query( "select e.id as employeeId,e.name as employeeName,d.id as departmentId,d.name as departmentName from Employee e , Department d where e.id= ?1 and d.id= ?2")
  23. EmployeeDetail getEmployeeJoinDepartment(Long eid, Long did);
  24. /**
  25. * 修改指定ID员工的姓名
  26. */
  27. @Modifying
  28. @Transactional(timeout = 10)
  29. @Query( "update Employee e set e.name = ?1 where e.id = ?2")
  30. int modifyEmployeeNameById(String name, Long id);
  31. /**
  32. * 删除指定ID的员工
  33. */
  34. @Transactional(timeout = 10)
  35. @Modifying
  36. @Query( "delete from Employee where id = ?1")
  37. void deleteById(Long id);
  38. }

JUnit测试

为例验证上面接口的正确性,我们使用JUnit来进行测试,先增加一个JPA的配置类,代码如下。 


 
 
  1. @Order(Ordered.HIGHEST_PRECEDENCE)
  2. @Configuration
  3. @EnableTransactionManagement(proxyTargetClass = true)
  4. @EnableJpaRepositories(basePackages = { "com.pengjunlee.repository"})
  5. @EntityScan(basePackages = { "com.pengjunlee.entity"})
  6. @EnableAutoConfiguration
  7. public class JpaConfiguration {
  8. @Bean
  9. PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
  10. return new PersistenceExceptionTranslationPostProcessor();
  11. }
  12. }

其中,@EnableTransactionManagement注解用来启用JPA事务管理,@EnableJpaRepositories注解用来启用JPA资源库发现,@EntityScan注解用来启用实体发现。

配置类定义好之后,编写一个JUnit Test Case测试程序。  


 
 
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(classes = JpaConfiguration.class)
  3. @TestPropertySource(locations = { "classpath:application.properties" })
  4. public class JpaTest {
  5. @Autowired
  6. DepartmentRepository departmentRepository;
  7. @Autowired
  8. EmployeeRepository employeeRepository;
  9. @Test
  10. public void addDepartmentTest() {
  11. }
  12. @Test
  13. public void initData() {
  14. // 清空员工表中的数据
  15. employeeRepository.deleteAll();
  16. // 清空部门表中的数据
  17. departmentRepository.deleteAll();
  18. // 部门表中添加一个开发部
  19. Department dept1 = new Department();
  20. dept1.setName( "开发部");
  21. departmentRepository.save(dept1);
  22. // 部门表中添加一个测试部
  23. Department dept2 = new Department();
  24. dept2.setName( "测试部");
  25. departmentRepository.save(dept2);
  26. String[] names = new String[] { "lucy", "tom", "hanmeime", "jacky", "francky", "lilly", "xiaoming", "smith",
  27. "walt", "sherry" };
  28. // 员工表中增加10条记录
  29. for ( int i = 0; i < 10; i++) {
  30. Employee emp = new Employee();
  31. emp.setName(names[i]);
  32. if (i < 5) {
  33. emp.setDepartment(dept1);
  34. } else {
  35. emp.setDepartment(dept2);
  36. }
  37. employeeRepository.save(emp);
  38. }
  39. }
  40. @Test
  41. public void testCountByDepartmentId() {
  42. int count = employeeRepository.countByDepartmentId( 19L);
  43. System.out.println(count);
  44. }
  45. @Test
  46. public void testQueryByDepartmentId() {
  47. Pageable pageable = new PageRequest( 0, 10, new Sort(Sort.Direction.ASC, "name"));
  48. Page<Employee> emps = employeeRepository.queryByDepartmentId( 19L, pageable);
  49. for (Employee emp : emps.getContent()) {
  50. System.out.println( "员工姓名:" + emp.getName() + ",所属部门:" + emp.getDepartment().getName());
  51. }
  52. }
  53. @Test
  54. public void testReadTop10ByOrderById() {
  55. List<Employee> emps = employeeRepository.readTop10ByOrderById();
  56. for (Employee emp : emps) {
  57. System.out.println( "员工姓名:" + emp.getName() + ",所属部门:" + emp.getDepartment().getName());
  58. }
  59. }
  60. @Test
  61. public void testGetFirstByName() {
  62. Employee emp = employeeRepository.getFirstByName( "xiaoming", new Sort(Direction.ASC, "id"));
  63. System.out.println( "员工姓名:" + emp.getName() + ",所属部门:" + emp.getDepartment().getName());
  64. }
  65. @Test
  66. public void testGetEmployeeJoinDepartment() {
  67. EmployeeDetail empDetail = employeeRepository.getEmployeeJoinDepartment( 5L, 19L);
  68. System.out.println( "员工姓名:" + empDetail.getEmployeeName() + ",部门名称:" + empDetail.getDepartmentName());
  69. }
  70. @Test
  71. public void testModifyEmployeeNameById() {
  72. employeeRepository.modifyEmployeeNameById( "chris", 5L);
  73. }
  74. @Test
  75. public void testDeleteById() {
  76. employeeRepository.deleteById( 11L);
  77. }
  78. }

本文项目源码已上传至CSDN,资源地址:https://download.csdn.net/download/pengjunlee/10366305  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值