如果希望提供一些基本的对象/关系映射功能,能够自动生成数据库表,用以存储JavaBean对象,可以使用XML描述文件,指明类的名字,每个成员以及数据库映射的相关信息,也可以使用注解,将所以的信息保存在JavaBean源文件中。这一点有点类似Hibernate JPA所做的,定义与Bean关联的数据库表的名字,以及与Bean属性关联的列的名字和SQL类型。下面就是这个功能的一些基本实现。
首先是一个注解的定义,它告诉注解器,生成一个数据库的表。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
}
在@Target注解中指定了该注解适用于类(数据库表对应的实体类),@DBTable有一个name()元素,为创建的数据库表提供表名。
接下来就是为修饰该实体类准备的注解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
注解处理器通过@Constraints注解提取出数据库表的元数据,这里提供约束,数据库数据varchar,int类型注解。注意SQL类型的注解具有name()元素和constraint()元素。后者利用了嵌套注解的功能,将column类型的数据库约束嵌入其中。
接下来就是一个简单的JavaBean类的定义,应用了以上的注解:
@DBTable(name = "PERSON")
public class Person {
@SQLString(30) String name;
@SQLInteger Integer age;
@SQLString(10) String sex;
@SQLString(value = 30, constraints = @Constraints(primaryKey = true)) String handle;
static int count;
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public String getHandle() {
return handle;
}
public String getSex() {
return sex;
}
}
类的注解@DBTable给了Person,它也作为数据库表的名字。注意handle这个属性的注解,首先handle是一个varchar类型的数据,同时它将成为这张表的主键,所以得同时使用@SQLString和primaryKey元素进行设定。
Hibernate JPA中使用了单一的注解类column,估计是带了一个enum元素,该枚举定义了String,Integer以及Float等枚举实例。这样消除了每个SQL类型都需要一个@interface定义的负担。
同样也可以使用多个注解来注解一个域,编译器允许对一个目标同时使用多个注解。但是使用多个注解时,同一个注解不能重复使用。
下面是一个注解处理器的例子,它将读取一个文件,检查其上的数据库注解,并生成用来创建数据库的SQL命令。
public class TableCreator {
public static void main(String args[]) throws Exception{
TableCreator tc = new TableCreator();
String tableCreateSql = tc.createSql("cn.zhouyi.javamindview.program.annotation.Person");
System.out.println(tableCreateSql);
}
public String createSql(String className) throws Exception{
Class<?> cls = Class.forName(className);
DBTable dbTable = cls.getAnnotation(DBTable.class);
if(null == dbTable){
System.out.println("NO DBTable Annotations in class " + className);
return null;
}
String tableName = dbTable.name();
if(tableName.length() < 1){
tableName = cls.getName().toUpperCase();
}
List<String> columnsDefs = new ArrayList<String>();
String tableCreate = null;
for(Field field : cls.getDeclaredFields()){
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1){
continue;
}
if(anns[0] instanceof SQLString){
SQLString sString = (SQLString)anns[0];
if(sString.name().length() < 1){
columnName = field.getName().toUpperCase();
}else{
columnName = sString.name();
}
columnsDefs.add(columnName + " VARCHAR(" + sString.value() + ") " + getConstraints(sString.constraints()));
}
if(anns[0] instanceof SQLInteger){
SQLInteger sInt = (SQLInteger)anns[0];
if(sInt.name().length() < 1){
columnName = field.getName().toUpperCase();
}else{
columnName = sInt.name();
}
columnsDefs.add(columnName + " INT " + getConstraints(sInt.constraints()));
}
StringBuilder createCommand = new StringBuilder("CREATE TBALE " + tableName +" (");
for(String columnDef : columnsDefs){
createCommand.append("\n " + columnDef + ",");
}
tableCreate = createCommand.substring(0, createCommand.length()-1) + " );";
}
return tableCreate;
}
private String getConstraints(Constraints con){
String constraints = "";
if(!con.allowNull()){
constraints += " NOT NULL";
}
if(con.primaryKey()){
constraints += " PRIMARY KEY";
}
if(con.unique()){
constraints += " UNIQUE";
}
return constraints;
}
}
//output
CREATE TBALE PERSON (
NAME VARCHAR(30) ,
AGE INT ,
SEX VARCHAR(10) ,
HANDLE VARCHAR(30) PRIMARY KEY );
这里使用Class.forName()方法加载一个类,并使用getAnnotation(DBTable.class)检查该类是否带有@DBTable注解。如果有,就将发现的表名保存下来,然后读取这个类的所有域,并用instanceof来判断这些域的注解是否包含@SQLinteger和@SQLString,如果是又包含的话,在对应的处理块中构造出相应的column名的字符串片段。
嵌套中的@Constraint注解被传递给getConstraints()方法,由它来负责构建一个包含SQL约束的String对象。
上述的例子实现了一个简单的对象/关系映射,不过实际上对真实的对象/关系映射而言还远远不够。好了,这么晚了,要睡了,好困!