资讯专栏INFORMATION COLUMN

注解的奇妙之旅

Markxu / 2474人阅读

摘要:注解是弃用不再使用的意思。是处理源码级别的注解,它会生成新的字节码或其它文件。这个该怎么实现呢我们需要在启动之后文件转为字节码文件之前,就需要生成对应的和方法,因为它只在编译期有效。我们在启动后,会根据注解,来创建相应的数据表。

导读

模拟hibernate的注解,创建数据表的源码地址:https://gitee.com/zbone/myanno

注解释义

java开发人员对注解,应该不会很陌生。我们在开发的过程中,经常会用到注解,那么,什么是注解呢?

注解,也被称为元数据,为我们在代码中添加信息,提供了一种形式化的方法,使我们在稍后某个时刻,可以非常方便地使用这些原数据(thinking in java)。

这句话是什么意思?举一个hibernate的@Table注解,我们在实体类上定义该注解,它不会立即生效。我们启动Tomcat时,并借助spring工具,便触发该注解,从而创建数据表。也就是说,我们先定义注解,等到合适的时间,我们在使用该注解。

注解定义

我们定义注解非常简单,只要在interface前加上@符号,这就是一个注解了,如代码所示:

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注解,光定义注解是没有意义的。它要配合注解处理器才能生效,我会在下文提到如何写注解处理器。

内置三大注解 override注解

我们在开发的过程中,经常会用到override注解,如代码所示: @Override public Result> listAccountById(Long memberId);该注解表示当前方法将覆盖超类中的方法。

阿里巴巴规范要求,如果当前方法覆盖超类的方法,必须写上override注解。因为我们如果不小心拼写错误,或者方法签名对不上覆盖的方法,编译器就会发出错误地提示。我们如果忘记写override,但这并不影响使用。比如,我们需要重写接口AccountService的auditAcct方法签名,但并没有加上override注解,编译器就会错误的提示,但不是报错。

java除了override内置注解,还有Deprecated注解和SuppressWarnings注解。

Deprecated注解

Deprecated是弃用、不再使用的意思。我们如果用其来修饰类、方法、属性、构造器等,当我们去调用被其修饰的类、方法、属性、构造器后,编辑器就会发出警告信息。我此时有一个ArrayUtil类,其中有个方法,判断数组是否为空数组,如代码所示:

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);
    }
 }

这也不影响使用,只是编辑器不建议你使用了,如图所示:

SuppressWarnings注解

SuppressWarnings拆开来看,Suppress是压制的意思,Warnings是警告的意思。它表示为压制警告,即关闭不当的警告信息。比如,还是上面的ArrayUtil类,其中有一个containsAny(String param, String[] filters)方法,它表示数组中是否包含某个值,但目前没有用到,其报出这个提示:

我们在上面的方法添加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表 注意事项

我们既然想通过注解创建SQL语句,我们必须非常熟悉SQL语句。我们现在想用纯SQL语句来创建表,以及表之间的外键关系。我们必须在创建外键表之前创建主表,否则,SQL会报出相应的错误。

我们拿到了实体类,如何区分主表和外键表,设置主表和外键的先后关系,即先创建主表,再创建外表。

怎么将java类型转为SQL类型?

对于columnDefinition方法来说,如果其存储了当前字段自定义的类型长度,而java类型转化为SQL类型时,默认是字段类型的最大值。如果该类型长度小于默认的字段类型,我们怎么拿到自定义类型?

。。。。。。

