Java ORM 规范 JPA 入门

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 概述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
目录
相关文章
|
6天前
|
Java 应用服务中间件 数据库连接
Java 新手入门:Spring Boot 启动揭秘,小白也能秒懂的超详细指南
Java 新手入门:Spring Boot 启动揭秘,小白也能秒懂的超详细指南
24 2
|
6天前
|
Java 测试技术 API
Java 新手入门:Java单元测试利器,Mock详解
Java 新手入门:Java单元测试利器,Mock详解
29 1
|
3天前
|
设计模式 前端开发 Java
【前端学java】SpringBootWeb极速入门-分层解耦(03)
【8月更文挑战第13天】SpringBootWeb极速入门-分层解耦(03)
8 2
【前端学java】SpringBootWeb极速入门-分层解耦(03)
|
4天前
|
开发框架 前端开发 Java
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
【8月更文挑战第12天】SpringBootWeb极速入门-实现一个简单的web页面01
15 3
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
|
4天前
|
JSON 前端开发 Java
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
【8月更文挑战第12天】SpringBootWeb极速入门-请求参数解析(02)
10 1
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
|
12天前
|
机器学习/深度学习 人工智能 算法
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
本文全面介绍了人工智能(AI)的基础知识、操作教程、算法实现及其在实际项目中的应用。首先,从AI的概念出发,解释了AI如何使机器具备学习、思考、决策和交流的能力,并列举了日常生活中的常见应用场景,如手机助手、推荐系统、自动驾驶等。接着,详细介绍了AI在提高效率、增强用户体验、促进技术创新和解决复杂问题等方面的显著作用,同时展望了AI的未来发展趋势,包括自我学习能力的提升、人机协作的增强、伦理法规的完善以及行业垂直化应用的拓展等...
96 3
AI入门必读:Java实现常见AI算法及实际应用,有两下子!
|
1天前
|
前端开发 IDE Java
"揭秘前端转Java的秘径:SpringBoot Web极速入门,掌握分层解耦艺术,让你的后端代码飞起来,你敢来挑战吗?"
【8月更文挑战第19天】面向前端开发者介绍Spring Boot后端开发,通过简化Spring应用搭建,快速实现Web应用。本文以创建“Hello World”应用为例,展示项目基本结构与运行方式。进而深入探讨三层架构(Controller、Service、DAO)下的分层解耦概念,通过员工信息管理示例,演示各层如何协作及依赖注入的使用,以此提升代码灵活性与可维护性。
|
7天前
|
存储 Java 编译器
|
6天前
|
Java 测试技术 Spring
Java 新手入门:依赖注入的 N 种姿势,总有一款适合你!
Java 新手入门:依赖注入的 N 种姿势,总有一款适合你!
15 2
|
6天前
|
安全 Java 开发者
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
Java 新手入门:Spring 两大利器IoC 和 AOP,小白也能轻松理解!
14 1