菜鳥學SSH(十五)——簡單模擬Hibernate實現原理

劉水鏡發表於2014-07-25

之前寫了Spring的實現原理,今天我們接著聊聊Hibernate的實現原理,這篇文章只是簡單的模擬一下Hibernate的原理,主要是模擬了一下Hibernate的Session類。好了,廢話不多說,先看看我們的程式碼:

package com.tgb.hibernate;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;

import com.tgb.hibernate.model.User;

public class Session {

    //表名
    String tableName = "user";  

    //存放資料庫連線配置
    private Map<String, String> conConfig = new HashMap<String, String>();

    //存放實體屬性
    private Map<String ,String > columns = new HashMap<String ,String >();  

    //實體的get方法集合
    String methodNames[];  
    
    public Session () {  

        //初始化實體,這裡就不用讀取配置檔案的方式了,有點麻煩。 
        columns.put("id", "id");  
        columns.put("name", "name");  
        columns.put("password", "password");  
        methodNames = new String[columns.size()];  

    }  
    
    /**
     * 建立資料庫連線
     * @return
     * @throws Exception
     */
    public Connection createConnection() throws Exception {
         //解析xml檔案,讀取資料庫連線配置      
        SAXBuilder sb = new SAXBuilder();  
        Document doc = sb.build(this.getClass().getClassLoader().getResourceAsStream("hibernate.cfg.xml"));  
        Element root = doc.getRootElement();  
        List list = XPath.selectNodes(root, "/hibernate-configuration/property");  

        for (int i = 0; i < list.size(); i++) {              
           Element property = (Element) list.get(i);  
           String name = property.getAttributeValue("name");  
           String value = property.getText();           
           conConfig.put(name, value);           
          }  

        //根據配置檔案獲得資料庫連線
        Class.forName(conConfig.get("driver"));  
        Connection con = DriverManager.getConnection(conConfig.get("url"),conConfig.get("username"),conConfig.get("password"));  

        return con;
    }
    
    /**
     * save方法,持久化物件
     * @param user
     */
    public void save(User user) {  

        String sql = createSql();  
        System.out.println(sql);  

        try {  
            Connection con = createConnection();  
            PreparedStatement state =  (PreparedStatement) con.prepareStatement(sql);  

            for(int i=0;i<methodNames.length;i++) {  

                //得到每一個方法的物件  
                Method method = user.getClass().getMethod(methodNames[i]);  

                //得到他的返回型別  
                Class cla = method.getReturnType();  

                //根據返回型別來設定插入資料庫中的每個屬性值。  
                if(cla.getName().equals("java.lang.String")) {  
                    String returnValue = (String)method.invoke(user);  
                    state.setString(i+1, returnValue);  
                }  
                else if(cla.getName().equals("int")) {  
                    Integer returnValue = (Integer) method.invoke(user);  
                    state.setInt(i+1, returnValue);  
                }                        
            }  

            state.executeUpdate();  
            state.close();  
            con.close();  

        } catch (ClassNotFoundException e) {      
            e.printStackTrace();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }   
    }  
    
    /** 
     * 得到sql語句 
     * @return 返回sql語句 
     */  
    private String createSql() {  

        //strColumn代表資料庫中表中的屬性列。並將其連線起來。  
        String strColumn = "";  
        int index=0;  
        for(String key :columns.keySet())  
        {  
            strColumn +=key+",";  
            String v = columns.get(key);  

            //獲得屬性的get方法,需要將屬性第一個字母大寫如:getId()
            v = "get" + Character.toUpperCase(v.charAt(0)) + v.substring(1);  
            methodNames[index] = v; 
            index++;  
        }  
        strColumn  = strColumn.substring(0, strColumn.length()-1);  

        //拼接引數佔位符,即:(?, ?, ?)
        String strValue = "";  
        for(int i=0;i<columns.size();i++)  
            strValue +="?,";  
            
        strValue = strValue.substring(0,strValue.length()-1);  

        String sql = "insert into " + tableName +"(" + strColumn + ")" + " values (" + strValue + ")";   
        return sql;  
    }  
}

 

以上程式碼主要是完成了Hibernate的save()方法,該類有一個構造方法,一個構建sql語句的方法,一個獲得資料庫連線的方法。最後通過save()方法結合前面幾個方法獲得結果,將實體物件持久化到資料庫。

基本原理就是:首先,獲得資料庫連線的基本資訊;然後,獲得實體的對映資訊;接著,也是最關鍵的步驟,根據前面獲得的資訊,組裝出各種sql語句(本例只有簡單的insert),將實體按照不同的要求查詢或更新(增、刪、改)到資料庫。

當然Hibernate的具體實現遠沒有這麼簡單,Hibernate中大量運用了cglib的動態代理,其中load()方法就是一個例子。大家都知道,呼叫load()方法是Hibernate不會向資料庫發sql語句,load()方法得到的是目標實體的一個代理類,等到真正用到實體物件的時候才會去資料庫查詢。這也是Hibernate的一種懶載入的實現方式。

總結一句話,這些框架之所以能夠做到靈活,就是因為它們都很好的利用了懶載入機制,在執行期在確定例項化誰,需要誰例項化誰,什麼時候需要,什麼時候例項化。這樣設計出來能不靈活嗎?這些思想值得我們好好研究,並運用到我們的設計中去。

相關文章