天天看点

Spring Data JPA 入门指南一、ORM概述二、JPA三、SpringDataJPA总结

一、ORM概述

ORM(Object-Relational Mapping) 表示对象关系映射。在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中。只要有一套程序能够做到建立对象与数据库的关联,操作对象就可以直接操作数据库数据,就可以说这套程序实现了ORM对象关系映射

简单的说:ORM就是建立实体类和数据库表之间的关系,从而达到操作实体类就相当于操作数据库表的目的。

1. 为什么使用ORM

当实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存数据、修改数据、删除数据,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称ORM),主要实现程序对象到关系数据库数据的映射。

2. 常见的ORM框架

Mybatis(ibatis)、Hibernate、Jpa

二、JPA

1. JPA概述

JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。

JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

三、SpringDataJPA

1. SpringDataJPA概述

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!

2. Springboot 整合 SpringDataJPA快速入门

2.1.引入相关依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>my_study</artifactId>
        <groupId>com.lfh</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.lfh</groupId>
    <artifactId>study</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>study</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

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

</project>

           

2.2 实体类

package com.lfh.study.entity;

import lombok.Data;

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

@Data
@Entity  //声明实体类
@Table(name = "user") //建立实体类和表的映射关系
public class User implements Serializable {
    private static final long serialVersionUID = -4482650827188250537L;

    @Id	//主键id
    @GeneratedValue(strategy= GenerationType.IDENTITY)//主键生成策略
    @Column(name="id")//数据库字段名
    private Integer id;

    @Column(name="name")
    private String name;

    @Column(name="age")
    private Integer age;

    @Column(name="address")
    private String address;
}

           

2.3 Dao层

package com.lfh.study.repository;

import com.lfh.study.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/*
    JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
    JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
 */
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {

}


           

2.4 完成基本的CRUD

package com.lfh.study.mytest;

import cn.hutool.core.util.ObjectUtil;
import com.lfh.study.entity.User;
import com.lfh.study.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.Optional;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class TestDemo {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void add(){
        User user = new User();
        user.setName("张三");
        user.setAge(18);
        user.setAddress("合肥市");
        userRepository.save(user);
    }
    /*
        对于save方法的解释:如果执行此方法是对象中存在id属性,即为更新操作会先根据id查询,再更新
        如果执行此方法中对象中不存在id属性,即为保存操作
     */
    @Test
    public void update(){
        Optional<User> userOptional = userRepository.findById(1L);
        User user = userOptional.get();
        if (ObjectUtil.isNotNull(user)){
            user.setName("李四");
            user.setAge(80);
            userRepository.save(user);
        }
    }
    @Test
    public void delete(){
        userRepository.deleteById(1L);
    }

    @Test
    public void list(){
        User user = new User();
        user.setAge(18);
        Example<User> e = Example.of(user);
        List<User> userList = userRepository.findAll(e);
        log.info("userList:{}",userList.toString());
    }
}

           

3 方法命名规则查询

顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询

按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

具体的关键字,使用方法和生产成SQL如下表所示

Keyword Sample JPQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

4 Example动态查询

Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require you to write queries that contain field names. In fact, Query by Example does not require you to write queries by using store-specific query languages at all.

谷歌翻译:

示例查询(QBE)是一种用户友好的查询技术,具有简单的界面。它允许动态查询创建,并且不需要您编写包含字段名称的查询。实际上,“按示例查询”根本不需要您使用商店特定的查询语言编写查询。

Example Api包含三部分:

  • Probe: 带有填充字段的域对象的实际示例。
  • ExampleMatcher:ExampleMatcher附带有关如何匹配特定字段的详细信息。可以在多个示例中重复使用它。
  • Example:Example由Probe和ExampleMatcher组成r。它用于创建查询。

    根据Example查询非常适合几种用例:

  • 使用一组静态或动态约束来查询数据存储。
  • 频繁重构域对象,而不必担心破坏现有查询。
  • 独立于基础数据存储区API进行工作。

    根据Example查询也有一些限制:

  • 不支持嵌套或分组属性约束,例如firstname = ?0 or (firstname = ?1 and lastname = ?2)。
  • 仅支持字符串的开始/包含/结束/正则表达式匹配,以及其他属性类型的完全匹配。
@Test
    public void list(){
        User user = new User();
        user.setName("张");
        user.setAddress("安徽");
        ExampleMatcher matcher = ExampleMatcher.matching()
               //相当于name like %{user.name}%
                .withMatcher("name", ExampleMatcher.GenericPropertyMatcher::contains)
                //相当于 address like %{user.address}
                .withMatcher("address", ExampleMatcher.GenericPropertyMatcher::startsWith);
        Example<User> example = Example.of(user, matcher);
        List<User> userList = userRepository.findAll(example);
        log.info(userList.toString());
        // select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_ from user user0_ where (user0_.address like ? escape ?) and (user0_.name like ? escape ?)
        //打印[User(id=2, name=张三, age=19, address=安徽省合肥市), User(id=6, name=张六, age=22, address=安徽省亳州市)]
    }
           

