SpringBoot22-spingboot数据访问-Spring Data JPA

一:点睛Spring Data JPA

1,什么事Spring Data JPA

        我们知道Hibernate是数据访问解决技术的绝对霸主,使用O/R映射技术实现数据访问,o/r映射即将领域模型类和数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无须关注数据库相关的技术。

       随着Hibernate的盛行,Hibernate主导了EJB3.0的JPA规范,JPA即Java Persistence API。JPA是一个基于O/R映射的标准规范。所谓规范即只定义标准规则,不提供实现,如果提供商可以按照标准规范来实现,而使用者只需按照规范中定义的方式来使用,而不用和软件提供商的实现打交道。

     Spring Data JPA是Spring Data的一个子项目,它通过提供基于JPA的Repository极大地减少了JPA作为数据访问方案的代码量


2,定义数据访问层

      使用Spring Data JPA建立数据访问层比较简单,之需定义一个继承JpaRepository的接口即可,定义如下:

package com.jack.springboot5jpa.dao;

import com.jack.springboot5jpa.entity.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;

/**
 * create by jack 2017/10/3
 */
public interface PersonRepository extends JpaRepository<Person,Integer> {
    //使用方法名查询,接受一个name参数,返回值为一个列表
    List<Person> findByAddress(String address);
    //使用方法名查询,接受一个name和address,返回值为单个对象
    Person findByNameAndAddress(String name,String address);
    //使用@Query查询,参数按照名称绑定
    @Query("select p from Person  p where p.name = :name and p.address = :address")
    Person withNameAndAddressQuery(@Param("name") String name,@Param("address") String address);
    //使用@NamedQuery查询,注意实体类中做的@NamedQuery的定义
    Person withNameAndAddressNamedQuery(String name,String address);
}


     继承JpaRepository接口意味着我们默认已经有了下面的数据访问操作方法:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.data.jpa.repository;

import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAll(Iterable<ID> var1);

    <S extends T> List<S> save(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}


3,配置使用Spring Data JPA

      在Spring环境中,使用Spring Data JPA可通过@EnableJpaRepositories注解来开启Spring Data Jpa的支持,@EnableJpaRepositories接收的value参数用来扫描数据访问层所在包下的数据访问的接口定义

import javax.persistence.EntityManagerFactory;

/**
 * create by jack 2017/10/3
 */
@Configuration
@EnableJpaRepositories("com.jack.springboot5jpa.dao")
public class JpaConfiguration {
    @Bean
    public EntityManagerFactory entityManagerFactory(){
        //....;
    }

    //还需配置DataSource,PlatformTransactionManager等相关必须bean

}


4,定义查询方法

     假设有一张数据表示persion有id,name,age,address几个字段对应的实体为Person,下面是实体类的定义属性:

/**
     * 主键id
     * @Id注解指明这个属性映射为数据库的主键
     * @GeneratedValue定义主键生成的方式,下面采用的是mysql的自增属性
     */
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Integer id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 地址
     */
    private String address;

1)根据属性名查询

     Spring Data JPA支持通过定义在Repository接口中的方法名来定义查询,而方法名是根据实体类的属性名来确定的。

     常规查询。根据属性名来定义查询方法,示例如下:

package com.jack.springboot5jpa.dao;

import com.jack.springboot5jpa.entity.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;

/**
 * create by jack 2017/10/3
 */
public interface PersonRepository extends JpaRepository<Person,Integer> {
    //使用方法名查询,接受一个name参数,返回值为一个列表
    List<Person> findByAddress(String address);

    /**
     *  使用方法名查询,接受一个name和address,返回值为单个对象
     *  通过名字和地址查询,参数为name和address
     *  相当于:JPQL:select p from Person p where p.name = ?1 and p.address=?2
     * @param name
     * @param address
     * @return
     */

    Person findByNameAndAddress(String name,String address);
    //使用@Query查询,参数按照名称绑定
    @Query("select p from Person  p where p.name = :name and p.address = :address")
    Person withNameAndAddressQuery(@Param("name") String name,@Param("address") String address);
    //使用@NamedQuery查询,注意实体类中做的@NamedQuery的定义
    Person withNameAndAddressNamedQuery(String name,String address);

