註解的奇妙之旅

導讀

模擬hibernate的註解,建立數據表的源碼地址:https://gitee.com/zbone/myannohtml

註解釋義

java開發人員對註解,應該不會很陌生。咱們在開發的過程當中,常常會用到註解,那麼,什麼是註解呢?java

註解,也被稱爲元數據,爲咱們在代碼中添加信息,提供了一種形式化的方法,使咱們在稍後某個時刻,能夠很是方便地使用這些原數據(thinking in java)。

這句話是什麼意思?舉一個hibernate的@Table註解,咱們在實體類上定義該註解,它不會當即生效。咱們啓動Tomcat時,並藉助spring工具,便觸發該註解,從而建立數據表。也就是說,咱們先定義註解,等到合適的時間,咱們在使用該註解。mysql


註解定義

咱們定義註解很是簡單,只要在interface前加上@符號,這就是一個註解了,如代碼所示:git

package com.zbystudy.anno;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Created By zby on 13:45 2019/3/31
 * 表名註解
 */
@Target(TYPE)
@Retention(RUNTIME)
public @interface Table {

    /**
     * 表名
     */
    String name() default "";

    /**
     * 表屬於哪一個數據庫
     */
    String catelog() default "";
}

這相似於hibernate中的@Table註解,光定義註解是沒有意義的。它要配合註解處理器才能生效,我會在下文提到如何寫註解處理器。spring


內置三大註解

override註解

咱們在開發的過程當中,常常會用到override註解,如代碼所示: @Override public Result<List<Account>> listAccountById(Long memberId);該註解表示當前方法將覆蓋超類中的方法。sql

阿里巴巴規範要求,若是當前方法覆蓋超類的方法,必須寫上override註解。由於咱們若是不當心拼寫錯誤,或者方法簽名對不上覆蓋的方法,編譯器就會發出錯誤地提示。咱們若是忘記寫override,但這並不影響使用。好比,咱們須要重寫接口AccountService的auditAcct方法簽名,但並無加上override註解,編譯器就會錯誤的提示,但不是報錯。數據庫

未標明override註解

java除了override內置註解,還有Deprecated註解和SuppressWarnings註解。數組


Deprecated註解

Deprecated是棄用、再也不使用的意思。咱們若是用其來修飾類、方法、屬性、構造器等,當咱們去調用被其修飾的類、方法、屬性、構造器後,編輯器就會發出警告信息。我此時有一個ArrayUtil類,其中有個方法,判斷數組是否爲空數組,如代碼所示:app

public class ArrayUtil {

    /**
     * Created By zby on 20:50 2019/2/13
     * 判斷字符串是否爲null
     */
    @Deprecated
    public static boolean isEmpty(Object[] objects) {
        if (null == objects || objects.length == 0)
            return true;
        for (Object object : objects) {
            if (null != object)
                return false;
        }
        return true;
    }
    
    public static void main(String[] args) {
        String[] str={"nihao","wohao"};
        boolean isEmpty=ArrayUtil.isEmpty(str);
        System.out.println(isEmpty);
    }
 }

這也不影響使用,只是編輯器不建議你使用了,如圖所示:框架

clipboard.png

SuppressWarnings註解

SuppressWarnings拆開來看,Suppress是壓制的意思,Warnings是警告的意思。它表示爲壓制警告,即關閉不當的警告信息。好比,仍是上面的ArrayUtil類,其中有一個containsAny(String param, String[] filters)方法,它表示數組中是否包含某個值,但目前沒有用到,其報出這個提示:

未寫SuppressWarnings註解

咱們在上面的方法添加SuppressWarnings註解,其便不會報出這樣的警告信息:

/**
 * Created By zby on 15:00 2019/2/14
 * 判斷參數是否在該數組中
 *
 * @param param   參數
 * @param filters 數組
 */
@SuppressWarnings("all")
public static boolean containsAny(String param, String[] filters) {
    if (isNotEmpty(filters)) {
        if (StringUtils.isNotBlank(param)) {
            for (String tmp : filters) {
                if (tmp.equals(param)) {
                    return true;
                }
            }
        }
    }
    return false;
}

