10 分鐘手擼極簡版 ORM 框架!

大家好,我是冰河~~

最近很多小夥伴對 ORM 框架的實現很感興趣,不少讀者在冰河的微信上問:冰河,你知道 ORM 框架是如何實現的嗎?比如像 MyBatis 和 Hibernte 這種 ORM 框架,它們是如何實現的呢?

爲了能夠讓小夥伴們更加深刻並且清晰的理解 ORM 框架的實現原理,冰河決定自己手擼一個極簡版的 ORM 框架,讓小夥伴們一看就能夠明白什麼是 ORM 框架?ORM 框架到底是如何運行的?ORM 框架是如何將程序對象與數據庫中的數據進行映射的?不過,在正式開始手擼 ORM 框架之前,我們要先來搞清楚什麼是 ORM 框架。

什麼是 ORM 框架?

ORM 全稱爲:Object Relational Mapping,翻譯成中文就是:對象關係映射。也就是說 ORM 框架就是對象關係映射框架,它通過元數據描述對象與關係映射的細節,ORM 框架在運行的時候,可以根據對應與映射之間的關係將數據持久化到數據庫中。

其實,從本質上講,ORM 框架主要實現的是程序對象到關係數據庫數據的映射。說的直白點:ORM 框架就是將實體和實體與實體之間的關係,轉化爲對應的 SQL 語句,通過 SQL 語句操作數據庫,將數據持久化到數據庫中,並且對數據進行相應的增刪改查操作。

最常用的幾種 ORM 框架爲:MyBatis、Hibernate 和 JFinal。

手擼 ORM 框架

這裏,我們模擬的是手擼 Hibernate 框架實現 ORM,小夥伴們也可以模擬其他的 ORM 框架實現,核心原理都是相通的。如果大家在模擬其他框架手擼實現 ORM 時,遇到問題的話,都可以私聊我溝通,我看到的話,會第一時間回覆大家。

好了,說幹就幹,我們開始吧。

@Table 註解的實現

首先,我們創建一個io.mykit.annotation.jdk.db.provider Java 包,在這個 Java 包創建一個 @Table 註解,@Table 註解標註在 Java 類上,表示當前類會被映射到數據庫中的哪張數據表上,如下所示。

package io.mykit.annotation.jdk.db.provider;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 自定義Table註解
 * @author binghe
 *
 */
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {
 String value() default "";
}

@Column 註解的實現

同樣的,在io.mykit.annotation.jdk.db.provider包下創建一個 @Column 註解,@Column 註解標註在類中的字段上,表示當前類中的字段映射到數據表中的哪個字段上,如下所示。

package io.mykit.annotation.jdk.db.provider;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
 * 自定義Column註解
 * @author binghe
 *
 */
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
 String value() default "";
}

看到這裏,不管是使用過 MyBatis 的小夥伴還是使用過 Hibernate 的小夥伴,應該都會有所體會吧?沒錯,@Table 註解和 @Column 註解,不管是在 MyBatis 框架還是 Hibernate 框架中,都會被使用到。這裏,我們在收錄極簡版 ORM 框架時,也使用了這兩個經典的註解。

創建實體類

io.mykit.annotation.jdk.db.provider.entity包下創建實體類 User,並且 @Table 註解和 @Column 註解會被分別標註在 User 類上和 User 類中的字段上,將其映射到數據庫中的數據表和數據表中的字段上,如下所示。

package io.mykit.annotation.jdk.db.provider.entity;
 
import io.mykit.annotation.jdk.db.provider.Column;
import io.mykit.annotation.jdk.db.provider.Table;
 
/**
 * 自定義使用註解的實體
 * @author binghe
 *
 */
@Table("t_user")
public class User implements Serializable{
 
 @Column("id")
 private String id;
 
 @Column("name")
 private String name;
 
 public User() {
  super();
 }
 
 public User(String id, String name) {
  super();
  this.id = id;
  this.name = name;
 }
 
 public String getId() {
  return id;
 }
 
 public void setId(String id) {
  this.id = id;
 }
 
