文章目录
- 一、定义注解
- 二、注解处理器
- 三、工具类
- 四、测试类
完善《java高级程序设计》中第四章注解的4.5实例
通过扫描指定路径下的所有类,找到含有指定注解的实体,解析这些实体,并生成对应的SQL命令,在数据库中创建相应的表
一、定义注解
用于指明那些类需要映射成数据库的字段
/**
* @Auther: Parsifal
* @Date: 2021/03/30/20:12
* @Description: 用于指明那些类需要映射成数据库的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
//字段的名字
public String value() default "";
//约束条件:
//是否可以为空
public boolean nullable() default true;
//字段长度
public int length() default -1;
}
用于指明那些需要映射成数据库的表
/**
* @Auther: Parsifal
* @Date: 2021/03/30/20:10
* @Description:
* 用于指明那些需要映射成数据库的表
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
//数据库映射后表的名字
public String value() default "";
}
作用与属性,表示属性作为映射表的主键
/**
* @Auther: Parsifal
* @Date: 2021/03/30/20:14
* @Description: 作用与属性,表示属性作为映射表的主键
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
public String value() default "";
}
二、注解处理器
注解处理器接口
/**
* @Auther: Parsifal
* @Date: 2021/03/31/15:39
* @Description: 注解处理器
*/
public interface IProcessor {
public String process(String url)throws Exception;
}
处理器实现类
- 通过扫描器获得类的信息,同过TableInfo 的toStirng方法获得该类的sql语句
/**
* @Auther: Parsifal
* @Date: 2021/03/31/15:40
* @Description:
*/
public class TableProcessor implements IProcessor {
@Override
public String process(String url) throws Exception {
//扫描 url下的实体
Scanner scanner = new Scanner(url);
//获得实体的Class<?>
List<Class<?>> entities = scanner.getEntity();
//获得创建表和属性的sql
StringBuilder sql = new StringBuilder();
entities.forEach(entity->{
TableInfo tableInfo = new TableInfo();
TableInfo parse = tableInfo.parse(entity);
if (parse!=null){
sql.append(tableInfo.toString());
}
});
return sql.toString();
}
}
三、工具类
定义的常量
/**
* @Auther: Parsifal
* @Date: 2021/03/30/21:15
* @Description:
*/
public class Symbol {
public static final String BLANK=" ";
public static final String TAB="\t";
public static final String LINE="\n";
}
Scanner类
- 扫描指定路径下的.java文件并获取该类信息
/**
* @Auther: Parsifal
* @Date: 2021/03/30/21:25
* @Description: 扫描指定路径下的实体
*/
public class Scanner {
private String path;
private File file;
public Scanner(String path) {
this.path = path;
file = new File(path);
}
/**
* 获取src目录下的指定路径的实体类信息
* 路径格式(src开头): src\EntityMapping\Entity
* @return
* @throws ClassNotFoundException
*/
public List<Class<?>> getEntity() throws ClassNotFoundException {
// 获得该文件下的作用子文件对象
String[] list = file.list();
List<Class<?>> classes =new ArrayList<>();
// path 替换/为 .
this.path= path.replace('\\','.');
//去除文件后的后缀名 '.java'
this.path = path.substring(4);
if (list == null){
return null;
}
for (String s : list) {
s =s.split("\\.")[0];
//获得实体的类信息
classes.add(Class.forName(path+"."+s));
}
return classes;
}
//测试
public static void main(String[] args) throws ClassNotFoundException {
Scanner scanner = new Scanner("src\\EntityMapping\\Entity");
List<Class<?>> entity = scanner.getEntity();
System.out.println(entity);
}
}
ColumInfo类
- 储存数据库中表的某一字段的信息,即是类中需要映射的属性信息
/**
* @Auther: Parsifal
* @Date: 2021/03/30/20:26
* @Description:
* 用于描述数据库中某一字段的信息
*/
public class ColumnInfo {
//字段名称
private String columnName;
//字段类型
private Class<?> type;
//是否是主键
private boolean isID =false;
//是否可为空
private boolean nullable=true;
//字段长度
private int length=32;
//该字段是否需要保存到数据库中
private boolean needPersist = false;
/**
* 根据Field对象,解析该字段信息
* @param field 字段
* @return
*/
public ColumnInfo parse(Field field){
this.columnName =field.getName();
this.type = field.getType();
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
//如果注解是@Column,则获取字段信息
if (annotation.annotationType().equals(Column.class)){
this.needPersist=true;
Column column = (Column) annotation;
if (!column.value().equals("")){
//如果字段的名字不为空,则获取该字段的名字
this.columnName=column.value();
}
this.nullable=column.nullable();
if (column.length()!=-1){
//如果该字段的长度不为默认值-1,则获取该长度
this.length = column.length();
}
}
//如果该注解是@ID
else if (annotation.annotationType().equals(ID.class)){
this.needPersist=true;
ID id = (ID)annotation;
this.isID = true;
//如果用户设置了value值,则以value值作为字段
if (!id.value().equals("")){
this.columnName =id.value();
}
}
}
if (this.needPersist){
return this;
}else {
return null;
}
}
/**
* 重写toString方法返回字段信息的sql语句
* @return
*/
@Override
public String toString() {
StringBuilder sql = new StringBuilder(columnName);
if (this.type.equals(String.class)){
sql.append(Symbol.BLANK + "VARCHAR(").append(this.length).append(")");
}else if(this.type.equals(Integer.class)){
sql.append(Symbol.BLANK+"INT");
}
if (this.isID){
sql.append(Symbol.BLANK+"PRIMARY KEY");
}
if(!this.nullable){
sql.append(Symbol.BLANK+"NOT NULL");
}
return sql.toString();
}
}
ColumnInfo 类解析解析的流程
- 获取Field对象的名称,作为字段名称
- 获取Field对象的类型,作为字段的类型
- 获取该属性上声明的注解集合,并遍历这些注解
- 如果注解是@Column,则表明该属性应映射成数据库中的字段
- 如果注解是@ID,则表明该属性作为数据库中表的主键
- 判断该属性是否需要持久化,如需要返回解析后的字段信息对象,否则返回nul1
TableInfo 类
- 储存数据库中的表的信息
/**
* @Auther: Parsifal
* @Date: 2021/03/30/22:29
* @Description: 储存数据库中表的信息
*/
public class TableInfo {
//表的名字
private String tableName;
//表对应的实体的信息
private Class<?> clazz;
//是否需要持久化储存
private boolean needPersist = false;
//该表的所有字段信息
private Map<String,ColumnInfo> columns = new HashMap<>();
public TableInfo parse(Class<?> clazz){
this.clazz = clazz;
this.tableName=this.clazz.getSimpleName();
Annotation[] annotations = this.clazz.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(Entity.class)){
//如果包含@Entity注解,则表明实体需要持久化储存
this.needPersist =true;
Entity entity = (Entity) annotation;
if (!entity.value().equals("")){
this.tableName = entity.value();
}
break;
}
}
if (this.needPersist){
//如果需要永久化储存
Field[] fields = this.clazz.getDeclaredFields();
for (Field field : fields) {
ColumnInfo column = new ColumnInfo();
column=column.parse(field);
if (column!=null){
this.columns.put(field.getName(),column);
}
}
return this;
}else {
return null;
}
}
@Override
public String toString() {
StringBuilder sql =new StringBuilder();
sql.append(Symbol.LINE)
.append("CREATE TABLE")
.append(Symbol.BLANK)
.append(this.tableName)
.append(Symbol.BLANK)
.append("(");
for (ColumnInfo value : this.columns.values()) {
sql.append(Symbol.LINE).append(Symbol.TAB).append(value.toString()).append(",");
}
sql.delete(sql.length()-1,sql.length());
sql.append(Symbol.LINE).append(");");
return sql.toString();
}
}
TableInfo 类解析实体的主要流程:
- 根据类型信息,获取实体类的简单名称作为表名。
- 获取在该类上使用的注解集合。
- 遍历这些集合。
- @ Entity注解的参数value.
- 如果参数不为空,则将表名设为此参数值,跳出循环。
- 如果没有找到该注解,则说明此实体不需要持久化存储,则返回null.
- 如果该实体需要持久化存储,则遍历该实体类型信息的所有属性描述对象列表,并将它们转换成表的字段信息对象,添加到字段信息map中。
- 返回解析好的表信息实体。
四、测试类
/**
* @Auther: Parsifal
* @Date: 2021/03/31/15:38
* @Description:
*/
public class MappingTest {
public static void main(String[] args) {
TableProcessor tp = new TableProcessor();
Connection connection = null;
PreparedStatement ps = null;
try {
String sql = tp.process("src\\EntityMapping\\Entity");
System.out.println(sql);
connection = JDBCUtil.getConnection();
if (connection != null) {
ps = connection.prepareStatement(sql);
}
if (ps != null) {
int i = ps.executeUpdate();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtil.close(ps, connection);
}
}
}
文件路径
Person实体
@Entity("Person")
public class Person{
@ID
@Column(nullable = false)
Integer id;
@Column(nullable = false,length = 16)
String name;
}
JDBCUtil类是我封装的一个工具类
结果
- sql语句
- 数据库