如何使用JPA和Hibernate调用Oracle存储过程和函数(转载)

介绍
本文是与从Hibernate调用各种关系数据库系统存储过程和数据库函数相关的一系列帖子的一部分。写下来的原因是因为有许多与底层JDBC驱动程序支持相关的特性,并不是每个关系数据库都支持每个JPA或Hibernate特性。
Oracle存储过程
Oracle支持存储过程和函数,因此首先我们将从以下存储过程开始,该过程输出一个简单的值。
Oracle存储过程输出一个简单的值

CREATE OR REPLACE PROCEDURE count_comments (  
   postId IN NUMBER,  
   commentCount OUT NUMBER )  
AS  
BEGIN  
    SELECT COUNT(*) INTO commentCount  
    FROM post_comment  
    WHERE post_id = postId; 
END;

该存储过程具有两个参数:输入参数(例如postId)和输出参数(例如commentCount),其用于返回post_comment与给定post_id父行相关联的记录的计数。
要调用此存储过程,可以使用以下Java Persistence API 2.1语法:

StoredProcedureQuery query = entityManager
    .createStoredProcedureQuery("count_comments")
    .registerStoredProcedureParameter(1, Long.class, 
        ParameterMode.IN)
    .registerStoredProcedureParameter(2, Long.class, 
        ParameterMode.OUT)
    .setParameter(1, 1L);

query.execute();

Long commentCount = (Long) query.getOutputParameterValue(2);

Oracle存储过程输出SYS_REFCURSOR
存储过程还可以定义SYS_REFCURSOR与数据库游标关联的输出参数,该数据库游标可以迭代以获取多个数据库记录:

CREATE OR REPLACE PROCEDURE post_comments ( 
   postId IN NUMBER, 
   postComments OUT SYS_REFCURSOR ) 
AS  
BEGIN 
    OPEN postComments FOR 
    SELECT *
    FROM post_comment 
    WHERE post_id = postId; 
END;

在Oracle上,可以使用JPA 2.1语法调用此存储过程:

StoredProcedureQuery query = entityManager
    .createStoredProcedureQuery("post_comments")
    .registerStoredProcedureParameter(1, Long.class, 
         ParameterMode.IN)
    .registerStoredProcedureParameter(2, Class.class, 
         ParameterMode.REF_CURSOR)
    .setParameter(1, 1L);

query.execute();

List<Object[]> postComments = query.getResultList();

与之前的存储过程调用不同,这次,我们正在使用getResultList()并获取Object[]包含与所选数据库记录关联的所有列值的内容。

Hibernate长期以来一直提供自己的存储过程API,并且可以按如下方式调用上述存储过程:

Session session = entityManager.unwrap(Session.class);
ProcedureCall call = session
    .createStoredProcedureCall("post_comments");

call.registerParameter(1, Long.class, 
    ParameterMode.IN).bindValue(1L);

call.registerParameter(2, Class.class, 
    ParameterMode.REF_CURSOR);

Output output = call.getOutputs().getCurrent();
if (output.isResultSet()) {
    List<Object[]> postComments = 
        ((ResultSetOutput) output).getResultList();
}

Oracle功能
Oracle还支持数据库函数,与存储过程不同,它不使用输入和输出参数,而是使用一个或多个函数参数和单个返回值。
Oracle函数返回一个简单的值
第一个存储过程可以转换为如下所示的函数:

CREATE OR REPLACE FUNCTION fn_count_comments ( 
    postId IN NUMBER ) 
    RETURN NUMBER 
IS
    commentCount NUMBER; 
BEGIN
    SELECT COUNT(*) INTO commentCount 
    FROM post_comment 
    WHERE post_id = postId; 
    RETURN( commentCount ); 
END;

不幸的是,在编写(Hibernate 5.1.0)时,Java Persistence 2.1存储过程和特定于Hibernate的API都不能用于调用函数。
但是,这种限制有几种解决方法。

首先,我们可以像调用任何其他SQL查询一样简单地调用Oracle函数:
BigDecimal commentCount = (BigDecimal) entityManager
.createNativeQuery(
“SELECT fn_count_comments(:postId) FROM DUAL”
)
.setParameter(“postId”, 1L)
.getSingleResult();
另一种方法是使用普通JDBC API调用数据库函数:

Session session = entityManager.unwrap( Session.class );

Integer commentCount = session.doReturningWork( 
    connection -> {
    try (CallableStatement function = connection
        .prepareCall(
            "{ ? = call fn_count_comments(?) }" )) {
        function.registerOutParameter( 1, Types.INTEGER );
        function.setInt( 2, 1 );
        function.execute();
        return function.getInt( 1 );
    }
} );

Oracle函数返回一个SYS_REFCURSOR
就像存储过程一样,Oracle函数也可以返回一个SYS_REFCURSOR,并且,为了使示例更加有趣,我们将获取一个Post与其相关的PostComment子实体。

Oracle函数如下所示:

CREATE OR REPLACE FUNCTION fn_post_and_comments ( 
    postId IN NUMBER ) 
    RETURN SYS_REFCURSOR 
IS
    postAndComments SYS_REFCURSOR; 
BEGIN
   OPEN postAndComments FOR
        SELECT
            p.id AS "p.id", 
            p.title AS "p.title", 
            p.version AS "p.version", 
            c.id AS "c.id", 
            c.post_id AS "c.post_id", 
            c.version AS "c.version", 
            c.review AS "c.review"
       FROM post p 
       JOIN post_comment c ON p.id = c.post_id 
       WHERE p.id = postId; 
   RETURN postAndComments; 
END;

要获取实体,我们需要指示Hibernate底层ResultSet和每个实体属性之间的映射。这可以使用特定于Hibernate的NamedNativeQuery注释来完成,因为与JPA NamedNativeQuery不同,它还支持调用存储过程和数据库函数。

该NamedNativeQuery映射是这样的:

@NamedNativeQuery(
    name = "fn_post_and_comments",
    query = "{ ? = call fn_post_and_comments( ? ) }",
    callable = true,
    resultSetMapping = "post_and_comments"
)
@SqlResultSetMapping(
    name = "post_and_comments",
    entities = {
        @EntityResult(
            entityClass = Post.class,
            fields = {
                @FieldResult( 
                    name = "id", 
                    column = "p.id"
                ),
                @FieldResult( 
                    name = "title", 
                    column = "p.title"
                ),
                @FieldResult( 
                    name = "version", 
                    column = "p.version"
                ),
            }
        ),
        @EntityResult(
            entityClass = PostComment.class,
            fields = {
                @FieldResult( 
                    name = "id", 
                    column = "c.id"
                ),
                @FieldResult( 
                    name = "post", 
                    column = "c.post_id"
                ),
                @FieldResult( 
                    name = "version", 
                    column = "c.version"
                ),
                @FieldResult( 
                    name = "review", 
                    column = "c.review"
                ),
            }
        )
    }
)

如您所见,SqlResultSetMapping如果您希望返回实体而不是Object[]数组,则需要提供。

有了这两个映射,获取Post和PostComment实体看起来像这样:

List<Object[]> postAndComments = entityManager
    .createNamedQuery("fn_post_and_comments")
    .setParameter(1, 1L)
    .getResultList();

Object[] postAndComment = postAndComments.get(0);
Post post = (Post) postAndComment[0];
PostComment comment = (PostComment) postAndComment[1];
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值