 public String getName() {
  return name;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 @Override
 public String toString() {
  return "User [id=" + id + ", ]";
 }
 
}

註解解析類的實現

io.mykit.annotation.jdk.db.provider.parser包中創建一個 AnnotationParser 類,AnnotationParser 類是整個框架的核心,它負責解析標註在實體類上的註解,並且將對應的實體類及其字段信息映射到對應的數據表和字段上,如下所示。

package io.mykit.annotation.jdk.db.provider.parser;
 
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
import io.mykit.annotation.jdk.db.provider.Column;
import io.mykit.annotation.jdk.db.provider.Table;
 
/**
 * 解析自定義註解
 * @author binghe
 *
 */
public class AnnotationParser {
 /** 
     * 通過註解來組裝查詢條件,生成查詢語句 
     * @param obj 
     * @return 
     */  
    public static String assembleSqlFromObj(Object obj) {  
        Table table = obj.getClass().getAnnotation(Table.class);  
        StringBuffer sbSql = new StringBuffer();  
        String tableName = table.value();  
        sbSql.append("select * from " + tableName + " where 1=1 ");  
        Field[] fileds = obj.getClass().getDeclaredFields();  
        for (Field f : fileds) {  
            String fieldName = f.getName();  
            String methodName = "get" + fieldName.substring(0, 1).toUpperCase()  
                    + fieldName.substring(1);  
            try {  
                Column column = f.getAnnotation(Column.class);  
                if (column != null) {  
                    Method method = obj.getClass().getMethod(methodName);  
                    Object v = method.invoke(obj);  
                    if (v != null) {  
                        if (v instanceof String) {  
                         String value = v.toString().trim();
                            // 判斷參數是不是 in 類型參數 1,2,3  
                            if (value.contains(",")) {  
                             //去掉value中的,
                             String sqlParams = value.replace(",""").trim();
                             //value中都是純數字
                             if(isNum(sqlParams)){
                              sbSql.append(" and " + column.value() + " in (" + value + ") ");  
                             }else{
                              String[] split = value.split(",");
                              //將value重置爲空
                              value = "";
                              for(int i = 0; i < split.length - 1; i++){
                               value += "'"+split[i]+"',";
                              }
                              value += "'"+split[split.length - 1]+"'";
                              sbSql.append(" and " + column.value() + " in (" + value + ") ");  
                             }
                            } else {  
                             if(value != null && value.length() > 0){
                              sbSql.append(" and " + column.value() + " like '%" + value + "%' ");  
                             }
                            }  
                        } else {
                            sbSql.append(" and " + column.value() + "=" + v.toString() + " ");  
                        }  
                    }  
                }  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
        }  
        return sbSql.toString();  
    }  
  
    /** 
     * 檢查給定的值是不是 id 類型 1.檢查字段名稱 2.檢查字段值 
     *  
     * @param target 
     * @return 
     */  
    public static boolean isNum(String target) {  
        boolean isNum = false;  
        if (target.toLowerCase().contains("id")) {  
            isNum = true;  
        }  
        if (target.matches("\\d+")) {  
            isNum = true;  
        }  
        return isNum;  
    }  
}

至此,我們的極簡版 ORM 框架就實現好了,不過實現完還不行,我們還要對其進行測試驗證。

測試類的實現

io.mykit.annotation.jdk.provider包下創建 AnnotationTest 類,用以測試我們實現的極簡 ORM 框架的效果,具體如下所示。

package io.mykit.annotation.jdk.provider;
 
import org.junit.Test;
 
import io.mykit.annotation.jdk.db.provider.entity.User;
import io.mykit.annotation.jdk.db.provider.parser.AnnotationParser;
import io.mykit.annotation.jdk.provider.parser.AnnotationProcessor;
 
/**
 * 測試自定義註解
 * @author binghe
 *
 */
public class AnnotationTest {
 
 @Test
 public void testDBAnnotation(){
  User testDto = new User("123""34");  
  User testDto1 = new User("123""test1");  
  User testDto2 = new User("""test1,test2,test3,test4");  
        String sql = AnnotationParser.assembleSqlFromObj(testDto);  
        String sql1 = AnnotationParser.assembleSqlFromObj(testDto1);  
        String sql2 = AnnotationParser.assembleSqlFromObj(testDto2);  
        System.out.println(sql);  
        System.out.println(sql1);  
        System.out.println(sql2);  
 }
}

運行測試

我們運行AnnotationTest#testDBAnnotation()方法,命令行會輸出如下信息。

select * from t_user where 1=1  and id like '%123%'  and name like '%34%' 
select * from t_user where 1=1  and id like '%123%'  and name like '%test1%' 
select * from t_user where 1=1  and name in ('test1','test2','test3','test4')

可以看到,我們在測試程序中,並沒有在測試類中傳入或者執行任何 SQL 語句,而是直接創建 User 類的對象,並調用AnnotationParser#assembleSqlFromObj()進行解析,並且將對應的實體類對象轉換爲 SQL 語句返回。

其實,MyBatis 和 Hibernate 的底層核心原理都是這樣的,大家學會了嗎?有不懂的地方歡迎私聊我溝通。趕緊打開你的開發環境,手擼個極簡版 ORM 框架吧!!

好了,今天就到這兒吧,我是冰河,大家有啥問題可以在下方留言,也可以加我微信:sun_shine_lyz,我拉你進羣,一起交流技術,一起進階,一起進大廠~~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/G4jjTvhzec_C4GFQXTPQlg