Java ORM 规范 JPA 入门

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 概述JPA 全称 Java Persistence API,与 JDBC 规范类似,同样是 Java EE 规范的一部分,它定义了一套用面向对象的方式操作关系型数据库的接口,它只是一个 ORM 框架的规范,常见的实现包括 Hibernate、TopLink。

概述


JPA 全称 Java Persistence API,与 JDBC 规范类似,同样是 Java EE 规范的一部分,它定义了一套用面向对象的方式操作关系型数据库的接口,它只是一个 ORM 框架的规范,常见的实现包括 Hibernate、TopLink。


实际上 JPA 的发展晚于 Hibernate,EJB 2.0 版本由于实体 bean 过于复杂,很多开发人员使用轻量级的 Hibernate 持久化数据,EJB 3.0 借鉴 Hibernate 将 JPA 作为 EJB 规范的一部分,Hibernate 3.2 版本开始又逐渐实现了 JPA 规范。


sun 公司将 Java EE 捐赠给 Eclipse 基金会后,要求对方不得使用 Java EE 名称,包名也不能再使用 javax,因此 JPA 2.2 版本后来改名 Jakarta Persistence,并在 3.0 版本将 javax.persistence 重命名为 jakarta.persistence,由于包名的修改对现有项目影响很大,因此很多项目目前仍然在使用 JPA 2.2 版本。


关于 Hibernate 和 JPA 的关系,可以用如下的图来表示。


image.png


快速上手

先通过一个案例快速认识一下 JPA,这里我们使用的 JPA 实现是 Hibernate,上篇介绍 Hibernate 时主要用 Hibernate 原生的 API 操作数据库,这篇全部以 JPA 的 API 操作数据库,个人认为 JPA 比 Hibernate 原生 API 在使用上还要简单一些。


1. 依赖引入


首先需要引入 JPA 及其实现的依赖,当然了,必要的 JDBC 驱动也是必不可少的,这里使用的是 MySQL 数据库。


<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.9.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-hikaricp</artifactId>
    <version>5.6.9.Final</version>
</dependency>


除了 MySQL 驱动和 Hibernate,为了使用 HikariCP 数据源还引入了一个 hibernate-hikaricp 依赖。


2. 映射定义


假定数据库中有一个 user 表定义如下。

create table user
(
    id          bigint unsigned auto_increment comment '主键'
        primary key,
    username    varchar(20)  not null comment '用户名',
    password    varchar(20)  not null comment '密码',
    name        varchar(20)  null comment '姓名',
    sex         varchar(20)  null comment '性别',
    interests   varchar(100) null comment '兴趣爱好',
    version     int          null comment '版本号',
    create_by   varchar(20)  null comment '创建人',
    create_time datetime     null comment '创建时间',
    update_by   varchar(20)  null comment '修改人',
    update_time datetime     null comment '修改时间'
);


对应的 user 表对应的实体类如下。


@Setter
@Getter
public class User {
    private Long id;
    private String username;
    private String password;
    private String name;
    private SexEnum sex;
    private List<String> interests;
    private Integer version;
    private String createBy;
    private Date createTime;
    private String updateBy;
    private Date updateTime;
}


JPA 支持将映射关系定义在 xml 文件中,也支持使用注解定义映射关系,由于注解使用比较方便,这里使用注解表示映射关系,修改实体类如下。

@Setter
@Getter
@Entity(name = "User")
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String name;
    @Enumerated(EnumType.STRING)
    private SexEnum sex;
    @Convert(converter = StringListAttributeConverter.class)
    private List<String> interests;
    @Version
    private Integer version;
    @Column(name = "create_by")
    private String createBy;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_time")
    private Date createTime;
    @Column(name = "update_by")
    private String updateBy;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "update_time")
    private Date updateTime;
}


3. JPA 配置


按照 JPA 规范的约定, JPA 配置文件应该在类路径 /META-INF/persistence.xml 中,配置内容如下。


<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <class>com.zzuhkp.hibernate.entity.User</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/test"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="12345678"/>
            <property name="hibernate.hikari.idleTimeout" value="600000"/>
            <property name="hibernate.hikari.minimumIdle" value="5"/>
            <property name="hibernate.hikari.maximumPoolSize" value="10"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.cache.use_second_level_cache" value="false"/>
            <property name="hibernate.hbm2ddl.auto" value="validate"/>
        </properties>
    </persistence-unit>
