手把手教你寫一個java的orm(三)

何白白發表於2019-01-21

使用反射解析class

上一篇我們完成了class到表對映關係的建立,但是這個並不能被程式碼正確處理,我們還需要讓程式能夠正確的識別這些對映關係。

這一篇主要講的是建立一個從class到表的模型,使我們在class上新增的註解能夠正確的被識別並處理。這裡主要用到的是java中的反射相關的知識。不瞭解的同學請自行百度一下,不是很難~,另外這一篇也會稍微的提到一點反射的用法。

現在開始。

我們主要的需求是根絕我們新增的註解,生成各種型別的sql語句,所以我們首先要能夠獲取新增在java類名,屬性,方法上的註解,並獲取註解中的值。所以第一步:

獲取特定的註解

  1. 獲取class上的註解

    在Java的Class中提供了.getAnnotation(annotationClass)的方法,

    這裡我在這個方法的基礎上包了一層,主要是使用斷言做了一些驗證,在驗證不通過的時候丟擲我認識的異常資訊。下面的方法都是如此處理的。

    /**
     * 獲取類上的註解
     *
     * @param clz
     * @param annotationClass
     * @param <T>
     * @return
     */
    public static <T extends Annotation> T getAnnotation(Class<?> clz, Class<T> annotationClass) {
        Assert.notNull(clz, CLASS_NOT_NULL);
        Assert.notNull(annotationClass, ANNOTATIONCLASS_NOT_NULL);
        return clz.getAnnotation(annotationClass);
    }
  2. 獲取屬性上的註解

    /**
     * 獲取屬性上的註解
     *
     * @param field
     * @param annotationClass
     * @param <T>
     * @return
     */
    public static <T extends Annotation> T getAnnotation(Field field, Class<T> annotationClass) {
        Assert.notNull(field, FIELD_NOT_NULL);
        Assert.notNull(annotationClass, ANNOTATIONCLASS_NOT_NULL);
        return field.getAnnotation(annotationClass);
    }

這裡我們獲取註解的方法就寫完了,可以通過這些方法獲取我們需要的註解,並通過獲取到的註解拿到其中的值。

大致是這樣的:(這裡的User.class)可以看 上一篇。

//程式碼(獲取class上的註解)
@Test
public void getClassAnnotation() {
    Table annotation = EntityUtils.getAnnotation(User.class, Table.class);
    System.out.println(annotation.name());
}
//輸出結果
user
//程式碼(獲取field上的註解)
@Test
public void getFieldAnnotation() throws NoSuchFieldException {
    Class userClass = User.class;
    //getDeclaredField和getField是有一定區別的,這裡用getDeclaredField
    Field field = userClass.getDeclaredField("createDate");
    Column annotation = EntityUtils.getAnnotation(field, Column.class);
    System.out.println(annotation.name());
}
//輸出結果
create_date

這樣就可以獲取到我們在class上新增的註解,以及註解中的值了。下面是第二步

獲取Id以及Column

這裡依然是通過反射實現,主要就是獲取到這個class中的所有的屬性(只屬於這個class的,不包括父類)後,迴圈遍歷一遍,根據每個屬性上不同的註解加以區分就好了。這裡為了簡單,我定義了幾個方法:

  1. boolean isTable(Class aClass)

    是不是新增了@Table

  2. boolean isColumn(Field field)

    是不是新增了@Column

  3. boolean isId(Field field)

    是不是新增了@Id

這幾個方法的具體程式碼我就不貼出來了,很簡單的。下面是取出一個class中所有屬性的程式碼:

for (Field field : clz.getDeclaredFields()) {
    if (isColumn(field)) {
        //執行需要的操作。
    }
}

在這個遍歷個過程中我們可以新建一個類,裡面用來存放表和class的各種對應關係。比如:

  1. class屬性名稱與表欄位名稱的對應。
  2. 表中的id是class中哪一個屬性。
  3. 表的欄位名稱對應的是class裡的那個個屬性。
  4. 等等~~

程式碼大致是這樣的 EntityTableRowMapper.java

    /**
     * id的欄位名稱
     */
    private String idName = null;

    /**
     * table對應的class
     */
    private Class<T> tableClass = null;

    /**
     * 對應的資料庫名稱
     */
    private String tableName = null;

    /**
     * 表中所有的欄位
     */
    private Set<String> columnNames = null;

    /**
     * 表中所有的欄位對應的屬性名稱
     */
    private Set<String> fieldNames = null;

    /**
     * 屬性名稱和資料庫欄位名的對映
     * K: 屬性名
     * V:表欄位名稱
     */
    private Map<String, String> fieldNameColumnMapper = null;

    /**
     * 資料庫欄位名和class屬性的對映
     * K:表欄位名稱
     * V:class屬性
     */
    private Map<String, Field> columnFieldMapper = null;

這些用來描述表和class之間的關係就已經夠用了。只要按照關係將裡面的資料一一填充完畢就好。我寫了一個方法來填充這些資料,程式碼是這樣的:

//這裡只是示例
Class clz = User.class();
//這裡是主要程式碼
EntityTableRowMapper mapper = new EntityTableRowMapper();
Map<String, Field> columnFieldMap = EntityUtils.columnFieldMap(clz);
int size = columnFieldMap.size();
Map<String, String> fieldNameColumnMapper = new HashMap<>(size);
Set<String> columnNames = new HashSet<>(size);
Set<String> fieldNames = new HashSet<>(size);
mapper.setTableClass(clz);
mapper.setTableName(EntityUtils.tableName(clz));
mapper.setIdName(EntityUtils.idColumnName(clz));
mapper.setColumnFieldMapper(columnFieldMap);
for (Map.Entry<String, Field> entry : columnFieldMap.entrySet()) {
    String columnName = entry.getKey();
    Field field = entry.getValue();
    String fieldName = field.getName();
    fieldNameColumnMapper.put(fieldName, columnName);
    fieldNames.add(fieldName);
    columnNames.add(columnName);
}
mapper.setColumnNames(columnNames);
mapper.setFieldNameColumnMapper(fieldNameColumnMapper);
mapper.setFieldNames(fieldNames);

這裡漏了一個Map<String, Field> columnFieldMap = EntityUtils.columnFieldMap(clz);的程式碼,在下面補上:

/**
 * 獲取Table的列名與Entity屬性的對映Map
 *
 * @param clz
 * @param <T>
 * @return
 */
public static <T> Map<String, Field> columnFieldMap(Class<T> clz) {
    Field[] declaredFields = clz.getDeclaredFields();
    Map<String, Field> map = new HashMap<>(declaredFields.length);
    for (Field field : declaredFields) {
        if (isColumn(field)) {
            map.put(columnName(field), field);
        }
    }
    return map;
}

這時候,解析class裡面的工作就完成了,下一步就是要通過拿到的資料來拼裝sql了。

我下一篇再寫^_^~

相關文章