利用注解,自动生成建表语句&转换查询结果
转载请注明出处:
需求
前段时间,为了加快开发效率,较少冗余重复的代码,写了一个自动生成建表语句和自动转换查询对象的工具类。最近有空整理出一部分,跟大家分享一下。
自动生成建表语句
如我们有这么一个类映射一张表:people。
public class People extends Object implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
private float weight;
private Integer id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "People [name=" + name + ", age=" + age + ", weight=" + weight
+ ", id=" + id + "]";
}
}
我们要写它的建表语句:CREATE TABLE people (id INTEGER PRIMARY KEY AUTOINCREMENT , age INTEGER(128) , name VARCHAR(16) , weight FLOAT(128) );
现在还是四个字段,如果字段多一些的表,那么SQL语句会非常长,写起来很麻烦。这时候,利用注解,就可以快速生成。
Annotation(注解):
就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。类、方法、变量、参数、包都可以被注解。
自定义注解:
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
注解还有其他的元注解等其他属性,网上有很多,大家有兴趣可以去深入了解下。
一张表有表名,字段(字段类型,长度),Id。我们定义三个注解分分别对应:table,Column,Id。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
/**
* 别名
*/
String idName() default "id";
/**
* 是否自增
*/
boolean increment() default true;
/**
* 长度
*/
String length() default "125";
}
/**
*
* RetentionPolicy.RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到
*
* @Target 是Java的元注解(指修饰注解的注解)之一。用来指定注解修饰类的哪个成员。 ElementType.TYPE: 接口、类、枚举、注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
/**
* 表示字段名 默认为“”
*/
String columnName() default "";
/**
* 表示字段长度 默认为0
*/
String lengtn() default "0";
/**
* 是否大字符串 true的话对于数据库里的TEXT类型 不要在意命名
*/
boolean isBigString() default false;
}
/**
*
* 表名
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
/**
* 表名
*
* @return
*/
String tableName();
}
给我们的people加上注解:
@Table(tableName = "people")
public class People extends Object implements Serializable {
private static final long serialVersionUID = 1L;
@Column(columnName = "name", lengtn = "16")
private String name;
@Column(columnName = "age", lengtn = "128")
private Integer age;
@Column(columnName = "weight", lengtn = "128")
private float weight;
@Id(increment = true, idName = "id")
private Integer id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "People [name=" + name + ", age=" + age + ", weight=" + weight
+ ", id=" + id + "]";
}
}
通过注解获取内容,比如表名:
public <T>String getTableName(Class<T> c) {
String tableName = null;
try {
if (c.isAnnotationPresent(Table.class)) {
// 提取表名
tableName = c.getAnnotation(Table.class)
.tableName();
}
} catch (Exception e) {
e.printStackTrace();
}
return tableName;
}
注解解析:
1:getAnnotation(AnnotationName.class) 表示得到该 Target 某个 Annotation 的信息;
2:getAnnotations() 则表示得到该 Target 所有 Annotation;
3:isAnnotationPresent(AnnotationName.class) 表示该 Target 是否被某个 Annotation 修饰;
获取字段名和类型类似,先查询注解的内容,在根据注解的内容匹配表中字段的类型,如:String = VARCHAR;这里我们需要一个工具类,进行类型转换,这样就不需要再手动设置字段类型了。
public class Types {
// 自定义的基本类型,和数据对应,最后组成字符串
public static final String VARCHAR = "VARCHAR";
public static final String INTEGER = "INTEGER";
public static final String BOOLEAN = "BOOLEAN";
public static final String LONG = "BIGINT";
public static final String TEXT = "TEXT";
public static final String BLOB = "BLOB";
public static final String FLOAT = "FLOAT";
public static final String DOUBLE = "DOUBLE";
public static final String DATE = "DATE";
public static final String TIME = "TIME";
// 默认长度
public static final String VARCHAR_LENGTH = "50";
public static final String INTEGER_LENGTH = "50";
public static final String LONG_LENGTH = "200";
public static final String TEXT_LENGTH = "null";
public static final String BLOB_LENGTH = "null";
public static final String FLOAT_LENGTH = "200";
public static final String DOUBLE_LENGTH = "200";
public static final String DATE_LENGTH = "null";
public static final String TIME_LENGTH = "null";
// 将类型 默认长度,放入集合
public static Map<String, String> map = new HashMap<String, String>();
static {
map.put(VARCHAR, VARCHAR_LENGTH);
map.put(INTEGER, INTEGER_LENGTH);
map.put(BOOLEAN, "0");
map.put(LONG, LONG_LENGTH);
map.put(TEXT, TEXT_LENGTH);
map.put(BLOB, BLOB_LENGTH);
map.put(FLOAT, FLOAT_LENGTH);
map.put(DOUBLE, DOUBLE_LENGTH);
map.put(DATE, DATE_LENGTH);
map.put(TIME, TIME_LENGTH);
}
开始拼接sql语句:
/**
* 开始创建建表语句
*/
protected <T> String createSql(Class<T> c) {
// 表名
String tableName = mReflectUtil.getTableName(c);
// 创建建表语句
StringBuilder sql = new StringBuilder("CREATE TABLE " + tableName
+ " (");
// 字段集合
ArrayList<String> columns = new ArrayList<String>();
// 带注释的Field集合
ArrayList<Field> list = mReflectUtil.getColumnList(c);
try {
for (Field mField : list) {
// 字段名
String name = null;
// 类型
String type = null;
Annotation[] mAnnotations = mField.getDeclaredAnnotations();
if (mAnnotations.length == 0) {
continue;
} else {
// 获取注解信息
Column mColumn = (Column) mAnnotations[0];
// 获得sql需要的别名
String nameC = mColumn.columnName();
// 获取字段的类型
String typeC = mField.getType().getName();
// 获取注解里的长度
String Clength = mColumn.lengtn();
// 是否是TEXT格式
boolean isBig = mColumn.isBigString();
if (nameC.length() == 0) {
// 获得默认的字段名
name = mField.getName();
} else {
name = nameC;
}
if (typeC.length() == 0) {
// 获得默认的类型
type = mField.getType().toString();
} else {
type = Types.getType(typeC, isBig);
}
// 没有设置长度 则获取默认的长度
if (Clength.length() < 0) {
Clength = Types.map.get(type).toString();
}
// 对于boolean 特殊处理 0代表false
if ("0".equals(Clength)) {
// 获取默认的长度
Clength = Types.map.get(type).toString();
}
// 生成字段类型和长度sql语句
type = Types.getTypeSql(type, Clength);
// 拼接成sql语句格式 加入集合
columns.add(name + " " + type);
}
}
// 加入ID
sql.append(mReflectUtil.getIdSql(c));
// 遍历集合 生成sql语句
for (String str : columns) {
sql.append(" " + str + " ,");
}
// 最终的创建表语句
String finalSql = sql.substring(0, sql.length() - 1) + " );";
return finalSql;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
然后是我们常用的插入语句和查询表的数据:
protected <T> String insertObj(Object c) {
String insertSql = null;
boolean isBig;
// 获取表名
String tableName = mReflectUtil.getTableName(c.getClass());
if (tableName != null) {
// 要插入的别名
StringBuffer sqlStr = new StringBuffer("INSERT INTO ");
// 要插入的别名的值
StringBuffer valueStr = new StringBuffer(" VALUES (");
// 所有带注解的字段
List<Field> list = mReflectUtil.getFieldList(c.getClass());
if (list != null && list.size() > 0) {
// 开始拼接字符串
sqlStr.append(tableName + " (");
// 开始遍历所有带注释的字段
// 别名
String str = null;
// 字段的值
Object fieldValue = null;
try {
for (Field field : list) {
if (field.isAnnotationPresent(Id.class)) {
Id mId = field.getAnnotation(Id.class);
str = mId.idName();
}
if (field.isAnnotationPresent(Column.class)) {
Column anno = field.getAnnotation(Column.class);
str = anno.columnName();
// 是否是text类型
if (anno.isBigString()) {
isBig = true;
}
}
// 获取字段的值
fieldValue = mReflectUtil.getFieldValue(c, field);
// boleann 类型对应数据库的值 false =0 ture = 1
if ("false".equals(fieldValue)) {
fieldValue = "0";
}
if ("true".equals(fieldValue)) {
fieldValue = "1";
}
String type = field.getType().getName();
if (null == fieldValue || "".equals(fieldValue)) {
// 根据对象的类型 获取数据库里匹配类型的默认值
fieldValue = null;
}
// 拼接要插入的别名的sql语句
sqlStr.append(str + ",");
// 根据类型 设置sql语句中 不同的格式
if (field.getType().getName().indexOf("String") != -1
|| field.getType().getName().indexOf("Date") != -1
|| field.getType().getName().indexOf("Time") != -1) {
// varchar 要加''
valueStr.append("'" + fieldValue + "',");
} else {
// 其余直接拼接
valueStr.append(fieldValue + ",");
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 添加括号
insertSql = sqlStr.toString().substring(0, sqlStr.length() - 1)
+ ")"
+ valueStr.toString().substring(0,
valueStr.length() - 1) + ");";
}
}
return insertSql;
}
/**
* @Description: 自定义sql语句的查询
* @Date:2014年8月7日下午2:52:13
* @param mData
* 数据库
* @param sql
* sql语句
* @param mObject
* 对象类
* @return
* @return ArrayList<T> 集合
*/
protected <T> ArrayList<T> getListBySql(SQLiteDatabase mData, String sql,
Class<T> mObject) {
ArrayList<T> list = new ArrayList<T>();
try {
Cursor mCursor = mData.rawQuery(sql, null);
list = mReflectUtil.cursorToList(mCursor, mObject);
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
自动转换查询结果:
一般android里sqlite的查询结果都要经过转换。
比如:
public List<People> convertFavorite(Cursor cursor) {
List<People> list = new ArrayList<People>();
while (cursor.moveToNext()) {
FavorityInfo mInfo = new People();
mInfo.setId(cursor.getInt(cursor
.getColumnIndexOrThrow("id")));
mInfo.setName(cursor.getString(cursor
.getColumnIndexOrThrow("name")));
mInfo.setAge(cursor.getString(cursor
.getColumnIndexOrThrow("age")));
.......
list.add(mInfo);
}
return list;
}
同样在字段多的情况下,写起来也非常耗时间和重复,我们也可以用注解来转换。
public <T> ArrayList<T> cursorToList(Cursor mCursor, Class<T> c) {
ArrayList<T> list = new ArrayList<T>();
// 获取所有方法
Method methods[] = c.getDeclaredMethods();
ArrayList<Field> listFields = getFieldList(c);
HashMap<String, String> map = getColumnMap(c);
String type = null;
try {
if (mCursor != null) {
while (mCursor.moveToNext()) {
// 新建一个该类型的对象
Object mObject = c.newInstance();
// 遍历方法
for (Method method : methods) {
// 获取方法的小写名字
String mName = method.getName().toLowerCase();
for (Field mField : listFields) {
// 对象里的字段名
String name = mField.getName();
// 如果是byte[] 则要去掉[]
if (name.indexOf("[]") != -1) {
name.replaceAll("[]", "");
}
if (mName.equals("set" + name.toLowerCase())) {
// 如果方法名跟字段名匹配
// 先找出该字段的类型
// 根据类型 和 别名 设置值
type = mField.getType().getName();
if (type.indexOf("String") != -1) {
method.invoke(mObject, mCursor
.getString(mCursor
.getColumnIndexOrThrow(map
.get(name))));
}
// int类型
if (type.indexOf("int") != -1
|| type.indexOf("Integer") != -1) {
method.invoke(mObject, mCursor
.getInt(mCursor
.getColumnIndexOrThrow(map
.get(name))));
}
// lang类型
if (type.indexOf("Long") != -1
|| type.indexOf("long") != -1) {
method.invoke(mObject, mCursor
.getLong(mCursor
.getColumnIndexOrThrow(map
.get(name))));
}
// boolean 类型
if (type.indexOf("boolean") != -1) {
method.invoke(
mObject,
mCursor.getLong(mCursor
.getColumnIndexOrThrow(map
.get(name))) == 0 ? false
: true);
}
// byte 类型
if (type.indexOf("byte") != -1
|| type.indexOf("Byte") != -1) {
method.invoke(mObject, (byte) (mCursor
.getInt(mCursor
.getColumnIndexOrThrow(map
.get(name)))));
}
// byte[]类型
if (type.indexOf("[B") != -1) {
method.invoke(mObject, (mCursor
.getBlob(mCursor
.getColumnIndexOrThrow(map
.get(name)))));
}
// double 类型
if (type.indexOf("double") != -1
|| type.indexOf("Double") != -1) {
method.invoke(mObject, mCursor
.getDouble(mCursor
.getColumnIndexOrThrow(map
.get(name))));
}
// float 类型
if (type.indexOf("float") != -1
|| type.indexOf("Float") != -1) {
method.invoke(mObject, mCursor
.getFloat(mCursor
.getColumnIndexOrThrow(map
.get(name))));
}
// 时间类型
if (type.indexOf("Date") != -1) {
String value = mCursor.getString(mCursor
.getColumnIndex(map.get(name)));
SimpleDateFormat formatter = new SimpleDateFormat(
"yyyy-MM-dd");
ParsePosition pos = new ParsePosition(0);
Date strtodate = formatter
.parse(value, pos);
method.invoke(mObject, strtodate);
}
}
}
}
list.add((T) mObject);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
mCursor.close();
}
return list;
}
这样,只要传入对象,就可以自动转换成我们需要的数据了。
总结###:
在实际项目中,这些方法能帮助我们快速开发,基本满足普通的增删改查和建表。但一些复杂的建表语句暂时还不支持,还需要完善功能,另外附上demo;