Apache OpenJPA 3.2.2 构建并运行示例应用指南

Apache OpenJPA 3.2.2 构建并运行示例应用指南(JPA 持久化应用示例):从零创建 Maven 工程、完整 pom.xml 与 persistence.xml、实体类与 CRUD 主类源码、构建期增强、编译运行命令与真实回显(H2 内存库新增 3 条/条件查询/聚合/删除,SQL=TRACE 建表与增删改查日志),含 JVM 堆限制验证。

Apache OpenJPAApache OpenJPA2026/10/12

Apache OpenJPA 3.2.2 构建并运行示例应用指南

目标:从零构建并运行一个基于 Apache OpenJPA 3.2.2 的最小 JPA 持久化应用(实体映射 + CRUD + JPQL)。给出完整 pom.xml、persistence.xml、实体类与主类源码,编译(含构建期增强)与运行命令及真实回显:H2 内存库自动建表、新增 3 条员工记录、JPQL 条件查询研发部 2 条、聚合平均薪资 20000.0、删除后总数 2。所有命令与输出均在 Ubuntu 22.04.5 LTS + JDK 11 + OpenJPA 3.2.2 环境实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS
  • Java:OpenJDK 11.0.32.1
  • 构建工具:Apache Maven 3.6.3
  • OpenJPA 版本:3.2.2(依赖 org.apache.openjpa:openjpa)
  • 数据库:H2 2.2.224(内存库 jdbc:h2:mem:testdb)
  • 示例工程目录:/root/openjpa-demo/
  • 演示账号:root

二、前置条件

  • 已按《Ubuntu 22.04 安装 Apache OpenJPA 3.2.2 指南》安装 JDK 11 与 Maven:

    java -version
    mvn -version
    
  • Java 版本回显需为 11(openjdk version "11.0.32.1" 2026-08-18)。

三、工程结构

标准 Maven 布局:

openjpa-demo/
├── pom.xml
└── src/main/
    ├── java/demo/
    │   ├── Employee.java
    │   └── Main.java
    └── resources/META-INF/
        └── persistence.xml
mkdir -p /root/openjpa-demo/src/main/java/demo
mkdir -p /root/openjpa-demo/src/main/resources/META-INF

四、完整 pom.xml

pom 引入 org.apache.openjpa:openjpa:3.2.2 与 com.h2database:h2:2.2.224,并通过 openjpa-maven-plugin 在 process-classes 阶段自动增强实体类:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demo</groupId>
    <artifactId>openjpa-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <openjpa.version>3.2.2</openjpa.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.openjpa</groupId>
            <artifactId>openjpa</artifactId>
            <version>${openjpa.version}</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.2.224</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.openjpa</groupId>
                <artifactId>openjpa-maven-plugin</artifactId>
                <version>${openjpa.version}</version>
                <configuration>
                    <includes>demo/Employee.class</includes>
                </configuration>
                <executions>
                    <execution>
                        <id>enhancer</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>enhance</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.1.0</version>
            </plugin>
        </plugins>
    </build>
</project>

⚠️ 注意:openjpa-maven-plugin 的 enhance 目标绑定 process-classes 阶段,mvn compile 不会触发,必须用 mvn process-classes / mvn package。

依赖解析结果(mvn dependency:tree):

[INFO] demo:openjpa-demo:jar:1.0-SNAPSHOT
[INFO] +- org.apache.openjpa:openjpa:jar:3.2.2:compile
[INFO] |  +- net.sourceforge.serp:serp:jar:1.15.1:compile
[INFO] |  +- org.apache.geronimo.specs:geronimo-jms_1.1_spec:jar:1.1.1:compile
[INFO] |  +- org.apache.geronimo.specs:geronimo-jta_1.1_spec:jar:1.1.1:compile
[INFO] |  +- org.apache.commons:commons-pool2:jar:2.9.0:compile
[INFO] |  +- org.apache.xbean:xbean-asm9-shaded:jar:4.20:compile
[INFO] |  \- org.apache.geronimo.specs:geronimo-jpa_2.2_spec:jar:1.1:compile
[INFO] +- com.h2database:h2:jar:2.2.224:compile

五、persistence.xml(持久化单元配置)

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">
    <persistence-unit name="openjpa-demo">
        <class>demo.Employee</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="openjpa.jdbc.DBDictionary" value="h2"/>
            <property name="openjpa.Log" value="DefaultLevel=WARN, Tool=INFO, SQL=TRACE"/>
            <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
        </properties>
    </persistence-unit>
</persistence>

