首页 > 技术文章 > 类和表的映射关系

forerver-elf 2015-08-12 14:33 原文

Hibernate映射类型分为两种:内置映射类型和客户化映射类型。内置映射类型负责把一些常见的java类型映射到相应的SQL类型;此外,Hibernate还允许用户实现UserType或CompositeUserType接口,来灵活定制客户化映射类型。客户化映射类型能够把用户定义的java类型映射到数据库表的相应字段。 

Java基本类型的Hibernate映射类型 
Hibernate映射类型         Java类型          标准SQL类型     大小和取值范围 
integer或int    int或java.lang.Integer    INTEGER   4字节,-2^31~2^31-1 
long           long或java.lang.Long       BIGINT    8字节,-2^63~2^63-1 
short          short或java.lang.Short     SMALLINT  2字节,-2^15~2^15-1 
byte           byte或java.lang.Byte       TINYINT   1字节,-128^127 
float          float或java.lang.Float     FLOAT     4字节,单精度浮点数 
double         double或java.lang.Double   DOUBLE    8字节,双精度浮点数 
character      char或java.lang.Character/String CHAR   定长字符 
string         java.lang.String           Varchar      变长字符串 
boolean        boolean或java.lang.Boolean  BIT         布尔类型 
yes_no         boolean或java.lang.Boolean  CHAR(1)('Y'/'N') 布尔类型 
true_false     boolean或java.lang.Boolean  CHAR(1)('T'/'F') 布尔类型 

Hibernate映射类型、对应的Java时间和日期类型及对应的标准SQL 
映射类型          Java类型                  标准SQL类型           描述 
date      java.util.Date或java.sql.Date   DATE     代表日期  YYYY-MM-DD 
time      java.util.Date或java.sql.Time   TIME     代表时间  HH:MM:SS 
timestamp java.util.Date或java.sql.Timestamp TIMESTAMP  YYYYMMDDHHMMSS 
celendar  java.util.Calender            TIMESTAMP   YYYYMMDDHHMMSS 
calendar_date java.util.Calender          DATE     代表日期  YYYY-MM-DD 

Hibernate映射类型、对应的Java大对象类型及对应的标准SQL 
映射类型         Java类型        标准SQL类型     MYSQL类型    Oracle类型      
binary         byte[]     VARBINARY(或BLOB)   BLOB        BLOB 
text     java.lang.String     CLOB            TEXT        CLOB 
serializable  实现该接口的类   VARBINARY(或BLOB)   BLOB     BLOB 
clob     java.sql.Clob        CLOB            TEXT        CLOB 
blob     java.sql.Blob        BLOB            BLOB        BLOB 

当应用程序运行时,Java应用程序通过Hibernate访问数据库,而Hibernate又通过JDBC驱动程序访问数据库。JDBC驱动程序对底层数据库使用的SQL进行封装,向上提供标准SQL类型接口,使得Hibernate可以使用标准SQL类型来生成DML(Data Manipulation Language)。 

若没有指定映射类型,Hibernate会运用反射机制,判别属性的java类型,采用相应的Hibernate映射类型。 
但在一下几种情况下必须显示指定Hibernate映射类型:若希望通过hbm2java工具由映射文件来生成持久化类,必须在映射文件中显式指定Hibernate映射类型。一个java类型对应多个Hibernate映射类型的场合。 

org.hibernate.UserType接口中的方法: 
sqlTypes()方法:设置java类型为int的在数据库中对应varchar类型 
privat static final int[] SQL_TYPES = {Types.VARCHAR}; 
public int[] sqlTypes(){ return SQL_TYPES;} 

returnedClass()方法:当从数据库中读取后运行在java应用程序中是integer类型 
public Class returnedClass(){return false;} 

isMutable()方法:了解这个类是否是可变类.Hibernate在处理不可变类时会采取一些性能优化 
public boolean isMutable(){return false;} 

deepCopy(Object value):生成对应属性的快照 
public Object deepCopy(Object value){return value;} 

