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


使用反射解析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了。

我下一篇再寫_


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM