前言
本章是GreenDAO3.2.2简单入门的最后一篇,是对前面两篇文章的进一步扩展。
GreenDAO 3.2.2 简单入门(二)多表查询和多表关联
GreenDAO 3.2.2 简单入门(一)增删改查
数据库升级原因和问题
原因:
- 已经完成的项目,要添加新的表
- 已经完成的项目,要对以前的表添加新的列或删除旧的列
问题:
- 已经完成的项目,数据库中已经存在数据甚至是大量数据
- GreenDAO 3.2.2默认的数据库升级,会将所有的表全部删除,在重建所有表
- 导致以前已经保存的数据全部丢失
实战
数据库升级的思路如下:
- 用临时表存储原有数据
- 用临时表的目的时为了数据库升级后不丢失原有数据
- 将原表更名后,成为对应的临时表,临时表中包含原有数据
- 创建新表拷贝原有数据
- 删除临时表完成升级
新建MigrationHelper
类,其代码如下:
public class MigrationHelper {
// 调用的升级方法,第二个参数表示,只要继承了AbstractDao的实体类的Dao类都可以
@SafeVarargs
public static void migrate(Database db, Class extends AbstractDao, ?>>... daoClasses) {
// 生成临时表,将旧表更名,编程临时表
generateTempTables(db, daoClasses);
// 创建新表
createAllTables(db, false, daoClasses);
// 将临时表的数据拷贝到新表,并删除临时表
restorData(db, daoClasses);
}
@SafeVarargs
private static void generateTempTables(Database db, Class extends AbstractDao, ?>>... daoClasses) {
// 循环参数中的Dao类,对参数中所有Dao类进行更名操作
for (int i = 0; i < daoClasses.length; i++) {
// 获得DaoConfing对象,这个对象里面封装了表名
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
// 如果表不存在跳过后续的操作
if (!checkTable(db, tableName)) {
continue;
}
String tempTableName = daoConfig.tablename.concat("_TEMP");// 临时表名
StringBuilder insertTableStringBuilder = new StringBuilder();
// 注意空格,拼接sql语句,将表改名变成临时表
insertTableStringBuilder.append("alter table ")
.append(tableName)
.append(" rename to ")
.append(tempTableName)
.append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}
private static boolean checkTable(Database db, String tableName) {
StringBuffer query = new StringBuffer();
query.append("select count(*) from sqlite_master where type='table' and name='")
.append(tableName)
.append("'");
Cursor c = db.rawQuery(query.toString(), null);
if (c.moveToNext()) {
int count = c.getInt(0);
if (count > 0) {
return true;
}
return false;
}
return false;
}
@SafeVarargs
private static void createAllTables(Database db, boolean ifNotExists,
@NotNull Class extends AbstractDao, ?>>... daoClasses) {
reflectMethod(db, "createTable", ifNotExists, daoClasses);
}
// 用反射的方法来获得createTable方法,创建个Dao类相对应的实体类的表
@SafeVarargs
private static void reflectMethod(Database db, String methodName, boolean ifNotExists,
@NotNull Class extends AbstractDao, ?>>... daoClasses) {
if (daoClasses.length < 1) return;
try {
for (Class> cls : daoClasses) {
/* 以User实体类为例,在生成的UserDao中有一个createTable()方法。
* 当User类的属性发生改变时,编译后UserDao的相应代码会发生改变,比如createTable()方法。
* 但是,表名并没有改变,当以下代码执行成功后,也就是新表创建了。
* 它的表名还是USER,而与其对应的临时表名时USER_TEMP。*/
Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);
method.invoke(null, db, ifNotExists);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@SafeVarargs
private static void restorData(Database db, Class extends AbstractDao, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
if (!checkTable(db, tempTableName)) continue;
// 获得临时表也就是旧表中的所有列
List columns = getColums(db, tempTableName);
// 如果旧表的列包含此新表列,将其添加到properties集合中
ArrayList properties = new ArrayList<>(columns.size());
for (int j = 0; j < daoConfig.properties.length; j++) {
// 获得新表的列
String columnName = daoConfig.properties[j].columnName;
// 如果旧表的列包含此新表列,将其添加到properties集合中
if (columns.contains(columnName)) {
properties.add(columnName);
}
}
if (properties.size() > 0) {
// 在集合中每个字符串元素(即列名)之间用英文下的逗号相连,用于构建sql语句中的列名
final String columnSQL = TextUtils.join(",", properties);
StringBuilder insertTableStringBuilder = new StringBuilder();
// 注意空格,拼接sql语句,拷贝旧表数据到新表
insertTableStringBuilder.append("insert into ")
.append(tableName)
.append("(")
.append(columnSQL)
.append(") select ")
.append(columnSQL)
.append(" from ")
.append(tempTableName)
.append(";");
db.execSQL(insertTableStringBuilder.toString());
}
// 删除临时表
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("drop table ").append(tempTableName);
db.execSQL(dropTableStringBuilder.toString());
}
}
// 获得表中所有的列名
private static List getColums(Database db, String tableName) {
List columns = null;
Cursor cursor = null;
try {
cursor = db.rawQuery("select * from " + tableName + " limit 0", null);
if (null != cursor && cursor.getColumnCount() > 0) {
columns = Arrays.asList(cursor.getColumnNames());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
if (columns == null) {
columns = new ArrayList<>();
}
}
return columns;
}
}
以User类对应的USER表为例,为其新增一列SEX
。第一步,为USER表准备数据:
// 在MainActivity中onCreate()方法中调用,要注掉其他方法
userDao.deleteAll();
insertMany();
编译运行后,更改数据库版本号(比之前的版本号高)以及User
类并make project
,其代码如下:
@Entity(
nameInDb = "USERS", // 表名
indexes = {
@Index(value = "name DESC"), // 为属性name设置索引
}
)
public class User {
@Id(autoincrement = true) // 主键,要求是Long型
private Long id;
private Long cardId;
@ToOne(joinProperty = "cardId") // 设置一对一关联,连接属性是cardId
private Card card;
@ToMany(referencedJoinProperty = "userId") // 设置一对多关联,连接属性是Orders类的外键userId
private List orders;
@Index(name = "usercode_index", unique = true) // 设置索引且是唯一索引
private String usercode;
@Property(nameInDb = "username") // 设置该属性对应的列名
@NotNull // 非空
private String name;
private String userAddress; // 可以为空
// 为数据库升级测试用
private String sex;
@Transient // 临时存储
private int tempUserSign;
}
在MainActivity中添加如下方法,然后将onCreate()中其他方法注掉并调用它们:
private void migrationTest() {
User user1 = userDao.queryBuilder().where(UserDao.Properties.Name.eq("孙悟空")).build().unique();
user1.setSex("男");
userDao.update(user1);
}
private void migrationQueryList() {
String result = "显示结果为:";
List users = userDao.loadAll();
int i = 0;
for (User user : users) {
i++;
String res = result + "i=" + i + ",id:" + user.getId() + ",name:" + user.getName() +
",address:" + user.getUserAddress() +
",sex:" + user.getSex();
Log.d("TAG", res);
}
}
// 依次调用
migrationTest();
migrationQueryList();
最后将HMROpenHelper
类更改如下,就可以运行测试了。
public class HMROpenHelper extends DaoMaster.OpenHelper{
public HMROpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
Log.d("TAG","更新了");
// DaoMaster.dropAllTables(db, true);
// onCreate(db);
// 目前用USER表来测试
MigrationHelper.migrate(db, UserDao.class);
}
}
总结
GreenDAO 3.2.2的简单入门就讲到这里了。
最终源码
GreenDAO 3.2.2 简单入门(一)增删改查
GreenDAO 3.2.2 简单入门(二)多表查询和多表关联