Androidデータベースコード自動ジェネレーター
データベースフレームワークを使用してデータベースデータを操作することは、私たちのアプリに必ずしも適しているとは限りません。実際、Google SQLiteOpenHelper
を使用することも非常に直感的です。データベースフレームワークが普及していない場合は、基本的にこのアプリを使用します。さまざまな方法でのデータベース処理。
しかし、アプリ内にN個を超えるテーブルが存在する場合があり、フレームワークで非常に多くのコメントを書き込んでコードを作成してからコピーするのSQLiteOpenHelper
は面倒ですが、繰り返しの多いコードや同じモードのコードも多数あります。したがって、私が開発したアプリのほとんどの機能によれば、これらのいくつかのデータベース操作要件は要約されています:
- テーブルを作成するためのステートメント
- 基本的な追加、削除、変更チェック。削除と変更チェックは、条件として主キー_idに基づいています。
- 追加、削除、変更におけるJavaオブジェクトとデータベースデータ間の不可避の変換
- Javaエンティティークラスとデータベース列名の対応(トラブルシューティングを容易にするため)
これらのニーズに基づいて、実装計画を事前に決定します。
- Java基本クラスを作成する
- 基本クラスに従って、データモデルを構築します(つまり、関連するデータベース操作メソッドを格納する新しいJavaクラスを作成します)
- クラスの特定のフィールドに特別な要件がある場合は、特別な処理を実行することもできるため、主キー、列名またはテーブル名、一意の識別などの注釈を追加する必要があります。
Step1。ノートを作成する
次の最初のステップでは、独自のアノテーションタイプを作成する必要があります。これは、データベースのテーブルと列に対応する2つのタイプのアノテーション、クラスアノテーションと属性アノテーションに分かれています。ここで、次のことに注意する必要があります。
- これは、すべての非Beanのではない
static
とfinal
変更された属性は、このテーブルを作成する必要があります。 - Beanには主キー列に対応する属性がありますか。
- 一意の制約があるかどうか。
- 属性に指定された列名があるかどうか。
- 注釈がない場合、デフォルトの関連スキームは、データベース関連の変換ルールを作成します。
これらのニーズをメモに反映するには:
表の注記:
/**
* 表注解
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
/**
* 设置表名
*/
String name() default "";
/**
* 是否所有属性建表列
* @return
*/
boolean all() default false;
}
プロパティの注釈:
/**
* 列注解
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
/**
* 数据库列名
* @return
*/
String name() default "";
/**
* 是否主键
* @return
*/Key() default false;
/**
boolean primary
* 是否唯一约束
* @return
*/
boolean unique() default false;
}
次の作業は私たちのコアです。Beanクラスを指定する場合、最初にBeanクラスのすべての属性を取得する必要がstatic
ありfinal
ます。属性をに変更してから、取得した属性セットに従ってテーブル列に変換することはできません。
Step2。Beanクラスのリフレクション抽出
基本的なアノテーションを使用すると、特別なニーズを増やすことができます。もちろん、アノテーションはなく、独自のデフォルトの処理スキームもあります。プロセスのこの部分で最初に行う必要があるのは、Javaクラスの基本属性がテーブルのマッピングプロセスに対応していることです。
- 列名、つまりSQLiteでのテーブルの列名は、注釈によってカスタマイズできます。デフォルトは、属性名のキャメルケースの下線に小文字を使用しています。
- Javaプログラムで使用するマッピング関係の名前には、列名の使用が含まれます。たとえば、もちろん、追加、削除、変更、および検査に必要な列名パラメーターは、パブリックStringオブジェクトに抽出する必要があります。下線
同時に、属性に対応する属性名とSQLiteタイプ、およびそれが主キーかどうかのフラグを、SQLite-Table-Columnの関連する構成エンティティ情報、つまりマッピング関係へのBeanクラス属性にカプセル化します。
public class ColumnDetail {
/**
* 列名
*/
private String columnName;
/**
* 属性名
*/
private String fieldName;
/**
* 定义名称,可以用来被调用的列名、表名的引用对象(即columnName值的引用对象)
*/
private String relativeName;
/**
* SQLite类型
*/
private String type;
/**
* 是否主键
*/
private boolean isPrimaryKey;
public ColumnDetail(String columnName, String fieldName, String connectName, String type, boolean isPrimaryKey) {
this.columnName = columnName;
this.fieldName = fieldName;
this.relativeName = connectName;
this.type = type;
this.isPrimaryKey = isPrimaryKey;
}
public String getRelativeName() {
return relativeName;
}
public void setRelativeName(String relativeName) {
this.relativeName = relativeName;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isPrimaryKey() {
return isPrimaryKey;
}
public void setPrimaryKey(boolean primaryKey) {
isPrimaryKey = primaryKey;
}
}
eg:
ColumnDetail属性 | プロパティの説明 | デモ |
---|---|---|
フィールド名 | JAVAエンティティークラスの属性 | mName |
columnName | SQLiteテーブル列 | m_name |
relativeName | fieldNameとcolumnNameの関係をマップするために使用される静的文字列定数Public static final String M_NAME = "m_name"; | M_NAME |
タイプ | SQLiteテーブルの列タイプ | テキスト |
マッピング関係の準備ができたら、Beanクラスを解析しましょう。非変数static
とfinal
変更された変数を解析する必要があることに注意してください。
/**
* 获取属性和列的对应关系
* 如果该属性指定了列名,则创建表时用指定列名,否则默认将属性名称的驼峰式写法转下划线写法
* 引用类型采用属性名称的驼峰式转下划线的方式定义
* 如果实体类中指定了主键,则引用属性和列名都用_id。
* 如果没有指定主键,若属性中存在int或long类型的_id,默认为主键,否则创建一个名为_ID的属性和列
* 只能指定一个主键,否则会使创建SQL语句出现问题
* 注意,注解的primaryKey必须为long类型,否则生成的JAVA文件会有错
* 目前支持的JAVA类型有int、long、float、double、String、byte,对应SQLite的INTEGER、TEXT、REAL
* @param clazz 实体类
* @return 对应关系集合
* @throws Exception
*/
private static List<ColumnDetail> getColumns(Class<?> clazz) throws Exception {
List<ColumnDetail> colums = new ArrayList<>();
List<Integer> unAssigned = new ArrayList<>();
// 是否全部建列
boolean isAll = true;
// 是否存在指定非主键列
boolean isColumn = false;
// 是否存在指定注解@Table
boolean isTable = false;
boolean hasPrimaryKey = false;
// 判断是否有@Table注解并标记isTable
// 如果有则判断是否全部属性建列,如果没有@Table注解,则默认全部属性建列,并标记isAll
if (clazz.isAnnotationPresent(Table.class)) {
Table table = clazz.getAnnotation(Table.class);
isTable = true;
isAll = table.all();
}
Field[] fields = clazz.getDeclaredFields();
if (fields != null) {
for (Field field : fields) {
// 排除掉静态变量和常量
if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {
// 如果使用@Column注解,则按照指定要求建列
if (field.isAnnotationPresent(Column.class)) {
String columnName;
Column column = field.getAnnotation(Column.class);
columnName = column.name();
boolean isPrimaryKey = false;
String type;
if (int.class.isAssignableFrom(field.getType()) || long.class.isAssignableFrom(field.getType())) {
// 主键
if (column.primaryKey()) {
columnName = "_id";
isPrimaryKey = true;
hasPrimaryKey = true;
type = "INTEGER PRIMARY KEY AUTOINCREMENT";
} else {
type = "INTEGER";
}
} else if (String.class.isAssignableFrom(field.getType())) {
type = "TEXT";
} else if (float.class.isAssignableFrom(field.getType()) || double.class.isAssignableFrom(field.getType())) {
type = "REAL";
} else if (byte.class.isAssignableFrom(field.getType())) {
type = "BLOB";
} else {
throw new Exception("TYPE ERROR: " + field.getType() + " : " + field.getName());
}
// 如果指定列名为空,则默认驼峰转下划线方式命名
if (StringUtil.isEmpty(columnName)) {
columnName = StringUtil.humpToUnderline(field.getName()).toLowerCase();
}
// 存在非主键的指定列,并标记
if (!column.primaryKey()){
isColumn = true;
}
if (column.unique()){
type = type + " UNIQUE";
}
// 添加到集合中
colums.add(new ColumnDetail(columnName.toLowerCase(), field.getName(), StringUtil.humpToUnderline(field.getName()).toUpperCase(), type, isPrimaryKey));
} else if (isAll) {
// 非指定列
String columnName = StringUtil.humpToUnderline(field.getName()).toLowerCase();
boolean isPrimaryKey = false;
if (field.getName().equalsIgnoreCase("_ID")) {
isPrimaryKey = true;
hasPrimaryKey = true;
}
String type;
if (int.class.isAssignableFrom(field.getType()) || long.class.isAssignableFrom(field.getType())) {
if (isPrimaryKey) {
type = "INTEGER PRIMARY KEY AUTOINCREMENT";
} else {
type = "INTEGER";
}
} else if (String.class.isAssignableFrom(field.getType())) {
type = "TEXT";
} else if (float.class.isAssignableFrom(field.getType()) || double.class.isAssignableFrom(field.getType())) {
type = "REAL";
} else if (byte.class.isAssignableFrom(field.getType())) {
type = "BLOB";
} else {
throw new Exception("TYPE ERROR: " + field.getType() + " : " + field.getName());
}
// 如果非主键列,则将集合的游标记录下,当确定除指定属性不建列时,从集合中移除
if (!isPrimaryKey){
unAssigned.add(unAssigned.size());
}
colums.add(new ColumnDetail(columnName, field.getName(), columnName.toUpperCase(), type, isPrimaryKey));
} else {
// 为防止自增主键列对应的属性_id被重复创建,在这里获取
if (field.getName().equalsIgnoreCase("_ID")) {
hasPrimaryKey = true;
colums.add(new ColumnDetail("_id", field.getName(), "_ID", "INTEGER PRIMARY KEY AUTOINCREMENT", true));
}
}
}
}
// 当指定了@Table的all属性为true或者除主键列的属性有注解外没有任何注解的情况下,全部属性自建列,否则只有指定的属性和主键对应的属性建列
if ((isTable && !isAll) || (!isTable && isColumn)){
for (int i = 0; i < unAssigned.size(); i++) {
colums.remove(unAssigned.get(i) + i);
}
}
// 如果类不存在指定的主键对应的属性时,创建_id的属性,并创建自增主键列,该条对应关系的属性名称为空,方便后续在实体类中增加该属性
if (!hasPrimaryKey) {
colums.add(new ColumnDetail("_id", "", "_ID","INTEGER PRIMARY KEY AUTOINCREMENT", true));
}
}
return colums;
}
列の解析に加えて、取得する必要があるテーブル名があります。
/**
* 获取实体类的表名(如果没有用@Table注解,则创建表的sql语句中表名默认为类名小写)
* @param clazz 实体类
* @return 注解的表名(如果没有指定表名则返回null)
*/
private static String getTableName(Class<?> clazz) {
String name = null;
if (clazz.isAnnotationPresent(Table.class)) {
Table table = clazz.getAnnotation(Table.class);
name = "tb_" + StringUtil.humpToUnderline(table.name()).toLowerCase();
}
return name;
}
Step3。テーブルを作成する
属性と列の間の対応により、List<ColumnDetail>
関連するSQLステートメントを開始できます。ただし、最初にテーブルと列の名前を、次のような呼び出しの静的定数として書き込む必要があります。public static final String M_NAME = "m_name";
/**
* 生成创建表的SQL语句和以_ID为查询条件的静态常量语句
* 创建表的SQL语句的静态常量名为CREATE_TABLE
* 创建以_ID为查询条件的静态常量名为_ID_CLAUSE
* public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COLUMN + " TYPE)";
* CREATE_TABLE拼接语句的使用使用StringBuilder
* public static final String _ID_CLAUSE = _ID + " = ?";
* @param columns {@link #getColumns}
* @return 创建表的SQL语句
* @throws Exception
*/
public static String createTable(List<ColumnDetail> columns) throws Exception {
String sql = null;
StringBuilder builder = new StringBuilder("\tpublic static final String CREATE_TABLE = new StringBuilder(\"CREATE TABLE \")").append(".append(TABLE_NAME).append(\"( \")");
if (!columns.isEmpty()) {
for (ColumnDetail column : columns) {
builder.append("\n\t\t\t.append(").append(column.getRelativeName()).append(").append(\" ");
if (column.isPrimaryKey()) {
builder.append(column.getType()).append(" , \")");
} else {
builder.append(column.getType()).append(", \")");
}
}
int start = builder.length() - ", \")".length();
builder.replace(start, start + 2, ")");
builder.append(".toString();\n");
builder.append("\n\tpublic static final String _ID_CLAUSE = _ID + \" = ?\";\n");
sql = builder.toString();
}
return sql;
}
このプロセスでは、条件としての主キーのその後の変更と削除を容易にするために、クエリ条件を静的定数として記述して、呼び出しを容易にします。public static final String _ID_CLAUSE = _ID + " = ?";
次の作業も非常に簡単です。データベースデータとJAVAオブジェクトを相互に変換するステートメントを作成します。
JAVAオブジェクトからデータベースのキーと値のペアのContetnValues
取得:
/**
* 生成增删改的所需要用到的键值对方法语句
* 主键默认自增,所以方法中不会包含将_ID设置到键值对的语句
* 创建的方法名为 getKeyAndValue,方法参数为实体类对象info(非空)
* 方法类型默认静态,方法返回键值对ContentValues
* public static ContentValue getKeyAndValue(Object obj){
* ContentValue cv = new ContentValue();
* cv.put(RELATIVE_NAME, obj.field);
* return cv;
* }
* @param clazz 实体类
* @param columns {@link #getColumns}
* @return 生成创建键值对的方法语句
* @throws Exception
*/
public static String createCV(Class<?> clazz, List<ColumnDetail> columns) throws Exception {
StringBuilder builder = new StringBuilder("\tpublic static ContentValues getKeyAndValue(");
builder.append(clazz.getSimpleName()).append(" info){\n").append("\t\tContentValues cv = new ContentValues();\n");
for (ColumnDetail column : columns) {
if (!StringUtil.isEmpty(column.getFieldName())) {
// 对非主键列的所有属性值装载
if (!column.isPrimaryKey()) {
builder.append("\t\tcv.put(").append(column.getRelativeName()).append(", info.").append(column.getFieldName()).append(");\n");
}
}
}
builder.append("\t\treturn cv;\n\t}\n");
return builder.toString();
}
データベースカーソルカーソルからJava型への取得:
/**
* 生成读取表后转实体类的对应方法语句
* 根据反射的字段类型判断需要获取的基本类型
* 如果指定了主键或存在_id则用指定主键的属性名称,否则默认_id
* 创建的方法名为 getModel,参数为实体类对象info(非空)和游标对象cr(Cursor),方法返回值为空,通过参数的引用赋值
* public static void getModel(Object obj, Cursor cr){
* info.field = cr.getInt(cr.getColumnIndex(RELATIVE_NAME)); // int类型,RELATIVE_NAME替换称列的关联静态常量
* info.field = cr.getLong(cr.getColumnIndex(RELATIVE_NAME)); // long类型,RELATIVE_NAME替换称列的关联静态常量
* info.field = cr.getFloat(cr.getColumnIndex(RELATIVE_NAME)); // float类型,RELATIVE_NAME替换称列的关联静态常量
* info.field = cr.getDouble(cr.getColumnIndex(RELATIVE_NAME)); // double类型,RELATIVE_NAME替换称列的关联静态常量
* info.field = cr.getString(cr.getColumnIndex(RELATIVE_NAME)); // string类型,RELATIVE_NAME替换称列的关联静态常量
* info.field = cr.getBlob(cr.getColumnIndex(RELATIVE_NAME)); // blob类型,RELATIVE_NAME替换称列的关联静态常量
* info._id = cr.getLong(cr.getColumnIndex(_ID)); // 主键赋值
* }
* @param clazz 实体类
* @param columns {@link #getColumns}
* @return 读取表后转实体类的对应方法语句
* @throws Exception
*/
public static String createModel(Class<?> clazz, List<ColumnDetail> columns) throws Exception {
StringBuilder builder = new StringBuilder("\tpublic static void getModel(");
builder.append(clazz.getSimpleName()).append(" info, Cursor cr){\n");
if (columns != null) {
for (ColumnDetail column : columns) {
if (!StringUtil.isEmpty(column.getFieldName())) {
if (!column.isPrimaryKey()) {
builder.append("\t\tinfo.").append(column.getFieldName()).append(" = ");
Field field = clazz.getDeclaredField(column.getFieldName());
if (int.class.isAssignableFrom(field.getType())){
builder.append("cr.getInt(");
} else if(long.class.isAssignableFrom(field.getType())) {
builder.append("cr.getLong(");
} else if (String.class.isAssignableFrom(field.getType())) {
builder.append("cr.getString(");
} else if (float.class.isAssignableFrom(field.getType())) {
builder.append("cr.getFloat(");
} else if (double.class.isAssignableFrom(field.getType())) {
builder.append("cr.getDouble(");
} else if (byte.class.isAssignableFrom(field.getType())) {
builder.append("cr.getBlob(");
} else {
throw new Exception("TYPE ERROR: " + field.getType() + " : " + field.getName());
}
builder.append("cr.getColumnIndex(").append(column.getRelativeName()).append("));\n");
} else {
builder.append("\t\tinfo.").append(column.getFieldName()).append(" = cr.getLong(cr.getColumnIndex(_ID));\n");
}
} else {
// 如果不存在属性对应主键列,则用创建的属性_id获取主键值
builder.append("\t\tinfo._id = cr.getLong(cr.getColumnIndex(_ID));\n");
}
}
}
builder.append("\t}\n");
return builder.toString();
}
最後に、CRUDメソッドを作成できます。
/**
* 创建增删改查方法
* insert, 单体插入返回行号或根据行号判定是否新增成功,集合插入,可根据需求自行修改,包括开启事务,中断插入等操作
* update,包括参数primaryKey和obj对象,可自行修改参数为clazz对象的方法以满足需求,返回类型为int值,影响行数
* delete,包括参数primaryKey和obj对象,可自行修改参数为clazz对象的方法以满足需求,返回类型为int值,影响行数
* query, 包括查询全部(无参查询,集合输出)和按primaryKey查询单体、obj对象查询单体和集合,可修改obj对象查询方式以自定义满足需求
* @param clazz
* @param details
* @param dbObj
* @return
*/
public static String createCRUD(Class<?> clazz, List<ColumnDetail> details, String dbObj){
String primaryField = "_id";
for (ColumnDetail detail : details) {
if (detail.isPrimaryKey()){
primaryField = detail.getFieldName();
if (null != primaryField && primaryField.isEmpty()){
primaryField = "_id";
}
break;
}
}
String clazzName = clazz.getSimpleName();
StringBuilder builder = new StringBuilder("\t");
// insert, 单体插入返回行号或根据行号判定是否新增成功,集合插入,可根据需求自行修改,包括开启事务,中断插入等操作
// 返回long类型,插入的行号,主键自增,因此行号=主键
builder.append("public static long insert(").append(clazzName).append(" info){\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("info.").append(primaryField).append(" = db.insert(TABLE_NAME, null, getKeyAndValue(info));\n\t\t")
.append("return info.").append(primaryField).append(";\n\t}\n\n\t");
// 返回布尔类型是否插入成功
builder.append("public static boolean insertRow(").append(clazzName).append(" info){\n\t\t")
.append("return insert(info) > 0;\n\t}\n\n\t");
// 集合插入
builder.append("public static boolean insertRow(List<").append(clazzName).append("> infos){\n\t\t")
.append("boolean isInsert = false;\n\t\t")
.append("for(").append(clazzName).append(" info : infos){\n\t\t\t")
.append("isInsert &= insertRow(info);\n\t\t}\n\t\t")
.append("return isInsert;\n\t}\n\n\t");
// update,参数obj对象,返回类型为int值,影响行数, 可自行添加方法以满足需求
// obj对象
builder.append("public static int update(").append(clazzName).append(" info){\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("return db.update(TABLE_NAME, getKeyAndValue(info), _ID_CLAUSE, new String[]{String.valueOf(info.")
.append(primaryField).append(")});\n\t}\n\n\t");
// delete,包括参数primaryKey和obj对象,可自行修改参数为clazz对象的方法以满足需求,返回类型为int值,影响行数
// primaryKey
builder.append("public static int delete(long _id){\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("return db.delete(TABLE_NAME, _ID_CLAUSE, new String[]{String.valueOf(_id)});\n\t}\n\n\t");
// obj对象
builder.append("public static int delete(").append(clazzName).append(" info){\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("return db.delete(TABLE_NAME, _ID_CLAUSE, new String[]{String.valueOf(info.")
.append(primaryField).append(")});\n\t}\n\n\t");
// query, 包括查询全部(无参查询,集合输出)和按primaryKey查询单体、obj对象查询单体和集合,可修改obj对象查询方式以自定义满足需求
// 无参数,查询全部
builder.append("public static List<").append(clazzName).append("> queryAll(){\n\t\t")
.append("ArrayList<").append(clazzName).append("> infos = new ArrayList<>();\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("Cursor cr = db.query(TABLE_NAME, null, null, null, null, null, null);\n\t\t")
.append(clazzName).append(" info;\n\t\t")
.append("while (cr.moveToNext()){\n\t\t\t")
.append("info = new ").append(clazzName).append("();\n\t\t\t")
.append("getModel(info, cr);\n\t\t\t")
.append("infos.add(info);\n\t\t}\n\t\treturn infos;\n\t}\n\n\t");
// 参数primaryKey,查询单条
builder.append("public static ").append(clazzName).append(" query(long _id){\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("Cursor cr = db.query(TABLE_NAME, null, _ID_CLAUSE, new String[]{String.valueOf(_id)}, null, null, null);\n\t\t")
.append(clazzName).append(" info = new ").append(clazzName).append("();\n\t\t")
.append("if (cr.moveToNext()){\n\t\t\t")
.append("getModel(info, cr);\n\t\t}\n\t\t")
.append("return info;\n\t}\n\n\t");
// 参数obj,查询单条
builder.append("public static ").append(clazzName).append(" query(").append(clazzName).append(" info){\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("Cursor cr = db.query(TABLE_NAME, null, _ID_CLAUSE, new String[]{String.valueOf(info.").append(primaryField).append(")}, null, null, null);\n\t\t")
.append(clazzName).append(" data = new ").append(clazzName).append("();\n\t\t")
.append("if (cr.moveToNext()){\n\t\t\t")
.append("getModel(data, cr);\n\t\t}\n\t\t")
.append("return data;\n\t}\n\n\t");
// 参数obj,查询列表
builder.append("public static List<").append(clazzName).append("> queryAll(").append(clazzName).append(" info){\n\t\t")
.append("ArrayList<").append(clazzName).append("> infos = new ArrayList<>();\n\t\t")
.append("SQLiteDatabase db = ").append(dbObj).append(";\n\t\t")
.append("Cursor cr = db.query(TABLE_NAME, null, _ID_CLAUSE, new String[]{String.valueOf(info.").append(primaryField).append(")}, null, null, null);\n\t\t")
.append(clazzName).append(" data;\n\t\t")
.append("while (cr.moveToNext()){\n\t\t\t")
.append("data = new ").append(clazzName).append("();\n\t\t\t")
.append("getModel(data, cr);\n\t\t\t")
.append("infos.add(data);\n\t\t}\n\t\treturn infos;\n\t}\n");
return builder.toString();
}
新しいModelクラスを作成し、上記のメソッドによって構築されたコンテンツをこのクラスに出力します。これにより、基本的な同様のモジュールの迅速な構築が完了します。
Step4.Beanファイルの逆書き込み
ここに書いている時点で、実際にはほとんどの作業が完了していますが、もちろん、柔軟に対応する必要のある細かい点がいくつかあります。
なぜそれを書き戻すのですか?
実際、その理由は非常に単純です。長い時間をかけてコードを確認する場合、特にデータベースをチェックした後、テーブルの列に組み込む必要のある属性と、対応する列の名前を比較することは非常に辛抱する必要があります列が多数ある場合(SQLiteは軽量のデータベースであるため、データが多い場合は、通常、テーブルの関連付けを使用してデータを分割することはほとんどありません)この時点では、カスタムアノテーションをBeanに書き込みます。コメントとなります。
ここでは、通常の一致クエリのパターンを採用しています。行ごとの判断は、属性が定義されている行であるかどうかです(もちろん、突然の小さなケースがたくさんあります。たとえば、このクラスはネイキッドになることを意図していませんが、コメントはたくさんあります)または不規則な条件の場合、逆のコードにも特定の問題があります)、そうである場合は、属性行が注釈と一致しない場合は切り取り、対応する注釈と一致する場合は属性行に注釈を追加しますその後、より標準化されたアノテーションメソッドに置き換えられました。
/**
* 将构建的表名列名替换掉实体类的注解值
* 如果存在类的注解@Table,将类的注解@Table的name值替换称model类对应的常量
* 如果存在属性的注解@Column,将属性的注解@Column的name值替换称model类对应的常量
* 如果不存在表注解@Table,则将表名的注解添加上,name值为model类对应的常量
* 如果不存在属性注解@Column,则将表对应的列的注解添加上,name值model类对应的常量
* 使用正则表达式的匹配查询的方式对每一行进行判断,如果该行存在指定的属性,则进行替换
* 行的结束标志是;
* 注意行内不要有注释
* 针对主键做特殊处理
* 引入相应的包
* 处理结果
* @Table(name = modelClass.TABLE_NAME) // 是否全部建列属性即 @Table(all = true) 或 @Table(all = true) 则会被删除掉,可自行添加作为标记
* public class Object {
* @Column(name = modelClass.RELATIVE_NAME)
* public String field;
* @Column(name = modelClass._ID, primaryKey = true)
* public long _id;
* }
* 正则表达式的用法:
* [\s\S] 匹配任意字符(\s为空白字符,\S为非空白字符)
* . 匹配非换行之外的字符(\n\r)
* \t 缩进符
* * 匹配0-任意次
* + 匹配1-任意次(至少匹配一次)
* ? 匹配0或1次(至多匹配一次)
* [] 字符集合,包含包含的任意一个字符的内容。对某些特殊符号进行匹配时,也需要用[]包住
* [^] 字符集合,匹配未包含任意一个字符集的内容
* \b 匹配左右边界,即边界存在空白字符即可匹配,在左匹配做边界,在右匹配右边界
* $ 匹配行尾
* ^ 匹配行首
* JAVA中String.replace(String target, String replacement)和String.replaceAll(String regex, String replacement)的区别
* replace查找target的内容,并替换成指定内容replacement
* replaceAll通过正则匹配查找内容,并替换成指定内容replacement
* @param clazz 实体类
* @param modelClazz 生成对应常量名称和SQL语句、映射方法的model类
* @param filename 实体类的路径
* @param columns {@link #getColumns}
* @return 修改后的实体类
* @throws Exception
*/
public static String writeReflect(Class<?> clazz, Class<?> modelClazz, String filename, List<ColumnDetail> columns) throws Exception {
StringBuilder builder = new StringBuilder();
File file = new File(filename);
FileReader reader = new FileReader(file);
BufferedReader br = new BufferedReader(reader);
String line;
while ((line = br.readLine()) != null) {
builder.append(line).append("\n");
}
br.close();
reader.close();
Field[] fields = clazz.getDeclaredFields();
String content = builder.toString();
if (fields != null) {
boolean isNeedPrimaryKey = false;
for (ColumnDetail column : columns) {
if (!StringUtil.isEmpty(column.getFieldName())) {
Field field = clazz.getDeclaredField(column.getFieldName());
// 匹配查询,查询@Column...public/private/protected/default javaType fieldName;(目前只支持public模式,方便赋值)
String regexAnnotation = "(\\t*)( *)(((@Column( *\\t*[(] *[^)]*[)] *\\t*))|((@Column *\\t*)))|())";
String regexDeclare = Modifier.isPrivate(field.getModifiers()) ? "private" : Modifier.isPublic(field.getModifiers()) ? "public" : Modifier.isProtected(field.getModifiers()) ? "protect" : "";
String regexType = field.getType().getSimpleName();
String regexName = field.getName();
String regex = regexAnnotation + "\\s*" + regexDeclare + "\\s*" + regexType + "\\s*" + regexName + "\\s*;" + "\\t* *";
StringBuilder newLine = new StringBuilder("\n");
builder.append("\t");
if (column.isPrimaryKey()) {
newLine.append("\n\t@Column(name = ").append(modelClazz.getSimpleName()).append("._ID, primaryKey = true)");
} else {
newLine.append("\t@Column(name = ").append(modelClazz.getSimpleName()).append(".").append(column.getRelativeName()).append(")");
}
newLine.append("\n\t");
newLine.append("" == regexDeclare ? "" : regexDeclare + " ").append(regexType).append(" ").append(regexName).append(";");
content = content.replaceAll(regex, newLine.toString());
} else {
isNeedPrimaryKey = true;
}
}
Pattern pattern = Pattern.compile("((((@Table)[\\s\\S]*)|())public\\s*class\\s*" + clazz.getSimpleName() + ")((\\s*[{])|( +[^{]*[{]))");
Matcher m = pattern.matcher(content);
if (m.find()) {
String result = m.group();
pattern = Pattern.compile("@Table\\s*([\\(]+[^)]*[\\)]+)*");
m = pattern.matcher(result);
String tableResult = result;
if (m.find()){
tableResult = result.replace(m.group(), "");
}
String replaceContent = "import " + modelClazz.getName() + ";\nimport cn.ximoon.framework.db.Column;\n" + "import cn.ximoon.framework.db.Table;\n\n@Table(name = " + modelClazz.getSimpleName() + ".TABLE_NAME)\n" + tableResult + "\n";
if (isNeedPrimaryKey){
replaceContent = replaceContent + "\n\n\t@Column(name = " + modelClazz.getSimpleName() + "._ID, primaryKey = true)\n\tpublic long _id;\n";
}
content = content.replace(result, replaceContent);
}
System.out.println(content);
}
return content;
}
Step5。ツールを呼び出す
次に、完了したすべてのメソッドを組み合わせ、Beanクラスを変更して注釈の置き換えを完了し、Modelクラスを上書きしてデータベースの基本操作を完了します。同時に、パッケージをインポートする必要がある場合は、手動でパッケージをインポートして作業をさらに減らすこともできます。
私たちがいることをここで注意しなければならない5つのパラメータを定義含む、二つのクラスの選択、取得モード、豆とモデル、および実際のファイルパスのこの2つのクラス、右のクラスファイルによるプロジェクトのディレクトリ構造のパスをCopy Path
ショートカットキーCtrl+Shift+C
のことができますが最後のパラメーターは、継承を通じてプロジェクトデータベース全体の永続化エンジンを取得しSQLiteOpenHelper
、シングルトンモードを呼び出して取得する方法SQLiteDatabase
です。
/**
* 生成实体类的基本SQL语句,包括表的创建SQL和表查询结果和实体类的相互转化方法以及表名列名的属性常量
* 对实体类做基本替换
* 注意modelClass覆写模式,即内容全部被替换成新内容,CRUD需要导入SQLiteDatabase的对象需要的类,创建完后可手动导入
* 目前对属性只支持public,如果为其他修饰符(private、protected、default),在赋值和取值对时候可能存在问题,同包可能不存在问题,私有化请自行更改代码
* 即 info.field = ""; 和 info.setField("");
* @param clazz 实体类
* @param modelClazz 生成对应常量名称和SQL语句、映射方法的model类
* @param beanName 实体类的路径
* @param modelName model类的路径
* @param dbobj 如果需要创建CRUD方法,则必须传入SQLiteDatabase获取的方法,以文本的方式传入
* eg: DBOpenHelp.getInstance().getSQLiteDatabase();
* 生成 SQLiteDatabase db = DBOpenHelp.getInstance().getSQLiteDatabase();
* 用来增删改查
* er@throws Exception
*/
public static void writeFile(Class<?> clazz, Class<?> modelClazz, String beanName, String modelName, String dbobj) throws Exception {
List<ColumnDetail> columns = getColumns(clazz);
if (null != columns && columns.size() > 0) {
String dbColumns = createDBColumns(clazz, columns);
String createTable = createTable(columns);
String createCV = createCV(clazz, columns);
String createModel = createModel(clazz, columns);
String createCRUD = "";
if (!StringUtil.isEmpty(dbobj)) {
createCRUD = "\n" + createCRUD(clazz, columns, dbobj) + "\n";
}
// 对modelClass覆写,并导入所需要对包
StringBuilder builder = new StringBuilder("")
.append(modelClazz.getPackage())
.append(";\n\nimport android.content.ContentValues;\n")
.append("import android.database.Cursor;\n")
.append("import android.database.sqlite.SQLiteDatabase;\n")
.append("import android.text.TextUtils;\n\n")
.append("import java.util.ArrayList;\n")
.append("import java.util.List;\n")
.append("\nimport ")
.append(clazz.getPackage().getName())
.append(".")
.append(clazz.getSimpleName())
.append(";\n")
.append("\npublic class ")
.append(modelClazz.getSimpleName())
.append(" {\n\n")
.append(dbColumns)
.append("\n")
.append(createTable)
.append("\n")
.append(createCRUD)
.append(createCV)
.append("\n")
.append(createModel)
.append("\n}");
String content = writeReflect(clazz, modelClazz, beanName, columns);
FileWriter fileWriter = new FileWriter(beanName);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
content = builder.toString();
System.out.println(content);
fileWriter = new FileWriter(modelName);
fileWriter.write(content);
fileWriter.flush();
fileWriter.close();
}
}
ソースコードアドレスを添付します。
データベースコード自動ジェネレーターのソースコード
レンダリングをアタッチします。
実行前:
実行後:
ガイド付きパッケージの後: