SpringBoot—访问关系型数据库—SpringData JPA

来源:互联网 发布:proteus矩阵键盘 编辑:程序博客网 时间:2024/06/06 02:05

一、SpringData JPA是什么

上一节学习了如何入门SpringBoot,本篇章介绍springboot对数据库是如何进行访问的。在介绍之前,有必要了解一下SpringDataJPA,这是SpringCore中的一个项目,致力于简化对数据库的访问,增强了ORM的操作。对于JPA(Java persisten API),全称为Java持久化API,是JAVAEE中的一套规范API。它推出的目的是对ORM框架进行大统一,它提供一套接口,让厂商们(如hibernate)对JPA提供实现。JPA与hibernate的关系就像JDBC与Mysql、Oracle驱动一样的关系,只是它更加高度抽象,可以称之为ORM框架的接口,它的层递关系是这样的:

而SpringDataJPA是什么样子的呢?相信学过hibernate的同学一定也学过hibernate template,甚至自己实现过hibernate template。没错,SpringData JPA就很像这样一款template,把该有的东西都给你封装好,当然,其强大不止这一点点。相信学过mybatis的同学也知道,我们只需要一个mapper接口与一些mapper.xml,就可以让其代理实现的持久层。SpringDataJPA在使用的时候,也只需要声明一个接口,让其Spring以代理的形式生成Dao。对于SpringDataJPA与hibernate等ORM框架、JPA的关系是这样子的:


SpringDataJPA对类似hibernate这样的框架又做了一层封装,以便于我们编程的时候使用更方便。好了,说了那么多理论,下面就开始实践吧。

二、开始实践

1.创建maven web项目,名称为springboot-jpa,在pom.xml中导入以下依赖:

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.4.1.RELEASE</version></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version><springBoot.groupId>org.springframework.boot</springBoot.groupId></properties><dependencies><!-- SpringBoot Start --><dependency><groupId>${springBoot.groupId}</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- jpa --><dependency><groupId>${springBoot.groupId}</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>${springBoot.groupId}</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>

其中SpringDataJPA核心支持依赖是spring-boot-starter-data-jpa。

2.编辑配置

待maven构建导包完成后,请在mysql中创建好数据库springboot_test,在src/main/resources中添加application.yml配置,代码如下所示:
server:  port: 8080  tomcat:    uri-encoding: UTF-8spring:  datasource:    url: jdbc:mysql://127.0.0.1:3306/springboot_test?characterEncoding=utf8    username: root    password: root  jpa:    database: MYSQL    show-sql: true    #hibernate ddl auto(validate,create,update,create-drop)    hibernate:      ddl-auto: update      naming:        strategy: org.hibernate.cfg.ImprovedNamingStrategy    properties:      hibernate:        dialect: org.hibernate.dialect.MySQL5Dialect
这些配置相信大家都能看得懂,最后一行是数据库方言,如果是oracle,就有oracle的方言。

包结构组织如下图:

在src/main/java中新建org.fage包,在其中创建一个Jpa的Java配置类:
package org.fage;import org.springframework.boot.autoconfigure.domain.EntityScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.Ordered;import org.springframework.core.annotation.Order;import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.transaction.annotation.EnableTransactionManagement;/** *  * @author Caizhfy * @email caizhfy@163.com * @createTime 2017年10月30日 * @description JPA基础配置类 * */@Order(Ordered.HIGHEST_PRECEDENCE)@Configuration@EnableTransactionManagement(proxyTargetClass=true)@EnableJpaRepositories(basePackages={"org.fage.**.repository"})@EntityScan(basePackages={"org.fage.**.domain"})public class JpaConfiguration {@BeanPersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){return new  PersistenceExceptionTranslationPostProcessor();}}
@Order声明了组件加载的顺序,其中接受一个整形值,值越低约先加载;
@Configuration声明了这是一个配置类,该注解中包含有@Component注解,可以让SpringBoot自动扫描加载;@EnableTransactionManagement声明了开启事务管理器代理;
@EnableJpaRepositories声明repository(也就是原来的dao,SpringData中称其为Repository)所在位置,值中的两个星号是通配符,代表org.fage.任何路径下的.repository包中都是repository;
@EntityScan是对实体组件位置的声明与扫描,两个星号依旧是通配符。

在根包中创建入口类App.java:
package org.fage;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.ImportAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;/** *  * @author Caizhfy * @email caizhfy@163.com * @createTime 2017年10月30日 * @description Springboot-jpa学习创建步骤: * 1.创建项目  *2.添加依赖,填写配置类 *3.配置application.yml *4.配置实体jpa关系 *5.继承jpaRepository *6.编写测试用例 */@SpringBootApplication@ImportAutoConfiguration(value=JpaConfiguration.class)public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}}