要点:

  • 持久化单元名 openjpa-demo,实体类 demo.Employee 显式声明;
  • openjpa.jdbc.SynchronizeMappings=buildSchema:启动时自动建表(开发/演示用);
  • openjpa.Log 的 SQL=TRACE:控制台打印实际执行的 SQL。

六、实体类(Employee.java)

package demo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "employees")
public class Employee {

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

    @Column(nullable = false, length = 64)
    private String name;

    @Column(length = 32)
    private String department;

    private double salary;

    public Employee() {
    }

    public Employee(String name, String department, double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    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 String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }

    @Override
    public String toString() {
        return String.format("Employee[id=%d, name=%s, department=%s, salary=%.2f]",
                id, name, department, salary);
    }
}

七、主类(Main.java,完整 CRUD)

package demo;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

public class Main {

    public static void main(String[] args) {
        // 1. 创建 EntityManagerFactory(读取 META-INF/persistence.xml 的 openjpa-demo 持久化单元)
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("openjpa-demo");
        System.out.println("== EntityManagerFactory 创建成功 ==");

        // 2. 新增(CREATE)
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        Employee e1 = new Employee("张三", "研发部", 18000.0);
        Employee e2 = new Employee("李四", "市场部", 15000.0);
        Employee e3 = new Employee("王五", "研发部", 22000.0);
        em.persist(e1);
        em.persist(e2);
        em.persist(e3);
        tx.commit();
        System.out.println("== 新增完成: 3 名员工已提交 ==");

        // 3. 查询(READ):JPQL 全量
        TypedQuery<Employee> allQuery =
                em.createQuery("select e from Employee e order by e.id", Employee.class);
        List<Employee> all = allQuery.getResultList();
        System.out.println("== JPQL 查询全部员工 (" + all.size() + " 条) ==");
        for (Employee e : all) {
            System.out.println("   " + e);
        }

        // 4. 更新(UPDATE):李四调岗加薪
        tx = em.getTransaction();
        tx.begin();
        Employee e2Managed = em.find(Employee.class, e2.getId());
        e2Managed.setDepartment("运营部");
        e2Managed.setSalary(16000.0);
        tx.commit();
        System.out.println("== 更新完成: " + e2Managed.getId() + " 号员工部门/薪资已修改 ==");

        // 5. JPQL 带条件查询 + 聚合
        TypedQuery<Employee> devQuery = em.createQuery(
                "select e from Employee e where e.department = :dept order by e.salary desc",
                Employee.class);
        devQuery.setParameter("dept", "研发部");
        List<Employee> devs = devQuery.getResultList();
        System.out.println("== JPQL 条件查询 研发部 (" + devs.size() + " 条) ==");
        for (Employee e : devs) {
            System.out.println("   " + e);
        }

        Query avgQuery = em.createQuery(
                "select avg(e.salary) from Employee e where e.department = :dept");
        avgQuery.setParameter("dept", "研发部");
        Object avg = avgQuery.getSingleResult();
        System.out.println("== JPQL 聚合查询 研发部平均薪资: " + avg + " ==");

        // 6. 删除(DELETE)
        tx = em.getTransaction();
        tx.begin();
        Employee e3Managed = em.find(Employee.class, e3.getId());
        em.remove(e3Managed);
        tx.commit();
        System.out.println("== 删除完成: " + e3.getId() + " 号员工已删除 ==");

        long count = (Long) em.createQuery("select count(e) from Employee e").getSingleResult();
        System.out.println("== 删除后员工总数: " + count + " ==");

        em.close();
        emf.close();
        System.out.println("== 全部 CRUD 操作执行完毕 ==");
    }
}

八、构建与运行

8.1 构建(编译 + 实体增强)

cd /root/openjpa-demo
mvn -q clean process-classes

实测回显(openjpa-maven-plugin 在 process-classes 阶段增强 demo.Employee):

79  openjpa-demo  INFO   [main] openjpa.Tool - Enhancer running on type "class demo.Employee".

验证增强结果:

javap -p target/classes/demo/Employee.class | head -5

实测回显:

