关于Hibernate主键包含null值的改动方案
前言
最近公司跟另外一个公司在合作一个项目,不幸被卷入其中。这几天做了一个变态的工作,就是客户死活就要复合主键能够支持null值,甚至扬言不惜修改hibernate,于是他如愿了。命题
数据表中设置为主键的两个字段其中一个可以为空。
分析
经过分析,Hibernate3.0的beta1~3支持可以为空的主键,但是之后就不再支持。同时,oracle也不支持可以为null的主键,在建表的过程中,只能建立unique索引。
由此,建立可以为null的数据表主键的方法是一种非标准化的做法。就像参考文献中Hibernate作者所说的:
Comment by Gavin King [04/Mar/05 02:04 PM]
Hibernate 3 treats any key with a null value as null. This is reasonable because nulls in primary keys are completely evil and not allowed on most dbs.
为了满足命题要求,我们需要修改Hibernate的代码。
参考文献
l http://opensource.atlassian.com/projects/hibernate/browse/HHH-177
步骤
建立库表结构
以下是用于实验的表结构代码:
drop table CLASS1
create table class1
(field1 varchar2(10 char) not null,
field2 varchar2(30 char) ,
f3 number(10,0) not null,
f4 varchar2(10 char) not null)
create unique index uniq1 on CLASS1 (FIELD1 , FIELD2 );
代码
类定义的时候使用标准的Hibernate的复合主键结构。并且在映射配置文件中使用自定义的CRUD语句代替Hibernate的自动生成语句。
文件Class1.java
package com.chinainsurance.platform.service.impl.test;
import java.io.Serializable;
public class Class1 implements Serializable {
Class1ID id;
Integer field3;
String field4;
public Integer getField3() {
return field3;
}
public void setField3(Integer field3) {
this.field3 = field3;
}
public String getField4() {
return field4;
}
public void setField4(String field4) {
this.field4 = field4;
}
public Class1ID getId() {
return id;
}
public void setId(Class1ID id) {
this.id = id;
}
}
文件Class1ID.java
package com.chinainsurance.platform.service.impl.test;
import java.io.Serializable;
public class Class1ID implements Serializable {
String field1;
String field2; // nullable
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
public boolean equals(Object arg0) {
return super.equals(arg0);
}
public int hashCode() {
return field1.hashCode();
}
}
定义的hbm文件内容如下:
<?xml version="1.0"?>
<!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.chinainsurance.platform.service.impl.test.Class1" table="class1" lazy="false" >
<composite-id name="id" class="com.chinainsurance.platform.service.impl.test.Class1ID" >
<key-property name="field1" column="field1" type="java.lang.String" length="10" />
<key-property name="field2" column="field2" type="java.lang.String" length="30" />
</composite-id>
<property name="field3" column="f3" type="java.lang.Integer" length="20" not-null="true"/>
<property name="field4" column="f4" type="java.lang.String" length="10" not-null="true"/>
<loader query-ref="selectClass1"/>
<sql-insert>INSERT INTO class1 (f3, f4, field1, field2) VALUES ( ?, ?, ?, ? )</sql-insert>
<sql-update>UPDATE class1 SET f3=?, f4=? WHERE field1=? anD nvl(fielD2,'*')=nvl(?, '*')</sql-update>
<sql-delete>DELETE FROM class1 where field1=? and nvl(fielD2,'*')=nvl(?, '*')</sql-delete>
</class>
<sql-query name="selectClass1">
<return class="com.chinainsurance.platform.service.impl.test.Class1" >
<return-property name="id">
<return-column name="field1"/>
<return-column name="field2"/>
</return-property>
<return-property name="field3" column="f3" />
<return-property name="field4" column="f4" />
</return>
select * from class1 where field1=? and nvl(field2,'*')=nvl(?, '*')
</sql-query>
</hibernate-mapping>
修改的Hibernate源代码
经过调试跟踪,发现不支持null主键的代码在org.hibernate.type.ComponentType的hydrate函数中,代码如下:
if ( val == null ) {
if (isKey) return null; //different nullability rules for pk/fk
}
else {
notNull = true;
}
为此,我们需要把这一段代码修改为如下:
if( val != null )
notNull = true;
编译Hiberante
在Hibernate的根目录下有build.bat,运行就可以编译生成hibernate3.jar,该文件也会在根目录下。
测试
使用如下的测试用例来测试以下结果:
package com.chinainsurance.platform.service.impl.test;
import java.util.List;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.chinainsurance.platform.service.helper.TaskServiceHelper;
import junit.framework.TestCase;
/*
drop table CIUSER.CLASS1
create table class1 (field1 varchar2(10 char) not null, field2 varchar2(30 char) , f3 number(10,0) not null, f4 varchar2(10 char) not null)
create unique index uniq1 on CIUSER.CLASS1 (FIELD1 , FIELD2 );
*/
public class TestClasses extends TestCase {
HibernateTemplate dao;
public TestClasses()
{
TaskServiceHelper.context = TestUtil.getContext();
HibernateDaoSupport t = (HibernateDaoSupport) TaskServiceHelper.getDao("reportJobDao");
dao = t.getHibernateTemplate();
}
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
try{ clearClass1(); }catch(Exception e){}
}
private void clearClass1()
{
List list = dao.loadAll(Class1.class);
for(int i = 0; i < list.size(); ++i)
{
dao.delete(list.get(i));
}
}
private Class1 insertClass1()
{
Class1 obj1 = createClass1();
try{
dao.save(obj1);
}
catch(Exception e)
{
// System.out.println(e.toString());
}
return obj1;
}
private Class1 createClass1()
{
Class1 obj1 = new Class1();
Class1ID id = new Class1ID();
id.setField1("c
id.setField2(null);
obj1.setId(id);
obj1.setField3(new Integer(1));
obj1.setField4("c
return obj1;
}
public void testClass1Add()
{
System.out.println("testClass1Add");
dao.save(createClass1());
clearClass1();
}
public void testClass1Delete()
{
System.out.println("testClass1Delete");
Class1 obj1 = insertClass1();
dao.delete(obj1);
}
public void testClass1Load()
{
System.out.println("testClass1Load");
insertClass1();
Class1ID id = new Class1ID();
id.setField1("c
id.setField2(null);
Class1 obj1 = (Class1) dao.load(Class1.class, id);
assertEquals(obj1.getId().getField1(), "c
assertNull(obj1.getId().getField2());
clearClass1();
}
public void testClass1Update()
{
System.out.println("testClass1Update");
Class1 obj1 = insertClass1();
obj1.setField4("ok");
dao.update(obj1);
}
public void testClass1Find()
{
System.out.println("testClass1Find");
insertClass1();
List list = dao.find("from Class1 as c where c.id.field2 is null");
assertTrue(list.size() > 0);
clearClass1();
}
public void testClass1LoadAll()
{
System.out.println("testClass1LoadAll");
insertClass1();
List list = dao.loadAll(Class1.class);
//assertTrue(list.size() > 0);
//clearClass1();
System.out.println("size = " + list.size());
for(int i = 0; i < list.size(); ++i)
{
Class1 cls = (Class1)list.get(i);
System.out.println("" + cls.getId().getField1() + cls.getId().getField2());
}
clearClass1();
}
}