Node.js 文件系统与数据库的交互方式
2022-01-067.3k 阅读
Node.js 文件系统基础
1.1 文件系统模块的引入
在Node.js中,操作文件系统主要依赖于内置的fs
模块。通过require
语句即可引入该模块:
const fs = require('fs');
fs
模块提供了一系列用于文件和目录操作的方法,包括读取文件、写入文件、创建目录、删除文件等。
1.2 同步与异步操作
fs
模块的方法分为同步和异步两种形式。同步方法会阻塞Node.js的事件循环,直到操作完成,而异步方法则不会阻塞事件循环,操作完成后通过回调函数通知调用者。
例如,读取文件的同步方法fs.readFileSync
:
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
而异步方法fs.readFile
则使用回调函数来处理结果:
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
在实际应用中,尤其是在处理I/O密集型任务时,异步方法通常更受青睐,因为它们不会阻塞事件循环,从而保证应用程序的高并发性能。
1.3 文件读取
- 读取文本文件
- 异步读取:
fs.readFile('textfile.txt', 'utf8', (err, data) => { if (err) { console.error('Error reading file:', err); return; } console.log('File content:', data); });
- 同步读取:
try { const data = fs.readFileSync('textfile.txt', 'utf8'); console.log('File content:', data); } catch (err) { console.error('Error reading file:', err); }
- 异步读取:
- 读取二进制文件
- 异步读取:
fs.readFile('binaryfile.jpg', (err, data) => { if (err) { console.error('Error reading file:', err); return; } // 可以对二进制数据进行处理,例如写入另一个文件 fs.writeFile('newbinaryfile.jpg', data, (writeErr) => { if (writeErr) { console.error('Error writing file:', writeErr); } }); });
- 同步读取:
try { const data = fs.readFileSync('binaryfile.jpg'); // 处理二进制数据 fs.writeFileSync('newbinaryfile.jpg', data); } catch (err) { console.error('Error reading file:', err); }
- 异步读取:
1.4 文件写入
- 写入文本文件
- 异步写入:
const content = 'This is some text to write.'; fs.writeFile('newtextfile.txt', content, (err) => { if (err) { console.error('Error writing file:', err); return; } console.log('File written successfully.'); });
- 同步写入:
const content = 'This is some text to write synchronously.'; try { fs.writeFileSync('newtextfilesync.txt', content); console.log('File written successfully.'); } catch (err) { console.error('Error writing file:', err); }
- 异步写入:
- 追加写入
- 异步追加:
const newContent = 'This is additional content.'; fs.appendFile('newtextfile.txt', newContent, (err) => { if (err) { console.error('Error appending to file:', err); return; } console.log('Content appended successfully.'); });
- 同步追加:
const newContent = 'This is additional content synchronously.'; try { fs.appendFileSync('newtextfilesync.txt', newContent); console.log('Content appended successfully.'); } catch (err) { console.error('Error appending to file:', err); }
- 异步追加:
1.5 目录操作
- 创建目录
- 异步创建:
const newDir = 'newdirectory'; fs.mkdir(newDir, (err) => { if (err) { console.error('Error creating directory:', err); return; } console.log('Directory created successfully.'); });
- 同步创建:
const newDir = 'newdirectorysync'; try { fs.mkdirSync(newDir); console.log('Directory created successfully.'); } catch (err) { console.error('Error creating directory:', err); }
- 异步创建:
- 读取目录内容
- 异步读取:
const dir = '.'; fs.readdir(dir, (err, files) => { if (err) { console.error('Error reading directory:', err); return; } console.log('Files in directory:', files); });
- 同步读取:
const dir = '.'; try { const files = fs.readdirSync(dir); console.log('Files in directory:', files); } catch (err) { console.error('Error reading directory:', err); }
- 异步读取:
- 删除目录
- 异步删除:
const dirToDelete = 'newdirectory'; fs.rmdir(dirToDelete, (err) => { if (err) { console.error('Error deleting directory:', err); return; } console.log('Directory deleted successfully.'); });
- 同步删除:
const dirToDelete = 'newdirectorysync'; try { fs.rmdirSync(dirToDelete); console.log('Directory deleted successfully.'); } catch (err) { console.error('Error deleting directory:', err); }
- 异步删除:
数据库基础
2.1 常见数据库类型
- 关系型数据库
- MySQL:是一种广泛使用的开源关系型数据库,具有高性能、可靠性和丰富的功能。它使用SQL语言进行数据查询和操作。例如,创建一个简单的用户表:
CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50) );
- PostgreSQL:同样是开源的关系型数据库,以其强大的功能和对SQL标准的支持而闻名。它支持复杂的数据类型和高级查询功能。例如,创建一个包含JSONB类型字段的表:
CREATE TABLE data ( id SERIAL PRIMARY KEY, json_data JSONB );
- MySQL:是一种广泛使用的开源关系型数据库,具有高性能、可靠性和丰富的功能。它使用SQL语言进行数据查询和操作。例如,创建一个简单的用户表:
- 非关系型数据库
- MongoDB:是一种基于文档的非关系型数据库,数据以BSON(Binary JSON)格式存储。它具有高可扩展性和灵活的数据模型。例如,插入一个文档:
const MongoClient = require('mongodb').MongoClient; const uri = "mongodb://localhost:27017"; const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true }); async function insertDocument() { try { await client.connect(); const database = client.db('mydb'); const collection = database.collection('users'); const doc = { name: 'John', age: 30 }; const result = await collection.insertOne(doc); console.log('Inserted document:', result.insertedId); } catch (e) { console.error(e); } finally { await client.close(); } } insertDocument();
- Redis:是一种基于键值对的非关系型数据库,常用于缓存、消息队列和实时数据存储。例如,设置一个键值对:
const redis = require('redis'); const client = redis.createClient(); client.set('key', 'value', (err, reply) => { if (err) { console.error(err); } else { console.log('Set reply:', reply); } });
- MongoDB:是一种基于文档的非关系型数据库,数据以BSON(Binary JSON)格式存储。它具有高可扩展性和灵活的数据模型。例如,插入一个文档:
2.2 数据库连接
- 关系型数据库连接
- MySQL连接:
const mysql = require('mysql2'); const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'password', database: 'test' }); connection.connect((err) => { if (err) { console.error('Error connecting to MySQL:', err); return; } console.log('Connected to MySQL database!'); });
- PostgreSQL连接:
const { Pool } = require('pg'); const pool = new Pool({ user: 'user', host: 'localhost', database: 'test', password: 'password', port: 5432, }); pool.query('SELECT NOW()', (err, res) => { if (err) { console.error('Error querying PostgreSQL:', err); return; } console.log('Current time:', res.rows[0].now); pool.end(); });
- MySQL连接:
- 非关系型数据库连接
- MongoDB连接:
const { MongoClient } = require('mongodb'); const uri = "mongodb://localhost:27017"; const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true }); async function connectToMongo() { try { await client.connect(); console.log('Connected to MongoDB!'); } catch (e) { console.error('Error connecting to MongoDB:', e); } finally { await client.close(); } } connectToMongo();
- Redis连接:
const redis = require('redis'); const client = redis.createClient(); client.on('connect', () => { console.log('Connected to Redis!'); }); client.on('error', (err) => { console.error('Error connecting to Redis:', err); });
- MongoDB连接:
2.3 数据库操作
- 关系型数据库操作
- 插入数据:
- MySQL:
const mysql = require('mysql2'); const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'password', database: 'test' }); const insertQuery = 'INSERT INTO users (username, password) VALUES (?,?)'; const values = ['user1', 'pass1']; connection.query(insertQuery, values, (err, result) => { if (err) { console.error('Error inserting data:', err); return; } console.log('Inserted rows:', result.affectedRows); });
- PostgreSQL:
const { Pool } = require('pg'); const pool = new Pool({ user: 'user', host: 'localhost', database: 'test', password: 'password', port: 5432, }); const insertQuery = 'INSERT INTO users (username, password) VALUES ($1, $2) RETURNING id'; const values = ['user1', 'pass1']; pool.query(insertQuery, values, (err, res) => { if (err) { console.error('Error inserting data:', err); return; } console.log('Inserted user ID:', res.rows[0].id); pool.end(); });
- MySQL:
- 查询数据:
- MySQL:
const mysql = require('mysql2'); const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'password', database: 'test' }); const selectQuery = 'SELECT * FROM users'; connection.query(selectQuery, (err, results) => { if (err) { console.error('Error querying data:', err); return; } console.log('Query results:', results); });
- PostgreSQL:
const { Pool } = require('pg'); const pool = new Pool({ user: 'user', host: 'localhost', database: 'test', password: 'password', port: 5432, }); const selectQuery = 'SELECT * FROM users'; pool.query(selectQuery, (err, res) => { if (err) { console.error('Error querying data:', err); return; } console.log('Query results:', res.rows); pool.end(); });
- MySQL:
- 插入数据:
- 非关系型数据库操作
- MongoDB操作:
- 插入文档:
const { MongoClient } = require('mongodb'); const uri = "mongodb://localhost:27017"; const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true }); async function insertDocument() { try { await client.connect(); const database = client.db('mydb'); const collection = database.collection('users'); const doc = { name: 'John', age: 30 }; const result = await collection.insertOne(doc); console.log('Inserted document:', result.insertedId); } catch (e) { console.error(e); } finally { await client.close(); } } insertDocument();
- 查询文档:
const { MongoClient } = require('mongodb'); const uri = "mongodb://localhost:27017"; const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true }); async function findDocuments() { try { await client.connect(); const database = client.db('mydb'); const collection = database.collection('users'); const cursor = collection.find({ age: { $gt: 25 } }); const results = await cursor.toArray(); console.log('Query results:', results); } catch (e) { console.error(e); } finally { await client.close(); } } findDocuments();
- 插入文档:
- Redis操作:
- 设置键值对:
const redis = require('redis'); const client = redis.createClient(); client.set('key', 'value', (err, reply) => { if (err) { console.error(err); } else { console.log('Set reply:', reply); } });
- 获取键值对:
const redis = require('redis'); const client = redis.createClient(); client.get('key', (err, reply) => { if (err) { console.error(err); } else { console.log('Get reply:', reply); } });
- 设置键值对:
- MongoDB操作:
Node.js 文件系统与数据库的交互方式
3.1 从文件读取数据并写入数据库
- 关系型数据库场景
假设我们有一个文本文件
users.txt
,每行格式为username:password
,我们要将这些用户数据插入到MySQL数据库的users
表中。
const fs = require('fs');
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
fs.readFile('users.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
const lines = data.split('\n');
lines.forEach((line) => {
if (line.trim()!== '') {
const [username, password] = line.split(':');
const insertQuery = 'INSERT INTO users (username, password) VALUES (?,?)';
const values = [username, password];
connection.query(insertQuery, values, (queryErr, result) => {
if (queryErr) {
console.error('Error inserting data:', queryErr);
}
});
}
});
});
对于PostgreSQL,原理类似,只是数据库连接和SQL语句稍有不同:
const fs = require('fs');
const { Pool } = require('pg');
const pool = new Pool({
user: 'user',
host: 'localhost',
database: 'test',
password: 'password',
port: 5432,
});
fs.readFile('users.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
const lines = data.split('\n');
lines.forEach((line) => {
if (line.trim()!== '') {
const [username, password] = line.split(':');
const insertQuery = 'INSERT INTO users (username, password) VALUES ($1, $2) RETURNING id';
const values = [username, password];
pool.query(insertQuery, values, (queryErr, res) => {
if (queryErr) {
console.error('Error inserting data:', queryErr);
}
});
}
});
});
- 非关系型数据库场景
以MongoDB为例,假设我们有一个JSON文件
users.json
,内容是一个用户数组,我们要将这些用户数据插入到MongoDB的users
集合中。
const fs = require('fs');
const { MongoClient } = require('mongodb');
const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
async function insertUsersFromFile() {
try {
await client.connect();
const database = client.db('mydb');
const collection = database.collection('users');
const data = fs.readFileSync('users.json', 'utf8');
const users = JSON.parse(data);
const result = await collection.insertMany(users);
console.log('Inserted documents:', result.insertedIds);
} catch (e) {
console.error(e);
} finally {
await client.close();
}
}
insertUsersFromFile();
对于Redis,由于其数据结构的特殊性,假设users.txt
文件内容是username:password
格式,我们可以将其存储为哈希类型。
const fs = require('fs');
const redis = require('redis');
const client = redis.createClient();
fs.readFile('users.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
const lines = data.split('\n');
lines.forEach((line) => {
if (line.trim()!== '') {
const [username, password] = line.split(':');
client.hSet('users', username, password, (setErr) => {
if (setErr) {
console.error('Error setting data in Redis:', setErr);
}
});
}
});
});
3.2 从数据库读取数据并写入文件
- 关系型数据库场景
从MySQL数据库的
users
表中读取所有用户数据,并写入一个新的文本文件allusers.txt
。
const fs = require('fs');
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
const selectQuery = 'SELECT * FROM users';
connection.query(selectQuery, (err, results) => {
if (err) {
console.error('Error querying data:', err);
return;
}
let content = '';
results.forEach((user) => {
content += `${user.username}:${user.password}\n`;
});
fs.writeFile('allusers.txt', content, (writeErr) => {
if (writeErr) {
console.error('Error writing file:', writeErr);
}
});
});
对于PostgreSQL,操作类似:
const fs = require('fs');
const { Pool } = require('pg');
const pool = new Pool({
user: 'user',
host: 'localhost',
database: 'test',
password: 'password',
port: 5432,
});
const selectQuery = 'SELECT * FROM users';
pool.query(selectQuery, (err, res) => {
if (err) {
console.error('Error querying data:', err);
return;
}
let content = '';
res.rows.forEach((user) => {
content += `${user.username}:${user.password}\n`;
});
fs.writeFile('allusers.txt', content, (writeErr) => {
if (writeErr) {
console.error('Error writing file:', writeErr);
}
});
pool.end();
});
- 非关系型数据库场景
从MongoDB的
users
集合中读取所有用户数据,并写入一个JSON文件allusers.json
。
const fs = require('fs');
const { MongoClient } = require('mongodb');
const uri = "mongodb://localhost:27017";
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
async function writeUsersToFile() {
try {
await client.connect();
const database = client.db('mydb');
const collection = database.collection('users');
const cursor = collection.find({});
const users = await cursor.toArray();
const content = JSON.stringify(users, null, 2);
fs.writeFile('allusers.json', content, (writeErr) => {
if (writeErr) {
console.error('Error writing file:', writeErr);
}
});
} catch (e) {
console.error(e);
} finally {
await client.close();
}
}
writeUsersToFile();
从Redis中读取users
哈希类型的数据,并写入users.txt
文件。
const fs = require('fs');
const redis = require('redis');
const client = redis.createClient();
client.hGetAll('users', (err, obj) => {
if (err) {
console.error('Error getting data from Redis:', err);
return;
}
let content = '';
for (const [username, password] of Object.entries(obj)) {
content += `${username}:${password}\n`;
}
fs.writeFile('users.txt', content, (writeErr) => {
if (writeErr) {
console.error('Error writing file:', writeErr);
}
});
});
3.3 文件系统与数据库的同步机制
- 基于时间戳的同步
在实际应用中,可能需要确保文件系统和数据库中的数据保持同步。一种简单的方法是基于时间戳。假设我们有一个文件
data.txt
,每次文件内容更新时,我们记录其修改时间,并与数据库中记录的时间戳进行比较。
const fs = require('fs');
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
function checkSync() {
fs.stat('data.txt', (err, stats) => {
if (err) {
console.error('Error getting file stats:', err);
return;
}
const fileModifiedTime = stats.mtime.getTime();
const selectQuery = 'SELECT modified_time FROM file_sync WHERE file_name = "data.txt"';
connection.query(selectQuery, (queryErr, results) => {
if (queryErr) {
console.error('Error querying database:', queryErr);
return;
}
if (results.length === 0) {
// 文件是新的,插入数据库记录
const insertQuery = 'INSERT INTO file_sync (file_name, modified_time) VALUES ("data.txt",?)';
connection.query(insertQuery, [fileModifiedTime], (insertErr) => {
if (insertErr) {
console.error('Error inserting record:', insertErr);
}
});
} else {
const dbModifiedTime = results[0].modified_time;
if (fileModifiedTime > dbModifiedTime) {
// 文件已更新,同步数据库
const data = fs.readFileSync('data.txt', 'utf8');
const updateQuery = 'UPDATE file_sync SET data =?, modified_time =? WHERE file_name = "data.txt"';
connection.query(updateQuery, [data, fileModifiedTime], (updateErr) => {
if (updateErr) {
console.error('Error updating database:', updateErr);
}
});
}
}
});
});
}
setInterval(checkSync, 5000);
- 基于事件驱动的同步
在Node.js中,可以利用
fs.watch
来监听文件系统事件,实现更实时的同步。
const fs = require('fs');
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
fs.watch('data.txt', (eventType, filename) => {
if (eventType === 'change') {
fs.stat('data.txt', (err, stats) => {
if (err) {
console.error('Error getting file stats:', err);
return;
}
const fileModifiedTime = stats.mtime.getTime();
const data = fs.readFileSync('data.txt', 'utf8');
const updateQuery = 'UPDATE file_sync SET data =?, modified_time =? WHERE file_name = "data.txt"';
connection.query(updateQuery, [data, fileModifiedTime], (updateErr) => {
if (updateErr) {
console.error('Error updating database:', updateErr);
}
});
});
}
});
3.4 数据迁移与备份
- 从一种数据库迁移到另一种数据库并结合文件系统
假设我们要将MySQL数据库中的
users
表数据迁移到MongoDB,并在迁移过程中,将数据临时存储在文件系统中。
const fs = require('fs');
const mysql = require('mysql2');
const { MongoClient } = require('mongodb');
const mysqlConnection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
const uri = "mongodb://localhost:27017";
const mongoClient = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
async function migrateData() {
try {
await mongoClient.connect();
const mongoDatabase = mongoClient.db('mydb');
const mongoCollection = mongoDatabase.collection('users');
const selectQuery = 'SELECT * FROM users';
mysqlConnection.query(selectQuery, (err, results) => {
if (err) {
console.error('Error querying MySQL:', err);
return;
}
const data = JSON.stringify(results);
fs.writeFile('tempusers.json', data, (writeErr) => {
if (writeErr) {
console.error('Error writing temp file:', writeErr);
return;
}
const tempData = fs.readFileSync('tempusers.json', 'utf8');
const users = JSON.parse(tempData);
mongoCollection.insertMany(users, (insertErr) => {
if (insertErr) {
console.error('Error inserting to MongoDB:', insertErr);
} else {
console.log('Data migrated successfully.');
}
});
});
});
} catch (e) {
console.error(e);
} finally {
mysqlConnection.end();
await mongoClient.close();
}
}
migrateData();
- 数据库备份到文件系统
以MySQL为例,我们可以使用
mysqldump
命令结合Node.js的child_process
模块将数据库备份到文件系统。
const { exec } = require('child_process');
const backupFileName = 'backup.sql';
const command = `mysqldump -u root -ppassword test > ${backupFileName}`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Error executing command: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
}
console.log(`Backup created successfully: ${backupFileName}`);
});
对于MongoDB,可以使用mongodump
命令:
const { exec } = require('child_process');
const backupDir = 'backup';
const command = `mongodump --uri="mongodb://localhost:27017" --out=${backupDir}`;
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Error executing command: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
}
console.log(`Backup created successfully in: ${backupDir}`);
});
3.5 性能优化与注意事项
- 批量操作
在将数据从文件系统写入数据库或反之,尽量使用批量操作。例如,在向MySQL插入数据时,使用
INSERT INTO... VALUES (...),(...),...
的形式一次性插入多条记录,而不是逐条插入。
const fs = require('fs');
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
fs.readFile('users.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
const lines = data.split('\n');
const values = [];
lines.forEach((line) => {
if (line.trim()!== '') {
const [username, password] = line.split(':');
values.push([username, password]);
}
});
const insertQuery = 'INSERT INTO users (username, password) VALUES?';
connection.query(insertQuery, [values], (queryErr, result) => {
if (queryErr) {
console.error('Error inserting data:', queryErr);
}
});
});
- 资源管理 在进行文件系统和数据库操作时,要注意及时释放资源。例如,在使用完数据库连接后,要关闭连接;在读取大文件时,要注意内存管理,避免内存溢出。
const fs = require('fs');
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
fs.createReadStream('largefile.txt')
.on('data', (chunk) => {
// 处理数据块,避免一次性加载整个文件到内存
})
.on('end', () => {
connection.end();
});
- 错误处理 在文件系统和数据库交互过程中,要妥善处理各种错误。无论是文件读取失败、数据库连接错误还是数据操作失败,都应该有相应的错误处理机制,以确保应用程序的稳定性。
const fs = require('fs');
const mysql = require('mysql2');
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
fs.readFile('users.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
const lines = data.split('\n');
lines.forEach((line) => {
if (line.trim()!== '') {
const [username, password] = line.split(':');
const insertQuery = 'INSERT INTO users (username, password) VALUES (?,?)';
const values = [username, password];
connection.query(insertQuery, values, (queryErr, result) => {
if (queryErr) {
console.error('Error inserting data:', queryErr);
}
});
}
});
});
connection.on('error', (err) => {
console.error('Database connection error:', err);
});