本文目录导读:

- 四大NoSQL类型及Java对应场景
- 案例一:高并发电商——Redis (键值+缓存)
- 案例二:内容管理系统——MongoDB (文档)
- 案例三:大数据日志——HBase (列族)
- 案例四:社交网络——Neo4j (图)
- 决策流程图
Java NoSQL选型是一个典型的技术权衡问题,没有“银弹”,选型的关键不是看哪个数据库“最好”,而是看哪个数据库最匹配你的数据模型、访问模式和一致性要求。
下面我将根据Java开发中最常见的四类NoSQL数据库(键值、文档、列族、图),结合典型业务场景给出选型案例和Java代码示例。
四大NoSQL类型及Java对应场景
| 类型 | 代表产品 | 核心优势 | 最适合的Java场景 | 最不适合的场景 |
|---|---|---|---|---|
| 键值 | Redis, Aerospike | 极高性能、丰富数据结构 | 缓存、Session存储、计数器、排行榜、实时数据分析 | 复杂关联查询、强事务、多字段过滤 |
| 文档 | MongoDB | 灵活Schema、JSON友好、强大查询 | 内容管理、用户画像、IoT数据、日志、产品目录、电商购物车 | 强ACID事务(金融核心)、多表关联 |
| 列族 | HBase, Cassandra | 高可用、水平扩展、海量写入 | 时序数据、用户行为日志、推荐引擎、大数据分析底座 | 低延迟点查(不如Redis)、复杂聚合查询 |
| 图 | Neo4j, NebulaGraph | 深度关系遍历、最短路径 | 社交关系、推荐系统、权限图、知识图谱、欺诈检测 | 简单CRUD、高并发简单点查 |
高并发电商——Redis (键值+缓存)
场景:电商平台的商品详情页、购物车、秒杀库存,数据访问量极大(万级QPS),对延迟要求极高(<5ms),数据是KV结构。
选型理由: Redis基于内存,单线程模型(6.x后多线程处理网络IO),读写速度极快,支持丰富的数据类型(String、Hash、List、Sorted Set),能很好地模拟购物车(Session ID + 商品列表)和排行榜(商品热度)。
Java代码示例 (Spring Data Redis + RedisTemplate):
@Service
public class ProductCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 商品信息缓存 (使用Hash, 减少序列化开销)
public Product getProductById(Long productId) {
String key = "product:" + productId;
// 1. 先从缓存读
Product cached = (Product) redisTemplate.opsForHash().entries(key);
if (cached != null) return cached;
// 2. 缓存未命中,查数据库
Product product = productMapper.selectById(productId);
if (product != null) {
// 3. 写入缓存,设置过期时间防止缓存雪崩
redisTemplate.opsForHash().putAll(key, BeanMap.create(product));
redisTemplate.expire(key, 1, TimeUnit.HOURS);
}
return product;
}
// 秒杀扣减库存 (使用Redis原子操作)
public boolean deductStock(Long productId, int delta) {
// LUA脚本保证原子性
String script =
"if redis.call('exists', KEYS[1]) == 1 then " +
"local stock = redis.call('get', KEYS[1]); " +
"if stock - ARGV[1] >= 0 then " +
"return redis.call('decrby', KEYS[1], ARGV[1]); " +
"else return -1; " +
"end; " +
"else return -2; end";
// 执行LUA
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
singletonList("stock:" + productId),
String.valueOf(delta)
);
return result != null && result >= 0;
}
}
内容管理系统——MongoDB (文档)
场景:一个新闻资讯APP的后台,文章内容字段变化频繁(标题、标签、正文、封面图、点赞数、评论数),不同文章结构不同,需要快速全文搜索、按热度/时间排序。
选型理由: MongoDB的BSON文档天然适配文章这样的非结构化数据,不需要预先定义字段,新增一个“是否付费”字段不会影响线上服务,内置的索引和聚合管道能高效完成排序、分组和简单的文本搜索。
Java代码示例 (Spring Data MongoDB):
@Document(collection = "articles")
public class Article {
@Id
private String id;
private String title;
private String content;
private List<String> tags;
private Author author;
private Integer likes = 0;
private LocalDateTime createdTime;
// ... 动态字段可以用 Map<String, Object>
private Map<String, Object> extraFields;
}
@Service
public class ArticleService {
@Autowired
private MongoTemplate mongoTemplate;
// 基于标签和时间的复杂查询
public List<Article> findByTagsAndTime(List<String> tags, LocalDateTime since, int page, int size) {
Query query = new Query();
// 1. 标签必须包含(使用_all操作符)
query.addCriteria(Criteria.where("tags").all(tags));
// 2. 时间范围
query.addCriteria(Criteria.where("createdTime").gt(since));
// 3. 按点赞数降序,时间升序
query.with(Sort.by(Sort.Direction.DESC, "likes"));
query.with(PageRequest.of(page, size));
return mongoTemplate.find(query, Article.class);
}
// 全文搜索(需要创建文本索引)
public List<Article> searchByText(String keyword) {
TextCriteria textCriteria = TextCriteria.forDefaultLanguage()
.matchingAny(keyword);
Query query = TextQuery.queryText(textCriteria)
.sortByScore();
return mongoTemplate.find(query, Article.class);
}
}
大数据日志——HBase (列族)
场景:一个物联网(IoT)平台,每天有数十亿条传感器数据上报(设备ID,时间戳,温度,湿度,电量),需要按设备ID+时间范围查询,写入量极大(万级TPS),读请求较分散,对强一致性要求不高。
选型理由: HBase是Hadoop生态的列族数据库,其底层使用LSM-Tree,擅长海量数据的批量写入,行键(RowKey)按设备ID+时间戳设计后,能实现秒级扫描某设备一段时间内的所有数据,列族可以按数据冷热分离(如“实时数据”列族放在SSD,“历史归档”列族放在HDD)。
Java代码示例 (HBase Java API):
@Service
public class SensorDataService {
@Autowired
private Connection connection; // HBase Connection
private static final byte[] CF_F = Bytes.toBytes("f"); // 列族: factory
private static final byte[] CF_M = Bytes.toBytes("m"); // 列族: meta
/**
* RowKey设计: 反转设备ID(避免热点) + 时间戳(降序,最近的数据在前)
* 设备ID=1001,时间20250101120000 -> RowKey: "1001_20250101120000"
*/
public void batchInsert(List<SensorRecord> records) throws IOException {
try (Table table = connection.getTable(TableName.valueOf("sensor_data"))) {
List<Put> puts = new ArrayList<>();
for (SensorRecord record : records) {
String rowKey = String.format("%s_%d",
reverse(record.getDeviceId()), // 反转设备ID前缀防热点
Long.MAX_VALUE - record.getTimestamp().getTime()); // 时间戳降序
Put put = new Put(Bytes.toBytes(rowKey));
put.addColumn(CF_F, Bytes.toBytes("temp"), Bytes.toBytes(record.getTemperature()));
put.addColumn(CF_F, Bytes.toBytes("humidity"), Bytes.toBytes(record.getHumidity()));
put.addColumn(CF_M, Bytes.toBytes("battery"), Bytes.toBytes(record.getBattery()));
puts.add(put);
}
table.put(puts); // 批量写入,避免逐条插入
}
}
// 按时间范围扫描
public List<SensorRecord> scanByDeviceAndTime(String deviceId, Date start, Date end) throws IOException {
try (Table table = connection.getTable(TableName.valueOf("sensor_data"))) {
// 构造Scan对象,指定RowKey范围
String startRow = String.format("%s_%d", reverse(deviceId), Long.MAX_VALUE - end.getTime());
String stopRow = String.format("%s_%d", reverse(deviceId), Long.MAX_VALUE - start.getTime());
Scan scan = new Scan(Bytes.toBytes(startRow), Bytes.toBytes(stopRow));
scan.addFamily(CF_F);
ResultScanner scanner = table.getScanner(scan);
List<SensorRecord> results = new ArrayList<>();
for (Result result : scanner) {
SensorRecord record = new SensorRecord();
record.setTemperature(Bytes.toFloat(result.getValue(CF_F, Bytes.toBytes("temp"))));
record.setHumidity(Bytes.toFloat(result.getValue(CF_F, Bytes.toBytes("humidity"))));
results.add(record);
}
return results;
}
}
}
社交网络——Neo4j (图)
场景:一个兴趣社交APP,需要计算“你可能感兴趣的人”、“共同好友”、“二度人脉”等关系,数据模型是复杂的网状结构(用户、帖子、标签、点赞、关注)。
选型理由: 图数据库的核心优势是处理关系,对于社交场景,传统SQL做6度人脉查询需要几十次JOIN,性能急剧下降,而Neo4j使用Cypher语言,遍历边(关系)的时间复杂度与子图规模相关,相当于SQL的JOIN次数,能高效完成“推荐、路径查找、社区发现”。
Java代码示例 (Spring Data Neo4j):
@Node("Person")
public class Person {
@Id @GeneratedValue
private Long id;
private String name;
@Relationship(type = "FOLLOWS", direction = Direction.OUTGOING)
private List<Person> follows;
@Relationship(type = "POSTED")
private List<Post> posts;
}
@Node("Post")
public class Post {
@Id @GeneratedValue
private Long id;
private String content;
@Relationship(type = "TAGGED_WITH")
private List<Tag> tags;
}
@RelationshipProperties
public class TagRelation {
@TargetNode
private Tag tag;
private int weight; // 标签权重
}
// 业务查询: 推荐好友(基于共同关注的人)
@Service
public class RecommendService {
@Autowired
private Neo4jOperations neo4jOperations;
public List<Person> recommendFriends(Long personId, int limit) {
// Cypher语句: 查找person1关注的人关注的其他人(2度),且person1未关注
String cypher = """
MATCH (me:Person)-[:FOLLOWS]->(followedByMe:Person)-[:FOLLOWS]->(candidate:Person)
WHERE me.id = $personId
AND NOT (me)-[:FOLLOWS]->(candidate)
AND me <> candidate
RETURN candidate, count(*) as commonFollowers
ORDER BY commonFollowers DESC
LIMIT $limit
""";
return neo4jOperations.findAll(Person.class, cypher,
Map.of("personId", personId, "limit", limit));
}
}
决策流程图
当你在Java项目中纠结时,可以参考这个阶梯式判断逻辑:
- 数据模型是不是KV或简单对象?
- 是 → Redis (如果需要持久化且写多读少,考虑RocksDB)
- 否 → 继续
- 数据模型是不是JSON文档?(频繁变更字段、对象嵌套)
- 是 → MongoDB
- 否 → 继续
- 数据模型是不是稀疏列?(海量行、固定列族、写多读少)
- 是 → Cassandra (高可用、无单点) 或 HBase (强一致性、Hadoop生态)
- 否 → 继续
- 数据模型是不是社交网络/图谱?(强关系、路径查询)
- 是 → Neo4j 或 NebulaGraph (开源、国产、支持Java)
- 否 → 可能你用错了维度,回到需求重新分析。
| 维度 | Redis | MongoDB | HBase/Cassandra | Neo4j |
|---|---|---|---|---|
| Java客户端成熟度 | 极高(Spring Data Redis) | 高(Spring Data MongoDB) | 中等(HBase原生API) | 高(Spring Data Neo4j) |
| 事务支持 | 弱(单键原子性) | 中等(副本集事务) | 弱(行事务) | 强(ACID) |
| 运维复杂度 | 低(单机即可) | 中等(分片/副本) | 高(依赖Hadoop组件) | 中等(单机可跑) |
| 常见误区 | 持久化能力弱 | 不适合大量复杂JOIN | 不适合简单点查 | 不适合简单CRUD |
最后建议: 在实际项目中,通常会采用混合持久化架构,使用Redis做热数据缓存 + MongoDB做业务主库 + Elasticsearch做全文搜索,选择时,可优先用Spring Data系列封装,降低集成成本和迁移风险。