JPA简介和环境搭建

JPA(Java Persistence API)是JavaEE中的持久化规范,JPA规范定义了标准JavaEE应用程序中,数据实体和关系型数据库表之间如何映射,以及JavaEE应用程序如何操作数据库表。JPA不是一个ORM框架而是一套ORM的规范接口,Hibernate、Oracle TopLink、Apache OpenJPA等才是具体的ORM实现框架,但JPA规范的存在能让我们无缝的切换这些ORM框架,而不用关心绝大多数的底层配置。

本系列笔记我们将以Wildfly14、MySQL5.7数据库和JavaEE8环境为例介绍JPA的用法。注意我们这里使用的是传统JavaEE应用服务器环境,对于Spring中如何使用JPA请参考Spring相关章节。

JPA规范的历史意义

JPA规范是在JavaEE5中首次发布的。在早期的EJB2.0时代,操作数据库需要使用EJB规范的实体Bean,但随着时代的发展,实体Bean这个概念在EJB3.0中被废弃了,数据库操作则单独抽取出了JPA规范,这让数据库ORM能够脱离EJB,JavaEE程序的耦合性进一步降低,EJB3.0也变得更加轻量级了。

其实JPA不仅能在JavaEE环境中使用,也可以在JavaSE中使用,我们只需要引入JPA接口和任意的实现框架就可以在任何Java程序中使用JPA。另一点要说明的是JPA和基本没人用的EJB不同,JPA至今也还是一个相当成功的JavaEE规范,Spring生态中也对JPA进行了封装实现了SpringDataJPA项目,它也是Spring中非常流行的数据库ORM操作方式。我们实际开发中无论使用标准JavaEE技术栈还是Spring技术栈,如果要使用ORM也都应该优先考虑JPA,而不是直接使用Hibernate等具体的ORM框架的API,这样我们的程序会具有更好的可移植性。

在Wildfly14中,JPA的默认实现是Hibernate,当然正如前面所说,我们其实不必太关心JPA的实现框架,因为我们使用的基本都是JPA规范,底层实现对我们来说是透明的。如果你对Hibernate十分了解,使用JPA并不意味着Hibernate的知识就“白学了”,相反你甚至可以直接上手使用JPA,因为JPA的用法和Hibernate非常相似,很多API和基础概念基本就是改了下名字。

配置Wildfly14数据源

在JavaEE环境下,使用JPA前我们需要先在JavaEE应用服务器内配置数据源,这里以Wildfly14应用服务器和MySQL数据库为例进行介绍。

首先我们需要获取MySQL的JDBC驱动程序Jar包,这个Jar包可以从Maven中央仓库手动下载获得,我这里使用的是mysql-connector-java-8.0.27.jar。我们需要将这个Jar包放置在modules/system/layers/base/com/mysql/main目录下。

modules/system/layers/base/com/mysql/main
  |_ mysql-connector-java-8.0.27.jar
  |_ module.xml

此外,我们还需要一个Wildfly的模块描述文件module.xml,它的内容如下。这个描述文件中配置了模块名、Jar包名、依赖等信息。Wildfly应用服务器中有一个默认的测试用h2数据库的数据源,如果不会写,可以参考旁边的h2数据库描述文件编写。

<?xml version="1.0" encoding="UTF-8"?>
<module name="com.mysql" xmlns="urn:jboss:module:1.5">
    <resources>
        <resource-root path="mysql-connector-java-8.0.27.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
        <module name="javax.servlet.api" optional="true"/>
    </dependencies>
</module>

配置好模块后,我们还需要编辑Wildfly的主配置文件,在启动时加载模块和数据源。我们这里使用的配置文件是standalone-full,因此编辑standalone/configuration/standalone-full.xml文件。在<subsystem xmlns="urn:jboss:domain:datasources:5.0">下我们添加数据源配置和驱动配置,原来默认有一个测试用的h2数据源,我们新增MySQL相关的配置。

<subsystem xmlns="urn:jboss:domain:datasources:5.0">
    <datasources>
        <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
            <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
            <driver>h2</driver>
            <security>
                <user-name>sa</user-name>
                <password>sa</password>
            </security>
        </datasource>
        <datasource jndi-name="java:jboss/datasources/NetstoreDS" pool-name="NetstoreDS" enabled="true" use-java-context="true">
            <connection-url>jdbc:mysql://localhost:3306/netstore?useSSL=false&amp;serverTimezone=Asia/Shanghai</connection-url>
            <driver>mysql</driver>
            <security>
                <user-name>root</user-name>
                <password>root</password>
            </security>
            <validation>
                <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/>
                <background-validation>true</background-validation>
                <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"/>
            </validation>
        </datasource>
        <drivers>
            <driver name="h2" module="com.h2database.h2">
                <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
            </driver>
            <driver name="mysql" module="com.mysql">
                <xa-datasource-class>com.mysql.cj.jdbc.MysqlXADataSource</xa-datasource-class>
            </driver>
        </drivers>
    </datasources>
