前言:
最近比较忙,拖了好久才开始写springboot相关的博客,springboot实战那本书已经练习完毕,感觉挺有意思的,从第八章开始才是后端最关心的部分,经常用到。
好了,言归正传,本章关于spring data jpa的介绍挺多的,但是还是不够详细,在实际应用中我们还要处理好表与表之间的关系,各种相关注解,比如一对多的关系@OneToMany,@ManyToOne等等。还有懒加载的问题,比如在一对多中我在A表类中写了个子表类B的列表,采用懒加载的方式,不让每次查A的时候也查出所有的B,只有在需要B的时候才触发对B的查询。这个如果处理不好很容易出问题,比如数据库连接已经失效了,那么就不能再自动去查了,或者json解析的时候,这个临时类就会报错。
废话了一大堆,这个以后再遇到可以单独讲讲,正片开始:
1.什么是spring data jpa
以下是作者的原话,介绍的挺不错。
在介绍Spring Data JPA的时候,我们首先认识下Hibernate。Hibernate是数据访问解决技术的绝对霸主,使用O/R映射(Object-Relational Mapping) 技术实现数据访问,O/R映射即将领域模型类和数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无需关注数据库相关的技术。
随着Hibernate的盛行,Hibernate主导了EGB3.0的JPA规范,JPA即Java Persistence API。JPA是一个基于O/R映射的标准规范。所谓规范只定义标准规则(如注解,接口),不提供实现,而使用者只需要按照规范中定义的方式来使用,而不用和软件提供商的实现打交道。JPA的主要实现由Hibernate、EclipseLink和OpenJPA等,这也意味着我们只要使用JPA来开发,无论哪一个开发方式都是一样的。
Spring Data JPA是Spring Data的一个子项目,关于Spring Data可以看springboot官网,有很多子项目,只不过目前遇到的业务还没用得到。
2.定义数据访问层
只需要定义一个类继承JpaRepository的接口,就可以使用默认的数据访问操作方法。
3.配置使用Spring Data JPA
在Spring环境中,可以通过@EnableJpaRepositories注解开启Spring Data JPA的支持,@EnableJpaRepositories接收的value参数用来扫描数据库访问层所在包下的数据访问的接口定义。
4.定义查询方法
(1)根据属性名查询
根据属性名和关键字来查询
(2)限制结果数量
通过top和first关键字来实现,例如:
findFirst10ByName
findTop10ByName
(3)使用JPA的NamedQuery
一个名称映射一个查询语句,在领域模型上面定义@NameQuery,在数据库操作接口里使用
(4)@Query
这也是比较常见的查询了,用这个注解在接口的方法上实现查询,在sql语句里查询参数上可以用参数索引,从1开始,比如?1,?2
也可以使用命名参数:比如:address
@Modifying和@Query注解组合来事件更新查询
(5)Specification
JPA提供了基于准则查询的方式,即Criteria查询。Spring Data JPA提供了一个Specification接口让我们更方便的构造准则查询,Specification接口定义了一个toPredicate方法用来构造查询条件。
(6)排序与分页
Spring Data JPA提供了Sort类,page接口和Pageable接口,可以方便的排序和分页
(7)自定义Repository
我们可以将自己常用的数据库操作封装起来,自定义Repository,具体看实战代码
4.springboot的支持
spring-boot-starter-data-jpa依赖于spring-boot-starter-jdbc,而spring boot对JDBC做了一些自动配置。
spring boot默认JPA的实现者是Hibernate
Spring Boot自动开启了对Spring Data JPA的支持,我们无需在配置类里显式声明@EnableJpaRepositories
5.实战:
(1)为了省时间,并没有按照作者所说安装oracle,使用的依旧是mysql。首先新建spring boot项目,依赖选择spring-boot-starter-data-jpa和spring-boot-starter-data-web。本实例基于spring boot2.0
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
(2)配置信息
在application.properties配置数据库相关东西,注意mysql的配置,高版本一定要配置useSSL=false,还有时区
spring.jpa.database=mysql
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver mysql connect jar包高版本用这个
#我后来改成5.1.42版本改成原始的配置了,mysql-connector-java如下所示:
#<dependency>
#<groupId>mysql</groupId>
#<artifactId>mysql-connector-java</artifactId>
#<version>5.1.42</version>
#</dependency>
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#hibernate 根据实体类维护数据表结构的功能
#create:启动时删除上一次生成的表,并根据实体类生成表,表中数据会被清空
#create-drop:启动时根据实体类生成表,sessionFactory关闭时表会被删除
#update:启动时会根据实体类生成表,当实体类属性改变的时候,表结构也会更新,在初期开发阶段使用此项
#validate:启动时校验实体类和数据表是否一致,当我们数据结构稳定时采用此选项
#none:不采取任何措施
spring.jpa.hibernate.ddl-auto=update
#在控制台显示真实的sql语句
spring.jpa.show-sql=true
#让控制器输出的json字符串更美观
spring.jackson.serialization.indent-output=true
数据表:
/*
Navicat MySQL Data Transfer
Source Server : local
Source Server Version : 50716
Source Host : localhost:3306
Source Database : test
Target Server Type : MYSQL
Target Server Version : 50716
File Encoding : 65001
Date: 2018-08-05 18:10:03
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `person`
-- ----------------------------
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
`id` bigint(20) NOT NULL,
`address` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('1', '徐州', '14', '二狗');
INSERT INTO `person` VALUES ('2', '北京', '21', '哈德');
INSERT INTO `person` VALUES ('3', '北京', '12', '三国杀');
INSERT INTO `person` VALUES ('4', '上海', '23', '大噶湖人');
INSERT INTO `person` VALUES ('5', '南京', '42', '国家统一');
INSERT INTO `person` VALUES ('6', '北京', '21', '家人');
(3)定义映射实体类
package com.just.springjpa.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
//这里普通属性没有写@Column(属性映射字段名),会自动根据属性名生成字段名
@Entity
@NamedQuery(name="Person.withNameAndAddressNamedQuery",query = "select p from Person p where p.name=?1 and p.address=?2")
public class Person {
@Id //指定主键
@GeneratedValue //主键生成方式为自增
private Long id;
private String name;
private Integer age;
private String address;
public Person(){
super();
}
public Person(Long id, String name, Integer age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
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;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
(4)定义数据访问接口
package com.just.springjpa.dao;
import com.just.springjpa.domain.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PersonRepository extends JpaRepository<Person,Long> {
//使用方法名查询,返回列表
List<Person> findByAddress(String address);
//使用方法名查询,返回单个对象
Person findByNameAndAddress(String name,String address);
//使用query查询,参数按照名称绑定
@Query(value = "select p from Person p where p.name=:name and p.address=:address")
Person withNameAndAddressQuery(@Param("name")String name,@Param("address")String address);
//使用NamedQuery查询,在实体类中做了定义
List<Person> withNameAndAddressNamedQuery(String name,String address);
}
(5)控制器以及部分测试结果
package com.just.springjpa.web;
import com.just.springjpa.dao.PersonRepository;
import com.just.springjpa.dao.PersonRepository2;
import com.just.springjpa.domain.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class DataController {
//Spring data jpa已经自动注册bean,可以直接自动注入
@Autowired
PersonRepository personRepository;
/**
* jpa自带的方法,可以直接保存
* 保存单个,多个,根据id查找,id列表查找,查找所有,判断是否存在,计算总数,根据id删除,删除对象,删除一堆对象,删除所有等等,
* 自带很多实现,不需要我们单独写了
* <S extends T> S save(S var1);
*
* <S extends T> Iterable<S> saveAll(Iterable<S> var1);
*
* Optional<T> findById(ID var1);
*
* boolean existsById(ID var1);
*
* Iterable<T> findAll();
*
* Iterable<T> findAllById(Iterable<ID> var1);
*
* long count();
*
* void deleteById(ID var1);
*
* void delete(T var1);
*
* void deleteAll(Iterable<? extends T> var1);
*
* void deleteAll();
*
*/
@RequestMapping("/save")
public Person save(String name,String address,Integer age){
Person person=new Person(null,name,age,address);
return personRepository.save(person);
}
@RequestMapping("/q1")
public List<Person> findByAddress(String address){
List<Person> people=personRepository.findByAddress(address);
return people;
}
@RequestMapping("/q2")
public Person findByNameAndAddress(String name,String address){
Person person=personRepository.findByNameAndAddress(name,address);
return person;
}
@RequestMapping("/q3")
public Person withNameAndAddressQuery(String name,String address){
Person person=personRepository.withNameAndAddressQuery(name,address);
return person;
}
@RequestMapping("/q4")
public List<Person> withNameAndAddressNamedQuery(String name,String address){
List<Person> people=personRepository.withNameAndAddressNamedQuery(name,address);
return people;
}
//测试排序
@RequestMapping("/sort")
public List<Person> sort(){
List<Person> people=personRepository.findAll(new Sort(Sort.Direction.ASC,"age"));
return people;
}
//测试分页,分页参数里面也可以加排序
@RequestMapping("/page")
public Page<Person> page(Pageable pageable){
Page<Person> personPage=personRepository.findAll(pageable);
return personPage;
}
}
部分测试结果展示:
(6)自定义Repository实现
自定义Repository实现的目标:定制一个自动模糊查询,对于任意实体对象进行查询,对象里有几个值就查几个值,当值为字符类型时就自动like查询,其余类型自动等于查询
a.定义Specification
package com.just.springjpa.specs;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.SingularAttribute;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.collect.Iterables.toArray;
/**
* 定制一个自动模糊查询,对于任意的实体对象进行查询,
* 有几个值就查几个值,值为字符串类型就执行模糊查询
* 本次demo重要的知识点:
* jpa提供了基于准则查询的方式,即Criteria查询。而Spring Data Jpa提供了一个Specification(规范)接口让我们
* 更方便的构造准则查询,Specification接口定义了一个toPredicate方法来构造查询条件
* 其中,ROOT来获得需要查询的属性,criteriaBuilder来构造查询条件
*/
public class CustomerSpecs {
/**
* 定义一个返回值对象为Specification的方法(Specification:规格)
* @param entityManager
* @param example
* @param <T>
* @return
*/
public static <T> Specification<T> byAuto(final EntityManager entityManager,T example){
//获取当前实体类对象的类型
final Class<T> type=(Class<T>) example.getClass();
return new Specification<T>() {
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//新建Predicate列表存储构造的查询条件
List<Predicate> predicates=new ArrayList<>();
//获得实体类的EntityType,从EntityType可以获得实体类的属性
EntityType<T> entityType=entityManager.getMetamodel().entity(type);
//对实体类的所有属性做循环
for(Attribute<T,?> attr:entityType.getDeclaredAttributes()){
//获得实体类某个对象的值
Object attrValue=getValue(example,attr);
if(attrValue!=null){
//当前属性为字符类型的时候,用模糊查询
if(attr.getJavaType()==String.class){
if(!StringUtils.isEmpty(attrValue)){
//构造当前属性like属性值查询
predicates.add(criteriaBuilder.like(root.get(attribute(entityType,attr.getName(),String.class)),
pattern((String)attrValue)));
}
}else{
//其余情况按照构造属性和属性值equal查询
predicates.add(criteriaBuilder.equal(root.get(attribute(entityType,attr.getName(),String.class)),
attrValue));
}
}
}
return predicates.isEmpty()?criteriaBuilder.conjunction()
:criteriaBuilder.and(toArray(predicates,Predicate.class)); //将条件列表转换成predicate
}
//通过反射获得实体类对象对应的属性值
private <T> Object getValue(T example,Attribute<T,?> attr){
return ReflectionUtils.getField((Field) attr.getJavaMember(),example);
}
//获得实体类的当前属性的SingularAttribute,SingularAttribute包含的是实体类的某个单独属性
private <E,T>SingularAttribute<T,E> attribute(EntityType<T> entity,String fieldName,Class<E> fieldClass){
return entity.getDeclaredSingularAttribute(fieldName,fieldClass);
}
};
}
/**
* 构造like的查询模式,前后都加%
* @param str
* @return
*/
private static String pattern(String str){
return "%"+str+"%";
}
}
b.定义接口
package com.just.springjpa.support;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import java.io.Serializable;
/**
* 定义接口
* @param <T>
* @param <ID>
*/
@NoRepositoryBean
public interface CustomRepository<T,ID extends Serializable> extends JpaRepository<T,ID>,JpaSpecificationExecutor<T> {
Page<T> findByAuto(T example, Pageable pageable);
}
c.定义实现
package com.just.springjpa.support;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import javax.persistence.EntityManager;
import java.io.Serializable;
import static com.just.springjpa.specs.CustomerSpecs.*;
public class CustomRepositoryImpl<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements CustomRepository<T,ID> {
private final EntityManager entityManager;
public CustomRepositoryImpl(Class<T> domainClass,EntityManager entityManager){
super(domainClass,entityManager);
this.entityManager=entityManager;
}
@Override
public Page<T> findByAuto(T example, Pageable pageable) {
return findAll(byAuto(entityManager,example),pageable);
}
}
d.定义repositoryFactoryBean
package com.just.springjpa.support;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import javax.persistence.EntityManager;
import java.io.Serializable;
/**
* 自定义CustomRepositoryFactoryBean替代默认的RepositoryFactoryBean,我们会获得一个RepositoryFactory,
* RepositoryFactory将会注册我们自定义的Repository的实现
* @param <T>
* @param <S>
* @param <ID>
*/
public class CustomRepositoryFactoryBean<T extends JpaRepository<S,ID>,S,ID extends Serializable> extends JpaRepositoryFactoryBean<T,S,ID> {
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomRepositoryFactory(entityManager);
}
public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
private static class CustomRepositoryFactory extends JpaRepositoryFactory{
public CustomRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
@Override
protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
return new CustomRepositoryImpl<T,ID>((Class<T>)information.getDomainType(),entityManager);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomRepositoryImpl.class;
}
}
}
e.使用
package com.just.springjpa.dao;
import com.just.springjpa.domain.Person;
import com.just.springjpa.support.CustomRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PersonRepository2 extends CustomRepository<Person,Long> {
//使用方法名查询,返回列表
List<Person> findByAddress(String address);
//使用方法名查询,返回单个对象
Person findByNameAndAddress(String name,String address);
//使用query查询,参数按照名称绑定
@Query(value = "select p from Person p where p.name=:name and p.address=:address")
Person withNameAndAddressQuery(@Param("name")String name, @Param("address")String address);
//使用NamedQuery查询,在实体类中做了定义
List<Person> withNameAndAddressNamedQuery(String name,String address);
}
f.配置
package com.just.springjpa;
import com.just.springjpa.support.CustomRepositoryFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class SpringjpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringjpaApplication.class, args);
}
}
g.controller里测试
@GetMapping("/auto")
public Page<Person> auto(Person person,Pageable pageable){
Page<Person> people=personRepository2.findByAuto(person,pageable);
return people;
}
自定义Repository实现测试结果:
简单的就到这里啦,以后项目中遇到相关的问题再单独写一个博客聊聊。
最后推荐一波springboot官网关于Spring Data JPA的说明,很好的参考文档
官网参考:
https://spring.io/projects/spring-data-jpa
使用:
https://docs.spring.io/spring-data/jpa/docs/2.0.9.RELEASE/reference/html/