</persistence>


这里每个 persistence-unit 可用于配置一个数据库连接,使用 name 属性为其指定一个名称,并使用 transaction-type 属性指定使用的事务类型,这里我们使用 JDBC 进行事务操作,如果使用 JTA 则将其改为 jta 即可。


class 标签用于指定实体类,需要在实体类上添加表示映射信息的注解。


此外剩余的配置就是 property 了,除了 JPA 中定义的配置项,还可以定义具体 JPA 提供者的配置。


4. 测试代码


这里使用的测试代码如下:


@Slf4j
public class Application {
    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("default");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
            User user = new User();
            user.setUsername("hkp");
            user.setPassword("321");
            entityManager.persist(user);
            transaction.commit();
            log.info("id:{}", user.getId());
        } catch (Exception e) {
            log.error(e.getMessage());
            transaction.rollback();
        } finally {
            entityManager.close();
            entityManagerFactory.close();
        }
    }
}


JPA 使用方式与 Hibernate 基本保持一致。


核心组件


从上面的代码中可以看到 JPA 有一些关键的组件,包括 EntityManagerFactory、EntityManager、EntityTransaction,它们与 Hibernate 组件的关系可以用如下的图来表示。


image.png


Hibernate 的 SessionFactory 接口继承 JPA EntityManagerFactory 接口,Hibernate 的 Session 接口继承 JPA EntityManager 接口,Hibernate 的 Transaction 接口继承 JPA EntiryTransaction 接口。


此外 EntityManagerFactory 和 EntityManager 还定义了一个 unwarp 方法用于获取具体的实现。示例如下。


SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
Session session = entityManager.unwrap(Session.class);

常用注解


JPA 的很多功能特性都由注解提供元数据,这里总结一些常用的注解。


映射


@Entity


这个注解添加在实体类上,表示这个类是一个实体类,可以使用 name 属性指定实体的名称,实体名称可用于 JPQL 中,默认情况下实体的名称与类名保持一致,如类全限定名为 com.zzuhkp.hibernate.entity.User,则实体的名称为 User。


@Table


这个注解添加在实体类上,指定实体类对应数据库表的元数据,如使用 name 属性指定表名。


@Id


表示主键的注解,加在实体类的字段或 getter 方法上。


@GeneratedValue


加在表示主键的字段或 getter 方法上,指定主键值的生成方式, type 属性值 IDENTITY 表示自增。


@Column


这个注解添加在实体类的字段或 getter 方法上,表示实体类属性对应的数据库表字段,可以使用 name 指定字段名称,如果使用该注解,默认情况下 JPA 认为表字段名称和类属性名称保持一致。


@Temporal


这个注解加在实体类的 Date 或 Calendar 类型的属性上,表示这个属性对应的数据库字段类型,例如是 date、time,还是 timestamp。


@Enumerated


这个注解加在实体类的枚举类型的属性上,使用注解的 value 属性指定数据库表中存储的值。EnumType.STRING 表示将枚举值的名称存至数据库,EnumType.ORDINAL 表示存储枚举值的索引至数据库。


@Version


添加到实体类的属性上,表示该属性为乐观锁字段,支持 int、short、long 及其包装类,以及各种日期类型。JPA 将在 insert 时插入值,update 时将乐观锁字段作为条件。


@Convert


这个注解加在实体类的属性上,用来自定义 Java 类型与数据库类型之间的映射关系。假定 user 表有一个 varchar 类型的 interests 字段记录用户的兴趣爱好,我们希望在 Java 中用一个 List<String> 类型来表示,则可以使用如下的方式自定义映射关系。


@Converter
public class StringListAttributeConverter implements AttributeConverter<List<String>, String> {
    @Override
    public String convertToDatabaseColumn(List<String> attribute) {
        if (attribute == null) {
            return null;
        }
        return JSON.toJSONString(attribute);
    }
    @Override
    public List<String> convertToEntityAttribute(String dbData) {
        if (dbData == null) {
            return null;
        }
        return JSON.parseArray(dbData, String.class);
    }
}
public class User {
    @Convert(converter = StringListAttributeConverter.class)
    private List<String> interests;
}


