用于简单的指定表名,类名,外键的类反向生成mysql表的注解功能
java包下的com.xx文件夹才能用,不过项目一般都是com文件夹开头,问题不大
用于注解属性,指定属性名或者外键,也可以不写
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 "";//外键 格式是 表明-键
}
指定主键
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 "";//可以指定属性名
}
注解在类上,标明要以哪个类为底
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 ""; //可以指定表名,没有就默认为类名
}
-
存储带有 @Table 注解的类:
- 使用
newC
列表存储找到的带有@Table
注解的类。
- 使用
-
createTable 方法:
- 设置包名和包路径。
- 调用
findPackageClasses
方法查找指定包路径下的所有类。 - 遍历找到的类,如果类带有
@Table
注解,则生成对应的 SQL 创建表语句。
-
findPackageClasses 方法:
- 递归查找指定包路径下的所有文件和目录。
- 对于每个类文件,使用类加载器加载类,并检查是否带有
@Table
注解。 - 将带有
@Table
注解的类添加到newC
列表中。
生成 SQL 语句的逻辑:
- 对于每个带有
@Table
注解的类,获取其所有字段。 - 检查字段是否带有
@Field
注解,如果没有,则使用字段名作为列名。 - 根据字段类型(int, double, varchar 等)拼接 SQL 语句。
- 如果字段带有
@PrimaryKey
注解,则将其设置为主键。 - 如果字段带有外键,则生成外键约束。
执行 SQL 语句:
- 最后调用
db.doUpdate(sql)
方法执行生成的 SQL 创建表语句。
NewTable
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
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接口
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
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
driverClassName = com.mysql.cj.jdbc.Driver
mysqlurl = jdbc:mysql://localhost:3306/shop?serverTimezone=Asia/Shanghai
mysqluname =
mysqlpwd =
log4j配置
log4j.properties
# 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要导入的依赖包
<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
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; //备注
}
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; //备注
}
启动类
/**
* 启动的时候对于已经存在的类进行建表操作
*/
public class App {
public static void main(String[] args) throws Exception {
NewTable.createTable();
}
}