Java ORM 规范 JPA 入门

概述

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 的关系,可以用如下的图来表示。
在这里插入图片描述

快速上手

先通过一个案例快速认识一下 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 有一些关键的组件,包括 EntityManagerFactoryEntityManagerEntityTransaction,它们与 Hibernate 组件的关系可以用如下的图来表示。
在这里插入图片描述Hibernate 的 SessionFactory 接口继承 JPA EntityManagerFactory 接口,Hibernate 的 Session 接口继承 JPA EntityManager 接口,Hibernate 的 Transaction 接口继承 JPA EntiryTransaction 接口。

此外 EntityManagerFactoryEntityManager 还定义了一个 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
这个注解加在实体类的 DateCalendar 类型的属性上,表示这个属性对应的数据库字段类型,例如是 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@ManyToOneManyToMany,支持的集合类型包括 ListSetMap

@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;
}
  • Useraddresses 字段上的 @OneToMany 注解表示一个用户有多个地址;
  • mappedBy 属性的值表示关系被维护的一端,这里的值为 Address 类的 user 字段,表示关系由 Address 类维护;
  • cascade 属性值表示级联操作,ALL 表示 AddressUser 状态保持一致。
  • orphanRemoval 值为 true 表示删除 user 表记录后,同时将 address 记录删除,而不是将 addressuser_id 值设置为 null。
  • Addressuser 字段上的 @JoinColumn 注解则用于描述用于关联的列,这里使用 user_id 字段与 user 表关联。

@MappedSuperclass
这个注解用于添加到实体类的父类上,表示有多个实体类将会继承这个父类。例如,有很多表都有 idcreate_bycreate_timeupdate_byupdate_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 执行前后附加一些额外的操作。

注解功能
@PrePersistEntityManager 保存记录前回调方法
@PostPersistEntityManager 保存记录后被回调方法
@PreRemoveEntityManager 删除记录前被回调方法
@PostRemoveEntityManager 删除记录后回调方法
@PreUpdate数据库记录修改前回调方法
@PostUpdate数据库记录修改后回调方法
@PostLoad实体加载到 Entity 后回调方法

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

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 进行了支持,后续将进行总结。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
JavaORM是一款用于Java开发的IDEA插件,它能够简化开发者对数据库操作的流程,提高开发效率和代码质量。 JavaORM的主要功能包括: 1. 实体类自动生成:JavaORM可以根据数据库表结构自动生成对应的实体类,省去了手动编写实体类的麻烦。开发者只需要指定数据库连接信息和需要生成实体类的表,JavaORM就能够自动生成符合JavaBean规范的实体类,大大减少了开发时间和错误。 2. CRUD操作封装:JavaORM为开发者提供了便捷的增删改查操作封装方法,开发者只需要调用相应的方法,就能够完成对数据库的增删改查操作,无需编写繁琐的SQL语句,简化了数据库操作逻辑。 3. 数据库事务管理:JavaORM支持数据库事务的管理,可以确保一组操作的原子性,保证了数据的一致性和完整性。开发者可以通过JavaORM提供的事务管理方法来管理事务,并进行回滚或提交操作。 4. 查询支持:JavaORM提供了强大的查询支持,可以通过链式调用的方式构建复杂的查询条件,支持分页、排序、过滤等功能。开发者只需简单地编写相关的查询条件,JavaORM就能够自动生成对应的SQL语句,并执行查询操作。 5. 连接池管理:JavaORM集成了连接池管理功能,可以有效地管理数据库连接,提高连接的重复利用率,减轻数据库的负载。 总之,JavaORM是一款功能全面、易用性高的IDEA插件,可以帮助开发者快速、高效地进行数据库操作,提升开发效率和代码质量。无论是刚开始学习Java开发还是有一定经验的开发者,都可以受益于JavaORM的使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值