简单的自定义java类反向生成sql表的注解及功能类

用于简单的指定表名,类名,外键的类反向生成mysql表的注解功能

java包下的com.xx文件夹才能用,不过项目一般都是com文件夹开头,问题不大

用于注解属性,指定属性名或者外键,也可以不写

1
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {
public String value();//可以指定属性名 //不写就自己原名
public String foreignKey() default "";//外键 格式是 表明-键
}

指定主键

1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimaryKey {
public String value() default "";//可以指定属性名
}

注解在类上,标明要以哪个类为底

1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
public String value() default ""; //可以指定表名,没有就默认为类名
}
  1. 存储带有 @Table 注解的类
    • 使用 newC 列表存储找到的带有 @Table 注解的类。
  2. createTable 方法
    • 设置包名和包路径。
    • 调用 findPackageClasses 方法查找指定包路径下的所有类。
    • 遍历找到的类,如果类带有 @Table 注解,则生成对应的 SQL 创建表语句。
  3. findPackageClasses 方法
    • 递归查找指定包路径下的所有文件和目录。
    • 对于每个类文件,使用类加载器加载类,并检查是否带有 @Table 注解。
    • 将带有 @Table 注解的类添加到 newC 列表中。

生成 SQL 语句的逻辑

  • 对于每个带有 @Table 注解的类,获取其所有字段。
  • 检查字段是否带有 @Field 注解,如果没有,则使用字段名作为列名。
  • 根据字段类型(int, double, varchar 等)拼接 SQL 语句。
  • 如果字段带有 @PrimaryKey 注解,则将其设置为主键。
  • 如果字段带有外键,则生成外键约束。

执行 SQL 语句

  • 最后调用 db.doUpdate(sql) 方法执行生成的 SQL 创建表语句。

NewTable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import com.newtable.annotation.PrimaryKey;
import com.newtable.annotation.Table;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class NewTable {
private static Logger log = Logger.getLogger(NewTable.class); // 日志记录器

private static List<Class> newC = new ArrayList<>(); // 存储找到的带有 @Table 注解的类
private static DBHelper db = new DBHelper(); // 数据库操作类实例

public static void createTable() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
String packageName = "com";
String packagePath = System.getProperty("user.dir") + "\\src\\main\\java\\com";
packagePath = packagePath.replaceAll("\\\\", "/");


//查找此包下的文件
findPackageClasses(packagePath, packageName);

//目前我的规则是必须有@Table,才能创建表
//@Field是用来指定名字的,没有就用默认名
//@PrimaryKey用来指定主键
String sql = "";

for (Class<?> c : newC) {
// 已经找到所有带@Table的类
// 现在开始遍历每个类的每个属性并做好拼接
Object o = c.newInstance();

// 表名
String tableName = ((Table) c.getAnnotation(Table.class)).value();
if (tableName == null || "".equals(tableName)) {
tableName = c.getSimpleName();
}

Field[] fields = c.getDeclaredFields();
StringBuilder tableSql = new StringBuilder("CREATE TABLE " + tableName + " (");

StringBuilder foreignKeys = new StringBuilder();

for (Field field : fields) {
// 判断是否有注解,没有就默认值
Class<?> type = field.getType();

// 属性名
String fieldName = "";
if (field.getAnnotation(com.newtable.annotation.Field.class) == null || "".equals(field.getAnnotation(com.newtable.annotation.Field.class).value())) {
// 说明没有指定值
fieldName = field.getName();
} else {
fieldName = field.getAnnotation(com.newtable.annotation.Field.class).value();
}

String foreignTable = ""; // 对应的表
String foreign = ""; // 外键字段名
// 现在是外键
if (field.getAnnotation(com.newtable.annotation.Field.class) != null && !"".equals(field.getAnnotation(com.newtable.annotation.Field.class).foreignKey())) {
// 说明有外键
String temp = field.getAnnotation(com.newtable.annotation.Field.class).foreignKey();
String[] split = temp.split("-");
foreignTable = split[0];
foreign = split[1];
}

if ("int".equals(type.getName()) || "java.lang.Integer".equals(type.getName()) || "byte".equals(type.getName()) || "java.lang.Byte".equals(type.getName()) || "short".equals(type.getName()) || "java.lang.Short".equals(type.getName())) {
if (field.getAnnotation(PrimaryKey.class) != null) {
tableSql.append(fieldName).append(" INT PRIMARY KEY AUTO_INCREMENT,");
} else {
tableSql.append(fieldName).append(" INT,");
if (!foreign.equals("")) {
foreignKeys.append("FOREIGN KEY (").append(fieldName).append(") REFERENCES ").append(foreignTable).append("(").append(foreign).append("),");
}
}
} else if ("double".equals(type.getName()) || "java.lang.Double".equals(type.getName()) || "float".equals(type.getName()) || "java.lang.Float".equals(type.getName())) {
if (field.getAnnotation(PrimaryKey.class) != null) {
tableSql.append(fieldName).append(" DOUBLE PRIMARY KEY AUTO_INCREMENT,");
} else {
tableSql.append(fieldName).append(" DOUBLE,");
if (!foreign.equals("")) {
foreignKeys.append("FOREIGN KEY (").append(fieldName).append(") REFERENCES ").append(foreignTable).append("(").append(foreign).append("),");
}
}
} else {
if (field.getAnnotation(PrimaryKey.class) != null) {
tableSql.append(fieldName).append(" VARCHAR(255) PRIMARY KEY AUTO_INCREMENT,");
} else {
tableSql.append(fieldName).append(" VARCHAR(255),");
if (!foreign.equals("")) {
foreignKeys.append("FOREIGN KEY (").append(fieldName).append(") REFERENCES ").append(foreignTable).append("(").append(foreign).append("),");
}
}
}
}

// 移除最后一个逗号
tableSql.setLength(tableSql.length() - 1);

// 添加外键约束
if (foreignKeys.length() > 0) {
tableSql.append(", ").append(foreignKeys.toString().substring(0, foreignKeys.length() - 1));
}

tableSql.append(")");

sql = tableSql.toString();
System.out.println(sql);
db.doUpdate(sql);
}


}


