如果希望提供一些基本的对象/关系映射功能,能够自动生成数据库表,用以存储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对象。
上述的例子实现了一个简单的对象/关系映射,不过实际上对真实的对象/关系映射而言还远远不够。好了,这么晚了,要睡了,好困!