JPA

6 阅读6分钟

JPA是什么

JPA = Java Persistence API (Java持久化规范)

JPA不是框架,而是一套“标准接口规范”

它规定了:

  • 实体怎么写
  • 怎么把Java对象存进数据库
  • 怎么把从数据库查出来的再变回对象

真正去实现的是实现框架,比如:

  • Hibernate(最常见)
  • EclipseLink
  • OpenJPA

平时我们说“用 JPA”,90% 实际是在用 Hibernate + JPA 规范

JPA想解决什么问题

不用JPA -> 纯SQL/MyBatis

需要写很多

SELECT * FROM user WHERE id = 1;

然后:

  • 手动映射成 Java 对象
  • 表结构变了 → SQL 改一堆
  • 业务里 SQL 越来越多

JPA的思路:你操作的是对象,不是表

`User user = entityManager.find(User.class, 1L);`

JPA 帮你做了:

  • SQL 生成
  • 字段 ↔ 属性映射
  • 事务管理
  • 对象状态管理

JPA的核心概念

Entity(实体)

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
}
  • @Entity:这是一个会映射到表的对象
  • @Id:主键
  • 一个实体 ≈ 一张表

EntityManager(核心)

JPA的所有操作都围绕它:

entityManager.persist(user); // 新增
entityManager.find(User.class, 1L); // 查
entityManager.remove(user); // 删

在 Spring Boot 里你一般用不到它本体,因为有更好用的东西↓

Repository(你常见的那个)

public interface UserRepository extends JpaRepository<User, Long> {
}

你什么都没写,却能直接用:

userRepository.save(user);
userRepository.findById(1L);
userRepository.deleteById(1L);

这就是 Spring Data JPA

  • 在 JPA 之上
  • 帮你封装了 EntityManager
  • 自动生成常见 CRUD

JPA的优点/缺点

优点

  • CRUD 极快(写得非常少)
  • 对象化思维,业务代码干净
  • 事务、缓存、懒加载都帮你管了
  • 中小项目、后台系统很省心

缺点

  • 复杂 SQL 很难受
  • 性能不可控(N+1 问题)
  • JPQL 学习成本
  • 出问题时不好定位

和MyBatis的区别

MyBatis和JPA是两条完全不同的技术路线

MyBatis = 写 SQL 的 Java 框架 (你告诉我你要什么对象,我帮你处理数据库。) = ORM(对象关系映射) JPA = 用对象“代替”SQL 的规范 (你把 SQL 写清楚,我只负责帮你跑。)

ORM

ORM是对象关系映射,简单来说,ORM就是把数据库里的表,映射成Java对象,让你写操作对象而不是写SQL

为什么要用ORM

  1. 数据库和对象的差异
  • 数据库是表格形式(行列)
  • Java是对象形式(类,属性,方法)

如果不做映射,每次都要:

// SQL 查询
SELECT * FROM user WHERE id = 1;

// 手动把结果变成对象
User u = new User();
u.setId(rs.getLong("id"));
u.setName(rs.getString("name"));

麻烦,容易出错,表结构一改就要改很多代码

ORM是什么

  • 映射表和对象
  • 生成 SQL
  • 对象状态管理
  • 支持事务、缓存、关联关系

用了ORM后,可以直接:

User user = entityManager.find(User.class, 1L);
user.setName("Tom"); // JPA 会自动生成 update

不用再手写SQL

常见 ORM 框架

框架特点
Hibernate最经典的 JPA 实现,功能全
MyBatis (严格来说不是 ORM)半 ORM,SQL 可控,结果映射对象
EclipseLink / OpenJPA其他 JPA 实现

注意:严格意义上,MyBatis 更像 SQL 映射框架,只做结果集映射,不做对象状态管理。

什么是对象状态管理

在 JPA 或 ORM 框架里,每个实体对象(Entity)都有自己的状态,JPA 会跟踪这个状态,并在合适的时候自动生成 SQL,把对象的变化同步到数据库。

对象状态管理 = 框架帮你跟踪对象“现在是什么状态”,自动决定什么时候插入、更新或删除数据库

demo

pom.xml

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.8.RELEASE</version>
</parent>

<dependencies>

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

    <!-- H2 内存数据库 -->
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>

</dependencies>
  • Spring Boot 自带 依赖管理,不需要特意指定jpa和h2的版本,它会帮你选合适的版本。
  • JPA依赖:JPA + Hibernate + Spring Data
  • h2: H2 Database 是一个 轻量级、纯 Java 的数据库,通常用于测试,不需要安装mySQL
spring.datasource.url=jdbc:h2:mem:testdb

mem:testdb 就表示数据库在内存中,程序关闭数据就消失

application.properties

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=root
spring.datasource.password=

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
  • ddl-auto:update : 如果表不存在会创建表,如果字段不存在会新增字段,如果字段类型变了会尝试修改,但不会删除表和字段
行为常用场景
none什么都不做生产(推荐)
validate只校验,不改生产 / 预发布
update自动改表本地开发
create每次删表重建测试
create-drop启动建,关闭删集成测试
  • show-sql: 控制台能看到生成的sql

定义实体

