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.
git.text/src/data/NotesDatabaseHelper.java

546 lines
23 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;
/**
* 便签应用的数据库核心帮助类,负责数据库的创建、升级和维护
* <p>
* 核心职责:
* <ul>
* <li>负责 "note.db" 数据库的创建 ({@link #onCreate})</li>
* <li>负责数据库版本升级与数据迁移 ({@link #onUpgrade})</li>
* <li>定义表名常量(如 {@link TABLE#NOTE})供外部调用</li>
* <li>维护 SQL Triggers 以保证数据一致性(如文件夹计数更新)</li>
* <li>提供单例实例,确保数据库连接的高效管理</li>
* <li>创建和维护系统文件夹(根文件夹、临时文件夹、通话记录文件夹、回收站)</li>
* </ul>
* <p>
* 设计意图:
* <ul>
* <li>采用单例模式,避免频繁创建和销毁数据库连接</li>
* <li>使用SQL Triggers自动维护数据一致性减少应用层逻辑复杂度</li>
* <li>提供清晰的版本升级机制,支持平滑的数据迁移</li>
* <li>将数据库结构定义集中管理,便于维护和扩展</li>
* <li>支持多种便签类型和关联数据存储</li>
* </ul>
* <p>
* 继承关系:
* <ul>
* <li>直接继承自 {@link SQLiteOpenHelper}利用Android系统提供的数据库管理机制</li>
* </ul>
* <p>
* 与其他类的关键关联:
* <ul>
* <li>被 {@link NotesProvider} 使用作为Content Provider的底层数据存储</li>
* <li>使用 {@link Notes} 类中定义的常量和列名,确保数据一致性</li>
* <li>为整个应用提供统一的数据库访问入口</li>
* </ul>
* <p>
* 数据一致性保障:
* <ul>
* <li>使用SQL Triggers自动更新文件夹的便签计数</li>
* <li>实现级联删除,确保数据完整性</li>
* <li>自动更新便签的摘要内容</li>
* <li>支持回收站功能,实现安全删除</li>
* </ul>
*
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* 数据库名称常量
* <p>数据库文件名note.db</p>
*/
private static final String DB_NAME = "note.db";
/**
* 数据库当前版本号
* <p>
* <strong>警告:</strong>
* 任何表结构的修改(如 addColumn或 Trigger 逻辑的变更,都必须手动 +1。
* 这将导致系统自动调用 {@link #onUpgrade} 方法,执行相应的数据迁移操作。
* </p>
* <p>
* 版本历史:
* <ul>
* <li>版本1初始版本</li>
* <li>版本2完全重建数据库</li>
* <li>版本3添加Google Task ID支持并添加回收站文件夹</li>
* <li>版本4添加版本号字段</li>
* </ul>
* </p>
*/
private static final int DB_VERSION = 4;
/**
* 表名常量接口,定义数据库中所有表的名称
* <p>
* 设计意图:
* <ul>
* <li>集中管理表名常量,避免硬编码</li>
* <li>提供统一的表名访问入口,便于维护和扩展</li>
* <li>被外部类如{@link NotesProvider}使用,确保表名一致性</li>
* </ul>
*/
public interface TABLE {
/**
* 笔记表,存储便签和文件夹的基本信息
* <p>包含字段ID、PARENT_ID、ALERTED_DATE、BG_COLOR_ID、CREATED_DATE等</p>
*/
public static final String NOTE = "note";
/**
* 数据表,存储便签的详细内容数据
* <p>包含字段ID、MIME_TYPE、NOTE_ID、CONTENT、DATA1-DATA5等</p>
*/
public static final String DATA = "data";
}
/**
* 日志标签,用于调试和日志记录
*/
private static final String TAG = "NotesDatabaseHelper";
/**
* 单例实例,确保整个应用只有一个数据库连接实例
* <p>
* 设计意图:
* <ul>
* <li>避免频繁创建和销毁数据库连接,提高性能</li>
* <li>确保数据库操作的线程安全</li>
* <li>通过{@link #getInstance(Context)}方法获取实例</li>
* </ul>
*/
private static NotesDatabaseHelper mInstance;
/**
* 创建笔记表的SQL语句
* 包含笔记的基本信息字段ID、父文件夹ID、提醒日期、背景颜色、创建日期等
*/
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID默认为0
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期时间戳
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID
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," + // 关联的小部件ID
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标记
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父文件夹ID
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google Task ID
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号
")";
/**
* 创建数据表的SQL语句
* 存储笔记的具体内容数据支持多种MIME类型文本、图片等
*/
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型标识数据类型
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID
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," + // 扩展字段1整数
DataColumns.DATA2 + " INTEGER," + // 扩展字段2整数
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 扩展字段3文本
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 扩展字段4文本
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 扩展字段5文本
")";
/**
* 在数据表的note_id字段上创建索引提高查询性能
*/
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* SQL 触发器:移动笔记时自动增加目标文件夹计数
* <p>
* <strong>逻辑解析:</strong><br>
* 1. 监听 {@link TABLE#NOTE} 表的 {@link NoteColumns#PARENT_ID} 字段。<br>
* 2. 捕捉更新后的新文件夹 ID (new.parent_id)。<br>
* 3. 将该目标文件夹的 'note_count' 字段 +1。
* </p>
*/
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";
/**
* SQL 触发器:当笔记从文件夹移出时,减少原文件夹的笔记计数
* 注意只减少计数大于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";
/**
* SQL 触发器:插入新笔记时增加父文件夹的笔记计数
*/
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";
/**
* SQL 触发器:删除笔记时减少父文件夹的笔记计数
* 注意只减少计数大于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";
/**
* SQL 触发器当插入类型为NOTE的数据时更新对应笔记的摘要内容
* 仅当MIME_TYPE为DataConstants.NOTE时触发
*/
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";
/**
* SQL 触发器当更新类型为NOTE的数据时更新对应笔记的摘要内容
*/
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";
/**
* SQL 触发器当删除类型为NOTE的数据时清空对应笔记的摘要内容
*/
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";
/**
* SQL 触发器:删除笔记时,级联删除其关联的所有数据
*/
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";
/**
* SQL 触发器:删除文件夹时,级联删除其下的所有笔记
* 注意这会触发delete_data_on_delete触发器从而删除所有关联数据
*/
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";
/**
* SQL 触发器:当文件夹被移动到回收站时,将其下的所有笔记也移动到回收站
*/
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";
/**
* 构造方法
* @param context Android上下文
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 创建笔记表及其相关触发器
* @param db SQLite数据库对象
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
/**
* 重建笔记表的所有触发器
* 先删除旧触发器(如果存在),然后创建新触发器
* @param db SQLite数据库对象
*/
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);
}
/**
* 创建系统文件夹
* 包括:通话记录文件夹、根文件夹、临时文件夹、回收站
* @param db SQLite数据库对象
*/
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);
}
/**
* 创建数据表及其相关触发器
* @param db SQLite数据库对象
*/
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");
}
/**
* 重建数据表的所有触发器
* 先删除旧触发器(如果存在),然后创建新触发器
* @param db SQLite数据库对象
*/
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);
}
/**
* 获取单例实例(线程安全)
* @param context Android上下文
* @return NotesDatabaseHelper实例
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
/**
* 数据库首次创建时调用
* 创建所有表和触发器
* @param db SQLite数据库对象
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
/**
* 数据库版本升级时调用
* 处理不同版本间的数据迁移和结构变更
* @param db SQLite数据库对象
* @param oldVersion 旧版本号
* @param newVersion 新版本号
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 是否需要重建触发器
boolean skipV2 = false; // 是否跳过V2升级
// 从版本1升级到版本2
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // V1到V2的升级包含了V2到V3的内容
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完全重建数据库
* @param db SQLite数据库对象
*/
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 ID支持并添加回收站文件夹
* @param db SQLite数据库对象
*/
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添加版本号字段
* @param db SQLite数据库对象
*/
private void upgradeToV4(SQLiteDatabase db) {
// 添加版本号字段
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}