CRUD 是数据库操作的四大基本功能:创建(Create)、读取(Read)、更新(Update)、删除(Delete)。在 IndexedDB 中,这些操作对应 add、get/put、delete 等方法。本章将深入讲解每种操作的使用方式、注意事项和最佳实践。
add() 方法用于向对象存储空间插入新记录。如果主键已存在,操作会失败。
// 基本语法 const request = objectStore.add(value); const request = objectStore.add(value, key); // 外联键时使用 // 完整示例 function addUser(user) { return new Promise((resolve, reject) => { const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); const request = store.add(user); request.onsuccess = function(event) { // event.target.result 是新增记录的主键 resolve(event.target.result); }; request.onerror = function(event) { reject(event.target.error); }; }); } // 使用示例 addUser({ id: 'user_001', username: 'zhangsan', email: 'zhangsan@example.com', createdAt: new Date().toISOString() }).then(key => { console.log('用户创建成功,主键:', key); }).catch(error => { if (error.name === 'ConstraintError') { console.error('主键已存在,请使用 put() 更新'); } else { console.error('创建失败:', error); } });
当需要添加多条记录时,应该在同一个事务中执行:
function addMultipleUsers(users) { return new Promise((resolve, reject) => { const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); const results = []; let completed = 0; users.forEach(user => { const request = store.add(user); request.onsuccess = () => { results.push({ status: 'success', key: request.result }); completed++; if (completed === users.length) { resolve(results); } }; request.onerror = () => { results.push({ status: 'error', error: request.error, user }); completed++; if (completed === users.length) { resolve(results); // 部分成功也 resolve } }; }); transaction.onerror = () => reject(transaction.error); }); } // 批量添加 1000 条测试数据 async function seedDatabase() { const users = []; for (let i = 1; i <= 1000; i++) { users.push({ id: `user_${String(i).padStart(6, '0')}`, username: `user${i}`, email: `user${i}@example.com`, age: Math.floor(Math.random() * 50) + 18, department: ['技术', '产品', '设计', '运营'][Math.floor(Math.random() * 4)], createdAt: new Date(Date.now() - Math.random() * 365 * 24 * 3600 * 1000).toISOString() }); } const startTime = performance.now(); const results = await addMultipleUsers(users); const duration = performance.now() - startTime; const successCount = results.filter(r => r.status === 'success').length; console.log(`批量添加完成: ${successCount}/${users.length} 成功, 耗时: ${duration.toFixed(2)}ms`); }
get() 通过主键获取单条记录:
// 基本语法 const request = objectStore.get(key); // 示例 function getUserById(userId) { return new Promise((resolve, reject) => { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const request = store.get(userId); request.onsuccess = () => { if (request.result) { resolve(request.result); } else { resolve(null); // 记录不存在 } }; request.onerror = () => reject(request.error); }); } // 使用 const user = await getUserById('user_001'); if (user) { console.log('用户信息:', user); } else { console.log('用户不存在'); }
getAll() 获取所有或指定范围内的记录:
// 获取所有记录 const request1 = store.getAll(); // 获取指定主键的记录(配合索引使用) const index = store.index('ageIndex'); const request2 = index.getAll(25); // 获取所有 25 岁的用户 // 获取范围内的记录 const range = IDBKeyRange.bound(20, 30); const request3 = index.getAll(range); // 获取年龄在 20-30 之间的用户 // 限制返回数量 const request4 = index.getAll(null, 10); // 最多返回 10 条 // 完整示例:分页查询 async function getUsersByPage(page = 1, pageSize = 20) { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const allUsers = await store.getAll(); const start = (page - 1) * pageSize; const end = start + pageSize; return { data: allUsers.slice(start, end), total: allUsers.length, page, pageSize, totalPages: Math.ceil(allUsers.length / pageSize) }; }
// 使用索引查询 function findUsersByDepartment(department) { return new Promise((resolve, reject) => { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const index = store.index('departmentIndex'); const request = index.getAll(department); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); } // 使用复合索引查询 function findUsersByAgeRange(minAge, maxAge) { return new Promise((resolve, reject) => { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const index = store.index('ageIndex'); const range = IDBKeyRange.bound(minAge, maxAge); const request = index.getAll(range); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }
put() 用于更新现有记录或插入新记录:
// 基本语法 const request = objectStore.put(value); const request = objectStore.put(value, key); // 外联键时使用 // 更新示例 async function updateUser(userId, updates) { const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 先获取原记录 const user = await new Promise((resolve, reject) => { const request = store.get(userId); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); if (!user) { throw new Error('用户不存在'); } // 合并更新 const updatedUser = { ...user, ...updates, updatedAt: new Date().toISOString() }; // 保存更新 return new Promise((resolve, reject) => { const request = store.put(updatedUser); request.onsuccess = () => resolve(updatedUser); request.onerror = () => reject(request.error); }); } // 使用 await updateUser('user_001', { email: 'newemail@example.com', department: '产品' });
// 策略一:全量替换(简单但可能丢失数据) store.put({ id: 'user_001', name: '张三', email: 'new@example.com' }); // 策略二:先读后写(推荐) async function partialUpdate(storeName, key, updates) { const transaction = db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const existing = await store.get(key); if (!existing) throw new Error('记录不存在'); const updated = { ...existing, ...updates }; await store.put(updated); return updated; } // 策略三:使用补丁对象 function applyPatch(original, patch) { const result = { ...original }; for (const [key, value] of Object.entries(patch)) { if (value === undefined) { delete result[key]; // undefined 表示删除 } else { result[key] = value; } } return result; }
// 删除单条记录 const request = store.delete(key); // 删除范围内的记录 const range = IDBKeyRange.bound('A', 'M'); const request2 = store.delete(range); // 完整示例 function deleteUser(userId) { return new Promise((resolve, reject) => { const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 先检查记录是否存在 const getRequest = store.get(userId); getRequest.onsuccess = () => { if (!getRequest.result) { resolve({ success: false, message: '记录不存在' }); return; } const deleteRequest = store.delete(userId); deleteRequest.onsuccess = () => { resolve({ success: true, deleted: getRequest.result }); }; deleteRequest.onerror = () => reject(deleteRequest.error); }; getRequest.onerror = () => reject(getRequest.error); }); }
// 清空整个对象存储空间(谨慎使用!) function clearStore(storeName) { return new Promise((resolve, reject) => { const transaction = db.transaction([storeName], 'readwrite'); const store = transaction.objectStore(storeName); const request = store.clear(); request.onsuccess = () => { console.log(`${storeName} 已清空`); resolve(); }; request.onerror = () => reject(request.error); }); }
IndexedDB 不直接支持多条件 AND/OR 查询,需要通过代码实现:
// AND 查询:年龄 > 25 且部门 = '技术' async function findSeniorDevelopers() { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const ageIndex = store.index('ageIndex'); // 先通过索引过滤年龄 const range = IDBKeyRange.lowerBound(26); const candidates = await ageIndex.getAll(range); // 再在内存中过滤部门 return candidates.filter(user => user.department === '技术'); } // OR 查询:部门 = '技术' 或 部门 = '产品' async function findTechAndProduct() { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const deptIndex = store.index('departmentIndex'); const techUsers = await deptIndex.getAll('技术'); const productUsers = await deptIndex.getAll('产品'); // 合并并去重 const combined = [...techUsers, ...productUsers]; const seen = new Set(); return combined.filter(user => { if (seen.has(user.id)) return false; seen.add(user.id); return true; }); }
IndexedDB 不支持 LIKE 查询,但可以通过范围查询模拟:
// 前缀匹配(模拟 LIKE '张%') function findUsersByPrefix(prefix) { const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); const index = store.index('nameIndex'); // 创建前缀范围 const range = IDBKeyRange.bound(prefix, prefix + '\uffff'); return index.getAll(range); } // 使用示例 const zhangUsers = await findUsersByPrefix('张'); // 返回 '张三', '张三丰', '张无忌' 等
// 按索引排序并分页 async function getUsersSortedAndPaged(options = {}) { const { sortBy = 'createdAt', sortOrder = 'desc', page = 1, pageSize = 20 } = options; const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); // 使用索引排序 let index; try { index = store.index(`${sortBy}Index`); } catch (e) { // 如果没有对应索引,使用主键 index = store; } const direction = sortOrder === 'desc' ? 'prev' : 'next'; return new Promise((resolve, reject) => { const results = []; let skipped = 0; const skip = (page - 1) * pageSize; const request = index.openCursor(null, direction); request.onsuccess = (event) => { const cursor = event.target.result; if (!cursor) { resolve(results); return; } if (skipped < skip) { skipped++; cursor.continue(); return; } if (results.length < pageSize) { results.push(cursor.value); cursor.continue(); } else { resolve(results); } }; request.onerror = () => reject(request.error); }); }
本章详细介绍了 IndexedDB 的 CRUD 操作:
继续阅读 第六部分:事务(Transaction)详解。