`
mypyg
  • 浏览: 545712 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

通过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将类信息、字段名和字段索引对应关系等信息缓冲,这样不必每次都通过反射获取。
2
1
分享到:
评论
2 楼 mypyg 2011-12-22  
clazz.getAnnotation(Annotation类)
1 楼 Chansh 2011-12-21  
请问如果annotation标记在类上面的,应该怎么获取呢?

相关推荐

    JAVA_API1.6文档(中文)

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    Java 1.6 API 中文 New

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    java api最新7.0

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    JavaAPI1.6中文chm文档 part1

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    JavaAPI中文chm文档 part2

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    [Java参考文档].JDK_API 1.6

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    [Java参考文档]

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    java jdk-api-1.6 中文 chmd

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    JDK_1_6 API

    java.sql 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 java.text 提供以与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。 java.text.spi java.text ...

    Spring.3.x企业应用开发实战(完整版).part2

    3.2.3 Java反射机制 3.3 资源访问利器 3.3.1 资源抽象接口 3.3.2 资源加载 3.4 BeanFactory和ApplicationContext 3.4.1 BeanFactory介绍 3.4.2 ApplicationContext介绍 3.4.3 父子容器 3.5 Bean的生命周期 3.5.1 ...

    Spring3.x企业应用开发实战(完整版) part1

    3.2.3 Java反射机制 3.3 资源访问利器 3.3.1 资源抽象接口 3.3.2 资源加载 3.4 BeanFactory和ApplicationContext 3.4.1 BeanFactory介绍 3.4.2 ApplicationContext介绍 3.4.3 父子容器 3.5 Bean的生命周期 3.5.1 ...

Global site tag (gtag.js) - Google Analytics