equals(Object x, Object y)方法:比较当前值和快照是否相同 
public boolean equals(Object x, Object y){ 
   if(x == y)  return true; 
   if(x == null || y == null ) return false; 
   return x.equals(y); 


hasCode(Objec x)方法:获得该属性的哈希码 
public int hasCode(Object x){ 
   return x.hasCode(); 


nullSafeGet(ResultSet resultSet, String[] names, Object owner)方法:获取属性的属性值 
public Object nullSafeGet(ResultSet resultSet, String[] names, Object ower) throws HibernateException, SQLException{ 
     String phone = resultSet.getString(name[0]); 
     if(resultSet.wasNull) return null; 
     return new Integer(XX); 


nullSafeSet(PrepareStatement statement, Object value, int index)方法:当Hibernate把对象持久化到数据库时,调用nullSafeSet()方法把属性添加到SQL insert语句中。 
public void nullSafeSet(PrepareStatement statement, Object value, int index) throws HibernateException, SQLException{ 
    if(value == null){ 
         statement.setNull(index, Types.VARCHAR); 
    }else{ 
         String phone = ((Interger)value).toString(); 
         statement.setString(index, phone); 
    } 


assemble(Serializable cached, Object owner)方法:当Hibernate把第二级缓存中的对象加载到Session缓存中,调用assemble()方法来获得属性的反序列化数据 
public Object assemble(Serializable cached, Object owner){ 
    return cached; 


disassemble(Object value)方法:Hibernate把第二级缓存中的对象加载到Session缓存中,调用disassemble()方法来获得属性的序列化数据 
public Serializable disassemble(Object value){ 
    return (Serializable)value; 


replace(Object original, Object target, Object owner)方法:当Session的merge()方法把一个游离对象融合到持久化对象中时,会调用此replace()方法来获得用于替代持久化对象的属性的值。 
public Object replace(Object original, Object target, Object owner){ 
    return original; 

   
Hibernate组件采用的是XML配置方式,具有较好的维护性。客户化映射类型采用的是编程方式,能够完成更加复杂灵活的映射。 

在持久化类中,二进制大对象可以声明为byte[]或java.sql.Blob类型;字符串大对象可以声明为java.lang.String或java.sql.Clob类型。java.sql.Blob和java.sql.Clob是JDBC API中的接口。在默认的情况下,Blob和Clob接口的实现会使用SQL定位器,当程序从数据库加载Blob类型或Clob类型的数据时,实际上加载的是Blob类型或Clob类型的数据的逻辑指针。接下来程序需要通过Blob.getBinaryStream()或Clob.getCharacterStream()方法得到Blob或Clob类型的数据的输入流,才可以真正读取到大数据对象。 
Customer customer = (Customer)session.get(Customer.class, new Long(1)); 
Blob image = customer.getImage();     //逻辑指针 
InputStream in = image.getBinaryStream();   //读取大数据 

org.hibernate.Hibernate类提供了一系列用于创建Blob和Clob对象的静态方法: 
public static Blob createBlob(byte[] bytes) 
public static Blob createBlob(InputStream stream, int length) 
public static Blob createBlob(InputStream stream) 
public static Clob createClob(String string) 
public static Clob createClob(Reader reader, int length) 

public class BusinessService{ 
    public static SessionFactory sessionFactory; 
    static{...}  //初始化Hibernate 

    public Long saveCustomer() throw Exception{ 
        //读取photo.gif的二进制文件 
        InputStream in = this.getClass().getResourceAsStream("photo.gif"); 
       byte[] buffer = new byte[in.available]; 
       in.read(buffer); 
       in.close(); 
       
       Session session = sessionFactory.openSession(); 
       Transaction tx = session.beginTransaction(); 

       Customer customer = new Customer():; 
       customer.setName("Tom"); 
       
       //创建一个Blob对象 
        customer.setImage(Hibernate.createBlob(buffer)); 
       session.save(customer); 

       tx.commit(); 
       session.close(); 
       return customer.getId(); 
    } 

    //加载Customer对象 
    public void loadCustomer(Long id) throws Exception{ 
       Session session = sessionFactory.openSession(); 
       Transaction tx = session.beginTransaction(); 
       Customer customer = (Customer) session.get(Customer.class,id); 
       getBlob(customer); 
       tx.commit(); 
       session.close(); 
    } 

    public void getBlob(Customer customer) throws Exception{ 
       Blob image = customer.getImage(); 
       InputStream in = image.getBinaryStream(); 
       FileOutputStream fout = new FileOutputStream("11.3\\photo_bak.gif"); 
       int b = -1; 
       while((b = in.read())!= -1) 
       fout.write(b); 
       fout.close(); 
       in.close(); 
    } 


使用java.sql.Blob和java.sql.Clob受到一下限制: 
程序只有在一个数据库事务范围内,才可以访问Blob或Clob类型的实例;有些数据库系统的驱动程序不支持java.sql.Blob或java.sql.Clob;持久化类中必须引入JDBC API中的java.sql.Blob或java.sql.Clob类型。 

数据库表之间并不存在继承关系,有3种映射方式可以把域模型的继承关系映射到关系数据模型中: 
继承关系树的每个具体类对应一个表:关系数据模型完全不支持域模型中的继承关系和多态。 
继承关系树的根类对应一个表:对关系数据模型进行非常规设计,在数据库表中加入额外的区分子类型的字段。通过这种方式,可以使关系数据模型支持继承关系和多态。 
继承关系树的每个类对应一个表:在关系数据模型中用外键参照关系来表示继承关系。 

继承关系树的每个具体类对应一个表 
把每个具体类映射到一张表是自简单的映射方式。这种映射方式不支持多态查询。 

继承关系树的根类对应一个表 
这种映射方式只需为继承关系树的根创建一张表。在这张表中,会提供它的子类的所有属性对应的字段。此外,还要添加一个标识符字段用来区分不同的子类。 
<hibernate-mapping> 
   <class name="Employee" table="EMPLOYEE"> 
       <id name="id"  type="long"> 
           <generator class="increment" /> 
       </id> 
       <discriminator column="EMPLOYEE_TYPE"  type="string" /> 
       <many-to-one 
           name="company"  column="COLUMN_ID" class="Company" 
       /> 
  
       <subclass name="HourlyEmployee" discriminator-value="HE"> 
           <property name="rate" column="RATE" type="double" /> 
       </subclass> 

       <subclass name="SalaryEmployee" discriminator-value="SE"> 
           <property name="salary" column="SALARY" type="double" /> 
       </subclass> 
   </class> 
</hibernate-mapping> 

继承关系树的每个类对应一个表 
继承关系树的每个类及接口都对应一个表,这种映射方式支持多态关联。 
<hibernate-mapping> 
   <class name="Employee" table="EMPLOYEE"> 
       <id name="id"  type="long"> 
           <generator class="increment" /> 
       </id> 
       <many-to-one 
           name="company"  column="COLUMN_ID" class="Company" 
       /> 
    
       <joined-subclass name="HourlyEmployee" table="HOURLY_EMPLOYEE"> 
           <key column="EMPLOYEE_ID" /> 
           <property name="rate" column="RATE" type="double" /> 
       </joined-subclass> 
     
       <joined-subclass name="HourlyEmployee" table="HOURLY_EMPLOYEE"> 
           <key column="EMPLOYEE_ID" /> 
           <property name="salary" column="SALARY" type="double" /> 
       </joined-subclass> 
   </class> 
</hibernate-mapping> 

比较三种映射方式: 
关系数据模型的复杂度: 
每个具体类对应一个表:每个具体类对应一个表,这些表中包含重复字段 
根对应一个表:只需创建一个表 
每个类对应一个表:表的数目最多,并且表之间还有外键参照关系 
查询性能: 
每个具体类对应一个表:如果查询父类的对象,必须查询所有具体子类对应的表   
根对应一个表:有很好的查询性能,无需进行表的连接    
每个类对应一个表:需要进行表的内连接或左外连接 
数据库Schema的可维护性: 
每个具体类对应一个表:若父类的属性发生变化,必须修改所有具体的子类对应的表   
根对应一个表:只需修改一张表    
每个类对应一个表:如果某个类的属性发生变化,只需修改和这个类对应的表 
是否支持多态查询和多态关联 
每个具体类对应一个表:不支持   
根对应一个表:支持   
每个类对应一个表:支持 
是否符合关系数据模型的常规设计规则 
每个具体类对应一个表:符合   
根对应一个表:在表中引入额外的区分子类的类型的字段;若子类中的某个属性不允许为null,在表中无法为对应的字段创建not null约束    
每个类对应一个表:符合 

若不需支持多态查询和多态关联,可以采用给每个具体类对应一个表的映射方式,若需要支持多态查询和多态关联,并且子类包含的属性不多,可以采用根类对应一个表的映射方式,若需要支持多态查询和多态关联,并且子类包含的属性很多,可以采用每个类对应一个表的映射方式。 

由于关系数据库模型不允许一个表的外键同时参照两个表的主键,因此,可以通过触发器来保证字段的完整性。 
<any name="a" meta-type="string" id-type="long" 
     cascade="save-update"> 
     <meta-value value="B" class="ClassB"  /> 
     <meta-value value="C" class="ClassC"  /> 
     <column name="A_TYPE"  />  //指定继承子类的类型 
     <column name="A_ID"    />  //子类的id 
</any> 

推荐阅读