private static void findPackageClasses(String packagePath, String packageName) throws UnsupportedEncodingException {
if (packagePath.startsWith("/")) {
packagePath = packagePath.substring(1); // 去掉路径开头的斜杠
}
packagePath = URLDecoder.decode(packagePath, "utf-8"); // 防止路径中文,统一转utf-8
// 获取路径下所有的文件
File file = new File(packagePath);
File[] classFiles = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {//过滤文件,只保留文件夹和java类
return pathname.isDirectory() || pathname.getName().endsWith(".java"); // 过滤目录和 .java 文件
}
});

if (classFiles != null && classFiles.length > 0) {
for (File classFile : classFiles) {
if (classFile.isDirectory()) {
findPackageClasses(classFile.getAbsolutePath(), packageName + "." + classFile.getName()); // 递归查找目录
} else {
if (!classFile.getName().endsWith(".java")) {
continue; // 跳过非 .java 文件
}
// 使用类加载器加载 class 文件
URLClassLoader uc = new URLClassLoader(new URL[]{});
try {
//loadClass只能用java下面的包.xx来找想要的java类
Class cls = uc.loadClass(packageName + "." + classFile.getName().replace(".java", ""));
if (cls.getAnnotation(Table.class) != null) {
log.info(cls); // 记录找到的类
newC.add(cls); // 添加到列表
}
} catch (Exception e) {
e.printStackTrace(); // 处理异常
}
}
}
}
}
}

以下是基于jdbc的简单工具类,封装了统一执行mysql语句的代码,避免重复写

DBHelper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class DBHelper {

// 如何来获取一个Connection
public Connection getConnection() throws SQLException, ClassNotFoundException {
DbProperties p = DbProperties.getInstance();

// Class.forName("oracle.jdbc.driver.OracleDriver");
Class.forName("com.mysql.cj.jdbc.Driver");
// Connection con = DriverManager.getConnection(p.getProperty("oracleurl") ,
// p.getProperty("oracleuname"),
// p.getProperty("oraclepwd"));
Connection con = DriverManager.getConnection(p.getProperty("mysqlurl"),
p.getProperty("mysqluname"),
p.getProperty("mysqlpwd"));
return con;
}

// 设置参数的方法
private void setParams(PreparedStatement pstmt, Object... params) throws SQLException {
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
}
}


/**
* 基于模板设计模式的查询方法
*
* @param rowMapper: 对一行结果集的处理,返回一个对应的对象
* @param sql
* @param params
* @param <T>
* @return
* @throws SQLException
*/
public <T> List<T> select(RowMapper<T> rowMapper, String sql, Object... params) throws SQLException, ClassNotFoundException {
List<T> list = new ArrayList<>();

// 查询步骤的模板
try (
Connection con = getConnection();
PreparedStatement pstmt = con.prepareStatement(sql);
) {
this.setParams(pstmt, params);
ResultSet rs = pstmt.executeQuery();
int num = 0;
while (rs.next()) {
// 结果集的每一行的处理,由 RowMapper 接口的实现决定
T t = rowMapper.mapRow(rs, num);
num++;
list.add(t);
}
} catch (Exception e) {
throw e;
}
return list;
}