    /**
     * 通过名字相等查询,参数为name
     * 相当于JPQL:select p from Person p where p.name = ?1
     * @param name
     * @return
     */
    List<Person> findByName(String name);

    /**
     * 通过名字like查询,参数为name
     * 相当于JPQL:select p from Person p where p.name like ?1
     * @param name
     * @return
     */
    List<Person> findByNameLike(String name);
}


     上面的代码,这里使用了findBy,Like,And这样的关键字。其中findBy可以用find,read,readBy,query,queryBy,get,getBy来代替


      限制结果数量。结果数量是用top和first关键字来实现,例如:

/**
     * 获得符合查询条件的前10条数据
     * @param name
     * @return
     */
    List<Person> findFirst10ByName(String name);

    /**
     * 获得符合查询条件的前30条数据
     * @param name
     * @return
     */
    List<Person> findTo30ByName(String name);


2)使用JPA的NameQuery查询

     Spring Data JPA支持用JPA的NameQuery来定义查询方法,即一个名称映射一个查询语句。定义如下:

@Entity
@NamedQuery(name = "Person.withNameAndAddressNamedQuery",
query = "select p from Person p where p.name =?1 and p.address=?2")
public class Person {}


使用如下语句:

 //使用@NamedQuery查询,注意实体类中做的@NamedQuery的定义
    Person withNameAndAddressNamedQuery(String name,String address);


3)使用@Query查询

       使用参数索引:Spring Data JPA还支持使用@Query注解在接口的方法上实现查询,例如:

@Query("select  p from Person p where p.address=?1")
    List<Person> findByAddress(String address);

       使用命名参数:上面的例子是使用参数索引号来查询的,在Spring Data JPA里还支持在语句里用名称来匹配查询参数,例如:

//使用@Query查询,参数按照名称绑定
    @Query("select p from Person  p where p.name = :name and p.address = :address")
    Person withNameAndAddressQuery(@Param("name") String name,@Param("address") String address);

       更新查询:Spring Data JPA支持@Modifying和@Query注解组合来事件更新查询,例如:

@Modifying
    @Transactional
    @Query
    int setName(String name);

      其中返回值int表示更新语句影响的行数。


4)Specification

       JPA提供了基于准则查询的方式,即Criteria查询。而Spring Data JPA提供了一个Specification(规范)接口让我们可以更方便的构造准则查询,Specification接口定义了一个toPredicate方法用来构造查询条件。

      定义:我们的接口类必须实现JpaSpecificationExecutor接口,代码如下:

public interface PersonRepository extends JpaRepository<Person,Integer>,JpaSpecificationExecutor<Person> 

然后需定义Criterial查询,代码如下:

package com.jack.springboot5jpa.pojo;

import com.jack.springboot5jpa.entity.Person;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

/**
 * create by jack 2017/10/3
 */
public class CustomerSpecs {
    public static Specification<Person> personFromSZ(){
        return new Specification<Person>(){
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                return criteriaBuilder.equal(root.get("address"),"深圳");
            }
        };
    }
}

       我们使用Root属性来获得需要查询的属性,通过CriteriaBuilder构造查询条件,本例的含义是查询出所有来自深圳的人。

       注意:CriteriaBuilder,CriteriaQuery,Predicate,Root都是来自JPA的接口。
       CriteriaBuilder包含的条件构造有:exists,and,or,not,conjunction,disjunction,isTrue,isFalse,isNull,isNotNull,equal等

      使用:静态导入

      import static com.jack.springboot5jpa.pojo.CustomerSpecs.*;

     注入personRepository的bean后:

    List<Person> people = new personRepository.findAll(personFromSZ());


5,排序与分页

     Spring Data JPA充分考虑了在实际开发中所必需的排序和分页的场景,为我们提供了Sort类以及Page接口和Pageable接口:

在接口定义方法里面使用分页和排序:

List<Person> findByName(String name, Sort sort);
    Page<Person> findByName(String name, Pageable pageable);