Compiled from "Employee.java"
public class demo.Employee implements org.apache.openjpa.enhance.PersistenceCapable {
  private long id;
  private java.lang.String name;
  private java.lang.String department;

8.2 生成 classpath 并运行

mvn -q dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt
java -Xms256m -Xmx256m -cp "target/classes:$(cat /tmp/cp.txt)" demo.Main

实测回显(完整运行输出):

== EntityManagerFactory 创建成功 ==
== 新增完成: 3 名员工已提交 ==
== JPQL 查询全部员工 (3 条) ==
   Employee[id=1, name=张三, department=研发部, salary=18000.00]
   Employee[id=2, name=李四, department=市场部, salary=15000.00]
   Employee[id=3, name=王五, department=研发部, salary=22000.00]
== 更新完成: 2 号员工部门/薪资已修改 ==
== JPQL 条件查询 研发部 (2 条) ==
   Employee[id=3, name=王五, department=研发部, salary=22000.00]
   Employee[id=1, name=张三, department=研发部, salary=18000.00]
== JPQL 聚合查询 研发部平均薪资: 20000.0 ==
== 删除完成: 3 号员工已删除 ==
== 删除后员工总数: 2 ==
== 全部 CRUD 操作执行完毕 ==

8.3 验证 JVM 堆参数生效

运行期间检查进程参数:

ps -ef | grep "[j]ava" | grep -oE "java -Xms[^ ]+ -Xmx[^ ]+|demo.Main"

实测回显:

java -Xms256m -Xmx256m
demo.Main

九、SQL 日志解读(SQL=TRACE)

开启 openjpa.Log 的 SQL=TRACE 后,控制台打印 OpenJPA 生成的 SQL(节选):

949  openjpa-demo  TRACE  [demo.Main.main()] openjpa.jdbc.SQL - <t 751936279, conn 1551666102> executing stmnt 1318924920 CREATE TABLE employees (id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, department VARCHAR(32), name VARCHAR(64) NOT NULL, salary DOUBLE, PRIMARY KEY (id))
1115 openjpa-demo  TRACE  [demo.Main.main()] openjpa.jdbc.SQL - <t 751936279, conn 1417561088> executing prepstmnt 1702493641 INSERT INTO employees (department, name, salary) VALUES (?, ?, ?) [params=?, ?, ?]
1212 openjpa-demo  TRACE  [demo.Main.main()] openjpa.jdbc.SQL - <t 751936279, conn 1476229122> executing prepstmnt 379916524 SELECT t0.id, t0.department, t0.name, t0.salary FROM employees t0 ORDER BY t0.id ASC
1247 openjpa-demo  TRACE  [demo.Main.main()] openjpa.jdbc.SQL - <t 751936279, conn 1890934476> executing prepstmnt 1436583577 UPDATE employees SET department = ?, salary = ? WHERE id = ? [params=?, ?, ?]
1290 openjpa-demo  TRACE  [demo.Main.main()] openjpa.jdbc.SQL - <t 751936279, conn 1553697462> executing prepstmnt 1217883989 SELECT AVG(t0.salary) FROM employees t0 WHERE (t0.department = ?) [params=?]
1305 openjpa-demo  TRACE  [demo.Main.main()] openjpa.jdbc.SQL - <t 751936279, conn 480540393> executing prepstmnt 602412335 DELETE FROM employees WHERE id = ? [params=?]
1318 openjpa-demo  TRACE  [demo.Main.main()] openjpa.jdbc.SQL - <t 751936279, conn 1497263599> executing prepstmnt 1134173540 SELECT COUNT(t0.id) FROM employees t0

对应关系:JPQL 全量查询 → SELECT t0.* FROM employees t0 ORDER BY t0.id ASC;条件查询 → WHERE (t0.department = ?) ORDER BY t0.salary DESC;聚合 → SELECT AVG(t0.salary) ...。SQL 中的 t0 为实体别名,? 为预编译参数。

十、常见问题

Q1:运行报 Table "EMPLOYEES" not found? A:未开启自动建表或实体未增强。确认 persistence.xml 含 openjpa.jdbc.SynchronizeMappings=buildSchema,并已执行 mvn clean process-classes 完成增强。

Q2:JPQL 查询返回结果与数据库不一致? A:内存库 jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 与进程生命周期一致;多进程/重启后数据不复存在,属正常现象。生产请改用文件数据库(H2 文件模式 jdbc:h2:/opt/data/testdb 或 Derby 文件模式)。

Q3:如何切换到底层数据库 Derby? A:pom 增加 org.apache.derby:derby:10.14.2.0 依赖,persistence.xml 修改为:

<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:derby:memory:testdb;create=true"/>
<property name="openjpa.jdbc.DBDictionary" value="derby"/>

实测同一程序在 Derby 上完整 CRUD 全部通过(3 条插入/查询/更新/聚合/删除,回显与 H2 相同)。

Q4:EntityManager 或 EntityManagerFactory 用完不关闭会怎样? A:连接与缓存不释放。务必在 finally 或 try-with-resources 中调用 em.close()、emf.close()。

十一、参考链接