Java 注解入门 自动生成SQL语句

引言

在用hibernate的时候发现idea能自动生成JavaBean,同时带有一些注解,这引起了我的好奇。当在学习Android的时候,我发现XUtils这个工具包中的DBUtils也能够使用类似hibernate的注解。于是乎在java编程思想中找了找有关注解的用法。

一 注解定义

注解(也称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。注解来源于C#之类的其他语言。

注解的语法比较简单,除了@符号外,它与java的固有语法一致。javaSE5中内置了三种注解:

@Override:定义覆盖超类,当覆写对应不上被覆盖的方法,编译器发出错误提示。

@Deprecated:当使用了该注解,即表示这个方法已经不推荐被使用。

@SuppressWarnings:关闭不当的编译器警告。

二 基本语法

我们使用自定义的注解对一个方法进行注解:

1
2
3
4
5
6
7
8
9
10
11
public class Testable{
public void execute()
{
System.out.println("execute...");
}
@WETest
void taskStart()
{
execute();
}
}

在上边的代码中,我们对taskStart方法使用了注解,接下来我们对WETest注解进行定义:

1
2
3
4
5
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WETest{}

三 定义注解

我们给上边的注解添加一些内容:

1
2
3
4
5
6
7
8
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WETest{
public int id();
public String Notes() default "there is no Notes";
}

同样,我们对Testable类使用最新的注解:

1
2
3
4
5
6
7
8
9
10
11
12
public class Testable{
@WETest(id=666)
public void execute()
{
System.out.println("execute...");
}
@WETest(id=666,Notes="this is a method")
void taskStart()
{
execute();
}
}

注解就是这么使用的,当注解内容没有填写时,他会使用默认的值,如execute方法,他没有定义Notes,那么Notes默认值为”there is no Notes”。

四 元注解

我们看到注解上边有两行内容,它们是元注解,专门对注解的解释。元注解一共有四种,分别是:

@Target:表示该注解可以用到哪些地方,ElementType,CONSTRUCTOR构造器声明,FIELD域声明(包括enum实例),LOCAL_VARIABLE局部变量声明,METHOD方法,PACKAGE包,PARAMETER参数,TYPE类、接口或enum。

@Retention:表示需要在什么级别上使用,RetentionPolicy,SOURCE注解会被编译器丢掉,CLASS在class文件中可用会被VM抛弃,RUNTIME在VM运行期也会保留可以通过反射获取注解信息。

@Documented:将注解包含在Javadoc中。

@Inherited:允许子类继承父类中的注解。

五 通过注解反射生成SQL语句

接下来,我用一个例子来解释注解的作用。先编写一些注解定义:

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
//DBTable.java 用来生成数据表
package annotations;
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 DBTable {
public String name() default "";
}
//Constraints.java 用来定义约束项
package annotations;
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 Constraints {
boolean primarykey() default false;
boolean allownull() default true;
}
//PrimaryKey.java 将Constraints中的primarykey定义为真,表示为主键
package annotations;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimaryKey {
Constraints constraints() default @Constraints(primarykey = true);
}
//SQLInteger.java 定义列的类型
package annotations;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
//SQLString.java 定义列的类型
package annotations;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 64;
String name() default "";
Constraints constraints() default @Constraints;
}
接下来写一个javabean,使用上述注解:
//User.java
import annotations.Constraints;
import annotations.DBTable;
import annotations.SQLInteger;
import annotations.SQLString;
@DBTable(name="user")
public class User {
@SQLInteger(name="id",constraints = @Constraints(primarykey=true))
public Integer id;
@SQLString(value=30)
public String name;
@SQLString(name="passwd",constraints=@Constraints(allownull=false))
public String password;
/*可以不用
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}*/
}

我们看到注解中可以使用注解,在SQLInteger中我们使用了Constraints注解。

接下来我们写一个注解处理器:

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
//Test.java
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import annotations.Constraints;
import annotations.DBTable;
import annotations.SQLInteger;
import annotations.SQLString;
public class Test {
public static String getConstraints(Constraints con)
{
String constraints = "";
if(!con.allownull())
{
constraints +=" NOT NULL";
}
if(con.primarykey())
{
constraints += " PRIMARY KEY";
}
return constraints;
}
public static void main(String[] args) throws ClassNotFoundException {
Scanner s = new Scanner(System.in);
String name = s.next(); //从控制台输入一个类名,我们输入User即可
Class<?> cl = Class.forName(name); //加载类,如果该类不在默认路径底下,会报 java.lang.ClassNotFoundException
DBTable dbTable = cl.getAnnotation(DBTable.class); //从User类中获取DBTable注解
if(dbTable == null){ //如果没有DBTable注解,则直接返回,我们写了,当然有
return;
}
String tableName = (dbTable.name().length()<1)?cl.getName():dbTable.name();//获取表的名字,如果没有在DBTable中定义,则获取类名作为Table的名字
List<String> columnDefs = new ArrayList<String>();
for(Field field : cl.getDeclaredFields()) //获取声明的属性
{
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();//获取注解,一个属性可以有多个注解,所以是数组类型
if(anns.length < 1)
{
continue;
}
if(anns[0] instanceof SQLInteger) //判断注解类型
{
SQLInteger sInt = (SQLInteger)anns[0];
columnName = (sInt.name().length()<1)?field.getName():sInt.name();//获取列名称与获取表名一样
columnDefs.add(columnName+" INT"+getConstraints(sInt.constraints()));//使用一个方法,自己写的getConstraints(Constraints constraints)获取列定义
}
if(anns[0] instanceof SQLString)
{
SQLString sStr = (SQLString)anns[0];
columnName = (sStr.name().length()<1)?field.getName().toUpperCase():sStr.name();
columnDefs.add(columnName + " VARCHAR("+sStr.value()+")"+getConstraints(sStr.constraints()));
}
}
StringBuilder createCommand = new StringBuilder("CREATE TABLE "+tableName+"(");
for(String columnDef :columnDefs)
{
createCommand.append("\n "+columnDef+",");
}
String tableCreate = createCommand.substring(0,createCommand.length()-1)+"\n);";
System.out.println(tableCreate); //打印出来
}
}

我们可以采用上述方法动态的处理一些数据,例如创建数据表。

六 总结

注意:注解不支持继承例如 extends @xxx。
     注解的default默认值不可以为null

使用注解可以减少对xml等外部文件的依赖,使得对类的定义可以在一处实现,避免了一个类两处定义的麻烦。spring和hibernate就采用的这样的方法。