3.创建实体建模:

建模关系是有部门、用户、角色三个实体;部门与用户是一对多的关系,用户与角色是多对多的关系,在org.fage.domain包中创建三个实体,建模代码如下
部门实体
package org.fage.domain;import java.io.Serializable;import java.util.List;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.OneToMany;import javax.persistence.Table;@Entity@Table(name = "department")public class Department implements Serializable {/** *  */private static final long serialVersionUID = 159714803901985366L;@Id@GeneratedValue(strategy=GenerationType.IDENTITY)private Long id;private String name;@OneToMany(mappedBy="department",fetch=FetchType.LAZY)private List<User> users;public List<User> getUsers() {return users;}public void setUsers(List<User> users) {this.users = users;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}}

用户实体:
package org.fage.domain;import java.io.Serializable;import java.util.Date;import java.util.List;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.ForeignKey;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.ManyToOne;import javax.persistence.Table;import javax.persistence.Temporal;import javax.persistence.TemporalType;import org.springframework.format.annotation.DateTimeFormat;import com.fasterxml.jackson.annotation.JsonBackReference;import com.fasterxml.jackson.annotation.JsonIgnore;@Entity@Table(name = "user")public class User implements Serializable{@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;@Column(name = "create_date")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@Temporal(TemporalType.TIMESTAMP)private Date createDate;//一对多映射@ManyToOne@JoinColumn(name = "department_id")@JsonBackReference // 防止对象的递归访问private Department department;//多对多映射@ManyToMany(fetch=FetchType.EAGER)@JoinTable(name = "user_role",joinColumns = {@JoinColumn(name="user_id")},inverseJoinColumns = {@JoinColumn(name = "role_id")})private List<Role> roles;public List<Role> getRoles() {return roles;}public void setRoles(List<Role> roles) {this.roles = roles;}public Department getDepartment() {return department;}public void setDepartment(Department department) {this.department = department;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public Date getCreateDate() {return createDate;}public void setCreateDate(Date createDate) {this.createDate = createDate;}}

角色实体:
package org.fage.domain;import java.io.Serializable;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name="role")public class Role implements Serializable{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)private Long id;private String name;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}}

实体映射关系建立完成,解释一下其中的注解,这些注解其实大多数都是在学JPA与hibernate的时候学会的:@Entity声明这是一个实体类;@Table声明该实体在表中对应的表名是什么;@Id声明该属性为实体对应表的主键;@GeneratedValue声明了主键策略是什么,这里使用的是自动增长主键策略;@ManyToOne与@OneToMany声明了该实体与对应属性的实体是多对一或者一对多的关系,其中如果设立双向关系记得设置mappedBy,fetch的值声明了加载方式是懒加载还是立即加载;@ManyToMany是实体间多对多的关系,@JoinTable设置了两个多对多的实体的中间表外键。

4.创建持久层Dao

在SpringDataJPA中,我们不在称其为mapper或者dao,而是称为repository,我们来为三个实体分别创建自己的repository。在org.fage.repository中创建如下三个接口,继承自JpaRepository,泛型左边为实体类型,右边为该实体的主键类型:
部门repository:
package org.fage.repository;import org.fage.domain.Department;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface DepartmentRepository extends JpaRepository<Department, Long>{}

