前言

在SpringBoot环境支持中,通过反射机制获取到实体类的属性列表,判断属性是否是主键,是否该属性不能为空,是否该属性是唯一索引。这我的想法是根据每个字段的上注解来进行特定的代码编写。例如IDField、FieldUnique、NotNull等注解。

自定义注解

自定义注解有很广的用途。例如在SpringAOP中,可以在需要拦截的方法上添加自定义注解或官方注解。
在Java中,class是类、abstract是抽象类、interface是接口而@interface就是注解

import java.lang.annotation.*;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldUnique {
	String value();
}

其中@Documented、@Target、@Retention是元注解,可以称为注解的注解 @Target 该注解是判断当前注解是使用在哪里的注解,常用的类别有如下:

ElementType.TYPE 针对实体类的注解,元注解都是类注解
ElementType.METHOD 针对方法的注解,RequestMapping,GetMapping等是方法注解
ElementType.FIELD 针对字段的注解,Id,Column等是字段注解
ElementType.PARAMETER 针对方法中参数的注解,Param,PathParam等是参数注解



@Retention 该注解是判断设定当前注解的生命周期,有如下类别:

RetentionPolicy.SOURCE 当前注解只存在于源码中,编译为class的时候丢弃。
RetentionPolicy.CLASS 当前注解存在于class文件中,JVM运行的时候丢弃。
RetentionPolicy.RUNTIME 注解不仅存在于class中,JVM运行时也存在,反射的时候可以获取到。

@Documented是表明了当前注解会被javadoc记录。

Java反射

之前学习的过程中,写的一篇关于反射的文章,还不完善。

在反射实体类时,主要的就是需要获取到所有属性,这就可以使用

Class clz = Class.forName("");
Type[] types = clz.getDeclaredFields();//获取所有状态的属性,

然而有些实体类有继承父类,那么也需要获取到父类中的属性。

Class superclz = clz.getSuperclass();
Type[] types = superclz.getDeclaredFields();

再用笨方法,把这两个属性都放到List中。

List<Field> fields = new ArrayList<>();
Collections.addAll(fields, superfields);
Collections.addAll(fields, fs);

针对每个字段配置的注解,进行针对性的SQL语句拼接

if (f.getAnnotation(IDField.class) != null) {//判断是否是主键
        sb.append(underline(name)).append(" ").append(convertType(type)).append(" NOT NULL");
        primarykey = name;
    } else if (f.getAnnotation(NotNull.class) != null) {//判断是否不可为空
        sb.append(underline(name)).append(" ").append(convertType(type)).append(" NOT NULL");
    } else {
        sb.append(underline(name)).append(" ").append(convertType(type));
    }
    sb.append(",");
    if (f.getAnnotation(FieldUnique.class) != null) {
        uniques.add(name);
    }

驼峰转下划线

public static String underline(String name) {
        name = (name.charAt(0) + "").toLowerCase() + name.substring(1);
        Pattern p = Pattern.compile("[A-Z]");
        Matcher matcher = p.matcher(name);
        StringBuffer sb = new StringBuffer();
        //循环匹配到[A-Z]
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

其中appendReplacement方法有两个参数(StringBuffer,replaceContext),作用是
将匹配到的值,替换成replaceContext,并且将第一次匹配到的和下一次匹配到的之间的字符都追加到stringBuffer中。例如:baseUser 其中第一次匹配到U,那么将U替换成_u,并将替换的字符追加到匹配到字符之前的字符之后形成base_u。

而appendTail方法是将剩余的字符ser进行追加形成base_user。

实现思路

创建表之前将实体类的名称根据驼峰转换成_的方式。然后需要获取到当前数据库中是否存在即将创建的表,再通过反射获取到所有的属性,判断每个属性的指定类别,接下来需要判断属性的类型再把类型转换成数据库类型,其中可能还有实体类的类型,就需要设置外键关联,在设置外键关联时,就需要创建关联的实体类的表,而创建外键的语句中需要关联表的主键,那么就需要获取指定表的主键。

拼接成一个SQL语句,就可以执行SQL语句。

PreparedStatement ps = conn.prepareStatement(createSQL);
int j = ps.executeUpdate();

附上github代码地址