StringMatcher 参数

Matching 生成的语句 说明
DEFAULT (case-sensitive) firstname = ?0 默认(大小写敏感)
DEFAULT (case-insensitive) LOWER(firstname) = LOWER(?0) 默认(忽略大小写)
EXACT (case-sensitive) firstname = ?0 精确匹配(大小写敏感)
EXACT (case-insensitive) LOWER(firstname) = LOWER(?0) 精确匹配(忽略大小写)
STARTING (case-sensitive) firstname like ?0 + ‘%’ 前缀匹配(大小写敏感)
STARTING (case-insensitive) LOWER(firstname) like LOWER(?0) + ‘%’ 前缀匹配(忽略大小写)
ENDING (case-sensitive) firstname like ‘%’ + ?0 后缀匹配(大小写敏感)
ENDING (case-insensitive) LOWER(firstname) like ‘%’ + LOWER(?0) 后缀匹配(忽略大小写)
CONTAINING (case-sensitive) firstname like ‘%’ + ?0 + ‘%’ 模糊查询(大小写敏感)
CONTAINING (case-insensitive) LOWER(firstname) like ‘%’ + LOWER(?0) + ‘%’ 模糊查询(忽略大小写)

注:

  • 在默认情况下,不调用withIgnoreCase()都是大小写敏感的。

5 Specifications动态查询

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

/**
 *	JpaSpecificationExecutor中定义的方法
 **/
 public interface JpaSpecificationExecutor<T> {
   	//根据条件查询一个对象
 	T findOne(Specification<T> spec);	
   	//根据条件查询集合
 	List<T> findAll(Specification<T> spec);
   	//根据条件分页查询
 	Page<T> findAll(Specification<T> spec, Pageable pageable);
   	//排序查询查询
 	List<T> findAll(Specification<T> spec, Sort sort);
   	//统计查询
 	long count(Specification<T> spec);
}

           

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

Specification接口中只定义了如下一个方法:

//构造查询条件
    /**
    *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
    *	query	:代表一个顶层查询对象,用来自定义查询
    *	cb		:用来构建查询,此对象里有很多条件方法
    **/
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

           

5.1 根据Specifications完成条件查询

@Test
    public void queryBySpecifications(){
        //使用匿名内部类的方式,创建一个Specification的实现类,并实现toPredicate方法
        //Specification <User> spec = new Specification<User>() {
        //    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        //        //cb:构建查询,添加查询方式   like:模糊匹配
        //        //root:从实体Customer对象中按照custName属性进行查询
        //        return cb.like(root.get("custName").as(String.class), "传智播客%");
        //    }
        //};
        Specification<User> spec = (Specification<User>) (root, query, cb) -> {
            //cb:构建查询,添加查询方式   like:模糊匹配
            //root:从实体User对象中按照custName属性进行查询
            return cb.like(root.get("name").as(String.class), "张三%");
        };
        Optional<User> user = userRepository.findOne(spec);
        System.out.println(user.orElse(new User()));
        //打印select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_ from user user0_ where user0_.name like ?
        //User(id=2, name=张三, age=19, address=安徽省合肥市)
    }
           

5.2 基于Specifications的分页查询

@Test
    public void getUserPageList(){
        Specification<User> spec = (Specification<User>) (root, query, cb) -> {
            //cb:构建查询,添加查询方式   like:模糊匹配
            //root:从实体User对象中按照custName属性进行查询
            return cb.like(root.get("name").as(String.class), "张三%");
        };
        /**
         * 构造分页参数
         * 		Pageable : 接口
         * 			PageRequest实现了Pageable接口,调用构造方法的形式构造
         * 				第一个参数:页码(从0开始)
         * 				第二个参数:每页查询条数
         */
        PageRequest pageRequest = PageRequest.of(0, 5);
        Page<User> userPage = userRepository.findAll(spec, pageRequest);
        log.info("content:-------------{}",userPage.getContent());
        log.info("totalElements:----------{}",userPage.getTotalElements());
        log.info("totalPage:-------------{}", userPage.getTotalPages());
        log.info("size:---------{}", userPage.getSize());
        log.info("sort:------------{}", userPage.getSort());
        log.info("number:------------{}", userPage.getNumber());
        //sql; select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_ from user user0_ where user0_.name like ? limit ?
        //content:-------------[User(id=2, name=张三, age=19, address=安徽省合肥市)]
        //totalElements:----------1
        //totalPage:-------------1
        //size:---------5
        //sort:------------UNSORTED
        //number:------------0
    }
           

5.3 方法对应关系

方法名称 Sql对应关系
equle filed = value
gt(greaterThan ) filed > value
lt(lessThan ) filed < value
ge(greaterThanOrEqualTo ) filed >= value
le( lessThanOrEqualTo) filed <= value
notEqule filed != value
like filed like value
notLike filed not like value

总结

以上是对SpringDataJPA的部分整理。后期继续进行修改添加

继续阅读