電商首頁需展示:多個熱門分類,每分類下推薦商品 4 個。數據來源:數據庫分類表 + 商品表,要求首屏秒開,不查數據庫。
public interface CategoryService extends BaseService {
// 查詢熱門分類(hot=true)
List queryHotCategories();
}
public interface ProductService extends BaseService {
// 根據分類 ID 查詢推薦商品(commend=true, open=true),按時間倒序,限制 4 條
List queryRecommendByCategoryId(Integer categoryId);
}
@Service("categoryService")
public class CategoryServiceImpl extends BaseServiceImpl implements CategoryService {
@Override
public List queryHotCategories() {
return getSession().createQuery("FROM Category WHERE hot = true", Category.class).list();
}
}
@Service("productService")
public class ProductServiceImpl extends BaseServiceImpl implements ProductService {
@Override
public List queryRecommendByCategoryId(Integer categoryId) {
String hql = "FROM Product p JOIN FETCH p.category " +
"WHERE p.commend = true AND p.open = true AND p.category.id = :cid " +
"ORDER BY p.date DESC";
return getSession().createQuery(hql, Product.class)
.setParameter("cid", categoryId)
.setMaxResults(4)
.list();
}
}
@WebListener
public class HomeDataInitListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext application = sce.getServletContext();
// 從 Spring 容器獲取 Bean(Listener 非 Spring 管理)
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
CategoryService categoryService = ctx.getBean(CategoryService.class);
ProductService productService = ctx.getBean(ProductService.class);
// 1. 查詢熱門分類
List hotCategories = categoryService.queryHotCategories();
// 2. 為每個分類查推薦商品,構建二維列表
List> categoryProducts = new ArrayList<>();
for (Category cat : hotCategories) {
List products = productService.queryRecommendByCategoryId(cat.getId());
categoryProducts.add(products);
}
// 3. 存入 Application 域,前台 EL 表達式直接讀取
application.setAttribute("categoryProducts", categoryProducts);
application.setAttribute("hotCategories", hotCategories);
System.out.println("首頁數據預熱完成,分類數: " + hotCategories.size());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 清理工作
}
}
@Component
public class HomeDataSyncScheduler {
private static final Logger log = LoggerFactory.getLogger(HomeDataSyncScheduler.class);
@Autowired CategoryService categoryService;
@Autowired ProductService productService;
@Autowired ServletContext application;
// 每 6 小時執行一次,啟動後立即執行一次
@Scheduled(fixedRate = 6 * 60 * 60 * 1000, initialDelay = 0)
public void refreshHomeData() {
log.info("開始同步首頁數據...");
try {
List hotCategories = categoryService.queryHotCategories();
List> categoryProducts = new ArrayList<>();
for (Category cat : hotCategories) {
categoryProducts.add(productService.queryRecommendByCategoryId(cat.getId()));
}
// 原子性替換,讀取端無感知
application.setAttribute("categoryProducts", categoryProducts);
application.setAttribute("hotCategories", hotCategories);
log.info("首頁數據同步完成");
} catch (Exception e) {
log.error("首頁數據同步失敗", e);
}
}
}
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<div class="home-floor">
<c:forEach items="${applicationScope.hotCategories}" var="cat" varStatus="vs">
<div class="floor-header">
<h3>${cat.name}</h3>
<a href="/category/${cat.id}">更多></a>
</div>
<div class="floor-products">
<c:forEach items="${applicationScope.categoryProducts[vs.index]}" var="product">
<div class="product-card">
<img src="${product.imageUrl}" alt="${product.name}"/>
<p class="name">${product.name}</p>
<p class="price">¥${product.price}</p>
<a href="/product/${product.id}" class="btn-buy">購買</a>
</div>
</c:forEach>
</div>
</c:forEach>
</div>
<!-- hibernate.cfg.xml / Spring 配置 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 實體類註解 -->
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Cacheable
public class Product { ... }
<!-- 查詢緩存 -->
query.setCacheable(true);
query.setCacheRegion("product.recommend");
| 決策點 | 方案 | 理由 |
|---|---|---|
| 數據存儲位置 | ServletContext (Application 域) | 全局共享、生命周期匹配、無需序列化 |
| 同步策略 | Listener 初始化 + Scheduled 定時刷新 | 啟動即可用、定期修正、實現簡單 |
| 數據結構 | List<List<Product>> 對應分類順序 | 前台直接索引遍歷,無需 Map 查找 |
| 緩存層級 | 二級緩存 + Application 域雙層 | 數據庫層面攔截重複查詢,內存層面極速讀取 |
@Configuration
public class HomeCacheConfig {
@Bean
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}
}
@Service
public class HomeDataService {
@Autowired CategoryRepository categoryRepo;
@Autowired ProductRepository productRepo;
@Autowired CacheManager cacheManager;
@PostConstruct
@Scheduled(fixedRate = 6 * 3600 * 1000)
public void refresh() {
Cache cache = cacheManager.getCache("homeData");
List cats = categoryRepo.findByHotTrue();
Map> map = new LinkedHashMap<>();
for (Category c : cats) {
map.put(c.getId(), productRepo.findTop4ByCategoryIdAndCommendTrueAndOpenTrueOrderByDateDesc(c.getId()));
}
cache.put("categories", cats);
cache.put("products", map);
}
public List getCategories() {
return cacheManager.getCache("homeData").get("categories", List.class);
}
}
這套「預熱 + 定時同步 + 多級緩存」方案,是高並發電商首頁架構的標準範式,無論 SSH 還是 Spring Boot/Cloud 皆適用。