前言
在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();