Real-Time Data Visualization Application

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
  1. Project Overview

  2. Tech Stack

  3. WebSocket and STOMP Configuration

  4. Scheduler Configuration

  5. Creating Entities, DTOs and Mapper

  6. React

  7. Conclusion

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

+-----------------------+    +--------------------+    +-------------------+
|    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)

  • Frontend: React.js

  • 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

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

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

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

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

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

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

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


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

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