需求:查詢 User 表中 newsh 字段為「政治類新聞」或「娛樂類新聞」的用戶總數,結果封裝為 Map 返回。本文提供標準 JDBC、Spring JdbcTemplate、MyBatis、JPA/Hibernate 四種主流數據訪問技術的完整實現,並優化 SQL 寫法與異常處理。
CREATE TABLE `User` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50),
`newsh` VARCHAR(50) -- 新聞偏好類別:'政治類新聞'、'娛樂類新聞'、'體育新聞' 等
);
-- 方案 A:條件聚合,單行多列返回
SELECT
SUM(CASE WHEN newsh = '政治類新聞' THEN 1 ELSE 0 END) AS politics_count,
SUM(CASE WHEN newsh = '娛樂類新聞' THEN 1 ELSE 0 END) AS entertainment_count
FROM `User`
WHERE newsh IN ('政治類新聞', '娛樂類新聞');
-- 方案 B:分組聚合,多行返回 (更通用、易擴展)
SELECT newsh, COUNT(*) AS cnt
FROM `User`
WHERE newsh IN ('政治類新聞', '娛樂類新聞')
GROUP BY newsh;
推薦方案 B:擴展新類別只需修改 IN 列表,代碼無需改動。
public Map countNewsPreferences(Connection conn) throws SQLException {
String sql = "SELECT newsh, COUNT(*) AS cnt FROM `User` " +
"WHERE newsh IN ('政治類新聞', '娛樂類新聞') GROUP BY newsh";
Map result = new LinkedHashMap<>(); // 保持插入順序
result.put("政治類新聞", 0L);
result.put("娛樂類新聞", 0L);
try (PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String category = rs.getString("newsh");
long count = rs.getLong("cnt");
result.put(category, count);
}
}
return result;
}
@Repository
public class UserStatisticsDao {
@Autowired private JdbcTemplate jdbcTemplate;
public Map countNewsPreferences() {
String sql = "SELECT newsh, COUNT(*) AS cnt FROM `User` " +
"WHERE newsh IN (?, ?) GROUP BY newsh";
// 使用 query 方法自動映射 RowMapper
List
<!-- UserMapper.xml -->
<select id="countNewsPreferences" resultType="java.util.Map">
SELECT newsh as category, COUNT(*) as cnt
FROM `User`
WHERE newsh IN ('政治類新聞', '娛樂類新聞')
GROUP BY newsh
</select>
// UserMapper.java
@Mapper
public interface UserMapper {
List
// Repository
@Repository
public interface UserRepository extends JpaRepository {
@Query("SELECT u.newsh, COUNT(u) FROM User u WHERE u.newsh IN :categories GROUP BY u.newsh")
List
public static Map executeCountQuery(DataSource ds, String sql, Object... params)
throws SQLException {
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
try (ResultSet rs = ps.executeQuery()) {
Map map = new LinkedHashMap<>();
ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
while (rs.next()) {
String key = (cols >= 2) ? rs.getString(1) : "total";
long val = rs.getLong(cols >= 2 ? 2 : 1);
map.put(key, val);
}
return map;
}
}
}
// 調用
Map stats = executeCountQuery(dataSource,
"SELECT newsh, COUNT(*) FROM `User` WHERE newsh IN (?,?) GROUP BY newsh",
"政治類新聞", "娛樂類新聞");
| 優化點 | 措施 | 效果 |
|---|---|---|
| 索引 | CREATE INDEX idx_user_newsh ON `User`(newsh); | 全表掃描 → 索引範圍掃描,百萬級數據毫秒級返回 |
| 覆蓋索引 | CREATE INDEX idx_user_newsh_cover ON `User`(newsh, id); | 僅掃描索引不回表,極致快 |
| 緩存 | Redis 緩存結果 5-10 分鐘,Key: news:pref:count | 高併發讀場景減輕 DB 壓力 99%+ |
| 異步預計算 | 定時任務每小時跑批寫入統計表 news_pref_daily | 查詢變為單表主鍵查,O(1) |
統計特定分類計數並封裝為 Map,核心是優化 SQL 使用 GROUP BY 聚合,配合預設 0 值初始化 Map確保類別缺失時返回 0 而非 null。四大主流框架均可在 10 行核心代碼內完成,建議根據項目技術棧選擇:Spring Boot 項目優先 JdbcTemplate 或 MyBatis,微服務/高併發場景加 Redis 緩存或異步預計算。