原生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 sortEntityPath() {
    int maxPriority = getMaxPriority();
    Set entityPathSet = new LinkedHashSet<>();
    for (int i = 1; i <= maxPriority; i++) {
        if (entityPathMap != null && entityPathMap.size() > 0) {
            for (Map.Entry 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 dbTableMap = new LinkedHashMap<>();链表式的map,这里也涉及到先后顺序。因为链表存储的是指向下一个对象的节点,对象最开始在哪个位置,以后也在那个位置,位置始终是不会变的。

以下是生成SQL语句的代码,可能有点长,细心看,还是能看完的哈:

static {
        Set 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 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&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&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
     * 

* 批量删除 */ public static void batchDropTable(Map tableSqls) { if (isKeyBlank(tableSqls)) { throw new RuntimeException("表名为空,请核查后再删除表"); } for (Map.Entry 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 tableSQLs = TableInit.getDbTableMap(); if (isNotBlank(ignoreExistTable) && ignoreExistTable.equalsIgnoreCase("true")) { // 如果表名为空,则无法删除 if (isKeyBlank(tableSQLs)) { return false; } for (Map.Entry entry : tableSQLs.entrySet()) { boolean tableExists = existsTable(entry.getKey()); // 如果表存在,则跳过循环 if (tableExists) { continue; } tranSuccess = CreateTable(entry.getKey(), conn, entry.getValue().toString()); } } else { // map数据反转 Map reverseTableSqls = reverseMap(tableSQLs); // 如果表名为空,则无法删除 if (isKeyBlank(reverseTableSqls)) { return false; } // 先删除所有表,在创建表 batchDropTable(reverseTableSqls); for (Map.Entry 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框架,确实学到了不少东西,可能这就是成长吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77535.html

相关文章

  • Spring之旅第七站:面向切面编程(AOP)

    摘要:面向切面的本章主要内容面向切面编程的基本原理通过创建切面使用注解为切面注入依赖。什么是面向切面编程切面能够帮我们模块化横切关注点。在使用面向切面编程时,我们仍然在一个地方定义通知功能,而无需修改受影响的类。切面切面是通知和切点的结合。 面向切面的Spring 本章主要内容: 面向切面编程的基本原理 通过POJO创建切面 使用@Aspect注解 为AspectJ切面注入依赖。 说明 ...

    赵连江 评论0 收藏0
  • Spring之旅 - 3.0、3.1、4.0导引

    摘要:之旅简化开发的使命简化开发为了降低开发的复杂性,采取如下关键策略基于的轻量级和最小侵入性编程通过依赖注入和面向接口实现松耦合基于切面和惯例进行声明式编程通过切面和模版减少样式代码依赖注入耦合性具有两面性一方面,紧密耦合的代码难以测试难以复 Spring之旅 简化Java开发 Spring的使命:简化Java开发 为了降低Java开发的复杂性,采取如下关键策略:基于POJO的轻量级和最...

    leon 评论0 收藏0
  • Spring之旅第二站:bean、新特性。。。

    摘要:除了,还简单介绍了对的支持,可以帮助应用将散落在各处的逻辑汇集于一处切面。当装配的时候,这些切面能够运行期编织起来,这样就能呢个非常有效的赋予新功能。 第1章 Spring之旅 说明 1、本文参考了《Spring 实战》重点内容,参考了GitHub上的代码 2、每个人的学习方式不一样,但目的是一样的,活学活用。最近一直在听《我们不一样》 3、本文只为记录作为以后参考,要想真正领悟Sp...

    luodongseu 评论0 收藏0
  • 第一章--Spring之旅

    摘要:两种方式来表示的应用上下文。日志,事务管理和安全这样的系统服务经常融入到具有核心业务逻辑的组件中去,这些系统服务通过被称为横切关注点。容器使用管理构成应用的组件,他会创建相互协作的组件之间的关联。的生命周期四俯瞰的风景线模块,,,, 完整代码请见:https://github.com/codercuixi...为了降低Java开发的复杂性,Spring采用了以下4种策略: 基于poj...

    pkwenda 评论0 收藏0
  • FEDAY2016之旅

    摘要:前戏补上参会的完整记录,这个问题从一开始我就是准备自问自答的,希望可以通过这种形式把大会的干货分享给更多人。 showImg(http://7xqy7v.com1.z0.glb.clouddn.com/colorful/blog/feday2.png); 前戏 2016/3/21 补上参会的完整记录,这个问题从一开始我就是准备自问自答的,希望可以通过这种形式把大会的干货分享给更多人。 ...

    red_bricks 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<