用户repository:
package org.fage.repository;import java.util.Collection;import java.util.Date;import java.util.List;import org.fage.domain.User;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Query;import org.springframework.stereotype.Repository;/** *  * @author Caizhfy * @email caizhfy@163.com * @createTime 2017年10月31日 * @description 继承repository接口,获得支持 * 原理:单顶层接口+抽象类+本接口+代理本接口的实现类 * */@Repositorypublic interface UserRepository extends JpaRepository<User, Long>{//And用法User findById(long id);User findByIdAndUsername(long id, String username);//Or用法User findByIdOrUsername(long id, String name);//Between用法User findByCreateDateBetween(Date start, Date end);//LessThan用法List<User> findByCreateDateLessThan(Date start);//GreaterThan用法List<User> findByCreateDateGreaterThan(Date start);//IsNull/IsNutNull用法List<User> findByUsernameIsNull();//Like/NotLike用法List<User> findByUsernameLike(String username);//OrderBy用法List<User> findByUsernameOrderByIdAsc(String username);//Not用法List<User> findByUsernameNot(String username);//In/NotIn用法List<User> findByUsernameIn(Collection<String> nameList);}

角色repository:
package org.fage.repository;import org.fage.domain.Role;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface RoleRepository extends JpaRepository<Role, Long>{}

至此,我们可以创建Junit程序,测试使用这三个持久层接口了,在src/test/java中可以创建测试程序进行测试了,hibernate实现会自动帮我们生成表,SpringDataJPA提供了常用的CRUD操作:
package org.fage.repository.test;import java.util.ArrayList;import java.util.Date;import java.util.List;import javax.transaction.Transactional;import org.fage.JpaConfiguration;import org.fage.domain.Department;import org.fage.domain.Role;import org.fage.domain.User;import org.fage.repository.DepartmentRepository;import org.fage.repository.RoleRepository;import org.fage.repository.UserRepository;import org.junit.Assert;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.domain.Page;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Pageable;import org.springframework.data.domain.Sort;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringRunner;import com.mysql.jdbc.log.LogFactory;@RunWith(SpringRunner.class)@SpringBootTestpublic class RepositoryTest {private final Logger log = LoggerFactory.getLogger(RepositoryTest.class);@AutowiredUserRepository userRepository;@AutowiredRoleRepository roleRepository;@AutowiredDepartmentRepository departmentRepository;@Beforepublic void initData() {departmentRepository.deleteAll();roleRepository.deleteAll();userRepository.deleteAll();Department d = new Department();d.setName("开发部");departmentRepository.save(d);Assert.assertNotNull(d.getId());Role r = new Role();r.setName("部门经理");roleRepository.save(r);Assert.assertNotNull(r.getId());List<Role> roles = new ArrayList<Role>();roles.add(r);User u = new User();u.setUsername("蔡智法");u.setCreateDate(new Date());u.setDepartment(d);u.setRoles(roles);userRepository.save(u);Assert.assertNotNull(u.getId());}@Testpublic void testGeneralMethod(){System.out.println(userRepository.findByUsernameLike("蔡智法"));}@Testpublic void testFindPage() {//hibernate一对多分页原理:先分页,然后在将id作为引子查询(效率低)Pageable pageable = new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "id"));Page<Department> page = departmentRepository.findAll(pageable);System.out.println(page.getNumberOfElements());}}
至此,运行成功。其实如果你也动手试一试,发现增删改查都不需要实现代码就能拥有这些功能,这些Repository接口中还有很多方法可以尝试,希望大家自行踊跃尝试,太便捷了。

啥?没搞错吧?这些接口甚至一行业务代码都不用写(UserRepository中的代码待会儿解释),就能实现增删改查、分页操作??没错,确实就是这么简单。不难发现,我们获得支持的最重要一点是继承了JpaRepository(该接口提供了上层接口更多的查询操作)接口,跟踪源码发现JpaRepository接口继承了上层的PageAndSortingRepository(该接口提供了分页以及排序的支持),PageAndSortingRepository继承自CrudRepository(该接口提供基础的增删改查操作),而CrudRepository又继承自Repository。我们知道,他们都是接口,本身没有实现方法,但是SpringDataJPA帮我们提供了一套实现,在运行的时候会以代理的形式给我们生成实现类,只要你继承了Spring给你的这些Repository接口,那么你就能获得这些方法支持。
问题又来了,UserRepository接口中自定义声明的方法我们也没有实现,为啥也能正常使用呢?
那是因为在SpringDataJPA中,自定义的方法一般有两种,第一种就是这种“约定命名”法,这种方法一定要查询命名规范,比如findByXXX,SpringData会根据前缀、中间连接词(Or、And、Like、NotNull等等类似Sql中的关键词),详情使用请看下表,内部会自己转换成JPQL使用:


内部帮我们拼接sql代理生成方法的实现,不得不感叹,真的太方便了。
第二种方法就是使用@Query注解使用JPQL(类似SQL与OGNL的组合)语句查询,这种查询一般不使用代理,是直接内部转化成SQL进行执行,这种方法比前一种灵活一些,后面章节会提到(还有一种方法,就是自定义增强Repository实现,使用原生SQL查询,这样方便优化SQL)。自定义增强Repository与框架原理解析,在后续文章中会陆续更新。

以上源码在https://github.com/Phapha1996/springboot-jpa能检出。
在本文中,最重要的词就是代理了,如果不了解代理模式的同学,希望能恶补一波代理,这是一组非常值得学习而有用的模式。

下一篇简介:SpringBoot操作数据库性能优化—在SpringDataJPA基础上集成redis做缓存,使用druid做数据库连接池,时刻监控sql与请求质量。

原创粉丝点击