排序:

    

/**
     * 测试排序
     * @return
     */
    @RequestMapping("/sort")
    public List<Person> sort(){
        List<Person> personList = personRepository.findAll(new Sort(Sort.Direction.ASC,"age"));
        return personList;
    }


  分页:

/**
     * 测试分页
     * @return
     */
    @RequestMapping("/page")
    public Page<Person> page(){
        Page<Person> personPage = personRepository.findAll(new PageRequest(1,2));
        return personPage;
    }

     其中Page接口可以获得当前页面的记录,总页数,总记录数是否有上一页,或下一页等。


5,自定义Repository的实现

   Spring Data提供了CrudRepository,PagingAndSortingRepository;Spring Data JPA也提供了JpaRepository。如果我们想把自己常用的数据库操作封装起来,像JpaRepository一样提供我们领域类的Repository接口使用,应该怎么操作?

   1)定义自定义Repository接口:

package com.jack.springboot5jpa.dao;

import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

import java.io.Serializable;

/**
 * create by jack 2017/10/3
 */
//指明@NoRepositoryBean当前这个接口不是我们领域类的接口(如:PersonRepository)
@NoRepositoryBean
//自定义的Repository实现PagingAndSortingRepository接口,具备分页和排序的能力
public interface CustomRepository<T,ID extends Serializable> extends PagingAndSortingRepository<T,ID>{
    //要定义的数据操作方法在接口中的定义
    public void doSomething(ID id);
}


2)定义接口实现

package com.jack.springboot5jpa.impl;

import com.jack.springboot5jpa.dao.CustomRepository;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import java.io.Serializable;

/**
 * create by jack 2017/10/3
 */
//首先要实现CustomRepository接口,继承SimpleJpaRepository类让我们可以使用其提供的方法
public class CustomRepositoryImpl <T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements CustomRepository<T,ID> {

    //让数据库操作方法中可以使用entityManager
    private final EntityManager entityManager;

    /**
     * CustomRepositoryImpl的构造函数,需当前处理的领域类类型和entityManager作为构造函数,
     * 在这里也给我们的entityManager赋值了
     * @param domainClass
     * @param entityManager
     */
    public CustomRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    public void doSomething(Serializable serializable) {
        //在此定义数据访问操作,如调用findAll方法并构造一些查询条件
    }
}

 3)自定义RepositoryFactoryBean。自定义JpaRepositoryFactoryBean替代默认的RepositoryFactoryBean,我们会获得一个RepositoryFactory,RepositoryFactory将会注册我们自定义的Repository的实现

package com.jack.springboot5jpa.pojo;

import com.jack.springboot5jpa.impl.CustomRepositoryImpl;
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.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

/**
 * create by jack 2017/10/3
 */
public class CustomRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable>
        extends JpaRepositoryFactoryBean<T, S, ID>
{//自定义RepositoryFactoryBean,继承JpaRepositoryFactoryBean


    public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    /**
     * 重写createRepositoryFactory方法,用当前的CustomRepositoryFactory创建实例
     * @param entityManager
     * @return
     */
    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new CustomRepositoryFactory(entityManager);
    }

    /**
     * 创建CustomRepositoryFactory,并继承JpaRepositoryFactory
     */
    public static class CustomRepositoryFactory extends JpaRepositoryFactory{
        public CustomRepositoryFactory(EntityManager entityManager) {
            super(entityManager);
        }

        /**
         * 重写getTargetRepository,获得当前自定义的epository实现
         * @param information
         * @param entityManager
         * @param <T>
         * @param <ID>
         * @return
         */
        @Override
        protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
            return new CustomRepositoryImpl<T,ID>((Class<T>) information.getDomainType(),entityManager);
        }

        /**
         * 重写getRepositoryBaseClass,获得当前自定义的Repository实现的类型
         * @param metadata
         * @return
         */
        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return CustomRepositoryImpl.class;
        }
    }
}


4)开启自定义支持使用@EnableJpaRepositories的repositoryFactoryBeanClass来指定FactoryBean即可,代码如下:

