根据JavaBean 自动生成数据库表
根据JavaBean 自动生成数据库表
有了一个框架,只需要配置好数据库连接,就可以在java代码层操控database,对于写个model便在数据库中创建了一张表表示很好奇,隐约想起以前看《Thinking in Java》中关于注解(Annotation)一张中对于自动生成SQL语句的操作。
首先略微介绍下注解(亦称为与数据metadata(ORM-对象/关系映射中的核心))。
Annotation源自JavaSE1.5,内置3个标准注解,4个元注解:
java.lang.*中的@Override,@Deprecated, @SuppressWarnings
java.lang.annotations.*中的@Target, @Inherited, @Retention, @Documented
对于后4个元注解,稍后再在代码中解释。
对于一个创建表的SQL Create语句,我们要确定几个元素:表名,列名,列名类型,类型长度,约束等,这些都可以在实体类的属性加以注解说明来实现。
对于表名注解:
1 package annotiation; 2 import java.lang.annotation.*; 3 4 @Inherited // 允许子类继承父类中的注解 5 @Documented // 将此注解包含在Javadoc中 6 @Target(ElementType.TYPE) // 类、接口(包括注解类型)或枚举类型声明 7 @Retention(RetentionPolicy.RUNTIME) // VM在运行时保留注解,从而通过反射获取信息 8 9 public @interface DBTable { 10 public String name() default ""; // 注解未赋值是,默认为空 11 }
对于字段注解:(这边先只设定了String类型,其实实际情况没这么单纯,下篇再优化)
1 package annotiation; 2 import java.lang.annotation.*; 3 4 @Inherited 5 @Documented 6 @Target(ElementType.FIELD) // 域声明(包括枚举类型实例) 7 @Retention(RetentionPolicy.RUNTIME) 8 9 public @interface SQLInteger { 10 String name() default ""; 11 Constraints constraints() default @Constraints; // 约束注解,详细见下面代码 12 }
对于约束注解:
1 package annotiation; 2 import java.lang.annotation.*; 3 4 @Inherited 5 @Documented 6 @Target(ElementType.FIELD) 7 @Retention(RetentionPolicy.RUNTIME) 8 9 public @interface Constraints { 10 boolean primaryKey() default false; // 主键,默认为空 11 boolean allowNull() default true; // 默认允许为空 12 boolean unique() default false; // 默认允许重复 13 }
实体类:
1 package model; 2 3 import annotiation.*; 4 5 @DBTable(name = "User") // 设置表名为User 6 public class User { 7 @SQLString(size = 50) // 设置字段 username, varchar(50) 8 String username; 9 10 @SQLString(size = 50) 11 String password; 12 13 @SQLString(size = 30, constraints = @Constraints(primaryKey = true)) // 设置为主键 14 String handle; 15 16 static int memberCount; 17 18 public String getUsername() { return username; } 19 20 public void setUsername(String username) { this.username = username; }// 个人感觉set方法可以去掉 21 22 public String getPassword() { return password; } 23 24 public void setPassword(String password) { this.password = password; } 25 26 public String getHandle() { return handle; } 27 28 public void setHandle(String handle) { this.handle = handle; } 29 30 public String toString() { return handle; } 31 }
准备工作之后,就是如何根据注解和反射拼接SQL语句:
1 package creator; 2 import java.lang.reflect.*; 3 import java.lang.annotation.*; 4 import java.util.*; 5 6 import annotiation.Constraints; 7 import annotiation.DBTable; 8 import annotiation.SQLString; 9 10 11 public class TableCreator { 12 private static String getConstraints(Constraints constraints) { // 获取字段约束属性 13 String cons = ""; 14 if (!constraints.allowNull()) { 15 cons += " NOT NULL"; 16 } 17 if (constraints.primaryKey()) { 18 cons += " PRIMARY KEY"; 19 } 20 if (constraints.unique()) { 21 cons += " UNIQUE"; 22 } 23 return cons; 24 } 25 26 /* 这边还需要通过IO来遍历指定model包下所有实体类, 如上,待下一篇优化 27 private static ArrayList<String> getTables() { 28 ArrayList<String> tables = new ArrayList<String>(); 29 Package pckg = Package.getPackage("model"); 30 Class<?>[] cls = pckg.; 31 for (Class<?> cl : cls) { 32 tables.add(cl.getName()); 33 } 34 return tables; 35 } 36 */ 37 38 public static String getSql() throws ClassNotFoundException { 39 String sql = null; 40 //ArrayList<String> tables = getTables(); 41 String[] tables = {"model.User"}; 42 for (String className : tables) { 43 /* 44 String[] table = className.split("\\."); 45 for (String tb : table) { 46 System.out.println(tb); 47 } 48 */ 49 Class<?> cl = Class.forName(className); // 通过类名得到该实体类 50 DBTable dbtable = cl.getAnnotation(DBTable.class); // 通过注解得到表明 51 String tableName = dbtable.name().length() > 1 ? dbtable.name() : cl.getName().toUpperCase(); 52 /* comments 53 System.out.println("tableName: " + tableName); 54 */ 55 List<String> columns = new ArrayList<String>(); 56 for (Field field : cl.getDeclaredFields()) { // 得到该类下所有属性 57 String columnName = null; 58 Annotation[] annotations = field.getAnnotations(); 59 if (annotations.length < 1) { 60 continue; 61 } 62 if (annotations[0] instanceof SQLString) { 63 SQLString sStr = (SQLString)annotations[0]; 64 columnName = sStr.name().length() < 1 ? field.getName() : sStr.name(); 65 columns.add(columnName + " VARCHAR(" + sStr.size() + ")" + getConstraints(sStr.constraints())); 66 } 67 } 68 69 StringBuilder sb = new StringBuilder("Create Table " + tableName + "("); 70 for (String column : columns) { 71 sb.append("\n " + column + ","); // 拼接各个字段的定义语句 72 } 73 sql = sb.substring(0, sb.length() - 1) +");"; 74 } 75 System.out.println("=========" + sql + "========="); // 测试输出 76 return sql; 77 } 78 }
输出的语句应该是:
1 Create Table User( 2 username VARCHAR(50), 3 password VARCHAR(50), 4 handle VARCHAR(30) PRIMARY KEY);
既然有了SQL语句,只需要通过JDBC连接数据库执行即可(其实还可以封装之后实现相同CRUD操作,下篇优化):
1 package dbconnect; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.SQLException; 6 import java.sql.Statement; 7 8 9 public class DBConnect { 10 static Connection connect; 11 static String driver = "com.mysql.jdbc.Driver"; 12 static String password = "thoupin'spassword"; 13 static String username = "thoupin"; 14 static String dbName = "test"; 15 static String url = "jdbc:mysql://localhost/" + dbName; 16 17 public static void connect() { // 连接 18 try { 19 Class.forName(driver); 20 } catch (ClassNotFoundException e) { 21 System.out.println("Can not find the Driver!"); 22 e.printStackTrace(); 23 } 24 25 try { 26 connect = DriverManager.getConnection(url, username, password); 27 } catch (SQLException e) { 28 System.out.println("Database connect failed!"); 29 e.printStackTrace(); 30 } 31 } 32 33 public static void execute(String sql) { // 执行语句 34 Statement stmt; 35 try { 36 stmt = connect.createStatement(); 37 stmt.executeUpdate(sql); 38 } catch (SQLException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 } 43 44 public static void close() { // 关闭连接 45 if (connect != null) { 46 try { 47 connect.close(); 48 } catch (SQLException e) { 49 e.printStackTrace(); 50 } 51 } 52 } 53 }
最后就是主程序了:
1 package Main; 2 3 import creator.TableCreator; 4 import dbconnect.DBConnect; 5 6 public class run { 7 public static void main(String[] args) { 8 DBConnect.connect(); 9 try { 10 DBConnect.execute(TableCreator.getSql()); 11 } catch (ClassNotFoundException e) { 12 // TODO Auto-generated catch block 13 e.printStackTrace(); 14 } 15 DBConnect.close(); 16 } 17 }
最后数据库中变出现了一张新表:
至此,一个自己粗糙简陋的自动生成工具算是做好了,但实际情况很复杂,远远没有这么简单, 类似不同字段类型的判断,多张表的同时创建,判断新旧表从而决定是否重新执行SQL, 实体改动对数据库的影响等等问题,就此一系列后面几篇做优化和研究。