以上三個註解是java內置的三大註解,Override和SuppressWarnings是源碼級別(RetentionPolicy.SOURCE)的註解,而Deprecated是運行時(RetentionPolicy.RUNTIME)註解。源碼級別和和運行時有什麼區別,這個會在下文中講解。

同時,除了以上三個內置註解,java還提供了四種自定義註解的註解,分別是@Target、@Retention、@Documented、@Inherited,這四種註解是我寫這篇文檔的重點,在下面便講解這四種註解,也成爲元註解。


元註解

元註解幫助咱們自定義註解,它自己包含四種註解,如下是四種註解的做用域:

元註解的信息


SOURCE和RUNTIME的區別

正如咱們上文提到的,光定義註解是徹底沒有意義,咱們須要手動書寫註解的處理器。

SOURCE

SOURCE是處理源碼級別的註解,它會生成新的字節碼或其它文件。好比說,咱們如今有一個javabean文件,咱們想經過一個註解來自動生成set和get方法。這個該怎麼實現呢?咱們須要在啓動jvm以後、.java文件轉爲字節碼(.class)文件以前,就須要生成對應的set和get方法,由於它只在JVM編譯期有效。

咱們事先定義好註解類,其是將屬性生成set和get方法,以下代碼所示

/**
 * Created By zby on 14:35 2019/4/11
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Property {
}

這裏,有一個項目的java類,如代碼所示:

public class Project {

    @Property
    private String name;

}

咱們這樣定義以後,預想在啓動了jvm後,生成以下.class文件

public class Project {
    
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

事實上,不會生成這樣的.class文件,爲何呢?生成這樣的.class文件超出了註解的功能,它須要再寫其它的工具類來實現。由於咱們定義好註解後,須要手動去寫註解的處理器,lombok就是這樣實現的。關於,如何書寫SOURCE的註解處理器,你能夠參考這個文檔:插件化註解處理API(Pluggable Annotation Processing API)

若是你的實體類上寫了lombok註解後,你能夠去看看當前java文件所對應的.class文件,其和java原文件是徹底的不一樣。


RUNTIME

RUNTIME不會生成新的文件,它只是在運行時,處理程序中的某些功能,好比hibernate的註解。咱們在啓動jvm後,hibernate會根據註解,來建立相應的數據表。可是,咱們定義好註解,也是沒有任何意義的。註解自己不會執行任何操做,全部的操做都是在咱們定義的處理器中執行。

如今有一個註解類,標記字段是不是主鍵:

/**
 * Created By zby on 23:17 2019/4/1
 * 自增主鍵
 */
@Target({METHOD,FIELD})
@Retention(RUNTIME)
public @interface Id {
}

它自己沒有任何意義,咱們能夠經過反射獲取註解,從而執行相應的操做。

在下文中,我會模擬hibernate的註解,來建立生成數據表的代碼,源碼的地址在:https://gitee.com/zbone/myanno


註解建立SQL表

注意事項

  1. 咱們既然想經過註解建立SQL語句,咱們必須很是熟悉SQL語句。咱們如今想用純SQL語句來建立表,以及表之間的外鍵關係。咱們必須在建立外鍵表以前建立主表,不然,SQL會報出相應的錯誤。
  2. 咱們拿到了實體類,如何區分主表和外鍵表,設置主表和外鍵的前後關係,即先建立主表,再建立外表。
  3. 怎麼將java類型轉爲SQL類型?
  4. 對於columnDefinition方法來講,若是其存儲了當前字段自定義的類型長度,而java類型轉化爲SQL類型時,默認是字段類型的最大值。若是該類型長度小於默認的字段類型,咱們怎麼拿到自定義類型?
  5. 。。。。。。

原生SQL語句

-- 若是學生表存在,即刪除學生表
DROP TABLE if EXISTS tb_student ;