@EnableJpaRepositories(repositoryBaseClass = CustomRepositoryFactoryBean.class)

二:Spring Boot的支持

1,JDBC的自动配置

spring-boot-starter-data-jpa依赖spring-boot-starter-jdbc而spring boot对jdbc做了一些自动配置。源码放置在org.springframework.boot.autoconfigure.jdbc下,如下所示:



    从源码分析可以看出,我们通过spring.datasoure为前缀的属性自动配置dataSource;

    Spring Boot自动开启了注解事务的支持(@EnableTransactionManagement);还配置了一个jdbcTemplate。

   Spring Boot还提供了一个初始化数据的功能;放置在路径下的schema.sql文件会自动用来初始化表结构;放置在类路径下的data.sql文件会自动用来填充表数据。


2,对JPA的自动配置

      spring boot对JPA的自动配置放置在org.springframework.boot.autoconfigure.orm.jpa下,如下所示:



    从HibernateJpaAutoConfiguration可以看出,Spring Boot默认JPA的实现者是Hibernate;HibernateJpaAutoConfiguration依赖于DataSourceAutoConfiguration

    从JpaProperties的源码可以看出,配置JPA可以使用spring.jpa为前缀的属性在application.properties中配置。

     从JpaBaseConfiguration的源码中可以看出,Spring boot为我们配置了transactionManager,jpaVendorAdapter,entityManagerFactory等Bean。JpaBaseConfiguration还有一个getPackagesToScan方法,可以自动扫描注解有@Entity的实体类。

    在web项目中我们经常会遇到在控制器或者页面访问数据的时候出现会话连接已关闭的错误,这时候我们会配置一个Open EntityManager(session)In View这个过滤器。不过Spring BootWie我们自动配置了OpenEntityManagerInViewInterceptor这个Bean,并注册到Spring MVC的拦截器中。


3,对Spring Data JPA的自动配置

     Spring Boot 对Spring Data JPA的自动配置放置在org.springframework.boot.autoconfigure.data.jpa下,如下:



    从JpaRepositoriesAutoConfiguration和JpaRepositoriesAutoConfigureRegistrar源码可以看出JpaRepositoriesAutoConfiguration是依赖HibernateJpaAutoConfiguration配置的,且sprig boot自动开启了对Spring Data JPA的支持,即我们无须在配置类显示声明@EnableJpaRepositories


4,Spring Boot下的Spring Data JPA

    通过上面的分析可知,我们在Spring Boot下使用Spring Data JPA,在项目的maven依赖里添加spring-boot-starter-data-jpa,然后只需要定义DataSource,实体类和数据访问层,并在需要使用数据访问的地方注入数据访问层的Bean即可,无须任何额外配置。



三,实战

     在下面的实战里,演示基于方法名的查询,基于@Queryd 查询,分页及排序,最后将结合Specification和自定义Repository实现来完成一个通用实体查询,即对于任意类型的实体类的传值对象,只要对象有值的属性,我们就可以进行自动构造查询(字符型用like,其他类型用于等)

1,新建spring boot项目

    创建一个新的spring boot项目选择依赖JPA,WEB,数据库使用mysql

    pom.xml的代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.jack</groupId>
	<artifactId>springboot5jpa</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springboot5jpa</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<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>

		<!--mysql连接驱动-->
		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>


		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>


在src/main/resources目录下新建一个data.sql,内容是向表格增加一些数据,如下:

INSERT INTO person(name,age,address) VALUES ("jack1",18,"北京");
INSERT INTO person(name,age,address) VALUES ("jack2",19,"上海");
INSERT INTO person(name,age,address) VALUES ("jack3",20,"杭州");
INSERT INTO person(name,age,address) VALUES ("jack4",21,"广州");
INSERT INTO person(name,age,address) VALUES ("jack5",22,"深圳");
INSERT INTO person(name,age,address) VALUES ("jack6",23,"长沙");
INSERT INTO person(name,age,address) VALUES ("jack7",24,"南京");


2,配置基本属性

     在application.yml里配置数据源和jpa的相关属性

