JAVASpring

Spring Data JPA 入门

ORM


在解释什么是JPA和Spring Data JPA之前我们应该先来了解一下什么是ORM。ORM(Object-Relational Mapping, 对象关系映射),是一种面向对象编程语言中的对象和数据库中的数据之间的映射。使用ORM工具、框架可以让应用程序操作数据库。

在过去,有很多针对Java的ORM框架,但是每一套框架都有自己的一套操作方法和规范,这就使得Java程序操作不同数据库时显得杂乱无章。于是乎,Sun公司推出了一套操作持久层(数据库)的规范(API)用于结束这种乱象,这套规范也就是JPA

JPA


JPA(Java Persistence API,Java持久层API) 是Sun公司定义的一套基于ORM的接口规范,用于给Java程序操作数据库。JPA 通过 JDK 5.0 注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中 。在这之后很多ORM框架实现了JPA规范,其中最有名的有Hibernate、TopLink、JDO等。JPA 和 Hibernate 的关系就像 JDBC 和 JDBC 驱动的关系,JPA 是规范,Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现。

JPA的优势


  • 标准化

JPA 是 JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同
样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在 不同的 JPA 框架下运行。

  • 容器级特性的支持

JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的 局限,在企业应用发挥更大的作用。

  • 简单方便

JPA 的主要目标之一就是提供更加简单的编程模型:在 JPA 框架下创建实体和创建 Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity 进行注释,JPA的 框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。 JPA 基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成

  • 查询能力

JPA 的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是 Hibernate HQL 的等价物。JPA 定义了独特的 JPQL(Java Persistence Query Language),JPQL 是 EJB QL 的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而 不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询

  • 高级特性

JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系。这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性。
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data JPA 让我们解脱了 DAO 层的操作,基本上所有 CRUD 都可以依赖于它来实现,在实际的工作工程中,推荐使用 Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的 ORM 框架时提供了极大的方便,同时也使数据库层操作更加简单,方便。

Spring Data JPA


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

Spring Data JPA 让我们解脱了 DAO 层的操作,基本上所有 CRUD 都可以依赖于它来实现,在实际的 工作工程中,推荐使用 Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的 ORM 框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦。

第一个Spring Data JPA项目


步骤如下

  • 在pom.xml中导入依赖
    <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-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  • 配置application.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jpa_study?useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
  data:
    rest:
      basePath: /api
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

server:
  port: 8080
  • 编写实体类
package com.soul.jpastudy.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Data
@NoArgsConstructor
@AllArgsConstructor

/**
 * 客户的实体类
 *      配置映射关系
 *          1. 实体类和表的映射关系
 *          2. 实体类中属性和表中字段的映射关系
 *  @Entity:声明实体类
 *  @Table: 配置实体类和表的映射关系
 *      name: 配置数据库表的名称
 */

@Entity
@Table(name = "cst_customer")
public class Customer {

    @Id // 声明主键的配置
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增
    @Column(name = "cust_id")   // 映射数据库表中的字段
    private Long custId;

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

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

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

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

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

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

}
  • 编写Repository
package com.soul.jpastudy.repository;

import com.soul.jpastudy.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@RepositoryRestResource(collectionResourceRel = "customers", path = "customers")
public interface CustomerRepository extends JpaRepository<Customer, Long>{

    Customer findByCustName(String custName);

    @Query("select c from Customer c")
    List<Customer> findAll();

    @Transactional
    @Modifying
    @Query("update Customer c set c.custName = ?1 where c.custId = ?2")
    int updateCustomer(@Param("custName") String custName, @Param("custId") Long custId);

    @Transactional
    @Modifying
    @Query("delete from Customer c where c.custId = :custId and c.custIndustry = :custIndustry")
    int deleteCustomer(@Param("custId") Long custId, @Param("custIndustry") String custIndustry);

}

  • 单元测试
@SpringBootTest
class JpaStudyApplicationTests {

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    void testFindByCustName(){
        Customer customer = customerRepository.findByCustName("testName");
        System.out.println(customer);
    }

    @Test
    void testFindAll() {
        List<Customer> all = customerRepository.findAll();
        for (Customer customer : all) {
            System.out.println(customer);
        }
    }

    @Test
    void testUpdateCustomer() {
        int row = customerRepository.updateCustomer("testUpdate", 1L);
        System.out.println(row);
        testFindAll();
    }

    @Test
    void testDeleteCustomer() {
        int row = customerRepository.deleteCustomer(1L, "testIndustry");
        System.out.println(row);
        testFindAll();
    }

    @Test
    void testSaveCustomer() {
        Customer customer = new Customer();
        customer.setCustName("save test");
        customer.setCustLevel("save level");
//        customer.setCustId(1L);   // 加上是更新某某,不加是添加新的一行数据
        customerRepository.save(customer);
        testFindAll();
    }

JPQL


基于首次在 EJB2.0 中引入的 EJB 查询语言(EJB QL),Java JPQL(Java Persistence Query Language)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将 SQL 语法和简单查询语义绑定在一起使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的 SQL。

其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性。

基本语法

select 实体别名.属性名,
from 实体名 as 实体别名 
where 实体别名.实体属性 op 比较值

# example
update Customer c set c.custName = ?1 where c.custId = ?2 # ?后跟变量序号(详见第一个Spring Data JPA项目)
delete from Customer c where c.custId = :custId and c.custIndustry = :custIndustry # :后跟变量名(详见第一个Spring Data JPA项目)

动态查询


在Spring Data JPA中,可以使用匿名内部类的方式添加动态条件,实现动态查询。步骤如下:

示例代码

    @Test
    public void testDynamicQuery() {
        List<User> users = userRepository.findAll(new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                Predicate predicate = null;

                Path<String> pwdPath = root.get("pwd");
                Predicate pwdPredicate = criteriaBuilder.equal(pwdPath, "123");
                predicate = criteriaBuilder.and(predicate, pwdPredicate);
                Path<String> namePath = root.get("name");
                Predicate namePredicate = criteriaBuilder.like(namePath, "%Jack%");
                predicate = criteriaBuilder.and(pwdPredicate, namePredicate);
                Path<Integer> idPath = root.get("id");
                Predicate idPredicate = criteriaBuilder.equal(idPath, 2);
                predicate = criteriaBuilder.and(predicate, idPredicate);
                return predicate;
            }
        });
        
        // print results
        for (User user : users) {
            System.out.println(user);
        }
    }

Spring Data JPA 对比 MyBatis


  • 表关联较多的项目,优先使用mybatis
  • 持续维护开发迭代较快的项目建议使用mybatis,因为一般这种项目需要变化很灵活,对sql的灵活修改要求较高
  • 对于传统项目或者关系模型较为清晰稳定的项目,建议JPA(比如DDD设计中的领域层)
  • 目前微服务比较火,基于其职责的独立性,如果模型清晰,可以考虑使用JPA,但如果数据量较大全字段返回数据量大的话可能在性能有影响,需要根据实际情况进行分析

Spring Data JPA 是Spring Data中一款强大的用于操作关系型数据库的技术。它的出现,省去了我们编写Dao层的步骤。在业务逻辑相对简单的时候,使用它会极大的提高开发效率。但是当业务逻辑太过复杂时,Spring Data JPA缺乏灵活性的问题就会暴露出来,这个时候MyBatis反而是更好的选择。