You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rjgc/NotesDatabaseHelper.java

749 lines
29 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
/**
* 笔记数据库帮助类
*
* 功能概述:
* 1. 继承SQLiteOpenHelper管理SQLite数据库的创建和升级
* 2. 定义note表和data表的SQL创建语句
* 3. 定义数据库触发器,实现自动化数据维护
* 4. 管理数据库版本升级逻辑
*
* 数据库设计:
* - note表存储笔记基本信息ID、类型、时间、摘要等
* - data表存储笔记具体内容文本、通话记录等
* - 触发器:自动维护文件夹计数、笔记摘要、级联删除等
*
* 单例模式:
* - 使用getInstance()获取唯一实例
* - 确保整个应用中只有一个数据库连接
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库文件名
private static final String DB_NAME = "note.db";
// 数据库版本号当前为版本5
// 版本历史:
// V1: 初始版本
// V2: 重建表结构
// V3: 添加Google Task支持和回收站
// V4: 添加版本号字段
// V5: 当前版本
private static final int DB_VERSION = 5;
/**
* 表名常量接口
* 定义数据库中的表名
*/
public interface TABLE {
// 笔记表,存储笔记基本信息
public static final String NOTE = "note";
// 数据表,存储笔记具体内容
public static final String DATA = "data";
}
private static final String TAG = "NotesDatabaseHelper";
// 单例实例,确保全局只有一个数据库连接
private static NotesDatabaseHelper mInstance;
/**
* 创建笔记表的SQL语句
*
* note表结构说明
* - ID: 主键,自增
* - PARENT_ID: 父文件夹ID0=根文件夹,负数=系统文件夹
* - ALERTED_DATE: 提醒时间0=无提醒
* - BG_COLOR_ID: 背景颜色ID
* - CREATED_DATE: 创建时间,默认当前时间
* - HAS_ATTACHMENT: 是否有附件0=无1=有
* - MODIFIED_DATE: 修改时间,默认当前时间
* - NOTES_COUNT: 笔记数量(仅文件夹有效)
* - SNIPPET: 内容摘要
* - TYPE: 类型0=笔记1=文件夹2=系统文件夹
* - WIDGET_ID: 桌面Widget ID
* - WIDGET_TYPE: Widget类型-1=无效0=2x21=4x4
* - SYNC_ID: Google同步ID
* - LOCAL_MODIFIED: 本地修改标记
* - ORIGIN_PARENT_ID: 原始父文件夹ID
* - GTASK_ID: Google Task ID
* - VERSION: 版本号
*/
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
/**
* 创建数据表的SQL语句
*
* data表结构说明
* - ID: 主键,自增
* - MIME_TYPE: MIME类型区分数据类型text_note、call_note
* - NOTE_ID: 所属笔记ID外键关联note表
* - CREATED_DATE: 创建时间
* - MODIFIED_DATE: 修改时间
* - CONTENT: 内容文本
* - DATA1-DATA5: 扩展字段,不同类型有不同用途
*
* 采用EAV模型设计
* - 一条笔记可以有多个data记录
* - 通过MIME_TYPE区分数据类型
* - 通过NOTE_ID关联到note表
*/
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
DataColumns.MIME_TYPE + " TEXT NOT NULL," +
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA1 + " INTEGER," +
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";
/**
* 创建数据表索引的SQL语句
*
* 索引说明:
* - 在data表的NOTE_ID字段上创建索引
* - 加速根据笔记ID查询数据记录的操作
* - 提高note-data关联查询的性能
*/
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* 触发器:更新笔记父文件夹时,增加目标文件夹的笔记计数
*
* 触发时机当note表的PARENT_ID字段被更新后
* 触发条件每次PARENT_ID更新都触发
* 执行动作将新父文件夹的NOTES_COUNT加1
*
* 使用场景:
* - 将笔记从一个文件夹移动到另一个文件夹
* - 自动维护文件夹中的笔记数量
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* 触发器:更新笔记父文件夹时,减少原文件夹的笔记计数
*
* 触发时机当note表的PARENT_ID字段被更新后
* 触发条件每次PARENT_ID更新都触发
* 执行动作将原父文件夹的NOTES_COUNT减1如果大于0
*
* 注意事项:
* - 使用old.PARENT_ID获取原父文件夹ID
* - 检查NOTES_COUNT>0防止出现负数
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
/**
* 触发器:插入新笔记时,增加父文件夹的笔记计数
*
* 触发时机向note表插入新记录后
* 触发条件:无(每次插入都触发)
* 执行动作将父文件夹的NOTES_COUNT加1
*
* 使用场景:
* - 创建新笔记
* - 自动维护文件夹中的笔记数量
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* 触发器:删除笔记时,减少父文件夹的笔记计数
*
* 触发时机从note表删除记录后
* 触发条件:无(每次删除都触发)
* 执行动作将父文件夹的NOTES_COUNT减1如果大于0
*
* 注意事项:
* - 使用old.PARENT_ID获取父文件夹ID
* - 检查NOTES_COUNT>0防止出现负数
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* 触发器:插入数据时,更新笔记的内容摘要
*
* 触发时机向data表插入新记录后
* 触发条件新记录的MIME_TYPE为普通笔记DataConstants.NOTE
* 执行动作将note表的SNIPPET字段更新为新记录的内容
*
* 使用场景:
* - 创建新笔记内容
* - 自动更新笔记列表显示的摘要
*
* 注意事项:
* - 只处理普通文本笔记,不处理通话记录
* - 摘要用于在笔记列表中快速预览内容
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* 触发器:更新数据时,更新笔记的内容摘要
*
* 触发时机更新data表记录后
* 触发条件旧记录的MIME_TYPE为普通笔记DataConstants.NOTE
* 执行动作将note表的SNIPPET字段更新为新记录的内容
*
* 使用场景:
* - 编辑笔记内容
* - 自动更新笔记列表显示的摘要
*
* 注意事项:
* - 检查旧记录的MIME_TYPE确保是普通笔记
* - 保持摘要与内容同步
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* 触发器:删除数据时,清空笔记的内容摘要
*
* 触发时机从data表删除记录后
* 触发条件旧记录的MIME_TYPE为普通笔记DataConstants.NOTE
* 执行动作将note表的SNIPPET字段设置为空字符串
*
* 使用场景:
* - 删除笔记内容
* - 清空笔记列表显示的摘要
*
* 注意事项:
* - 摘要设置为空字符串而非NULL
* - 保持数据库一致性
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
/**
* 触发器:删除笔记时,级联删除关联的数据记录
*
* 触发时机从note表删除记录后
* 触发条件:无(每次删除都触发)
* 执行动作删除data表中所有NOTE_ID等于被删除笔记ID的记录
*
* 使用场景:
* - 删除笔记
* - 自动清理关联的数据记录,防止数据孤岛
*
* 注意事项:
* - 这是级联删除操作,确保数据完整性
* - 删除笔记后,其所有内容也会被删除
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* 触发器:删除文件夹时,级联删除文件夹内的所有笔记
*
* 触发时机从note表删除记录后
* 触发条件:无(每次删除都触发)
* 执行动作删除note表中所有PARENT_ID等于被删除文件夹ID的记录
*
* 使用场景:
* - 删除文件夹
* - 自动删除文件夹内的所有笔记
*
* 注意事项:
* - 这是级联删除操作,删除文件夹会删除其下所有笔记
* - 用户删除文件夹前应给予警告
* - 递归删除:如果子笔记是文件夹,也会触发此触发器
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* 触发器:移动笔记到回收站时,级联移动子笔记
*
* 触发时机更新note表记录后
* 触发条件新记录的PARENT_ID等于回收站文件夹IDNotes.ID_TRASH_FOLER
* 执行动作将所有PARENT_ID等于被移动笔记ID的记录也移动到回收站
*
* 使用场景:
* - 将文件夹移动到回收站
* - 自动将文件夹内的所有笔记也移动到回收站
*
* 注意事项:
* - 保持文件夹结构的完整性
* - 防止出现孤儿笔记(父文件夹已删除但子笔记未删除)
* - 递归移动:如果子笔记是文件夹,也会触发此触发器
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* 构造方法
*
* 调用父类SQLiteOpenHelper的构造方法
* - context: 上下文环境
* - DB_NAME: 数据库文件名note.db
* - factory: Cursor工厂null表示使用默认工厂
* - DB_VERSION: 数据库版本号5
*
* @param context 上下文环境,用于访问应用文件和数据库
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 创建笔记表
*
* 执行步骤:
* 1. 执行CREATE_NOTE_TABLE_SQL创建note表
* 2. 重新创建note表相关触发器
* 3. 创建系统文件夹(根文件夹、临时文件夹等)
*
* 该方法在数据库首次创建时调用onCreate方法中
*
* @param db SQLiteDatabase对象用于执行SQL语句
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
/**
* 重新创建笔记表触发器
*
* 执行步骤:
* 1. 删除所有已存在的note表触发器如果存在
* 2. 重新创建所有note表触发器
*
* 触发器列表:
* - increase_folder_count_on_update: 更新文件夹时增加计数
* - decrease_folder_count_on_update: 更新文件夹时减少计数
* - decrease_folder_count_on_delete: 删除笔记时减少计数
* - delete_data_on_delete: 删除笔记时级联删除数据
* - increase_folder_count_on_insert: 插入笔记时增加计数
* - folder_delete_notes_on_delete: 删除文件夹时级联删除笔记
* - folder_move_notes_on_trash: 移动到回收站时级联移动
*
* 使用场景:
* - 数据库升级后重新创建触发器
* - 确保触发器定义与当前代码一致
*
* @param db SQLiteDatabase对象用于执行SQL语句
*/
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除所有旧触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 重新创建所有触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
/**
* 创建系统文件夹
*
* 创建的系统文件夹:
* 1. ID_CALL_RECORD_FOLDER-2: 通话记录文件夹
* 2. ID_ROOT_FOLDER0: 根文件夹
* 3. ID_TEMPARAY_FOLDER-1: 临时文件夹
* 4. ID_TRASH_FOLER-3: 回收站文件夹
*
* 所有系统文件夹的TYPE字段都设置为TYPE_SYSTEM2
*
* 使用场景:
* - 数据库首次创建时调用
* - 确保系统文件夹存在
*
* @param db SQLiteDatabase对象用于执行SQL语句
*/
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
// 创建通话记录文件夹
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 创建根文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 创建临时文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 创建回收站文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
* 创建数据表
*
* 执行步骤:
* 1. 执行CREATE_DATA_TABLE_SQL创建data表
* 2. 重新创建data表相关触发器
* 3. 创建data表索引note_id_index
*
* 该方法在数据库首次创建时调用onCreate方法中
*
* @param db SQLiteDatabase对象用于执行SQL语句
*/
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created");
}
/**
* 重新创建数据表触发器
*
* 执行步骤:
* 1. 删除所有已存在的data表触发器如果存在
* 2. 重新创建所有data表触发器
*
* 触发器列表:
* - update_note_content_on_insert: 插入数据时更新摘要
* - update_note_content_on_update: 更新数据时更新摘要
* - update_note_content_on_delete: 删除数据时清空摘要
*
* 使用场景:
* - 数据库升级后重新创建触发器
* - 确保触发器定义与当前代码一致
*
* @param db SQLiteDatabase对象用于执行SQL语句
*/
private void reCreateDataTableTriggers(SQLiteDatabase db) {
// 删除所有旧触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
// 重新创建所有触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
/**
* 获取单例实例
*
* 使用双重检查锁定模式实现线程安全的单例:
* 1. 检查mInstance是否为null
* 2. 如果为null创建新实例
* 3. 返回唯一实例
*
* 优点:
* - 确保全局只有一个数据库连接
* - 节省系统资源
* - 避免多线程并发创建多个实例
*
* 使用场景:
* - 整个应用中获取数据库帮助类实例
* - NotesProvider中初始化数据库连接
*
* @param context 上下文环境
* @return NotesDatabaseHelper的唯一实例
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
/**
* 数据库首次创建时调用
*
* 执行步骤:
* 1. 创建note表
* 2. 创建data表
*
* 该方法由SQLiteOpenHelper自动调用
* - 当数据库文件不存在时
* - 当数据库版本号与代码中定义的版本号一致时
*
* @param db SQLiteDatabase对象表示新创建的数据库
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
/**
* 数据库升级处理
*
* 升级策略:
* - 根据旧版本号逐步升级到新版本
* - 每个版本都有对应的升级方法
* - 升级过程中可能需要重建触发器
*
* 版本升级流程:
* V1 -> V2: 重建表结构upgradeToV2
* V2 -> V3: 添加Google Task支持和回收站upgradeToV3
* V3 -> V4: 添加版本号字段upgradeToV4
*
* 注意事项:
* - 升级过程中不删除用户数据
* - 升级失败会抛出IllegalStateException
* - 某些版本升级后需要重建触发器
*
* @param db SQLiteDatabase对象
* @param oldVersion 旧版本号
* @param newVersion 新版本号
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 从版本1升级到版本2
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // V2升级已在V1升级中完成跳过V2
oldVersion++;
}
// 从版本2升级到版本3
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true; // V3升级需要重建触发器
oldVersion++;
}
// 从版本3升级到版本4
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 如果需要重建触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 检查升级是否成功
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
/**
* 升级到版本2 - 重建表结构
*
* 升级内容:
* 1. 删除旧的note表和data表
* 2. 重新创建note表和data表
* 3. 重新创建所有触发器
* 4. 重新创建系统文件夹
*
* 注意事项:
* - 这是破坏性升级,会丢失所有数据
* - 仅在版本1时使用初始版本可能存在设计缺陷
* - 后续版本升级不会执行此方法
*
* @param db SQLiteDatabase对象
*/
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db);
createDataTable(db);
}
/**
* 升级到版本3 - 添加Google Task支持和回收站
*
* 升级内容:
* 1. 删除旧的修改日期触发器(已废弃)
* 2. 在note表中添加GTASK_ID字段
* 3. 创建回收站文件夹ID_TRASH_FOLER
*
* 新功能:
* - Google Task同步通过GTASK_ID字段与Google Tasks同步
* - 回收站功能:删除的笔记先放入回收站,而非直接删除
*
* 注意事项:
* - 升级后需要重建触发器reCreateTriggers=true
* - GTASK_ID字段默认值为空字符串
*
* @param db SQLiteDatabase对象
*/
private void upgradeToV3(SQLiteDatabase db) {
// 删除旧的触发器(已废弃)
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// 添加Google Task ID字段
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// 创建回收站文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
* 升级到版本4 - 添加版本号字段
*
* 升级内容:
* 1. 在note表中添加VERSION字段
*
* 新功能:
* - 版本控制通过VERSION字段跟踪笔记的修改版本
* - 冲突检测:在同步时通过版本号检测冲突
* - 并发控制:防止多人同时修改同一笔记
*
* 注意事项:
* - VERSION字段默认值为0
* - 每次修改笔记时VERSION字段会自动加1通过increaseNoteVersion方法
*
* @param db SQLiteDatabase对象
*/
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}