server:
  port: 9090
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jack
    username: root
    password: root
  jpa:
    hibernate:
    #hibernate提供了根据实体类自动维护数据库表结构的功能,可通过spring.jpa.hibernate.ddl-auto来配置,有下列可选项
    #create:启动时删除上一次生成的表,并根据实体类生成表,表中数据会被清空
    #create-drop:启动时根据实体类生成表,sessionFacotry关闭时表会被删除
    #update:启动时根据实体类生成表,当实体类属性变动的时候,表结构也会更新,在初期开发阶段使用此项
    #validate;启动时验证实体类和数据表是否一致,在我们数据结构稳定时采用此选项
    #none:不采取任何措施
      ddl-auto: update
    #show-sql用来设置hibernate操作的时候在控制台显示其真实的sql语句
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
  jackson:
    serialization:
    #让控制器输出的json字符串格式更美观
      indent_output: true




3,定义映射实体类

    hibernate支持自动将实体映射为数据表格:

package com.jack.springboot5jpa.entity;

import javax.persistence.*;

/**
 * create by jack 2017/10/3
 */
//@Entity注解指明这是一个和数据库表映射的实体类
@Entity
@NamedQuery(name = "Person.withNameAndAddressNamedQuery",
query = "select p from Person p where p.name =?1 and p.address=?2")
public class Person {
    /**
     * 主键id
     * @Id注解指明这个属性映射为数据库的主键
     * @GeneratedValue定义主键生成的方式,下面采用的是mysql的自增属性
     */
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Integer id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 地址
     */
    private String address;

    public Person() {
        super();
    }

    public Person(Integer id,String name, Integer age, String address) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer 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;
    }
}

        上面的类使用的注解可能和平时的不太一样,比如没使用@Table,@Column注解。这是因为我们是采用正向工程通过实体类生成表结构,而不是通过逆向工程从表结构生成数据库。

       我们没有通过@Column注解来注解普通属性,@Column是用来映射属性名和字段名,不注解的时候hibernate会自动根据属性名生成数据表的字段名。比如名name映射成字段name,多字母属性如testName会自动映射为test_name,表名映射规则也是这样。


4,定义数据访问接口

package com.jack.springboot5jpa.dao;

import com.jack.springboot5jpa.entity.Person;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;


import java.util.List;

/**
 * create by jack 2017/10/3
 */
public interface PersonRepository extends JpaRepository<Person,Integer> {
    //使用方法名查询,接受一个name参数,返回值为一个列表
    List<Person> findByAddress(String address);

    /**
     *  使用方法名查询,接受一个name和address,返回值为单个对象
     *  通过名字和地址查询,参数为name和address
     *  相当于:JPQL:select p from Person p where p.name = ?1 and p.address=?2
     * @param name
     * @param address
     * @return
     */

    Person findByNameAndAddress(String name,String address);
    //使用@Query查询,参数按照名称绑定
    @Query("select p from Person  p where p.name = :name and p.address = :address")
    Person withNameAndAddressQuery(@Param("name") String name,@Param("address") String address);
    //使用@NamedQuery查询,注意实体类中做的@NamedQuery的定义
    Person withNameAndAddressNamedQuery(String name,String address);

    /**
     * 通过名字相等查询,参数为name
     * 相当于JPQL:select p from Person p where p.name = ?1
     * @param name
     * @return
     */
    List<Person> findByName(String name);

    /**
     * 通过名字like查询,参数为name
     * 相当于JPQL:select p from Person p where p.name like ?1
     * @param name
     * @return
     */
    List<Person> findByNameLike(String name);

    /**
     * 获得符合查询条件的前10条数据
     * @param name
     * @return
     */
    List<Person> findFirst10ByName(String name);

    /**
     * 获得符合查询条件的前30条数据
     * @param name
     * @return
     */
    List<Person> findTo30ByName(String name);

}


5,编写一个控制器

    写一个控制器,来进行测试,如下:

package com.jack.springboot5jpa.controller;

import com.jack.springboot5jpa.dao.PersonRepository;
import com.jack.springboot5jpa.entity.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * create by jack 2017/10/3
 */
