一、普通方式返回100萬條數據
@RestController
@RequestMapping("/bad")
public class BadController {@Autowiredprivate UserRepository userRepository;/*** 危險!一次性加載 100 萬條到內存*/@GetMapping("/all-users")public List<User> getAllUsers() {// 問題1:把100萬條數據全查出來,加載到JVM堆內存List<User> users = userRepository.findAll(); // 問題2:序列化成JSON時還要再占一份內存return users; // 結果:可能占用 500MB~1GB 內存,多個請求就OOM!}
}
問題分析
1.內存爆炸(OutOfMemoryError)
2.接口超時(30s+)
3.數據庫壓力大
4.客戶端卡死
二、流式查詢:逐條處理(推薦用于導出、批處理)
核心思想:從數據庫讀取時不把所有數據加載到內存,而是"一條一條"地讀取和處理。
流式查詢(如 MyBatis + JDBC 流式)本質上是阻塞式的,但它可以避免內存溢出(OOM),卻仍然可能遇到連接或請求超時。
使用 MyBatis 流式查詢
// Mapper 接口
@Mapper
public interface UserMapper {@Select("SELECT id, name, age, email FROM user ORDER BY id")void streamAllUsers(ResultHandler<User> handler);
}
// Controller
@RestController
@RequestMapping("/streaming")
public class StreamingController {@Autowiredprivate UserMapper userMapper;/*** 流式返回:逐條寫入響應流,內存占用極低*/@GetMapping(value = "/users.csv", produces = "text/csv;charset=UTF-8")public void streamUsersAsCsv(HttpServletResponse response) throws IOException {response.setContentType("text/csv");response.setCharacterEncoding("UTF-8");response.setHeader("Content-Disposition", "attachment; filename=users.csv");PrintWriter writer = response.getWriter();// 寫入CSV頭writer.println("id,name,age,email");// 流式處理:每讀到一條,就寫入一次userMapper.streamAllUsers(resultContext -> {User user = resultContext.getResultObject();writer.printf("%d,%s,%d,%s%n", user.getId(), escapeCsv(user.getName()), user.getAge(), user.getEmail());writer.flush(); // 強制推送});}private String escapeCsv(String value) {if (value == null) return "";return value.replace("\"", "\"\"");}
}
優點
1.內存只保留當前一條數據
2.使用 Transfer-Encoding: chunked 分塊傳輸
3.適合導出 CSV、Excel 等大文件
三、響應式流:Reactive Stream(推薦用于高并發 API)
技術棧:Spring WebFlux、Spring Data R2DBC(響應式數據庫驅動)、Project Reactor
// Repository
@Repository
public interface UserReactiveRepository extends ReactiveCrudRepository<User, Long> {Flux<User> findAll(); // 返回響應式流
}
// Service
@Service
public class UserReactiveService {@Autowiredprivate UserReactiveRepository userRepository;public Flux<User> getAllUsers() {return userRepository.findAll();}
}
// Controller
@RestController
@RequestMapping("/reactive")
public class ReactiveController {@Autowiredprivate UserReactiveService userService;/*** ? 響應式流:非阻塞、背壓控制、持續推送*/@GetMapping(value = "/users", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<User> streamUsers() {// 返回 Flux<User>,Spring WebFlux 會自動流式推送return userService.getAllUsers();// 客戶端會一條一條收到數據}
}
配置 application.yml
spring:r2dbc:url: r2dbc:postgresql://localhost:5432/mydbusername: userpassword: pass
前端
// 使用 EventSource(SSE)
const eventSource = new EventSource('/reactive/users');eventSource.onmessage = (event) => {const user = JSON.parse(event.data);console.log('收到用戶:', user);// 可以實時顯示在頁面上
};eventSource.onerror = () => {eventSource.close();
};
優點
1.非阻塞 I/O,高并發
2.背壓(Backpressure)機制,客戶端慢?服務端自動減速
3.實時性好,適合“直播式”數據推送