有了一个框架,只需要配置好数据库连接,就可以在java代码层操控database,对于写个model便在数据库中创建了一张表而感到十分神奇,隐约想起以前看《Thinking in Java》中关于注解(Annotation)一张中对于自动生成SQL语句的操作。

首先略微介绍下注解(亦称为与数据metadata(ORM-对象/关系映射中的核心))。

Annotation源自JavaSE1.5,内置3个标准注解,4个元注解:

(1)java.lang.*中的@Override,@Deprecated, @SuppressWarnings

(2)java.lang.annotations.*中的@Target, @Inherited, @Retention, @Documented

对于后4个元注解,稍后再在代码中解释。

对于一个创建表的SQL Create语句,我们要确定几个元素:表名,列名,列名类型,类型长度,约束等,这些都可以在实体类的属性加以注解说明来实现。

对于表名注解:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 1 packageannotiation;2 2 import java.lang.annotation.*;3 3

4 4 @Inherited //允许子类继承父类中的注解

5 5 @Documented //将此注解包含在Javadoc中

6 6 @Target(ElementType.TYPE) //类、接口(包括注解类型)或枚举类型声明

7 7 @Retention(RetentionPolicy.RUNTIME) //VM在运行时保留注解,从而通过反射获取信息

8 8

9 9 public @interfaceDBTable {10 10 public String name() default ""; //注解未赋值是,默认为空

11 11 }

View Code

对于字段注解:(这边先只设定了String类型,其实实际情况没这么单纯,下篇再优化)

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 1 packageannotiation;2 2 import java.lang.annotation.*;3 3

4 4@Inherited5 5@Documented6 6 @Target(ElementType.FIELD) //域声明(包括枚举类型实例)

7 7@Retention(RetentionPolicy.RUNTIME)8 8

9 9 public @interfaceSQLInteger {10 10 String name() default "";11 11 Constraints constraints() default @Constraints; //约束注解,详细见下面代码

12 12 }

View Code

对于约束注解:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packageannotiation;2 import java.lang.annotation.*;3

4@Inherited5@Documented6@Target(ElementType.FIELD)7@Retention(RetentionPolicy.RUNTIME)8

9 public @interfaceConstraints {10 boolean primaryKey() default false; //主键,默认为空

11 boolean allowNull() default true; //默认允许为空

12 boolean unique() default false; //默认允许重复

13 }

View Code

实体类:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagemodel;2

3 import annotiation.*;4

5 @DBTable(name = "User") //设置表名为User

6 public classUser {7 @SQLString(size = 50) //设置字段 username, varchar(50)

8String username;9

10 @SQLString(size = 50)11String password;12

13 @SQLString(size = 30, constraints = @Constraints(primaryKey = true)) //设置为主键

14String handle;15

16 static intmemberCount;17

18 public String getUsername() { returnusername; }19

20 public void setUsername(String username) { this.username = username; }//个人感觉set方法可以去掉

21

22 public String getPassword() { returnpassword; }23

24 public void setPassword(String password) { this.password =password; }25

26 public String getHandle() { returnhandle; }27

28 public void setHandle(String handle) { this.handle =handle; }29

30 public String toString() { returnhandle; }31 }

View Code

准备工作之后,就是如何根据注解和反射拼接SQL语句:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecreator;2 import java.lang.reflect.*;3 import java.lang.annotation.*;4 import java.util.*;5

6 importannotiation.Constraints;7 importannotiation.DBTable;8 importannotiation.SQLString;9

10

11 public classTableCreator {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 returncons;24}25

26 /*这边还需要通过IO来遍历指定model包下所有实体类, 如上,待下一篇优化

27 private static ArrayList getTables() {

28 ArrayList tables = new ArrayList();

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() throwsClassNotFoundException {39 String sql = null;40 //ArrayList 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 columns = new ArrayList();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] instanceofSQLString) {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 returnsql;77}78 }

View Code

输出的语句应该是:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1Create Table User(2 username VARCHAR(50),3 password VARCHAR(50),4 handle VARCHAR(30) PRIMARY KEY);

View Code

既然有了SQL语句,只需要通过JDBC连接数据库执行即可(其实还可以封装之后实现相同CRUD操作,下篇优化):

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagedbconnect;2

3 importjava.sql.Connection;4 importjava.sql.DriverManager;5 importjava.sql.SQLException;6 importjava.sql.Statement;7

8

9 public classDBConnect {10 staticConnection 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{19Class.forName(driver);20 } catch(ClassNotFoundException e) {21 System.out.println("Can not find the Driver!");22e.printStackTrace();23}24

25 try{26 connect =DriverManager.getConnection(url, username, password);27 } catch(SQLException e) {28 System.out.println("Database connect failed!");29e.printStackTrace();30}31}32

33 public static void execute(String sql) { //执行语句

34Statement stmt;35 try{36 stmt =connect.createStatement();37stmt.executeUpdate(sql);38 } catch(SQLException e) {39 //TODO Auto-generated catch block

40e.printStackTrace();41}42}43

44 public static void close() { //关闭连接

45 if (connect != null) {46 try{47connect.close();48 } catch(SQLException e) {49e.printStackTrace();50}51}52}53 }

View Code

最后就是主程序了:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packageMain;2

3 importcreator.TableCreator;4 importdbconnect.DBConnect;5

6 public classrun {7 public static voidmain(String[] args) {8DBConnect.connect();9 try{10DBConnect.execute(TableCreator.getSql());11 } catch(ClassNotFoundException e) {12 //TODO Auto-generated catch block

13e.printStackTrace();14}15DBConnect.close();16}17 }

View Code

最后数据库中变出现了一张新表:

7453c3d753db3fd3d7156c62d883dcbd.png

至此,一个自己粗糙简陋的自动生成工具算是做好了,但实际情况很复杂,远远没有这么简单, 类似不同字段类型的判断,多张表的同时创建,判断新旧表从而决定是否重新执行SQL, 实体改动对数据库的影响等等问题,就此一系列后面几篇做优化和研究。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