1. 简介
在本篇文章中,我们将探讨如何在 Jakarta Persistence API (JPA) 中调用存储过程(Stored Procedure)。
2. 项目配置
2.1. Maven 配置
首先,我们需要在 pom.xml
文件中定义以下依赖项:
- 一个 JPA 实现:本例中使用的是 Hibernate(通过
hibernate-core
实现 Jakarta Persistence 3.1) jakarta.xml.bind-api
jakarta.annotation-api
- MySQL 数据库连接器:
mysql-connector-j
<properties>
<mysql.version>8.4.0</mysql.version>
<hibernate.version>6.5.2.Final</hibernate.version>
<jakarta.xml.bind-api.version>4.0.0</jakarta.xml.bind-api.version>
<jakarta.annotation.version>3.0.0</jakarta.annotation.version>
</properties>
<dependencies>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
<exclusions>
<exclusion>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>${jakarta.xml.bind-api.version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta.annotation.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
2.2. 持久化单元配置
第二步是创建 src/main/resources/META-INF/persistence.xml
文件,定义持久化单元:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd">
<persistence-unit name="jpa-db">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.baeldung.jpa.model.Car</class>
<properties>
<property name="jakarta.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/baeldung" />
<property name="jakarta.persistence.jdbc.user" value="baeldung" />
<property name="jakarta.persistence.jdbc.password" value="YourPassword" />
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.proc.param_null_passing" value="true" />
</properties>
</persistence-unit>
</persistence>
如果在 JEE 环境中使用 JNDI 数据源,上述 Hibernate 属性可以省略,只需指定:
<jta-data-source>java:jboss/datasources/JpaStoredProcedure</jta-data-source>
2.3. 表结构创建脚本
接下来创建数据库表 CAR
,包含三个字段:ID
, MODEL
, YEAR
:
CREATE TABLE `car` (
`ID` int(10) NOT NULL AUTO_INCREMENT,
`MODEL` varchar(50) NOT NULL,
`YEAR` int(4) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
假设数据库 schema 和权限已经配置完成。
2.4. 存储过程定义
在进入 Java 代码之前,我们需要在 MySQL 数据库中创建存储过程:
DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `FIND_CAR_BY_YEAR`(in p_year int)
begin
SELECT ID, MODEL, YEAR
FROM CAR
WHERE YEAR = p_year;
end
$$
DELIMITER ;
3. 使用 JPA 调用存储过程
现在我们可以使用 JPA 与数据库交互,调用上面定义的存储过程,并将结果映射为 Car
对象列表。
3.1. Car 实体类
以下是与 CAR
表对应的实体类。我们还通过 @NamedStoredProcedureQueries
注解将存储过程直接绑定到实体类上:
@Entity
@Table(name = "CAR")
@NamedStoredProcedureQueries({
@NamedStoredProcedureQuery(
name = "findByYearProcedure",
procedureName = "FIND_CAR_BY_YEAR",
resultClasses = { Car.class },
parameters = {
@StoredProcedureParameter(
name = "p_year",
type = Integer.class,
mode = ParameterMode.IN) })
})
public class Car {
private long id;
private String model;
private Integer year;
public Car(String model, Integer year) {
this.model = model;
this.year = year;
}
public Car() {
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID", unique = true, nullable = false, scale = 0)
public long getId() {
return id;
}
@Column(name = "MODEL")
public String getModel() {
return model;
}
@Column(name = "YEAR")
public Integer getYear() {
return year;
}
// 标准 setter 方法
}
3.2. 数据库访问测试
现在我们编写两个测试方法,使用 JPA 调用存储过程获取指定年份的汽车数据:
public class StoredProcedureTest {
private static EntityManagerFactory factory = null;
private static EntityManager entityManager = null;
@BeforeClass
public static void init() {
factory = Persistence.createEntityManagerFactory("jpa-db");
entityManager = factory.createEntityManager();
}
@Test
public void findCarsByYearWithNamedStored() {
StoredProcedureQuery findByYearProcedure =
entityManager.createNamedStoredProcedureQuery("findByYearProcedure");
StoredProcedureQuery storedProcedure =
findByYearProcedure.setParameter("p_year", 2015);
storedProcedure.getResultList()
.forEach(c -> Assert.assertEquals(new Integer(2015), ((Car) c).getYear()));
}
@Test
public void findCarsByYearNoNamedStored() {
StoredProcedureQuery storedProcedure =
entityManager
.createStoredProcedureQuery("FIND_CAR_BY_YEAR",Car.class)
.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN)
.setParameter(1, 2015);
storedProcedure.getResultList()
.forEach(c -> Assert.assertEquals(new Integer(2015), ((Car) c).getYear()));
}
}
✅ 注意:第二个测试没有使用实体类上定义的命名存储过程,而是动态创建的。
这在无法修改实体类或不想重新编译的场景下非常实用。
4. 向存储过程传递 null 参数
默认情况下,Hibernate 不会将 null 值传递给存储过程,这是为了允许数据库使用默认参数值。
但如果你确实需要传递 null,可以通过在 persistence.xml
中设置如下属性来启用:
<property name="hibernate.proc.param_null_passing" value="true" />
我们可以通过 JUnit 5 测试验证 null 参数的传递:
@Test
public void givenStoredProc_whenNullParamPassed_thenNoExceptionThrown() {
final StoredProcedureQuery storedProcedure =
entityManager.createStoredProcedureQuery("FIND_CAR_BY_YEAR", Car.class)
.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN);
assertDoesNotThrow(() -> {
storedProcedure.setParameter(1, null);
});
}
此外,还可以通过 API 为单个调用开启 null 传递:
ProcedureCall procedureCall = getSession()
.createStoredProcedureCall("findByYearProcedure");
procedureCall
.registerParameter("nullable_param", String.class, ParameterMode.IN)
.enablePassingNulls(true);
5. 总结
本文介绍了如何在 JPA 中调用存储过程,并展示了如何通过命名查询和动态创建两种方式来实现。同时也提到了如何处理 null 参数传递的问题。对于需要在 Java 应用中与数据库存储过程交互的开发者来说,这些内容非常实用。