@RestController
@RequestMapping("/jpa")
public class DataController {
    /**
     * Spring Data JPA自动为我们注册了bean,所以可以自动注入
     */
    @Autowired
    private PersonRepository personRepository;
    /**
     * 保存
     * save支持批量保存
     * 删除;
     * 支持使用id删除对象,批量删除以及删除全部:
     * void delete(ID id);
     * void delete(T entity);
     * void delete(Iterable<? extends T> entities)
     * void deleteAll();
     */
    /**
     * 保存
     *
     * @param name
     * @param age
     * @param address
     * @return
     */
    @RequestMapping("/save")
    public Person save(String name, Integer age, String address) {
        Person person = personRepository.save(new Person(null, name, age, address));
        return person;
    }

    @RequestMapping("/q1")
    public List<Person> q1(String address) {
        List<Person> personList = personRepository.findByAddress(address);
        return personList;
    }

    /**
     * 测试findByNameAndAddress
     * @param name
     * @param address
     * @return
     */
    @RequestMapping("/q2")
    public Person q2(String name, String address) {
        Person person = personRepository.findByNameAndAddress(name, address);
        return person;
    }

    /**
     * 测试withNameAndAddressNamedQuery
     * @param name
     * @param address
     * @return
     */
    @RequestMapping("/q3")
    public Person q3(String name, String address) {
        Person person = personRepository.withNameAndAddressNamedQuery(name, address);
        return person;
    }

    /**
     * 测试withNameAndAddressNamedQuery
     * @param name
     * @param address
     * @return
     */
    @RequestMapping("/q4")
    public Person q4(String name, String address) {
        Person person = personRepository.withNameAndAddressNamedQuery(name, address);
        return person;
    }

    /**
     * 测试排序
     * @return
     */
    @RequestMapping("/sort")
    public List<Person> sort(){
        List<Person> personList = personRepository.findAll(new Sort(Sort.Direction.ASC,"age"));
        return personList;
    }

    /**
     * 测试分页
     * @return
     */
    @RequestMapping("/page")
    public Page<Person> page(){
        Page<Person> personPage = personRepository.findAll(new PageRequest(1,2));
        return personPage;
    }

}


6,测试

     启动程序,在浏览器输入url地址进行测试,比如:http://localhost:9090/jpa/page,输出如下:




     上面还有一些测试就不进行了,只要输入正确的url和参数,会看到对应的结果的,比较简单,可以自己试试。

代码地址:https://github.com/wj903829182/SpringCloudTwo/tree/master/springboot5jpa



四:自定义Repository实现

       上面已经实战演示了Spring boot和Spring Data JPA组合的绝大多数功能,下面将结合Specification和自定义Repository实现来定制一个自动模糊查询。即对任意的实体对象进行查询,对象里有几个值我们就查几个值,当值为字符型时我们就自动like查询,其余的类型使用自动等于查询,没有值我们就查询全部。

1,定义Specification

package com.jack.springboot5jpa.pojo;

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.Arrays;
import java.util.List;

/**
 * create by jack 2017/10/3
 */
