在Oracle中,Hibernate和CHAR主键列的填充

5

我在使用Hibernate与Oracle中的char(6)列时遇到了一些问题。这是表的结构:

CREATE TABLE ACCEPTANCE
(
   USER_ID char(6) PRIMARY KEY NOT NULL,
   ACCEPT_DATE date
);

如果用户ID少于6个字符,我可以在使用SQuirreL运行查询时选择它们而无需填充用户ID。例如,如果有一个用户ID为"abc"的记录,则以下语句将返回该记录。

select * from acceptance where user_id = "abc"

很不幸,当通过Hibernate(JPA)执行select时,下面的语句返回null:
em.find(Acceptance.class, "abc");

如果我填充值,它会返回正确的记录:
em.find(Acceptance.class, "abc   ");

我正在处理的模块从系统的其他部分获取未填充的用户ID。除了在将其提供给Hibernate之前编写适应用户ID长度的代码(如果长度发生变化可能会带来维护问题),是否有更好的方法使Hibernate正常工作?

如果长度发生变化,那么您的主键也会改变,这将带来更多的问题需要担心。 - Gary Myers
2个回答

3

这是上帝告诉你永远不要使用CHAR()作为主键的方式 :-)

然而,由于您的user_id在实体中被映射为字符串,在Hibernate的Oracle方言中将其转换为varchar。由于Hibernate对其所有查询都使用预处理语句,因此语义会继承下来(与SQuirreL不同,其中该值被指定为文字并因此被转换成不同的值)。

基于Oracle 类型转换规则,列值随后被提升为varchar2并作为这样进行比较;因此您将得不到任何记录。

如果您无法更改底层列类型,则最好的选择可能是使用HQL查询和Oracle方言支持的rtrim()函数。


我考虑过像这样做:em.createQuery("from Acceptance where trim(userId) = :userId"); 但是那会不会破坏主键上的索引呢? - jthg
你只需要使用 rtrim(),而不是完整的 trim() - 并且它仍然应该使用非空格字符的索引。 - ChssPly76
你确定吗?我在谷歌上找到了一些论坛帖子,它们说rtrim确实会导致表扫描。 - jthg
如果常规索引无法提高效率,您可以针对 rtrim() 创建基于函数的索引。 - ChssPly76
我最终付出了努力,将该列更改为varchar2。我接受这个答案,因为我认为它是最具信息量的。 - jthg
我希望能够得到一个 rtrim() 函数应用于列的示例。 - krico

1

为什么你的模块从系统的其他部分获取到了一个未填充的值?

根据我的理解,如果系统的其他部分没有更改主键,它们应该从数据库中读取6个字符并将这6个字符一路传递下去--这样就可以了。唯一的例外是在生成主键时可能需要填充。

您可以通过修剪或填充每次必要的值来规避问题,但这不会事先解决您的主键不一致的问题。要事先解决问题,您必须:

  • 始终从模块的其他部分接收6个字符
  • 使用varchar2正确处理动态大小

如果您无法事先解决问题,则确实需要:

  • 在必要时添加修剪/填充
  • 在DAO中添加修剪/填充(如果有)
  • 在用户类型中添加修剪/填充(N. Hughes的建议)

我甚至可以说,如果数据库类型是CHAR(6),那么它在语义上期望该数据库的客户端使用6个字符,没有什么商量的余地。所以,要么更改数据库定义,要么客户端需要遵守 :) - Romain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接