/**
* 封装(insert, update, delete)
* sql:是要执行的 更新语句 这里面可能有 n 个 ? 占位符,及对应的n个参数
* Object...: 动态数组,长度不确定,这种参数只能加在一个方法参数列表的最后
* 例:update emp set ename = ?, mgr = ? where empno = ?
* params: '张三', '李四', '1101'
*/
public int doUpdate(String sql, Object... params) {
// 返回成功执行的条数
int result = -1;
try (
Connection con = getConnection(); //获取连接
PreparedStatement pstmt = con.prepareStatement(sql)
) {

// 问题一: ?对应的参数类型是什么, 这个类型是什么,则 setXxxx() ???
// 解决:将所有的参数类型指定为 Object, 则可以 setObject()
// 问题二:有多少个参数??? params到底有几个
// params 是动态数组, 则有length
setParams(pstmt, params);
result = pstmt.executeUpdate();
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}

/**
* 方法名相同,参数不同 -> 重载
* 查询返回值是一个List<T> T代表任意的类的对象
* T类的标准JavaBean:属性封装,对外提供get/set
*
* @param c:代表 T 类的反射类的对象
* @param sql:
* @param params
* @param <T>
* @return
*/
public <T> List<T> select(Class<T> c, String sql, Object... params) throws IllegalAccessException, InstantiationException, InvocationTargetException {

// System.out.println(sql);
// 1. sql, params => 查询得到数据表数据
List result = new ArrayList<>();
List<Map<String, Object>> list = this.select(sql, params);


for (Map<String, Object> map : list) {
T t = c.newInstance(); // 调用了这个T类的无参构造方法
// 2. 将Map<String, Object> 转换成 T 对象
// a、循环map中所有的键值 entrySet()
Set<Map.Entry<String, Object>> set = map.entrySet();
Iterator<Map.Entry<String, Object>> iterator = set.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
String key = entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1).toLowerCase();
Object value = entry.getValue();
if (value==null){
continue;
}
//System.out.println(value);
// 拼接为 setXxx 方法的名字
String methodName = "set" + key;
//System.out.println(methodName);
// b、找出 set 方法
Method setMethod = findSetMethod(methodName, c);
// c、激活这个方法,就是设置值进去
Class parameterTypeClass = setMethod.getParameterTypes()[0];
String parameterTypeName = parameterTypeClass.getName();

if ("int".equals(parameterTypeName) || "java.lang.Integer".equals(parameterTypeName)) {
setMethod.invoke(t, Integer.parseInt(value.toString()));
} else if ("double".equals(parameterTypeName) || "java.lang.Double".equals(parameterTypeName)) {
setMethod.invoke(t, Double.parseDouble(value.toString()));
} else if ("short".equals(parameterTypeName) || "java.lang.Short".equals(parameterTypeName)) {
setMethod.invoke(t, Short.parseShort(value.toString()));
} else if ("byte".equals(parameterTypeName) || "java.lang.Byte".equals(parameterTypeName)) {
setMethod.invoke(t, Byte.parseByte(value.toString()));
} else if ("boolean".equals(parameterTypeName) || "java.lang.Boolean".equals(parameterTypeName)) {
setMethod.invoke(t, Boolean.parseBoolean(value.toString()));
} else if ("float".equals(parameterTypeName) || "java.lang.Float".equals(parameterTypeName)) {
setMethod.invoke(t, Float.parseFloat(value.toString()));
} else if ("long".equals(parameterTypeName) || "java.lang.Long".equals(parameterTypeName)) {
setMethod.invoke(t, Long.parseLong(value.toString()));
} else if("java.time.LocalDateTime".equals(parameterTypeName)){
// 定义日期时间字符串
String dateTimeString = value.toString();

// 定义日期时间格式器
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

// 将字符串转换为LocalDateTime
LocalDateTime localDateTime = LocalDateTime.parse(dateTimeString, formatter);

// 输出结果
//System.out.println("Converted LocalDateTime: " + localDateTime);
setMethod.invoke(t, localDateTime);
}else{
setMethod.invoke(t, value.toString());
}

}

// 3. 将 T 对象存在 List 中
result.add(t);
}
return result;
}

private <T> Method findSetMethod(String methodName, Class<T> c) {
// 找出所有方法
Method[] ms = c.getDeclaredMethods();

for (Method m : ms) {

// 如果我要找的方法名在这个反射对象返回的方法中找到了
if (methodName.equals(m.getName())) {
return m;
}
}
return null;

}


// private static ArrayList allSetMethods( Method[] methods ) {
// ArrayList setMethods = new ArrayList();
// for (Method method : methods) {
//
// }
// }


