我正在回答这个问题,希望它能直接帮助你,但如果没有,其他曾与这条龙较量的人会在 SO 上找到答案。这是一个真正的doozie,你会认为在每个主要的Hibernate信息甲板上都会有一个关于如何处理这类事情的非常清晰的讨论,但不幸的是,这里和那里只有零散的部分和一路上的提示。经过几天的努力拼凑修复后,我找到了对我有用的方法。
首先,我必须使用 .hbm.xml 文件和映射,而不是注释。这是由于在使用的表上缺少声明的 PK,并且为它们制作 PK(实质上是代理键)被禁止(这超出了我的控制)。只有在表格上有声明的 PK 时,注释才会显得“快乐”。如果没有,您必须为每个表类使用一个标识类。(例如:您有一个表:“MyTable”。例如,如果您在 Eclipse 中使用 Hib.Reverse Engineering Tools,它将为您生成两个源文件:MyTable.java 和 MyTableId.java(还有抽象类,如果你要求他们)。MyTableId.java 是保存实际价值相关内容的那个。)
我正在使用休眠 3.3。我们还没有达到 4.x。
最后,使用的数据库是Oracle。我的挑战是运行一个带有一个参数但不返回任何记录的 SP。它是一个系统 SP,用于将任意字符串值绑定到与键值“CLIENT_IDENTIFIER”关联的当前连接会话(“USERENV”)。这应该允许开发人员为会话分配应用程序用户的身份字符串(例如登录的用户 ID),然后可以在数据库端的触发器或 SP 中提取该身份字符串。提取是容易的部分;让 Hibernate 让你运行这个 SP 是困难的部分。
过去,您可以获取基本的 Oracle 连接并通过它运行对 SP 的调用而不会大张旗鼓。看起来像这样:
String userid = "<something from someplace>";
Session session = getSession(); // whatever way you get your Hib. session.
/* 'WSCallHelper' as used below is a helper class found in the IBM Websphere API
programmer library. In some other context, a programmer would use a different
means to isolate the Oracle-native connection. */
OracleConnection oracleconnection =
( OracleConnection ) WSCallHelper.getNativeConnection( session.connection() );
CallableStatement st = oracleconnection.
prepareCall( "{call DBMS_SESSION.SET_IDENTIFIER(:userid)}");
st.setString( "userid", userid );
st.execute();
现在的问题: session.connection() 在 3.3 中已被弃用,在最新的 4.x 中,您根本不会在 Hibernate “Session” 类 Javadocs 中找到它。
这意味着你必须,如果你计划升级(??)你的 Hibernate 版本到 4.x 并且有这种代码,它将停止工作。如果你打算写一些新的东西并且不知道你是否会升级——安全总比后悔好。(你不想要凌晨 3:00 的电话,是吗?我也不想要。)
在寻找使用原生 SQL 查询对象 (SQLQuery) 或 HQL 查询(查询对象)在 Oracle 上运行 SP 的方法时,我遇到的第一件事是两者都存在仅支持选择操作或更新的问题行动:.list() 和 .executeUpdate()。没有像在其他 DAL 或 java.sql 中那样简单明了的 .execute()。我想运行的 SP [ DBMS_SESSION.SET_IDENTIFIER( userid ) ] 什么也不返回。此外,我为将字符串 {CALL DBMS_SESSION.SET_IDENTIFIER( userid )} 交给 Hibernate 会话所做的所有努力都失败了。我尝试使用语句语法等来解决问题。没有骰子。
最后经过大量的浏览,我意识到如果 Hibernate 期望从 SP 中返回一些东西(任何类型的值),那么它必须被视为某种数据库实体。这与尝试使用 Hibernate 运行一个简单的、不合格的选择语句是一致的;如果您没有带注释的 Java 文件将数据映射到表或 .hbm.xml 文件,那么 Hibernate 根本不会合作——这就是想法。所以我必须想出一种方法来表示 SP 与数据库的关系,即使没有映射到它的表。龙需要被欺骗。
第 1 步:为 Dual(是的,Oracle 中的伪表)创建一个 .hbm.xml 文件,但前提是您不使用注释。 它应该看起来很像这样,根据需要/需要针对您的包结构、查询名称和您要运行的实际 SP 进行修改:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.company.hibernate.dataaccess.model.Dual" table="DUAL">
<id name="dummy" type="java.lang.String">
<column name="DUMMY" />
<generator class="identity" />
</id>
</class>
<sql-query name="callDdbmsSessionSetIdentifier">
<return alias="dummy" class="com.company.hibernate.dataaccess.model.Dual"/>
<![CDATA[CALL DBMS_SESSION.SET_IDENTIFIER(:userid)]]>
</sql-query>
</hibernate-mapping>
请注意:对 DBMS_SESSION.SET_IDENTIFIER 的调用在 CDATA 块中,应该是任何其他类似这样的调用。(我要感谢 Mkyong 的提示:http ://www.mkyong.com/hibernate/hibernate-named-query-examples/ )还要注意 <class> 的东西不是可选的。没有它,此解决方案将无法工作。(如果您在登录 Oracle 时执行“select * from DUAL”,您将返回名为“DUMMY”的一列和返回值为“X”的单个字段的行。因此您需要声明“DUMMY” " 字段,如图所示。而且,不要担心 <generator class="identity" /> 引用,因为您永远不会将任何内容保存到 DUAL。)
如果您正在使用注解,请在“Dual.java”文件中执行与上述等效的注解,无论您使用注解还是 .hbm.xml 映射,这都是您需要的。接下来讨论这个文件。添加对 hibernate.cfg.xml 文件的正确引用将是最后一步。
第 2 步:创建“Dual.java”文件
如上所示,出于示例目的,我们假设一个 com.company.hibernate.dataaccess.model 包:
package com.company.hibernate.dataaccess.model;
public class Dual implements java.io.Serializable {
private String dummy = "";
public void setDummy (String s) {
dummy = s;
}
public String getDummy () {
return dummy;
}
}
而已!现在,如果您使用注释,则必须添加它们以适应。
2013 年 12 月 10 日更新:
转到 Hib 3.6.3,现在可以为此使用注释 OK,因此我们放弃了 .hbm 文件。现在使用的 Dual.java 文件如下所示:
package {whatever};
import javax.persistence.*; // Better to name each entity, but using * for brevity
import org.hibernate.annotations.NamedNativeQueries;
import org.hibernate.annotations.NamedNativeQuery;
@NamedNativeQueries({
@NamedNativeQuery(
name = "callDdbmsSessionSetIdentifier",
query = "CALL DBMS_SESSION.SET_IDENTIFIER(:userid)",
resultClass = Dual.class
)
})
@Entity
@Table( name = "DUAL", schema = "SYS" )
public class Dual implements java.io.Serializable {
private DualId id;
public Dual() {
}
public Dual( DualId id ) {
this.id = id;
}
@EmbeddedId
@AttributeOverrides( {
@AttributeOverride( name = "dummy", column = @Column( name = "DUMMY", nullable = false, length = 1 ) ),
} )
public DualId getId() {
return this.id;
}
public void setId( DualId id ) {
this.id = id;
}
}
如果你关心的话,DualId.java 看起来像这样:
package {whatever};
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class DualId implements java.io.Serializable {
private String dummy;
public DualId() {
}
public DualId( String dummy ) {
this.dummy = dummy;
}
// Bean compliance only; 'DUMMY' can't be changed in DUAL and
// why you'd care to get it when you know already it's just an "X",
// dunno. But these get.. and set.. methods are needed anyway.
@Column( name = "DUMMY", nullable = false, length = 1 )
public String getDummy() {
return this.dummy;
}
public void setDummy( String dummy ) {
}
public boolean equals( Object other ) {
if ( ( this == other ) )
return true;
if ( ( other == null ) )
return false;
if ( !( other instanceof DualId ) )
return false;
DualId castOther = ( DualId ) other;
return ((this.getDummy() == castOther.getDummy() ) || ( this.getDummy() != null && castOther.getDummy() != null && this
.getDummy().equals( castOther.getDummy())));
}
public int hashCode() {
int result = 17;
result = 37 * result + ( getDummy() == null ? 0 : this.getDummy().hashCode() );
return result;
}
}
第 3 步:更新 hibernate.cfg.xml 文件
将此行添加到映射资源条目列表中,如果使用 .hbm.xml 文件,调整路径或包引用以适应:
<mapping resource="com/company/hibernate/hbm/Dual.hbm.xml" />
如果使用注释,请添加以下行:
<mapping class="com.company.hibernate.dataaccess.model.Dual" />
快完成了!一切都保存了吗?伟大的。
在您想要从中调用 SP 的任何源文件中,至少在我的情况下,代码将如下所示:
Session session = getSession(); // somehow...
String userid = "<got this someplace>";
Query query = session.getNamedQuery( "callDdbmsSessionSetIdentifier" ).
setParameter( "userid", userid );
try {
query.list();
} catch (Exception e) { }
好的,怎么回事?请注意,命名查询是“callDdbmsSessionSetIdentifier”。这是我用来标记 .hbm.xml 文件中定义的实际查询的内容(见上文;查看 <sql-query name="callDdbmsSessionSetIdentifier"> 元素)。现在,请注意我正在捕获调用 query.list() 并使用它引发的异常。通常,这将是一个巨大的禁忌,对吧?好吧,如果你愿意,你可以举报。如果您想让您的日志不被大量垃圾消息填满,您可以只记录它的消息而不是整个跟踪来报告它。您将获得的例外情况如下:
(日期时间)- JDBCException E org.hibernate.util.JDBCExceptionReporter logExceptions无法对 PLSQL 语句执行提取:下一个
...(日期时间)- SystemErr R org.hibernate.exception.GenericJDBCException:无法在 org 执行查询.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:82) ... 原因:java.sql.SQLException:无法对 PLSQL 语句执行提取:下一个
在 oracle.jdbc.driver.OracleResultSetImpl.next(OracleResultSetImpl.java :240) ...
请注意共同的主题:Hibernate 无法获取任何类型的记录。如果您所做的只是执行一个 SP 并且不想从中检索记录,那么这不是您需要担心的那种异常。
与往常一样,测试和测试更多......确保它确实在做你想做的事情。是否要进行任何类型的错误日志记录或仅使用错误取决于您。
最后,如果像我一样,您想在 Oracle 中使用 DBMS_SESSION.SET_IDENTIFIER() 存储过程,然后您可以在数据库端触发器或 SP 中获取一些登录用户的用户 ID,您会怎么做?这是它的数据库端 PL/SQL,很简单:
USERNAME VARCHAR2(50) := NULL;
...
select SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER')
INTO USERNAME
from DUAL;
...
在我的特定情况下,我在触发器中使用它来记录对某个表的任何更改以用于审计目的。但更改可能来自任何地方:SQL*Plus 的桌面用户、可能使用或不使用 DBMS_SESSION.SET_IDENTIFIER() 过程的应用程序等,因此对于任何给定的 USERENV 中的 CLIENT_IDENTIFIER 可能没有设置值会话连接。如果是这种情况,则 USERNAME 将返回为 null。因此,在 CLIENT_IDENTIFIER 为空的情况下,我在上面获取连接 ID 的块之后有这个额外的块:
IF USERNAME IS NULL THEN
SELECT USER INTO USERNAME FROM DUAL;
END IF;
所以我有一些东西要放在审计表的 ID 字段中。
完毕。我希望这对那里的人有帮助。随意发表评论。