-- 建立學生表
CREATE TABLE `tb_student` (
    `id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主鍵' PRIMARY KEY,
    `name` VARCHAR (255) COMMENT '姓名' NULL,
    `sex` bit (1) COMMENT '性別' NULL
) ENGINE = INNODB DEFAULT CHARACTER SET = utf8;

-- 若是課程表存在,則刪除課程表
DROP TABLE if EXISTS tb_course ;

-- 建立課程表
CREATE TABLE `tb_course` (
    `id`  int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵' PRIMARY KEY,
    `score`  double NULL DEFAULT 0 COMMENT '分數' ,
    `subject_name`  enum('SUBJECT_TYPE_CHINESE','SUBJECT_TYPE_MATH','SUBJECT_TYPE_ENGLISH') NULL  COMMENT '課程名稱,存儲枚舉key值' ,
    `teacher_name`  varchar(255)  NULL DEFAULT NULL COMMENT '老師名稱' ,
    `student_id`  int(11) NOT NULL,
    CONSTRAINT fk_student_course  FOREIGN KEY (`student_id`) REFERENCES `tb_student` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
)ENGINE=InnoDB DEFAULT CHARACTER SET=utf8;

-- 若是分數表存在,則刪除分數表
DROP TABLE if EXISTS tb_score ;

-- 建立分數表
CREATE TABLE `tb_score`(
    id BIGINT(20) not null AUTO_INCREMENT  comment '主鍵' primary key,
    score DOUBLE null  ,
    course_id BIGINT(18) null  COMMENT '外鍵是課程表' ,
    CONSTRAINT fk_course_score FOREIGN KEY(`course_id`) REFERENCES `tb_course`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8

以上是建立tb_student表和tb_course表的原生SQL語句,咱們就是經過註解來拼裝原生mysql語句,並調用jdbc的方法來建立數據表。以上的SQL語句也是經過我寫的這個框架代碼生成的


定義註解

註解

定義的註解太多了,我在這裏,只選擇Column註解展現,其它註解等大家下載好源碼,本身能夠選擇性地看。

/**
 * Created By zby on 14:07 2019/3/31
 * 表名類型的信息
 */
@Target({FIELD,METHOD})
@Retention(RUNTIME)
public @interface Column {

    /**
     * 字段名
     */
    String name() default "";

    /**
     * 字符長度
     */
    int length() default 255;

    /**
     * 字段是否爲空
     */
    boolean nullable() default true;

    /**
     * 是不是惟一值
     */
    boolean unique() default false;

    /**
     * 字段類型定義
     */
    String columnDefinition() default "";

}

設置權重

在將javabean對象和對象屬性轉化爲SQL語句以前,我將javabean對象設置了權重。權重用來區分將javabean對象生成SQL語句的前後順序,避免在未建立主表就建立了外鍵表的錯誤。於是,對類集合進行處理排序,也就是根據權重的前後順序。

這裏使用到的是LinkedHashSet類,其存儲的是Javaban的類名。我沒有使用HashSet類,由於鏈表具備前後順序,而散列表沒有前後順序。同時,這裏使用了遞歸,由於,當前外鍵表多是別的外鍵表的主表,這時,就須要再次遍歷,如核心代碼所示:

/**
 * Created By zby on 23:22 2019/4/2
 * 設置權重
 *
 * @param entityPath    實體類的路徑
 * @param priority      權重值
 * @param fieldTypeName 屬性類型名稱
 */
private static void setPriority(String entityPath, Integer priority, String fieldTypeName) {
    try {
        Class clazz = Class.forName(isNotBlank(entityPath) ? entityPath : fieldTypeName);
        Field[] fields = clazz.getDeclaredFields();
        if (ArrayUtil.isNotEmpty(fields)) {
            boolean hasManyToOne = false;
            for (Field field : fields) {
                ManyToOne manyToOne = field.getDeclaredAnnotation(ManyToOne.class);
                if (null != manyToOne) {
                    fieldTypeName = field.getType().getName();
                    hasManyToOne = true;
                    break;
                }
            }
            if (hasManyToOne) {
                setPriority(null, ++priority, fieldTypeName);
            } else {
                entityPathMap.put(ENTITY_PATH, priority);
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

你會看到上面是私有方法,我這邊只對外開放一個方法,對外開放的是設置權重後的實體類的類路徑名:

/**
 * Created By zby on 23:31 2019/4/2
 * 獲取排序後的值
 */
public static Set<String> sortEntityPath() {
    int maxPriority = getMaxPriority();
    Set<String> entityPathSet = new LinkedHashSet<>();
    for (int i = 1; i <= maxPriority; i++) {
        if (entityPathMap != null && entityPathMap.size() > 0) {
            for (Map.Entry<String, Integer> entry : entityPathMap.entrySet()) {
                int value = entry.getValue();
                if (value == i) {
                    entityPathSet.add(entry.getKey());
                }
            }
        }
    }
    return entityPathSet;
}

生成SQL語句

通過上面的排序以後,開始生成建立表的SQL語句,這裏面就要用到了反射。由於是運行時註解類型,可使用反射來獲取對象類型。

咱們將建立好的SQL語句放置在map中,key值是表名,value值是SQL語句,可是,咱們使用的是 private static Map<String, StringBuilder> dbTableMap = new LinkedHashMap<>();鏈表式的map,這裏也涉及到前後順序。由於鏈表存儲的是指向下一個對象的節點,對象最開始在哪一個位置,之後也在那個位置,位置始終是不會變的。

如下是生成SQL語句的代碼,可能有點長,細心看,仍是能看完的哈:

static {
        Set<String> entityFileNames = EntityPriority.sortEntityPath();
//        建立類
        for (String entityFileName : entityFileNames) {
            try {
                Class clazz = Class.forName(entityFileName);
//                獲取表名
                Table dbTable = (Table) clazz.getDeclaredAnnotation(Table.class);
                if (null == dbTable) {
                    System.out.println("no table annotation found in class:" + entityFileName);
                    continue;
                }
                String tableName = dbTable.name();
                String currClazzName = clazz.getSimpleName();
                if (isBlank(tableName)) {
                    System.out.println("no table name found in class:" + entityFileName);
                    continue;
                }
                StringBuilder tableBuilder = new StringBuilder("CREATE TABLE" + BLANK_OP + BACK_QUOTE + tableName + BACK_QUOTE + LEFT_BRACKET + LINE_FEED_OP);
//                設置外鍵信息
//                存儲的是外鍵信息
                Set<ForeignTable> foreignTables = new LinkedHashSet<>();
//                獲取屬性
                Field[] fields = clazz.getDeclaredFields();
                if (isNotEmpty(fields)) {
                    for (Field field : fields) {
//                        設置初始值
                        TableColumn tableColumn = ColumnBuilder.instance();
                        Column column = field.getDeclaredAnnotation(Column.class);
                        if (null != column) {
//                            獲取字段名
                            tableColumn.setColName(isBlank(column.name()) ? field.getName() : column.name());
                            tableColumn.setColLength(column.length());
                            tableColumn.setColDefinition(column.columnDefinition());
                            tableColumn.setColNull(column.nullable() ? NULL : NOT_NULL);
                            tableColumn.setColUnique(column.unique() ? "unique" : NUL_OP);
                        }
//                        主鍵
                        Id id = field.getDeclaredAnnotation(Id.class);
                        if (null != id) {
                            tableColumn.setColPk("primary key");
                            tableColumn.setColPkVal(tableColumn.getColName());
                        }
//                        自動增加
                        GeneratedValue generated = field.getDeclaredAnnotation(GeneratedValue.class);
                        if (null != generated) {
                            tableColumn.setIncrementStrategy(generated.strategy().name().equals("AUTO") ? "AUTO_INCREMENT" : NUL_OP);
                        }
                        // 獲取屬性類型,同時獲取字段長度
                        String typeName = field.getType().getSimpleName();
                        tableColumn.setColType(TypeTransformer.javaToSql(typeName));
//                        處理枚舉類型
                        Enumerated enumerated = field.getDeclaredAnnotation(Enumerated.class);
                        if (null != enumerated) {
                            String enumKey = NUL_OP;
                            if ("STRING".equals(enumerated.value().name())) {
                                for (Object obj : field.getType().getEnumConstants()) {
                                    enumKey += SINGLE_QUOTES + obj.toString() + SINGLE_QUOTES + SERIES_COMMA_OP;
                                }
                            } else if ("ORDINAL".equals(enumerated.value().name())) {
                                Object[] objects = field.getType().getEnumConstants();
                                for (int i = 0; i < objects.length; i++) {
                                    enumKey += SINGLE_QUOTES + i + SINGLE_QUOTES + SERIES_COMMA_OP;
                                }
                            } else {
                                continue;
                            }
                            enumKey = substring(enumKey, 0, enumKey.lastIndexOf(SERIES_COMMA_OP));
                            tableColumn.setColType("enum" + LEFT_BRACKET + enumKey + RIGHT_BRACKET);
                        }
//                        處理多對一的關係
                        ManyToOne manyToOne = field.getDeclaredAnnotation(ManyToOne.class);
                        if (manyToOne != null) {
                            CascadeType[] cascadeTypes = manyToOne.cascade();
                            if (ArrayUtil.isEmpty(cascadeTypes)) {
                                tableColumn.setColCascade("ON DELETE CASCADE ON UPDATE CASCADE");
                            } else if (ArrayUtil.isNotEmpty(cascadeTypes) && cascadeTypes.length == 1) {
                                tableColumn.setColCascade("ON DELETE " + transNoAction(cascadeTypes[0].name()) +
                                        " ON UPDATE CASCADE");
                            } else if (ArrayUtil.isNotEmpty(cascadeTypes) && cascadeTypes.length == 2) {
                                tableColumn.setColCascade("ON DELETE " + transNoAction(cascadeTypes[0].name())
                                        + " ON UPDATE " + transNoAction(cascadeTypes[1].name()));
                            } else {
                                continue;
                            }
                        }
//                      關聯表
                        JoinColumn joinColumn = field.getDeclaredAnnotation(JoinColumn.class);
                        if (null != joinColumn) {
                            ForeignTable foreignTable = ForeignTableBuilder.instance();
                            foreignTable.setForeignKeyName(joinColumn.name());
                            foreignTable.setCascade(tableColumn.getColCascade());
                            foreignTable.setForeignTableName(joinColumn.table());
                            tableColumn.setColName(joinColumn.name());
                            tableColumn.setForeignTable(joinColumn.table());
                            tableColumn.setColDefinition(joinColumn.columnDefinition());
                            tableColumn.setColNull(joinColumn.nullable() ? NULL : NOT_NULL);
//                            外鍵類型類型忘記填寫
                            Class fieldType = Class.forName(field.getType().getName());
                            if (isBlank(tableColumn.getForeignTable())) {
                                dbTable = (Table) fieldType.getDeclaredAnnotation(Table.class);
                                if (dbTable != null && isNotBlank(dbTable.name())) {
                                    tableColumn.setForeignTable(dbTable.name());
                                    foreignTable.setForeignTableName(tableColumn.getForeignTable());
                                }
                            }
                            foreignTable.setForeignName("fk" + UNDERLINE + classNameToProName(fieldType.getSimpleName()) +
                                    UNDERLINE + classNameToProName(currClazzName));
                            fields = fieldType.getDeclaredFields();
                            if (ArrayUtil.isNotEmpty(fields)) {
                                for (Field fkField : fields) {
                                    id = fkField.getDeclaredAnnotation(Id.class);
                                    if (null != id) {
                                        tableColumn.setColType(TypeTransformer.javaToSql(fkField.getType().getSimpleName()));
                                        column = fkField.getDeclaredAnnotation(Column.class);
//                                    設置外鍵表的關聯字段
                                        foreignTable.setForeignTablePk(null != column && isBlank(column.name()) ? column.name() : fkField.getName());
                                    }
                                }
                            }
                            foreignTables.add(foreignTable);
                        }
//                      處理columnDefinition = "int(11) NOT NULL COMMENT '外鍵是學生表'"和真實的字段
                        String colType = tableColumn.getColType();
                        String colDefinition = tableColumn.getColDefinition();
                        if (isNotBlank(colDefinition) && isNotBlank(colType)) {
                            String[] sqlNumberType = {"INT(11)", "INTEGER(11)", "BIGINT(20)", "VARCHAR(255)", "SMALLINT(6)", "NUMERIC(10)", "TINYINT(4)", "BIT(1)"};
                            if (ArrayUtils.contains(sqlNumberType, colType)) {
                                int colNum = Integer.parseInt(substring(colType, colType.indexOf(LEFT_BRACKET) + 1, colType.lastIndexOf(RIGHT_BRACKET)));
                                colType = substring(colType, 0, colType.lastIndexOf(LEFT_BRACKET));
                                if (StringUtils.containsAny(colDefinition, colType)) {
                                    int typeEndLength = StringHelper.getEndLength(colDefinition, colType);
//                                    COL_DEFINITION包含字符串,且同時包含(  )
                                    if (typeEndLength != 0 && StringUtils.containsAny(colDefinition, LEFT_BRACKET, RIGHT_BRACKET)) {
                                        String definitionNum = StringUtils.substring(colDefinition, typeEndLength + 1, colDefinition.indexOf(")"));
                                        if (Integer.parseInt(definitionNum) <= colNum) {
                                            tableColumn.setColType(colType + LEFT_BRACKET + Integer.parseInt(definitionNum) + RIGHT_BRACKET);
                                        }
                                        tableColumn.setColDefinition(StringUtils.remove(colDefinition, tableColumn.getColType()));
                                    }
                                }
                            }
                        }
                        tableBuilder.append(TAB_OP + BLANK_OP + tableColumn.getColName() + BLANK_OP
                                + tableColumn.getColType() + BLANK_OP
                                + tableColumn.getColNull() + BLANK_OP
                                + (tableColumn.getIncrementStrategy() != null ? tableColumn.getIncrementStrategy() + BLANK_OP : NUL_OP)
                                + (tableColumn.getColDefinition() != null ? tableColumn.getColDefinition() + BLANK_OP : NUL_OP)
                                + (tableColumn.getColPk() != null ? tableColumn.getColPk() : NUL_OP)
                                + SERIES_COMMA_OP + LINE_FEED_OP
                        );
                    }
                }
                if (foreignTables.size() > 0) {
                    for (ForeignTable foreignTable : foreignTables) {
                        tableBuilder.append(TAB_OP + BLANK_OP + "CONSTRAINT" + BLANK_OP +
                                foreignTable.getForeignName() + BLANK_OP + "FOREIGN KEY" + LEFT_BRACKET + BACK_QUOTE +
                                foreignTable.getForeignKeyName() + BACK_QUOTE + RIGHT_BRACKET + BLANK_OP + "REFERENCES" + BLANK_OP + BACK_QUOTE +
                                foreignTable.getForeignTableName() + BACK_QUOTE + LEFT_BRACKET + BACK_QUOTE +
                                foreignTable.getForeignTablePk() + BACK_QUOTE + RIGHT_BRACKET + BLANK_OP +
                                foreignTable.getCascade() + SERIES_COMMA_OP + LINE_FEED_OP
                        );
                    }
                }
                tableBuilder = new StringBuilder(tableBuilder.substring(0, tableBuilder.lastIndexOf(SERIES_COMMA_OP)));
                tableBuilder.append(LINE_FEED_OP + BLANK_OP + RIGHT_BRACKET + BLANK_OP
                        + EngineCharator.tablEengine + BLANK_OP + EngineCharator.tableCharacter);
                if (MapUtil.existKey(tableName, dbTableMap)) {
                    continue;
                }
                dbTableMap.put(tableName, tableBuilder);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

上面的代碼有點冗長,沒有作具體的細分,之後,會慢慢地優化。

生成數據表

通過上面的步驟後,此時,得到了數據表的SQL語句,開始調用jdbc底層的代碼。在執行建立數據表的代碼以前,還須要些配置文件:

#實體類的路徑,能夠採用ant風格
jdbc.package=com.zbystudy.po.*

#是否忽略已建立表,若是爲true,不刪除已建立的表
#若是爲false,則刪除全部的表,從新建立新表
jdbc.ignoreExistTable=false

jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull&amp;transformedBitIsBoolean=true&useSSL=true

有了配置文件,根據配置文件的條件,來建立數據表和刪除數據表。根據先建立主表、再建立外鍵表的順來建立表,根據先刪除外鍵表、再刪除主表的方式來刪除數據表。

/**
 * Created By zby on 16:12 2019/4/3
 * 建立表
 */
public class CreationTable {

    /**
     * Created By zby on 16:15 2019/4/3
     * 判斷表是否存在
     */
    public static boolean existsTable(String tableName) {
        if (isBlank(tableName)) {
            return false;
        }
        String sql = "SELECT column_name FROM information_schema.columns WHERE table_name=?";
        Connection conn = sqlConnectFactory.createConnect();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            ps.setString(1, tableName);
            rs = ps.executeQuery();
            while (rs.next()) {
                return true;
            }
            return false;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            ConnCloseUtil.closeConn(conn, ps, rs);
        }
        return false;
    }


    /**
     * Created By zby on 10:48 2019/4/8
     * 刪除表
     */
    public static boolean dropTable(String tableName) {
        if (isBlank(tableName)) {
            return false;
        }
        String sql = "DROP TABLE " + tableName;
        Connection conn = sqlConnectFactory.createConnect();
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(sql);
            int result = ps.executeUpdate();
            return result == 0 ? true : false;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            ConnCloseUtil.closeConn(conn, ps);
        }
        return false;
    }

    /**
     * Created By zby on 0:12 2019/4/9
     * <p>
     * 批量刪除
     */
    public static void batchDropTable(Map<String, StringBuilder> tableSqls) {
        if (isKeyBlank(tableSqls)) {
            throw new RuntimeException("表名爲空,請覈查後再刪除表");
        }
        for (Map.Entry<String, StringBuilder> entry : tableSqls.entrySet()) {
            String tableName = entry.getKey();
//            表不存在,跳過此循環
            if (!existsTable(tableName)) {
                continue;
            }
            dropTable(entry.getKey());
        }
    }

    /**
     * Created By zby on 9:30 2019/4/8
     * 建立數據表
     */
    public synchronized static boolean batchCreateTable() {
//        是否忽略已存在的表
        String ignoreExistTable = sqlConnectFactory.getProperties().getProperty("jdbc.ignoreExistTable");
        Connection conn = sqlConnectFactory.createConnect();
        boolean tranSuccess = false;
        try {
            conn.setAutoCommit(false);
            conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
            Map<String, StringBuilder> tableSQLs = TableInit.getDbTableMap();
            if (isNotBlank(ignoreExistTable) && ignoreExistTable.equalsIgnoreCase("true")) {
//                若是表名爲空,則沒法刪除
                if (isKeyBlank(tableSQLs)) {
                    return false;
                }
                for (Map.Entry<String, StringBuilder> entry : tableSQLs.entrySet()) {
                    boolean tableExists = existsTable(entry.getKey());
//              若是表存在,則跳過循環
                    if (tableExists) {
                        continue;
                    }
                    tranSuccess = CreateTable(entry.getKey(), conn, entry.getValue().toString());
                }
            } else {
//                map數據反轉
                Map<String, StringBuilder> reverseTableSqls = reverseMap(tableSQLs);
//                若是表名爲空,則沒法刪除
                if (isKeyBlank(reverseTableSqls)) {
                    return false;
                }
//                先刪除全部表,在建立表
                batchDropTable(reverseTableSqls);
                for (Map.Entry<String, StringBuilder> entry : tableSQLs.entrySet()) {
                    tranSuccess = CreateTable(entry.getKey(), conn, entry.getValue().toString());
                }
            }
            if (tranSuccess) {
                conn.commit();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            ConnCloseUtil.closeConn(conn);
        }
        return tranSuccess;
    }

    /**
     * Created By zby on 9:30 2019/4/9
     * 建立數據表
     *
     * @param tableName 表名
     * @param conn      數據庫鏈接對象
     * @param sql       建立表的執行語句
     */
    public static boolean CreateTable(String tableName, Connection conn, String sql) {
        if (conn != null && isNotBlank(sql)) {
            PreparedStatement ps = null;
            try {
                ps = conn.prepareStatement(sql);
                return ps.executeUpdate() == 0 ? true : false;
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException("添加表tableName=" + tableName + "失敗");
            } finally {
                ConnCloseUtil.closeState(ps);
            }
        }
        return false;
    }

}

測試建立表

萬事俱備,只欠東風,既然寫好了代碼,那麼,就測試能不能建立成功,如下是測試代碼:

package com.zbystudy;

import com.zbystudy.core.vo.CreationTable;
import org.junit.Test;

/**
 * Created By zby on 11:32 2019/4/9
 */
public class CreationTableTest {
    @Test
    public void test(){
        boolean tracSucc = CreationTable.batchCreateTable();
        if (tracSucc) {
            System.out.println("建立數據表成功");
        } else {
            System.out.println("建立數據表失敗");
        }
    }
}

輸出結果如圖所示:

輸出結果

查看數據庫,發現有生成的數據表,表示是真的生成了數據表:

生成的數據表

總結

經過模擬hibernate框架,確實學到了很多東西,可能這就是成長吧。

相關文章
相關標籤/搜索