public class CustomerSpecs {
    /**
     * 定义一个返回值为Specification的方法byAuto,这里使用的是范型T,所以这个Specification
     * 是可以用于任意的实体类的。它接受的参数是entityManager和当前的包含值作为查询条件的实体类对象
     * @param entityManager
     * @param example
     * @param <T>
     * @return
     */
    public static <T> Specification<T> byAuto(final EntityManager entityManager,final 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,?> attribute : entityType.getDeclaredAttributes()){
                    //获得实体类对象某一个属性的值
                    Object attrValue =getValue(example,attribute);
                    if (attrValue != null){
                        //当前属性值为字符类型的时候
                        if (attribute.getJavaType() == String.class){
                            //若当前字符不为空的时候
                            if (!StringUtils.isEmpty(attrValue)){
                                //构造当前属性like(前后%)属性值查询条件,并添加到条件列表中
                                predicates.add(criteriaBuilder.like(root.get(attribute(entityType, attribute.getName(),String.class)),pattern((String) attrValue)));
                            }
                        }else {
                            //其余情况下构造属性和属性值equal查询条件,并添加到条件列表中
                            predicates.add(criteriaBuilder.equal(root.get(attribute(entityType,attribute.getName(),attrValue.getClass())),attrValue));
                        }
                    }
                }
                
//predicates.toArray(new Predicate[predicates.size()转换为数组
return predicates.isEmpty() ? criteriaBuilder.conjunction() : criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
} }; } /** *通过反射获得实体对象对应属性的属性值 * @param example * @param attribute * @param <T> * @return */ private static <T> Object getValue(T example,Attribute<T,?> attribute){ return ReflectionUtils.getField((Field) attribute.getJavaMember(),example); } /** *获得实体类的当前属性SingularAttribute,SingularAttribute包含的是实体类的某个单独属性 * @param entityType * @param fieldName * @param fieldClass * @param <E> * @param <T> * @return */ private static <E,T> SingularAttribute<T,E> attribute(EntityType<T> entityType,String fieldName,Class<E> fieldClass){ return entityType.getDeclaredSingularAttribute(fieldName, fieldClass); } /** *构造like的查询模式,即前后加% * @param str * @return */ private static String pattern(String str){ return "%"+str+"%"; }}


2,定义接口

package com.jack.springboot5jpa.dao;

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;

/**
 * create by jack 2017/10/3
 */
//指明@NoRepositoryBean当前这个接口不是我们领域类的接口(如:PersonRepository)
@NoRepositoryBean
//自定义的Repository实现PagingAndSortingRepository接口,具备分页和排序的能力
public interface CustomRepository<T,ID extends Serializable> extends JpaRepository<T,ID>,JpaSpecificationExecutor<T>{
   Page<T> findByAuto(T example,Pageable pageable);
}


     上面的接口集成了JpaRepository让我们具备了JpaRepository所提供的方法;继承了JpaSpecificationExecutor,让我们具备了使用Specification的能力。


3,定义实现

package com.jack.springboot5jpa.impl;

import com.jack.springboot5jpa.dao.CustomRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import javax.persistence.EntityManager;
import java.io.Serializable;
import static com.jack.springboot5jpa.pojo.CustomerSpecs.*;
/**
 * create by jack 2017/10/3
 */
//首先要实现CustomRepository接口,继承SimpleJpaRepository类让我们可以使用其提供的方法
public class CustomRepositoryImpl <T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements CustomRepository<T,ID> {

    //让数据库操作方法中可以使用entityManager
    private final EntityManager entityManager;

    /**
     * CustomRepositoryImpl的构造函数,需当前处理的领域类类型和entityManager作为构造函数,
     * 在这里也给我们的entityManager赋值了
     * @param domainClass
     * @param 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);
    }
}

      上面的类继承JpaRepository的实现类SimpleJpaRepository,让我们可以使用SimpleJpaRepository的方法;此类当然还要实现我们自定义的接口CustomRepository。

     findByAuto方法使用byAuto Specification构造的条件查询,并提供分页功能。


4,定义repositoryFactoryBean

package com.jack.springboot5jpa.pojo;

import com.jack.springboot5jpa.impl.CustomRepositoryImpl;
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.RepositoryFactoryBeanSupport;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

/**
 * create by jack 2017/10/3
 */
public class CustomRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable>
        extends JpaRepositoryFactoryBean<T, S, ID>
{//自定义RepositoryFactoryBean,继承JpaRepositoryFactoryBean


    public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    /**
     * 重写createRepositoryFactory方法,用当前的CustomRepositoryFactory创建实例
     * @param entityManager
     * @return
     */
    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new CustomRepositoryFactory(entityManager);
    }

    /**
     * 创建CustomRepositoryFactory,并继承JpaRepositoryFactory
     */
    public static class CustomRepositoryFactory extends JpaRepositoryFactory{
        public CustomRepositoryFactory(EntityManager entityManager) {
            super(entityManager);
        }

