<think>我们需要构建一个树形结构的JSON数据,通常职业数据有层级关系(例如:职业分类->具体职业)。假设我们有一个职业分类表,表结构如下:
表:occupation
字段:
id: 主键
name: 职业名称
parent_id: 父级ID,顶级分类的parent_id为0或null
目标:查询数据库,构建一个树形结构的JSON,例如:
[
{
"id": 1,
"name": "IT",
"children": [
{
"id": 2,
"name": "软件开发",
"children": [
{"id": 3, "name": "Java开发", "children": []},
{"id": 4, "name": "Python开发", "children": []}
]
}
]
},
...
]
步骤:
1. 从数据库查询所有职业数据(一次性查询,避免多次查询数据库)
2. 在内存中构建树形结构
3. 将树形结构转换为JSON
注意:如果数据量很大,需要考虑递归深度和性能(使用非递归方式构建树)
以下使用MyBatis Plus和Spring Boot为例,但核心逻辑是通用的。
首先,定义实体类:
*/
// Occupation.java
/*
public class Occupation {
private Long id;
private String name;
private Long parentId;
// 省略getter/setter
}
*/
// 构建树形结构的工具类
/*
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class TreeBuilder {
// 构建树形结构
public static List<OccupationNode> buildTree(List<Occupation> occupations) {
// 先用一个Map来存储id和节点的映射关系,同时将原始数据转换为节点
Map<Long, OccupationNode> nodeMap = new HashMap<>();
List<OccupationNode> nodes = new ArrayList<>();
// 第一步:将每个Occupation转换为OccupationNode,并放入map和list
for (Occupation occupation : occupations) {
OccupationNode node = new OccupationNode();
node.setId(occupation.getId());
node.setName(occupation.getName());
node.setChildren(new ArrayList<>());
nodeMap.put(occupation.getId(), node);
nodes.add(node);
}
// 第二步:构建树
List<OccupationNode> rootNodes = new ArrayList<>();
for (OccupationNode node : nodes) {
// 查找当前节点的父节点
Long parentId = occupations.stream()
.filter(occ -> occ.getId().equals(node.getId()))
.findFirst()
.map(Occupation::getParentId)
.orElse(null);
// 如果parentId为0或null,则为根节点
if (parentId == null || parentId == 0) {
rootNodes.add(node);
} else {
// 找到父节点,将当前节点加入父节点的children
OccupationNode parentNode = nodeMap.get(parentId);
if (parentNode != null) {
parentNode.getChildren().add(node);
}
}
}
return rootNodes;
}
}
// 树形节点类
public class OccupationNode {
private Long id;
private String name;
private List<OccupationNode> children;
// 省略getter/setter
}
*/
// 在Controller中调用
/*
@RestController
@RequestMapping("/occupation")
public class OccupationController {
@Autowired
private OccupationService occupationService;
@GetMapping("/tree")
public List<OccupationNode> getOccupationTree() {
// 查询所有职业数据
List<Occupation> occupations = occupationService.listAll();
// 构建树
return TreeBuilder.buildTree(occupations);
}
}
*/
// 但是,上面的方法在构建树时假设了parentId不为0或null的节点都有父节点(在nodeMap中)。如果数据有误(比如parentId指向了一个不存在的节点),则这些节点会被忽略(成为游离节点,不会被加入树中)。如果需要将这些游离节点也作为根节点,可以修改构建逻辑。
// 优化:使用递归方式(但数据量大时递归可能栈溢出,且效率不高)所以这里使用非递归。
// 另一种构建树的方法(使用Stream API):
/*
public static List<OccupationNode> buildTreeByStream(List<Occupation> occupations) {
// 先按parentId分组
Map<Long, List<Occupation>> groupByParentId = occupations.stream()
.collect(Collectors.groupingBy(Occupation::getParentId));
// 创建节点映射
Map<Long, OccupationNode> nodeMap = new HashMap<>();
occupations.forEach(occ -> {
OccupationNode node = new OccupationNode();
node.setId(occ.getId());
node.setName(occ.getName());
node.setChildren(new ArrayList<>());
nodeMap.put(occ.getId(), node);
});
// 构建树
List<OccupationNode> rootNodes = new ArrayList<>();
occupations.forEach(occ -> {
Long parentId = occ.getParentId();
if (parentId == null || parentId == 0) {
rootNodes.add(nodeMap.get(occ.getId()));
} else {
OccupationNode parentNode = nodeMap.get(parentId);
if (parentNode != null) {
parentNode.getChildren().add(nodeMap.get(occ.getId()));
}
}
});
// 排序(如果需要)
// ...
return rootNodes;
}
*/
// 注意:以上两种方法都是O(n)复杂度,效率较高。
// 如果使用递归构建树(不推荐大数据量,但代码简洁):
/*
public static List<OccupationNode> buildTreeRecursive(List<Occupation> occupations) {
// 先获取所有根节点(parentId为0或null)
List<Occupation> roots = occupations.stream()
.filter(occ -> occ.getParentId() == null || occ.getParentId() == 0)
.collect(Collectors.toList());
return roots.stream().map(root -> {
OccupationNode rootNode = new OccupationNode();
rootNode.setId(root.getId());
rootNode.setName(root.getName());
rootNode.setChildren(getChildren(root, occupations));
return rootNode;
}).collect(Collectors.toList());
}
private static List<OccupationNode> getChildren(Occupation parent, List<Occupation> occupations) {
List<Occupation> children = occupations.stream()
.filter(occ -> parent.getId().equals(occ.getParentId()))
.collect(Collectors.toList());
if (children.isEmpty()) {
return new ArrayList<>();
}
return children.stream().map(child -> {
OccupationNode childNode = new OccupationNode();
childNode.setId(child.getId());
childNode.setName(child.getName());
childNode.setChildren(getChildren(child, occupations));
return childNode;
}).collect(Collectors.toList());
}
*/
// 实际项目中,推荐非递归方式(避免栈溢出,性能更好)。
// 最终,返回的JSON就是树形结构。
// 示例:使用MyBatis Plus查询所有数据
/*
@Service
public class OccupationServiceImpl extends ServiceImpl<OccupationMapper, Occupation> implements OccupationService {
@Override
public List<Occupation> listAll() {
return baseMapper.selectList(null); // 查询所有
}
}
*/
// 注意:如果数据量很大(比如几十万条),一次性查询所有数据并构建树可能内存占用高,此时需要分页或懒加载树。但本问题假设数据量不大。
// 完整代码示例(使用非递归方式):</think># Java查询职业数据以树形JSON返回前端
我将提供一个完整的解决方案,用于从数据库中查询职业数据并以树形JSON格式返回给前端。这种结构特别适合展示层级关系(如职业分类体系)。
## 解决方案
### 数据结构设计
假设我们有以下职业分类表结构:
```sql
CREATE TABLE occupations (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
parent_id INT DEFAULT NULL,
description VARCHAR(255),
level INT DEFAULT 1,
FOREIGN KEY (parent_id) REFERENCES occupations(id)
);
```
### Java实现代码
```java
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OccupationTreeBuilder {
// 职业节点类
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
static class OccupationNode {
private int id;
private String name;
private String description;
private int level;
private List<OccupationNode> children = new ArrayList<>();
public OccupationNode(int id, String name, String description, int level) {
this.id = id;
this.name = name;
this.description = description;
this.level = level;
}
}
// 数据库连接信息
private static final String DB_URL = "jdbc:mysql://localhost:3306/career_db";
private static final String DB_USER = "username";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
// 1. 从数据库获取所有职业数据
List<OccupationNode> allOccupations = fetchOccupationsFromDB();
// 2. 构建职业树
List<OccupationNode> occupationTree = buildOccupationTree(allOccupations);
// 3. 转换为JSON
String jsonResult = convertToJson(occupationTree);
System.out.println("树形JSON结果:");
System.out.println(jsonResult);
}
// 从数据库获取所有职业
private static List<OccupationNode> fetchOccupationsFromDB() {
List<OccupationNode> occupations = new ArrayList<>();
String query = "SELECT id, name, description, level FROM occupations ORDER BY level, name";
try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String description = rs.getString("description");
int level = rs.getInt("level");
occupations.add(new OccupationNode(id, name, description, level));
}
} catch (SQLException e) {
e.printStackTrace();
}
return occupations;
}
// 构建职业树
private static List<OccupationNode> buildOccupationTree(List<OccupationNode> occupations) {
// 创建节点映射表
Map<Integer, OccupationNode> nodeMap = new HashMap<>();
List<OccupationNode> rootNodes = new ArrayList<>();
// 第一遍:将所有节点放入映射表
for (OccupationNode node : occupations) {
nodeMap.put(node.getId(), node);
}
// 第二遍:构建树结构
for (OccupationNode node : occupations) {
// 获取父节点ID
int parentId = getParentId(node.getId()); // 这里需要实现获取父节点ID的方法
if (parentId == 0) { // 根节点
rootNodes.add(node);
} else {
OccupationNode parent = nodeMap.get(parentId);
if (parent != null) {
parent.getChildren().add(node);
}
}
}
return rootNodes;
}
// 实际应用中应从数据库获取父ID,这里简化实现
private static int getParentId(int id) {
// 实际项目中应查询数据库获取父ID
// 这里仅作为示例,返回一个简化逻辑
if (id > 100) return id / 10; // 假设父ID是当前ID除以10
return 0; // 0表示根节点
}
// 转换为JSON字符串
private static String convertToJson(List<OccupationNode> tree) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tree);
} catch (JsonProcessingException e) {
e.printStackTrace();
return "{}";
}
}
}
```
### 代码说明
1. **数据结构**:
- `OccupationNode` 类表示职业节点,包含基本信息和子节点列表
- 使用 `@JsonInclude(JsonInclude.Include.NON_EMPTY)` 注解确保空子节点列表不输出
2. **数据库操作**:
- 使用JDBC连接数据库并查询所有职业数据
- 按层级和名称排序,确保父节点在子节点之前
3. **树形结构构建**:
- 使用两次遍历构建树:第一次创建节点映射,第二次建立父子关系
- 根节点的父ID为0(或null)
4. **JSON转换**:
- 使用Jackson库将Java对象转换为格式化的JSON
- 使用`writerWithDefaultPrettyPrinter()`实现美观的输出格式
### 示例JSON输出
```json
[
{
"id": 1,
"name": "信息技术",
"description": "IT相关职业",
"level": 1,
"children": [
{
"id": 11,
"name": "软件开发",
"description": "软件开发相关职位",
"level": 2,
"children": [
{
"id": 111,
"name": "前端开发",
"description": "Web前端开发",
"level": 3
},
{
"id": 112,
"name": "后端开发",
"description": "服务器端开发",
"level": 3
}
]
},
{
"id": 12,
"name": "数据科学",
"description": "数据分析与处理",
"level": 2,
"children": [
{
"id": 121,
"name": "数据分析师",
"description": "业务数据分析",
"level": 3
}
]
}
]
},
{
"id": 2,
"name": "医疗健康",
"description": "医疗相关职业",
"level": 1,
"children": [
{
"id": 21,
"name": "医生",
"description": "临床医生",
"level": 2
}
]
}
]
```
## 优化建议
1. **数据库查询优化**:
```java
// 使用预编译语句提高性能
String query = "SELECT id, name, description, level, parent_id FROM occupations";
```
2. **递归构建树**(适合不确定深度的情况):
```java
private static void buildTreeRecursive(OccupationNode parent, Map<Integer, List<OccupationNode>> parentMap) {
List<OccupationNode> children = parentMap.get(parent.getId());
if (children != null) {
for (OccupationNode child : children) {
parent.getChildren().add(child);
buildTreeRecursive(child, parentMap);
}
}
}
```
3. **添加缓存**:
```java
// 使用Guava Cache或Caffeine缓存树形结构
LoadingCache<String, List<OccupationNode>> occupationTreeCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.build(key -> buildOccupationTree(fetchOccupationsFromDB()));
```
4. **分页加载**(适合大数据量):
```java
public List<OccupationNode> getOccupationTree(int parentId, int page, int size) {
// 查询指定父ID的子节点
String query = "SELECT * FROM occupations WHERE parent_id = ? LIMIT ? OFFSET ?";
// ...
}
```
## 相关技术栈
- **数据库**:MySQL/PostgreSQL
- **JDBC**:Java数据库连接
- **Jackson**:JSON处理库
- **Lombok**:简化POJO代码
- **连接池**:HikariCP或Druid