@Embedded


这个注解加在实体类的属性上,用于对实体类的属性进行分组。假设我们想将 user 表的 namesex 字段作为用户基本信息拆到另一个类表示,可以使用如下的方式。


@Getter
@Setter
@Embeddable
public class Basic {
    private String name;
    @Enumerated(EnumType.STRING)
    private SexEnum sex;
}
public class User {
    @Embedded
    private Basic basic;
}


关联关系


JPA 提供了一些用于表示实体关联关系的注解,包括 @OneToOne、@OneToMany、@ManyToOne、ManyToMany,支持的集合类型包括 List、Set、Map。


以 @OneToMany 和 @ManyToOne 注解为例,假定一个用户可以有很多收货地址,地址表 address 定义如下。


create table address
(
    id            bigint unsigned auto_increment comment '主键'
        primary key,
    user_id       bigint unsigned null comment '用户ID',
    province_name varchar(20)     null comment '省份名称',
    city_name     varchar(20)     null comment '城市名称',
    area          varchar(20)     null comment '区域',
    detail        varchar(100)    null comment '详细地址',
    create_by     varchar(20)     null comment '创建人',
    create_time   datetime        null comment '创建时间',
    update_by     varchar(20)     null comment '修改人',
    update_time   datetime        null comment '更新时间'
);

address 表对应的实体类如下。


@Getter
@Setter
@Entity
@Table(name = "address")
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
    @Column(name = "province_name")
    private String provinceName;
    @Column(name = "city_name")
    private String cityName;
    private String area;
    private String detail;
    @Column(name = "create_by")
    private String createBy;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_time")
    private Date createTime;
    @Column(name = "update_by")
    private String updateBy;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "update_time")
    private Date updateTime;
}


User 实体类中可以使用如下的方式表示一对多关联关系。


public class User {
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Address> addresses;
}


User 类 addresses 字段上的 @OneToMany 注解表示一个用户有多个地址;

mappedBy 属性的值表示关系被维护的一端,这里的值为 Address 类的 user 字段,表示关系由 Address 类维护;

cascade 属性值表示级联操作,ALL 表示 Address 和 User 状态保持一致。

orphanRemoval 值为 true 表示删除 user 表记录后,同时将 address 记录删除,而不是将 address 的 user_id 值设置为 null。

Address 类 user 字段上的 @JoinColumn 注解则用于描述用于关联的列,这里使用 user_id 字段与 user 表关联。

@MappedSuperclass

这个注解用于添加到实体类的父类上,表示有多个实体类将会继承这个父类。例如,有很多表都有 id、create_by、create_time、update_by、update_time,可以将这几个字段抽象到一个公共的父类中。


@Getter
@Setter
@MappedSuperclass
public class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "create_by")
    private String createBy;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_time")
    private Date createTime;
    @Column(name = "update_by")
    private String updateBy;
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "update_time")
    private Date updateTime;
}


然后具体的实体类继承这个公共父类即可。


public class User extends BaseEntity {
}


回调


JPA 提供了一些用于回调的注解,被这些注解标注的方法成为回调方法,允许用户在 SQL 执行前后附加一些额外的操作。


image.png


可以将这些注解直接加在实体的无参无返回值的方法上,例如如果我们想记录操作人操作时间,可以使用如下的方式。


public class BaseEntity {
  ... 省略字段与 setter/getter 方法
    @PrePersist
    public void setCreateInfo() {
        this.setCreateBy(UserHolder.getUsername());
        this.setCreateTime(new Date());
    }
    @PreUpdate
    public void setUpdateInfo() {
        this.setUpdateBy(UserHolder.getUsername());
        this.setUpdateTime(new Date());
    }
}


此外,还可以将回调方法定义在单独的监听器类中,使用方式如下。


public class OperatorListener {
    @PrePersist
    public void setCreateInfo(BaseEntity entity) {
    }
    @PreUpdate
    public void setUpdateInfo(BaseEntity entity) {
    }
}
@EntityListeners(OperatorListener.class)
public class BaseEntity {
}


数据库操作