public List<Map<String, Object>> select(String sql, Object... params) {
List<Map<String, Object>> list = new ArrayList<>(); // 设置一个List集合
try (
Connection con = getConnection(); //获取连接
PreparedStatement pstmt = con.prepareStatement(sql) // 预编译语句对象
) {
setParams(pstmt, params);
ResultSet rs = pstmt.executeQuery();
// ResultSet 中有结果集中所有的信息
ResultSetMetaData rsmd = rs.getMetaData(); // 结果集元数据 =》有多少个列, 每个列叫什么名字
int columnCount = rsmd.getColumnCount(); // 列的数量


// 循环结果集将数据放入map中,然后map放入List中
while (rs.next()) {
HashMap<String, Object> map = new HashMap<>(); // 创建一个map对象
for (int i = 0; i < columnCount; i++) {
map.put(rsmd.getColumnName(i + 1), rs.getObject(i + 1)); // 存每一列
}
list.add(map);
}
} catch (Exception ex) {
ex.printStackTrace();
}
return list;

}


}

RowMapper接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.sql.ResultSet;
import java.sql.SQLException;

public interface RowMapper<T> {
/**
* 由最终用户决定如何处理 ResultSet 中的第 rowNum 行
* @param rs
* @param rowNum
* @return
* @throws SQLException
*/

T mapRow(ResultSet rs, int rowNum) throws SQLException;

}

用于读取mysql配置的

Dbporperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
* 此DbProperties继承 自 Properties,所以它也是 Map,也是一个键值对
* 但增的功能是 ,此DbProperties必须是单例
*/
public class DbProperties extends Properties {
private static DbProperties instance;
private DbProperties(){
//读取配置文件
InputStream iis= DbProperties.class.getClassLoader().getResourceAsStream("db.properties");
//Properties类的load方法加载
try {
this.load( iis ); // this就是 DbProperties 对象,
} catch (IOException e) {
throw new RuntimeException(e);
}


}
public static DbProperties getInstance(){
if( instance==null){
instance=new DbProperties();
}



return instance;
}
}

配上自己的mysql账号和密码,放在resources包下方便类加载器在target包中读取配置

db.properties

1
2
3
4
driverClassName = com.mysql.cj.jdbc.Driver
mysqlurl = jdbc:mysql://localhost:3306/shop?serverTimezone=Asia/Shanghai
mysqluname =
mysqlpwd =

log4j配置

log4j.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# rootLogger\u9ED8\u8BA4\u60C5\u51B5\u65E5\u5FD7\u914D\u7F6E
# debug \u7EA7\u522B # debug<info<warn<error<fatal<none
# \u65E5\u5FD7\u5728\u54EA\u91CC\u663E\u793A console,file\u5BF9\u5E94\u4E0B\u9762\u7684 \u4E24\u90E8\u5206
log4j.rootLogger=info, console, file
# debug<info<warn<error<fatal<all

#\u65E5\u5FD7\u663E\u793A\u5230\u63A7\u5236\u53F0
log4j.appender.console=org.apache.log4j.ConsoleAppender
#\u65E5\u5FD7\u7684\u683C\u5F0F
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# \u6570\u5B57(\u5E74-\u6708-\u65E5 \u5C0F\u65F6:\u5206:\u79D2) \u7C7B\u540D:\u7EA7\u522B \u4FE1\u606F \u6362\u884C

#\u6EDA\u52A8\u6587\u4EF6
log4j.appender.file=org.apache.log4j.RollingFileAppender
#\u6587\u4EF6\u540D
log4j.appender.file.File=logs/app.log
#\u6BCF10m\u751F\u6210\u4E00\u4E2A\u65B0\u6587\u4EF6
log4j.appender.file.MaxFileSize=10MB
log4j.appender.file.MaxBackupIndex=5
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

maven要导入的依赖包

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

测试bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.newtable.annotation.Field;
import com.newtable.annotation.PrimaryKey;
import com.newtable.annotation.Table;

@Table
public class Address {

@PrimaryKey
private Integer id;
@Field(value = "heiheihei")
private Integer userId; //用户id
private String province; //省
private String city; //市
private String town; //区
private String notes; //备注

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.newtable.annotation.Field;
import com.newtable.annotation.PrimaryKey;
import com.newtable.annotation.Table;

@Table
public class Address2 {

@PrimaryKey
private Integer id;
@Field(value = "heiheihei",foreignKey = "address-id")
private Integer userId; //用户id
private String province; //省
private String city; //市
private String town; //区
private String notes; //备注

}

启动类

1
2
3
4
5
6
7
8
9
10
/**

* 启动的时候对于已经存在的类进行建表操作
*/

public class App {
public static void main(String[] args) throws Exception {
NewTable.createTable();
}
}