        /**
         * 重写getTargetRepository,获得当前自定义的epository实现
         * @param information
         * @param entityManager
         * @param <T>
         * @param <ID>
         * @return
         */
        @Override
        protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
            return new CustomRepositoryImpl<T,ID>((Class<T>) information.getDomainType(),entityManager);
        }

        /**
         * 重写getRepositoryBaseClass,获得当前自定义的Repository实现的类型
         * @param metadata
         * @return
         */
        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return CustomRepositoryImpl.class;
        }
    }
}


    上面的代码可以作为模板代码使用,值需要修改和定义实现类相关的代码即可



5,使用

package com.jack.springboot5jpa.dao;

import com.jack.springboot5jpa.entity.Person;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;


import java.util.List;

/**
 * create by jack 2017/10/3
 */
public interface PersonRepository extends CustomRepository<Person,Integer>/*extends JpaRepository<Person,Integer>*/ {
    //使用方法名查询,接受一个name参数,返回值为一个列表
    List<Person> findByAddress(String address);

    /**
     *  使用方法名查询,接受一个name和address,返回值为单个对象
     *  通过名字和地址查询,参数为name和address
     *  相当于:JPQL:select p from Person p where p.name = ?1 and p.address=?2
     * @param name
     * @param address
     * @return
     */

    Person findByNameAndAddress(String name,String address);
    //使用@Query查询,参数按照名称绑定
    @Query("select p from Person  p where p.name = :name and p.address = :address")
    Person withNameAndAddressQuery(@Param("name") String name,@Param("address") String address);
    //使用@NamedQuery查询,注意实体类中做的@NamedQuery的定义
    Person withNameAndAddressNamedQuery(String name,String address);

    /**
     * 通过名字相等查询,参数为name
     * 相当于JPQL:select p from Person p where p.name = ?1
     * @param name
     * @return
     */
    List<Person> findByName(String name);

    /**
     * 通过名字like查询,参数为name
     * 相当于JPQL:select p from Person p where p.name like ?1
     * @param name
     * @return
     */
    List<Person> findByNameLike(String name);

    /**
     * 获得符合查询条件的前10条数据
     * @param name
     * @return
     */
    List<Person> findFirst10ByName(String name);

    /**
     * 获得符合查询条件的前30条数据
     * @param name
     * @return
     */
    List<Person> findTo30ByName(String name);

}

   值需让实体类Repository继承我们自定义的Repository接口,即可使用我们在自定义Respository中实现的功能。



写一个测试的方法:

/**
     *控制器中接受一个Person对象,当Person的name有值时会自动对name进行like查询;当age有值时,会进行等于查询;
     * 当Person中有多个值不为空的时候,会自动构造多个查询条件;当Person所有值为空的时候,默认查询出所有记录;
     * 注意:此处需要指出的是,在实体类中的数据类型要用包装类型,而不能用基本类型如long,int。因为在spring mvc中,使用原始
     * 数据类型会自动初始化为0,而不是空,导致我们构造条件失败。
     * @param person
     * @return
     */
    @RequestMapping("/auto")
    public Page<Person> auto(Person person){
        Page<Person> personPage = personRepository.findByAuto(person,new PageRequest(0,10));
        return personPage;
    }


6,进行配置

package com.jack.springboot5jpa;

import com.jack.springboot5jpa.pojo.CustomRepositoryFactoryBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
/**
 * 在配置类上配置@EnableJpaRepositories,并指定repositoryFactoryBeanClass,让我们自定义的Repository实现起效
 * 如果我们不需要自定义Repository实现,则在Spring Data JPA里无须添加@EnableJpaRepositories注解,因为
 * @SpringBootApplication包含的@EnableAutoConfiguration注解已经开启了对Spring Data JPA的支持
 */
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class Springboot5jpaApplication {

	public static void main(String[] args) {
		SpringApplication.run(Springboot5jpaApplication.class, args);
	}
}


7,运行测试

启动程序,输入:http://localhost:9090/jpa/auto?address=长

输出如下:



输入:http://localhost:9090/jpa/auto?name=ja



字符串进行模糊匹配,其他进行相等查询:



代码地址:https://github.com/wj903829182/SpringCloudTwo/tree/master/springboot5jpa



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值