通过Java annotation以及反射机制实现不同类型通用的数据库访问接口_移动开发_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > 移动开发 > 通过Java annotation以及反射机制实现不同类型通用的数据库访问接口

通过Java annotation以及反射机制实现不同类型通用的数据库访问接口

 2010/12/23 8:05:12  mypyg  http://mypyg.javaeye.com  我要评论(0)
  • 摘要:在日常开发中会遇到这种情况:多类对象需要保存到数据库中,每类对象都要创建一个表,创建表时的字段、索引序号、字段类型都要一一对应,如果保存到数组中,当需要增减字段就要更改数组,一是繁琐,二是很容易搞错序号导致程序运行错误,三是代码复用很难做到。为了解决上述几点问题,在实践摸索中想出了通过annotation来解决的方法。其原理是:创建表时:需要表名、字段名、字段类型保存数据时:需要表名、字段名、字段对应的值读取数据时:需要表名、字段索引、保存值的变量只要在进行以上操作时能提供所需要的信息
  • 标签:实现 not Java Annotation 反射 数据库 数据 接口 反射机制
在日常开发中会遇到这种情况:
多类对象需要保存到数据库中,每类对象都要创建一个表,创建表时的字段、索引序号、字段类型都要一一对应,
如果保存到数组中,当需要增减字段就要更改数组,一是繁琐,二是很容易搞错序号导致程序运行错误,三是代码复用很难做到。
为了解决上述几点问题,在实践摸索中想出了通过annotation来解决的方法。
其原理是:
创建表时:需要表名、字段名、字段类型
保存数据时:需要表名、字段名、字段对应的值
读取数据时:需要表名、字段索引、保存值的变量
只要在进行以上操作时能提供所需要的信息,那么就能进行操作,这些信息有些可以通过类信息获得,有些则需要通过annotation传入。

1.创建annotation类,只要被此annotation类修饰的成员变量都认为是需要保存到表中的。
/**
 * 使用此annotation指定一个成员变量为数据库的一个字段。
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbField {
	boolean primaryKey() default false;
	boolean notNull() default false;
	String fieldName() default "##default"; 	
}

代码中的"##default"是自己的一个约定,当检测到此字符串时自动用变量名作为字段名,否则用指定的名字作为字段名。
2.创建一个普通的要保存到数据库中的类:
public class SomeThing {
	@DbField
	public String field_all_default;
	
	@DbField (
		primaryKey = true,
		notNull = true,		
		fieldName = "specified_field_name"
	)
	public long field_specified_primary_key;
	
	@DbField
	public boolean field_boolean;
	
	public int not_db_field;
}

在上面的代码中:
成员变量field_all_default被用annotation标注,并都使用了默认值,那么在表中就会创建一个名为field_all_default的字段来保存此变量的值。
成员变量field_specified_primary_key被用annotation标注,并指定了是主key,不能为空,且指定了另外的字段名specified_field_name。
成员变量not_db_field没有被标注,那么就不会被保存。
3.创建一个数据库操作辅助类,在这个类的函数中会读取上面的标注信息,并根据标注信息进行处理。
3.1 创建表格:
private void createTbl(SQLiteDatabase db, String tblName, Class<?> clazz);

从clazz中读取类以及annotation获取字段信息,并在指定的数据库db中创建指定的表tblName。
此函数的核心是通过遍历类的所有Field,获取字段信息:
for( Field f : clazz.getFields()) {
		if(f.isAnnotationPresent(DbField.class)) { //如果被DbField annotation标注了认为是要保存到数据表中
			if(!first_field) {
				sql.append(", ");					
			}				
			DbField annotation = f.getAnnotation(DbField.class);
			String fieldName = annotation.fieldName();
			if(fieldName.equals(DEFAULT_FIELD_NAME)) {
				fieldName = f.getName();  //如果是约定的默认字段名,那么用FieldName作为字段名
			}
			sql.append(fieldName);
			sql.append(' ');
			sql.append(clazzToDbTypeString(f.getType()));	//根据字段的类型得到保存到数据表中的类型
			if(annotation.primaryKey()) {	//判断是不是主key
				sql.append(" PRIMARY KEY");
			}
			if(annotation.notNull()) {	//判断是不是not null
				sql.append(" NOT NULL");
			}
			first_field = false;
		}
	}

在上面的代码中根据字段的类型得到数据表类型的调用,函数实现如下:
private String clazzToDbTypeString(Class<?> clazz) {
		if( clazz == String.class 
				|| clazz == Character.class || clazz == char.class 
				|| clazz == Boolean.class || clazz == boolean.class) {
			return "TEXT";
		}else if(clazz == Integer.class || clazz == int.class 
				|| clazz == Long.class || clazz == long.class				
				|| clazz == Short.class || clazz == short.class) {
			return "INTEGER";
		}else if(clazz == Float.class || clazz == float.class || clazz == Double.class || clazz == double.class) {
			return "REAL";
		}else {
			return "BLOB";
		}
	}

即根据Field的类型转换成sqlite3支持的数据类型,无需通过annotation传入了。
3.2 写数据:
就是从对象中获得值,并保存到指定的字段名中。
ContentValues v = new ContentValues();
	for( Field f : object.getClass().getFields() ) {
		if(f.isAnnotationPresent(DbField.class)) { //如果被标注,认为要保存到数据表中
			DbField annotation = f.getAnnotation(DbField.class);
			String fieldName = annotation.fieldName();
			if(fieldName.equals(DEFAULT_FIELD_NAME)) {
				fieldName = f.getName();  //如果是约定的默认字段名,那么用FieldName作为字段名
			}				
			v.put(fieldName, f.get(object).toString()); //将值和字段名配对保存
		}
	}

然后将v保存到数据表中即可。
3.3 数据库执行查询获得Cursor,将Cursor转换为对象即可:
public Object cursorToObject(Cursor cursor, Class<?> clazz) throws Exception {
		Constructor<?> ct = clazz.getConstructor((Class[])null);
		Object sg = ct.newInstance((Object[])null);	//用无参构造函数构造对象
		for( Field f : clazz.getFields() ) {
			if(f.isAnnotationPresent(DbField.class)) { //被标注,从表中获取值
				DbField annotation = f.getAnnotation(DbField.class);
				String fieldName = annotation.fieldName();
				if(fieldName.equals(DEFAULT_FIELD_NAME)) {
					fieldName = f.getName(); //获取字段名
				}
				int index = cursor.getColumnIndex(fieldName); //字段名转为字段索引
				Class<?> fieldClass = f.getType();
				//根据成员变量的类型,将从表中读取的数据转换
				if(fieldClass == Integer.class || fieldClass == int.class) {
					f.setInt(sg, cursor.getInt(index));					
				}else if(fieldClass == Long.class || fieldClass == long.class) {
					f.setLong(sg, cursor.getLong(index));					
				}else if(fieldClass == String.class ) {
					f.set(sg, cursor.getString(index));					
				}else if(fieldClass == Boolean.class || fieldClass == boolean.class) {
					f.setBoolean(sg, Boolean.valueOf(cursor.getString(index)));					
				}															
			}
		}
		return sg;
	}


基本的原理框架就如上所述,如果要扩充自定义类型的支持,那么修改写数据以及读取数据代码增加支持即可。
为了提高效率,可以用Hashmap将类信息、字段名和字段索引对应关系等信息缓冲,这样不必每次都通过反射获取。
发表评论
用户名: 匿名