@Entity
@Table(name = "refund_order")
public class RefundOrder {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "out_trade_no", nullable = false)
    private String outTradeNo;

    private String flowCode;


    @Temporal(TemporalType.TIMESTAMP)
    @CreationTimestamp
    private Date createdAt;

    private BigDecimal money;

    private Integer payType;


    private String hospitalCode;

    public RefundOrder() {}

    public RefundOrder(String outTradeNo){
        this.outTradeNo = outTradeNo;
    }

    @Override
    public String toString(){
        return "RefundOrder{id=" + id + ", outTradeNo=" + outTradeNo + "}";
    }

}

类注解

  • @Entity:告诉 JPA:这个类是一个「实体类」,要映射到数据库表。JPA启动时会扫描这个类,允许你用这个Java对象操作数据库记录
  • @Table(name = "users"): 指定这个实体对应的数据库表名是 users,可以不写name,默认表名=类名

属性注解

  • @Id: 声明这是主键,每个 @Entity 必须有且只有一个 @Id,否则报错

  • @GeneratedValue: 主键的值由 JPA / 数据库自动生成 策略 | 含义 | | -------- | -------------------------- | | IDENTITY | 数据库自增(MySQL 最常用) | | AUTO | JPA 自动选择 | | SEQUENCE | 数据库序列(Oracle / PostgreSQL) | | TABLE | 用表模拟(很少用)|

  • @Column(name = "name", nullable = false): 映射数据库中的列,name:数据库列名,可以不加,默认命名策略会将userName -> user_name。 nullable: 是否允许为null。还有length,unique等

  • @Temporal(TemporalType.TIMESTAMP):处理“时间类型映射”的注解,只java.util.Date / Calendar 有关

  • @CreationTimestamp在 INSERT 时自动填充当前时间,类似的:@UpdateTimestamp

数据库类型含义
DATE只有年月日
TIME只有时分秒
TIMESTAMP年月日 + 时分秒

Repository

public interface RefundOrderRepository extends JpaRepository<RefundOrder, Long> {

}
  • RefundOrderRepository:数据访问接口,Spring在运行时会自动帮你生成实现类,用来操作refund_order表

-extends JpaRepository<RefundOrder, Long>: 泛型的第一个参数->RefundOrder,声明了这个Repository就是用来操作refundOrder这个实体类的。第二个参数->Long,对应实体类里的private Long id;也就是主键的字段类型

JPA为你实现了哪些类

save(entity)           // insert / update
findById(id)           // select by id
findAll()              // select *
deleteById(id)         // delete
count()                // select count(*)
existsById(id)         // 是否存在

Spring在启动时生成了一个代理类实现这些方法

  • save是插入还是更新主要取决于id有没有值

默认生成之外的方法

方法名派生(实现最简单)

  1. 按字段查
RefundOrder findByOutTradeNo(String outTradeNo);

SpringData会自动生成:

select * from refund_order where out_trade_no = ?
  1. 多个条件
List<RefundOrder> findByHospitalCodeAndPayType(
        String hospitalCode, Integer payType);

  1. 范围/排序
List<RefundOrder> findByCreatedAtBetween(
        LocalDateTime start, LocalDateTime end);

List<RefundOrder> findByHospitalCodeOrderByCreatedAtDesc(
        String hospitalCode);

核心规则:

  • findBy
  • 字段名(实体类的字段名)
  • And/Or/Between/Like/In/OrderBy

@Query(JPQL)

注意这里是用JPQL,不是SQL

JPQL特点:

  • 面向Entity
  • 不写表名
  • 不写列名
  • 写的是类名+字段名

示例:

@Query("select r from RefundOrder r where r.money > :amount")
List<RefundOrder> findBigRefund(@Param("amount") BigDecimal amount);

Hibernate 会自动翻译成 SQL。

适合:join,子查询,复杂条件

写SQL

示例:

@Query("select r from RefundOrder r where r.money > :amount")
List<RefundOrder> findBigRefund(@Param("amount") BigDecimal amount);

适合:数据库特有函数,复杂 SQL,legacy 表,性能关键路径

main

@SpringBootApplication
public class DemoApplication {

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

    @Bean
    CommandLineRunner demo(RefundOrderRepository refundOrderRepository){
        return args -> {
            RefundOrder order = new RefundOrder("1234");
            refundOrderRepository.save(order);

            System.out.println("All order:");
            refundOrderRepository.findAll().forEach(System.out::println);
        };
    }

}
  • CommandLineRunner:SpringBoot提供的接口,SpringBoot启动完成后,自动执行run方法
  • return args:lambda表达式:
return new CommandLineRunner() {
    @Override
    public void run(String... args) {
        ...
    }
};

自定义Repository

属于进阶用法

场景:动态SQL,查询条件非常复杂,查询逻辑无法表达成方法名/JPQL

RefundOrderRepositoryCustom.java

public interface RefundOrderRepositoryCustom {
    List<RefundOrder> findByComplexCondition(...);
}

RefundOrderRepositoryImpl.java

public class RefundOrderRepositoryImpl
        implements RefundOrderRepositoryCustom {

    @PersistenceContext
    private EntityManager em;

    @Override
    public List<RefundOrder> findByComplexCondition(...) {
        // JPQL / Criteria / Native SQL
    }
}

public interface RefundOrderRepository
        extends JpaRepository<RefundOrder, Long>,
                RefundOrderRepositoryCustom {
}