10分鐘手擼極簡版ORM框架!
大家好,我是冰河~~
最近很多小伙伴對ORM框架的實現(xiàn)很感興趣,不少讀者在冰河的微信上問:冰河,你知道ORM框架是如何實現(xiàn)的嗎?比如像MyBatis和Hibernte這種ORM框架,它們是如何實現(xiàn)的呢?
為了能夠讓小伙伴們更加深刻并且清晰的理解ORM框架的實現(xiàn)原理,冰河決定自己手擼一個極簡版的ORM框架,讓小伙伴們一看就能夠明白什么是ORM框架?ORM框架到底是如何運行的?ORM框架是如何將程序?qū)ο笈c數(shù)據(jù)庫中的數(shù)據(jù)進行映射的?不過,在正式開始手擼ORM框架之前,我們要先來搞清楚什么是ORM框架。
什么是ORM框架?
ORM全稱為:Object Relational Mapping,翻譯成中文就是:對象關(guān)系映射。也就是說ORM框架就是對象關(guān)系映射框架,它通過元數(shù)據(jù)描述對象與關(guān)系映射的細節(jié),ORM框架在運行的時候,可以根據(jù)對應(yīng)與映射之間的關(guān)系將數(shù)據(jù)持久化到數(shù)據(jù)庫中。
其實,從本質(zhì)上講,ORM框架主要實現(xiàn)的是程序?qū)ο蟮疥P(guān)系數(shù)據(jù)庫數(shù)據(jù)的映射。說的直白點:ORM框架就是將實體和實體與實體之間的關(guān)系,轉(zhuǎn)化為對應(yīng)的SQL語句,通過SQL語句操作數(shù)據(jù)庫,將數(shù)據(jù)持久化到數(shù)據(jù)庫中,并且對數(shù)據(jù)進行相應(yīng)的增刪改查操作。
最常用的幾種ORM框架為:MyBatis、Hibernate和JFinal。
手擼ORM框架
這里,我們模擬的是手擼Hibernate框架實現(xiàn)ORM,小伙伴們也可以模擬其他的ORM框架實現(xiàn),核心原理都是相通的。如果大家在模擬其他框架手擼實現(xiàn)ORM時,遇到問題的話,都可以私聊我溝通,我看到的話,會第一時間回復(fù)大家。
好了,說干就干,我們開始吧。
@Table注解的實現(xiàn)
首先,我們創(chuàng)建一個io.mykit.annotation.jdk.db.provider Java包,在這個Java包創(chuàng)建一個@Table注解,@Table注解標(biāo)注在Java類上,表示當(dāng)前類會被映射到數(shù)據(jù)庫中的哪張數(shù)據(jù)表上,如下所示。
- 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注解的實現(xiàn)
同樣的,在io.mykit.annotation.jdk.db.provider包下創(chuàng)建一個@Column注解,@Column注解標(biāo)注在類中的字段上,表示當(dāng)前類中的字段映射到數(shù)據(jù)表中的哪個字段上,如下所示。
- 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的小伙伴,應(yīng)該都會有所體會吧?沒錯,@Table注解和@Column注解,不管是在MyBatis框架還是Hibernate框架中,都會被使用到。這里,我們在收錄極簡版ORM框架時,也使用了這兩個經(jīng)典的注解。
創(chuàng)建實體類
在io.mykit.annotation.jdk.db.provider.entity包下創(chuàng)建實體類User,并且@Table注解和@Column注解會被分別標(biāo)注在User類上和User類中的字段上,將其映射到數(shù)據(jù)庫中的數(shù)據(jù)表和數(shù)據(jù)表中的字段上,如下所示。
- 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 + ", name=" + name + "]";
- }
- }
注解解析類的實現(xiàn)
在io.mykit.annotation.jdk.db.provider.parser包中創(chuàng)建一個AnnotationParser類,AnnotationParser 類是整個框架的核心,它負責(zé)解析標(biāo)注在實體類上的注解,并且將對應(yīng)的實體類及其字段信息映射到對應(yīng)的數(shù)據(jù)表和字段上,如下所示。
- 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();
- // 判斷參數(shù)是不是 in 類型參數(shù) 1,2,3
- if (value.contains(",")) {
- //去掉value中的,
- String sqlParams = value.replace(",", "").trim();
- //value中都是純數(shù)字
- 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框架就實現(xiàn)好了,不過實現(xiàn)完還不行,我們還要對其進行測試驗證。
測試類的實現(xiàn)
在io.mykit.annotation.jdk.provider包下創(chuàng)建AnnotationTest 類,用以測試我們實現(xiàn)的極簡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')
可以看到,我們在測試程序中,并沒有在測試類中傳入或者執(zhí)行任何SQL語句,而是直接創(chuàng)建User類的對象,并調(diào)用AnnotationParser#assembleSqlFromObj()進行解析,并且將對應(yīng)的實體類對象轉(zhuǎn)換為SQL語句返回。
本文轉(zhuǎn)載自微信公眾號「冰河技術(shù)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系冰河技術(shù)公眾號。