Building a Real-Time Data Transmission and Visualization Application with Spring Boot: STOMP, WebSocket, Scheduler, and React.js
In this blog post, we will walk through the process of building a real-time data transmission and visualization application using Spring Boot. This includes setting up STOMP for WebSocket communication, configuring a Scheduler to fetch data periodically, and visualizing the data using React.js.
Table of Contents WebSocket and STOMP Configuration
Creating Entities, DTOs and Mapper
1. Project Overview
This project aims to build a real-time data transmission and visualization system using Spring Boot. We will utilize STOMP for WebSocket communication, set up a Scheduler to periodically fetch data from the database, and visualize the data in real-time using React.js.
1.1 Architecture Diagram
Copy +-----------------------+ +--------------------+ +-------------------+
| Frontend (React) |<-->| WebSocket |<-->| Backend (Spring |
| | | STOMP Endpoint | | Boot) |
| - Receives data | | | | - Data Fetching |
| via WebSocket | | | | (Scheduler) |
| - Displays data | | | | - Data Mapping |
| on chart.js | | | | - Data Sending |
+-----------------------+ +--------------------+ +-------------------+
|
|
+------------------+
| Database |
| (PostgreSQL) |
+------------------+
2. Tech Stack
Backend: Spring Boot, Spring WebSocket, Spring Scheduler, Spring Data JPA
Database: PostgreSQL (MySQL setup also included)
Other Tools: Docker for containerization
3. WebSocket and STOMP
WebSocket
WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. It is designed to be implemented in web browsers and web servers but can be used by any client or server application.
STOMP
STOMP (Simple Text Oriented Messaging Protocol) is a simple and widely adopted messaging protocol that defines the format and rules for data exchange. It is used on top of WebSocket to allow messaging between a client and a server in web applications.
Key Benefits:
Real-time Communication: Both WebSocket and STOMP allow real-time bidirectional communication between a client and server.
Efficient Data Transfer: WebSocket reduces the overhead of traditional HTTP communication, making it more suitable for real-time applications.
Configuring WebSocket
3.1 build.gradle
Copy dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// MySQL JDBC Driver
implementation 'mysql:mysql-connector-java:8.0.32'
// PostgreSQL JDBC Driver
implementation 'org.postgresql:postgresql:42.5.1'
// Jackson for JSON serialization and deserialization
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.fasterxml.jackson.core:jackson-annotations'
implementation 'com.fasterxml.jackson.core:jackson-core'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
}
3.2 WebSocketConfig.java
Copy import org . springframework . context . annotation . Configuration ;
import org . springframework . messaging . simp . config . MessageBrokerRegistry ;
import org . springframework . web . socket . config . annotation . EnableWebSocketMessageBroker ;
import org . springframework . web . socket . config . annotation . StompEndpointRegistry ;
import org . springframework . web . socket . config . annotation . WebSocketMessageBrokerConfigurer ;
@ Configuration
@ EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@ Override
public void configureMessageBroker ( MessageBrokerRegistry config) {
config . enableSimpleBroker ( "/topic" );
config . setApplicationDestinationPrefixes ( "/app" );
}
@ Override
public void registerStompEndpoints ( StompEndpointRegistry registry) {
registry . addEndpoint ( "/gs-guide-websocket" )
. setAllowedOrigins ( "http://localhost:3000" )
. withSockJS ();
}
}
3.4 WenConfig for CORS configuration
Copy import org . springframework . context . annotation . Bean ;
import org . springframework . context . annotation . Configuration ;
import org . springframework . web . servlet . config . annotation . CorsRegistry ;
import org . springframework . web . servlet . config . annotation . WebMvcConfigurer ;
@ Configuration
public class WebConfig implements WebMvcConfigurer {
@ Override
public void addCorsMappings ( CorsRegistry registry) {
registry . addMapping ( "/**" )
. allowedOrigins ( "http://localhost:3000" )
. allowedMethods ( "GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" )
. allowedHeaders ( "*" )
. allowCredentials ( true );
}
}
4. Scheduler Configuration
Setting up a Scheduler to periodically fetch data from the database.
4.1 Creating the Scheduler Class
Copy import com . fasterxml . jackson . core . JsonProcessingException ;
import com . fasterxml . jackson . databind . ObjectMapper ;
import com . fasterxml . jackson . datatype . jsr310 . JavaTimeModule ;
import com . valentine . dto . GlucoseDTO ;
import com . valentine . mapper . GlucoseMapper ;
import com . valentine . model . Glucose ;
import com . valentine . repository . GlucoseRepository ;
import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . context . annotation . Configuration ;
import org . springframework . messaging . simp . SimpMessagingTemplate ;
import org . springframework . scheduling . annotation . EnableScheduling ;
import org . springframework . scheduling . annotation . Scheduled ;
import javax . annotation . PostConstruct ;
import java . time . LocalDateTime ;
import java . util . List ;
@ Configuration
@ EnableScheduling
public class SchedulerConfiguration {
@ Autowired
private GlucoseRepository glucoseRepository;
@ Autowired
private SimpMessagingTemplate messagingTemplate;
@ Autowired
private GlucoseMapper glucoseMapper;
@ Autowired
private ObjectMapper objectMapper;
@ PostConstruct
public void init () {
fetchData() ; // Run once at startup
}
@ Scheduled (fixedRate = 300000 ) // 5 minutes in milliseconds
public void fetchData () {
LocalDateTime oneDayAgo = LocalDateTime . now () . minusDays ( 1 );
List < Glucose > glucoseData = glucoseRepository . findByTimestampAfter (oneDayAgo);
List < GlucoseDTO > glucoseDTOs = glucoseMapper . toDtoList (glucoseData);
try {
String jsonData = objectMapper . writeValueAsString (glucoseDTOs);
System . out . println ( "Fetched data: " + jsonData);
messagingTemplate . convertAndSend ( "/topic/glucoseData" , jsonData);
} catch ( JsonProcessingException e) {
e . printStackTrace ();
}
}
}
4.2 ObjectMapper Configuration
Copy import com . fasterxml . jackson . databind . ObjectMapper ;
import com . fasterxml . jackson . datatype . jsr310 . JavaTimeModule ;
import org . springframework . context . annotation . Bean ;
import org . springframework . context . annotation . Configuration ;
@ Configuration
public class ObjectMapperConfig {
@ Bean
public ObjectMapper objectMapper () {
ObjectMapper objectMapper = new ObjectMapper() ;
objectMapper . registerModule ( new JavaTimeModule() );
return objectMapper;
}
}
5. Creating Entities, DTOs and Mapper
5.1 Glucose Entity
Copy import javax . persistence . * ;
import java . time . LocalDateTime ;
@ Entity
public class Glucose {
@ Id
@ GeneratedValue (strategy = GenerationType . IDENTITY )
private Long id;
private String sensor;
private String type;
private String data;
private LocalDateTime timestamp;
// Getters and setters
}
5.2 GlucoseDTO Class
Copy import java . time . LocalDateTime ;
public class GlucoseDTO {
private Long id;
private String sensor;
private String type;
private String data;
private LocalDateTime timestamp;
// Getters and setters
}
5.3 Glucose Mapper
Copy
import com . valentine . dto . GlucoseDTO ;
import com . valentine . model . Glucose ;
import org . springframework . stereotype . Component ;
import java . util . List ;
import java . util . stream . Collectors ;
@ Component
public class GlucoseMapper {
public GlucoseDTO toDto ( Glucose glucose) {
GlucoseDTO dto = new GlucoseDTO() ;
dto . setId ( glucose . getId ());
dto . setSensor ( glucose . getSensor ());
dto . setType ( glucose . getType ());
dto . setData ( glucose . getData ());
dto . setTimestamp ( glucose . getTimestamp ());
return dto;
}
public List < GlucoseDTO > toDtoList ( List < Glucose > glucoseList) {
return glucoseList . stream () . map ( this :: toDto) . collect ( Collectors . toList ());
}
}
6. React
6.1 GlucosesDataComponent.js
Copy import React , { useEffect , useState } from 'react' ;
import SockJS from 'sockjs-client' ;
import { Stomp } from '@stomp/stompjs' ;
import { Line } from 'react-chartjs-2' ;
import 'chart.js/auto' ;
import { Container , Typography , Box , Paper } from '@mui/material' ;
import './GlucoseDataComponent.css' ;
const GlucoseDataComponent = () => {
const [ glucoseData , setGlucoseData ] = useState ([]);
useEffect (() => {
const socket = new SockJS ( 'http://localhost:8080/gs-guide-websocket' );
const stompClient = Stomp .over (socket);
stompClient .connect ({} , () => {
stompClient .subscribe ( '/topic/glucoseData' , (message) => {
if ( message .body) {
const newData = JSON .parse ( message .body);
console .log ( 'Received data:' , newData);
setGlucoseData (newData);
}
});
});
return () => {
if (stompClient) {
stompClient .disconnect ();
}
};
} , []);
const data = {
labels : glucoseData .map (data => new Date ( data .timestamp) .toLocaleTimeString ()) ,
datasets : [
{
label : 'Glucose Levels' ,
data : glucoseData .map (data => parseFloat ( data .data)) ,
fill : false ,
borderColor : 'rgba(75,192,192,1)' ,
borderWidth : 2 ,
tension : 0.1
}
]
};
const options = {
responsive : true ,
maintainAspectRatio : false ,
scales : {
x : {
title : {
display : true ,
text : 'Time'
}
} ,
y : {
title : {
display : true ,
text : 'Glucose Level'
}
}
}
};
return (
< Container >
< Box mt = { 4 } mb = { 4 }>
< Typography variant = "h4" component = "h1" gutterBottom color = "primary" >
Glucose Data
</ Typography >
< Paper elevation = { 3 }>
< Box p = { 2 }>
< div className = "chart-container" >
< Line data = {data} options = {options} />
</ div >
</ Box >
</ Paper >
</ Box >
</ Container >
);
};
export default GlucoseDataComponent;
7. Conclusion
In this blog post, we demonstrated how to build a real-time data transmission and visualization system using Spring Boot with WebSocket and STOMP for real-time communication and a Scheduler for periodic data fetching. We also integrated a frontend using React to display the real-time data in a chart.
This setup provides a solid foundation for any real-time application that requires regular data updates and live data presentation.
Feel free to experiment further and expand upon this project to suit your specific needs. Happy coding!
Last updated 4 months ago