</subsystem>

数据源配置完成后,我们可以在Wildfly14的管理控制台中看到我们配置的数据源信息。

使用JPA操作数据库例子

我们这里直接以一个例子的形式介绍如何在标准JavaEE工程中使用JPA。我们创建一个EJB工程,工程目录结构如下。

netstore-ejb
  |_ src/main/java          # 源码目录
  |_ src/main/resources     # 资源目录
    |_ META-INF
      |_ persistence.xml    # JPA配置文件

工程中我们需要先配置persistence.xml,它是JPA的核心配置文件,其中定义了工程中使用的持久化单元(Persistence Unit),配置字段包括数据源JNDI、事务类型等信息,此外针对不同的实现ORM框架,这里还可以传入一些具体配置,这里也是使用JPA时唯一涉及配置具体ORM实现框架的地方。

persistence.xml

<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence">
    <persistence-unit name="jpa-netstorer-pu" transaction-type="JTA">
        <jta-data-source>java:jboss/datasources/NetstoreDS</jta-data-source>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="none"/>
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>
  • hibernate.hbm2ddl.auto:Entity发生变更时是否自动更新数据库中的表结构,大型JavaEE项目中通常采用Database First开发模式(也就是先设计数据库表再自动生成或手动编写实体类),因此设置为none
  • hibernate.show_sql:打印SQL执行日志

Hibernate其实还有很多其它配置,例如二级缓存、批量操作等,我们这里对于Hibernate的配置不再展开介绍,具体可以参考Java/Java企业级应用框架/Hibernate相关章节。

Student.java是一个对应于数据库表的实体类,JPA中这种类被称为Entity,一般来说实体类对应于一个数据库表,它的每个字段对应于数据库表的一列。实体类上会标注许多JPA注解,这些配置注解决定了实体类和数据库表之间的映射关系。实体类使用普通Java类即可,但对其有一些要求,首先实体类需要有public的无参构造函数,因为JPA操作数据时需要调用无参构造函数;其次对于标注了JPA注解的字段还需要提供Get/Set方法;此外还需要重写equals()hashCode()方法,这是因为有时我们需要将实体类放入Set集合,这可能涉及实体类对象的相等判断操作。

Student.java

package com.gacfox.netstore.ejb.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.Objects;

@Entity
@Table(name = "t_student")
public class Student implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;
    @Column(name = "name")
    private String name;
    @Column(name = "age")
    private Integer age;
    @Column(name = "create_time")
    private Date createTime;

    public Student() {
    }

    public Student(Long id, String name, Integer age, Date createTime) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.createTime = createTime;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", createTime=" + createTime +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(id, student.id) && Objects.equals(name, student.name) && Objects.equals(age, student.age) && Objects.equals(createTime, student.createTime);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, createTime);
    }
}

最后是我们的EJB类,它是一个无状态会话Bean,该类提供了一个createStudent()方法,能够根据传入参数创建学生实体对象并持久化到数据库中。

package com.gacfox.netstore.api;

import com.gacfox.netstore.api.model.StudentDto;

import javax.ejb.Local;

@Local
public interface StudentService {
    void createStudent(StudentDto studentDto);
}
package com.gacfox.netstore.ejb;

import com.gacfox.netstore.api.StudentService;
import com.gacfox.netstore.api.model.StudentDto;
import com.gacfox.netstore.ejb.entity.Student;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class StudentServiceImpl implements StudentService {
    @PersistenceContext(unitName = "jpa-netstorer-pu")
    private EntityManager entityManager;

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void createStudent(StudentDto studentDto) {
        Student student = new Student();
        student.setId(studentDto.getId());
        student.setName(studentDto.getName());
        student.setAge(studentDto.getAge());
        student.setCreateTime(studentDto.getCreateTime());
        entityManager.persist(student);
    }
}

JPA的所有操作都依赖于EntityManager,EJB中我们需要注入这个对象,JPA提供了@PersistenceContext注解来注入EntityManager,它的参数unitName需要指定我们在之前配置文件中配置的持久化单元名。

createStudent()方法上我们标注了@TransactionAttribute(TransactionAttributeType.REQUIRED),表示该方法需要在数据库事务中运行,如果没有事务则会新创建事务。这里的事务其实默认就是REQUIRED的,因此这行代码其实也可以省略。方法体中,我们实例化了Student这个Entity对象,并调用EntityManager将数据保存到了数据库。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap