6月5日 21:43

How does TypeORM's caching mechanism work? Including query cache, Redis cache, and caching strategies

Caching is an important means of improving application performance. TypeORM provides multiple caching strategies and mechanisms, allowing developers to flexibly manage data caching.

Cache Types

1. Query Cache

TypeORM provides built-in query caching functionality that can cache query results.

typescript
import { DataSource } from 'typeorm'; const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logging: true, // Enable query cache cache: { type: 'database', // or 'redis', 'ioredis' duration: 30000, // Cache duration (milliseconds) options: { // Database cache options tableName: 'query_cache', }, }, });

2. Redis Cache

Using Redis as cache storage:

typescript
import { DataSource } from 'typeorm'; const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logging: true, cache: { type: 'redis', // or 'ioredis' duration: 30000, options: { host: 'localhost', port: 6379, password: 'password', db: 0, }, }, });

3. Database Cache

Using database table as cache storage:

typescript
const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post], synchronize: false, logging: true, cache: { type: 'database', duration: 30000, options: { tableName: 'query_cache', }, }, });

Query Cache Usage

Basic Query Cache

typescript
import { DataSource } from 'typeorm'; // Query with cache enabled const users = await dataSource .getRepository(User) .find({ cache: true, // Enable cache }); // Custom cache duration const users = await dataSource .getRepository(User) .find({ cache: { id: 'users_list', // Cache ID milliseconds: 60000, // Cache for 60 seconds }, });

QueryBuilder Cache

typescript
// Enable cache using QueryBuilder const users = await dataSource .getRepository(User) .createQueryBuilder('user') .cache(true) // Enable cache .getMany(); // Custom cache configuration const users = await dataSource .getRepository(User) .createQueryBuilder('user') .cache('user_list', 60000) // Cache ID and time .where('user.age > :age', { age: 18 }) .getMany();

Relation Query Cache

typescript
// Cache relation queries const usersWithPosts = await dataSource .getRepository(User) .find({ relations: ['posts'], cache: true, }); // Use QueryBuilder to cache relation queries const usersWithPosts = await dataSource .getRepository(User) .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .cache('users_with_posts', 30000) .getMany();

Cache Management

Clearing Cache

typescript
// Clear specific query cache await dataSource .getRepository(User) .query('CLEAR CACHE'); // Clear all cache await dataSource.queryResultCache?.clear(); // Clear specific cache ID await dataSource.queryResultCache?.remove(['users_list']);

Cache Invalidation Strategy

typescript
// Clear related cache after updating data async function updateUser(userId: number, userData: Partial<User>) { const userRepository = dataSource.getRepository(User); // Update user await userRepository.update(userId, userData); // Clear related cache await dataSource.queryResultCache?.remove([ 'users_list', 'user_details', ]); } // Use transaction and cache clearing async function updateUserWithCache(userId: number, userData: Partial<User>) { await dataSource.transaction(async (manager) => { const userRepository = manager.getRepository(User); // Update user await userRepository.update(userId, userData); // Clear cache await manager.queryResultCache?.remove([ 'users_list', ]); }); }

Entity Cache

Entity-Level Cache

typescript
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; // Entity cache configuration @Column({ type: 'timestamp' }) cachedAt: Date; } // Use entity cache async function getUserWithCache(userId: number) { const cacheKey = `user:${userId}`; // Try to get from cache const cached = await dataSource.queryResultCache?.get(cacheKey); if (cached) { return JSON.parse(cached); } // Get from database const user = await dataSource.getRepository(User).findOne({ where: { id: userId }, }); // Store in cache await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(user), 60000 ); return user; }

Custom Cache Decorator

typescript
import { Cacheable } from './decorators/Cacheable'; @Entity() export class Product { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column({ type: 'decimal', precision: 10, scale: 2 }) price: number; @Cacheable({ ttl: 30000 }) // Cache for 30 seconds async getDetails(): Promise<Product> { return this; } } // Use custom cache decorator const product = await productRepository.findOne({ where: { id: 1 } }); const details = await product.getDetails();

Caching Strategies

Read-Write Separation Cache

typescript
// Read operations use cache async function getUser(userId: number) { const cacheKey = `user:${userId}`; // Try to read from cache const cached = await dataSource.queryResultCache?.get(cacheKey); if (cached) { return JSON.parse(cached); } // Read from database const user = await dataSource.getRepository(User).findOne({ where: { id: userId }, }); // Store in cache await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(user), 60000 ); return user; } // Write operations clear cache async function updateUser(userId: number, userData: Partial<User>) { const userRepository = dataSource.getRepository(User); // Update database await userRepository.update(userId, userData); // Clear cache const cacheKey = `user:${userId}`; await dataSource.queryResultCache?.remove([cacheKey]); }

Multi-Level Cache

typescript
// Level 1 cache: Memory cache const memoryCache = new Map<string, any>(); // Level 2 cache: Redis cache const redisCache = dataSource.queryResultCache; async function getProduct(productId: number) { const cacheKey = `product:${productId}`; // Try level 1 cache if (memoryCache.has(cacheKey)) { return memoryCache.get(cacheKey); } // Try level 2 cache const cached = await redisCache?.get(cacheKey); if (cached) { const product = JSON.parse(cached); memoryCache.set(cacheKey, product); return product; } // Get from database const product = await dataSource.getRepository(Product).findOne({ where: { id: productId }, }); // Store in level 2 cache await redisCache?.store(cacheKey, JSON.stringify(product), 60000); // Store in level 1 cache memoryCache.set(cacheKey, product); return product; }

Cache Warmup

typescript
// Warmup cache on application startup async function warmupCache() { // Warmup popular users const popularUsers = await dataSource .getRepository(User) .createQueryBuilder('user') .where('user.followersCount > :count', { count: 1000 }) .limit(100) .getMany(); for (const user of popularUsers) { const cacheKey = `user:${user.id}`; await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(user), 300000 // 5 minutes ); } // Warmup popular posts const popularPosts = await dataSource .getRepository(Post) .createQueryBuilder('post') .where('post.views > :views', { views: 1000 }) .limit(100) .getMany(); for (const post of popularPosts) { const cacheKey = `post:${post.id}`; await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(post), 300000 ); } } // Call on application startup warmupCache();

Caching Best Practices

1. Choose Appropriate Caching Strategy

typescript
// ✅ Good: Use different caching strategies for different data types // Static data: Long cache time const staticData = await dataSource .getRepository(Category) .find({ cache: { milliseconds: 3600000, // 1 hour }, }); // Dynamic data: Short cache time const dynamicData = await dataSource .getRepository(Post) .find({ cache: { milliseconds: 30000, // 30 seconds }, }); // Real-time data: No cache const realTimeData = await dataSource .getRepository(OnlineUser) .find({ cache: false, });

2. Cache Key Naming Convention

typescript
// ✅ Good: Use clear cache key naming const cacheKeys = { user: (id: number) => `user:${id}`, userList: (page: number) => `user_list:page:${page}`, userPosts: (userId: number) => `user:${userId}:posts`, popularPosts: () => `posts:popular`, }; // Usage example await dataSource.queryResultCache?.store( cacheKeys.user(1), JSON.stringify(user), 60000 );

3. Cache Invalidation Timing

typescript
// ✅ Good: Clear cache promptly when data changes class UserService { async updateUser(userId: number, userData: Partial<User>) { const userRepository = dataSource.getRepository(User); // Update data await userRepository.update(userId, userData); // Clear related cache await this.clearUserCache(userId); } async clearUserCache(userId: number) { const cacheKeys = [ `user:${userId}`, `user:${userId}:posts`, `user:${userId}:comments`, ]; await dataSource.queryResultCache?.remove(cacheKeys); } }

4. Monitor Cache Performance

typescript
// Cache performance monitoring class CacheMonitor { private hits = 0; private misses = 0; async get<T>(key: string): Promise<T | null> { const cached = await dataSource.queryResultCache?.get(key); if (cached) { this.hits++; return JSON.parse(cached); } this.misses++; return null; } getHitRate(): number { const total = this.hits + this.misses; return total > 0 ? this.hits / total : 0; } getStats() { return { hits: this.hits, misses: this.misses, hitRate: this.getHitRate(), }; } } // Use monitoring const monitor = new CacheMonitor(); async function getProduct(productId: number) { const cacheKey = `product:${productId}`; // Try to get from cache const cached = await monitor.get<Product>(cacheKey); if (cached) { return cached; } // Get from database const product = await dataSource.getRepository(Product).findOne({ where: { id: productId }, }); // Store in cache await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(product), 60000 ); return product; } // Periodically output cache statistics setInterval(() => { console.log('Cache Stats:', monitor.getStats()); }, 60000);

Advanced Caching Techniques

1. Cache Penetration Protection

typescript
// Use null value caching to prevent cache penetration async function getUser(userId: number) { const cacheKey = `user:${userId}`; // Try to get from cache const cached = await dataSource.queryResultCache?.get(cacheKey); if (cached) { const user = JSON.parse(cached); // Check if it's a null value marker if (user === null) { return null; } return user; } // Get from database const user = await dataSource.getRepository(User).findOne({ where: { id: userId }, }); // Store in cache (including null values) await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(user), 60000 ); return user; }

2. Cache Avalanche Protection

typescript
// Use random cache time to prevent cache avalanche async function getProduct(productId: number) { const cacheKey = `product:${productId}`; // Try to get from cache const cached = await dataSource.queryResultCache?.get(cacheKey); if (cached) { return JSON.parse(cached); } // Get from database const product = await dataSource.getRepository(Product).findOne({ where: { id: productId }, }); // Use random cache time (30-60 seconds) const cacheTime = 30000 + Math.random() * 30000; // Store in cache await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(product), cacheTime ); return product; }

3. Cache Breakdown Protection

typescript
// Use mutex lock to prevent cache breakdown const locks = new Map<string, Promise<any>>(); async function getHotProduct(productId: number) { const cacheKey = `product:${productId}`; // Try to get from cache const cached = await dataSource.queryResultCache?.get(cacheKey); if (cached) { return JSON.parse(cached); } // Check if there's a lock if (locks.has(cacheKey)) { return await locks.get(cacheKey); } // Create lock const promise = (async () => { try { // Get from database const product = await dataSource.getRepository(Product).findOne({ where: { id: productId }, }); // Store in cache await dataSource.queryResultCache?.store( cacheKey, JSON.stringify(product), 60000 ); return product; } finally { // Release lock locks.delete(cacheKey); } })(); locks.set(cacheKey, promise); return await promise; }

TypeORM's caching mechanisms provide powerful performance optimization capabilities. Proper use of caching can significantly improve application performance.

标签:TypeORM