与 Hibernate 一样,JPA 同样提供了四种方式操作数据库。


EntityManager API


JPA EntityManager 的作用与 Hibernate Session 的作用一致,有关 CRUD 的方法定义如下。


public interface EntityManager {
    public void persist(Object entity);
    public void remove(Object entity);
    public void flush();
    public <T> T find(Class<T> entityClass, Object primaryKey);
}


EntityManager 没有提供 update 方法,不过可以直接对 EntityManager 管理的实体类直接操作,然后手动调用 flush 方法将实体类的修改同步到数据库中。


CriteriaQuery API


EntityManager 每次只能直接操作数据库单条记录对应的某一个实体类,功能上相对受限一些。为了应对复杂的查询操作,EntityManager 还提供了一个 CriteriaQuery 接口以 Java API 的方式查询数据库。


以登录场景为例,根据用户名和密码查询用户的示例如下。


public User getUser(String username, String password) {
    CriteriaBuilder build = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> criteriaQuery = build.createQuery(User.class);
    Root<User> root = criteriaQuery.from(User.class);
    criteriaQuery.select(root);
    criteriaQuery.where(build.and(
            build.equal(root.get("username"), username),
            build.equal(root.get("password"), password)));
    User user = entityManager.createQuery(criteriaQuery).getSingleResult();
    return user;
}


上面的代码翻译成 SQL 可以简单理解如下:


select * from user where username = ? and password = ?


可以看到,即便是一个比较简单的查询也用了不少代码来描述,更别提一些复杂的场景了,因此 CriteriaQuery 在实际项目中使用并太多,这里也不再对上面的代码进行解释。


JPQL


除了 CriteriaQuery,JPA 还借鉴 SQL 提出了一种名为 JPQL 的查询语言,它以对象为中心,语法与 SQL 大同小异,查询时把表名改为实体名即可。还是以上面登录的场景为例,用 JPQL 查询用户的方式如下。


public User getUser(String username, String password) {
    User user = entityManager.createQuery(
                    "select u from User as u where username = :username and password = :password", User.class)
            .setParameter("username", username).setParameter("password", password)
            .getSingleResult();
    return user;
}


这种方式确实比 CriteriaQuery 简单了许多,如果不想使用 SQL 的话使用 JPQL 处理复杂场景是一个比较好的选择。

此外,如果有可以复用的 JPQL,还可以将其定义在实体上,示例如下。


@NamedQuery(name = "login",
        query = "select u from User as u where username = :username and password = :password")
public class User extends BaseEntity {
}
public User getUser(String username, String password) {
    User user = entityManager.createNamedQuery("login", User.class)
            .setParameter("username", username).setParameter("password", password)
            .getSingleResult();
    return user;
}


原生 SQL


JPA 支持使用原生 SQL 操作数据库,如果想要使用特定于数据库的功能,例如函数,这是个比较好的选择,使用 SQL 实现登录场景的用户查询方式如下。


public User getUser(String username, String password) {
    User user = (User) entityManager.createNativeQuery(
                    "select * from user where username = :username and password = :password", User.class)
            .setParameter("username", username).setParameter("password", password)
            .getSingleResult();
    return user;
}


可以看到 SQL 与 JPQL 的语法确实比较相似。


总结

JPA 作为 Java 中的 ORM 框架规范,虽然提供了众多特性,但都与映射或数据库操作有关,相对 Hibernate 简单一些,Spring 框架也对 JPA 与 Hibernate 进行了支持,后续将进行总结。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
关系型数据库 MySQL Java
【MySQL+java+jpa】MySQL数据返回项目的感悟
【MySQL+java+jpa】MySQL数据返回项目的感悟
44 1
|
1月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
50 3
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
16天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
29 3
|
17天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
24天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
63 5
|
21天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
32 1
|
27天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
44 3
|
29天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
1月前
|
开发框架 IDE Java
java制作游戏,如何使用libgdx,入门级别教学
本文是一篇入门级教程,介绍了如何使用libgdx游戏开发框架创建一个简单的游戏项目,包括访问libgdx官网、设置项目、下载项目生成工具,并在IDE中运行生成的项目。
50 1
java制作游戏,如何使用libgdx,入门级别教学
下一篇
无影云桌面