# Java Spring(전자정부프레임워크) 센서 시스템 개발 가이드 - Part 1

## 📋 프로젝트 개요

**프로젝트명**: Java Spring 기반 센서 데이터 수집 및 관리 시스템  
**기술 스택**: Java 11, Spring Boot 2.7.x, 전자정부프레임워크 4.0.x  
**아키텍처**: 계층형 아키텍처 + REST API  
**데이터베이스**: PostgreSQL, Redis  
**상태**: 개발 가이드 작성 완료  

---

## 🏗️ 시스템 아키텍처

### 전체 시스템 구성도
```
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   센서 디바이스   │────│   Java 브리지    │────│   Spring Boot   │
│   (RSNet SDK)   │    │   (RSNet SDK)   │    │   (REST API)    │
│                 │    │   (포트: 8020)   │    │   (포트: 8080)  │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                                                       │
                                                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   웹 대시보드    │◄───│   PostgreSQL    │◄───│   Redis 캐시    │
│   (Thymeleaf)   │    │   (데이터 저장)   │    │   (실시간 캐시)  │
│   (포트: 8080)  │    │   (포트: 5432)   │    │   (포트: 6379)  │
└─────────────────┘    └─────────────────┘    └─────────────────┘
```

### 서비스 구성
| 서비스 | 기술 스택 | 포트 | 역할 | 상태 |
|--------|-----------|------|------|------|
| **sensor-spring-app** | Spring Boot + 전자정부프레임워크 | 8080 | 메인 웹 애플리케이션 | 🔄 개발 중 |
| **sensor-bridge** | Java + RSNet SDK | 8020 | 센서 데이터 브리지 | ✅ 기존 유지 |
| **postgres** | PostgreSQL 15 | 5432 | 메인 데이터베이스 | ✅ 기존 유지 |
| **redis** | Redis 7 | 6379 | 실시간 캐시 | ✅ 기존 유지 |

---

## 📁 프로젝트 구조

### 새로운 Spring 애플리케이션 구조
```
sensor-spring-app/
├── 📁 src/
│   ├── 📁 main/
│   │   ├── 📁 java/
│   │   │   └── 📁 com/sensor/spring/
│   │   │       ├── 📁 config/           # 설정 클래스
│   │   │       ├── 📁 controller/       # REST 컨트롤러
│   │   │       ├── 📁 service/          # 비즈니스 로직
│   │   │       ├── 📁 repository/       # 데이터 접근 계층
│   │   │       ├── 📁 entity/           # JPA 엔티티
│   │   │       ├── 📁 dto/              # 데이터 전송 객체
│   │   │       ├── 📁 exception/        # 예외 처리
│   │   │       ├── 📁 util/             # 유틸리티
│   │   │       └── 📄 SensorSpringApplication.java
│   │   ├── 📁 resources/
│   │   │   ├── 📁 static/               # 정적 리소스
│   │   │   ├── 📁 templates/            # Thymeleaf 템플릿
│   │   │   ├── 📄 application.yml       # Spring 설정
│   │   │   ├── 📄 application-dev.yml   # 개발 환경 설정
│   │   │   └── 📄 application-prod.yml  # 운영 환경 설정
│   │   └── 📁 webapp/                   # 전자정부프레임워크 설정
│   └── 📁 test/                         # 테스트 코드
├── 📄 pom.xml                           # Maven 프로젝트 설정
├── 📄 Dockerfile                        # Docker 이미지 빌드
└── 📄 README.md                         # 프로젝트 문서
```

---

## 🚀 개발 환경 설정

### 1. 필수 요구사항
- **Java**: JDK 11 이상
- **Maven**: 3.6.x 이상
- **IDE**: IntelliJ IDEA, Eclipse, VS Code
- **데이터베이스**: PostgreSQL 15, Redis 7
- **기존 시스템**: sensor-bridge, postgres, redis

### 2. Maven 프로젝트 생성

#### **pom.xml 설정**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId>com.sensor</groupId>
    <artifactId>sensor-spring-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>Sensor Spring Application</name>
    <description>Spring Boot based sensor data management system</description>

    <properties>
        <java.version>11</java.version>
        <egovframework.rte.version>4.0.0</egovframework.rte.version>
        <spring-boot.version>2.7.18</spring-boot.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 전자정부프레임워크 -->
        <dependency>
            <groupId>org.egovframe.rte</groupId>
            <artifactId>org.egovframe.rte.ptl.mvc</artifactId>
            <version>${egovframework.rte.version}</version>
        </dependency>
        
        <dependency>
            <groupId>org.egovframe.rte</groupId>
            <artifactId>org.egovframe.rte.psl.dataaccess</artifactId>
            <version>${egovframework.rte.version}</version>
        </dependency>
        
        <dependency>
            <groupId>org.egovframe.rte</groupId>
            <artifactId>org.egovframe.rte.fdl.idgnr</artifactId>
            <version>${egovframework.rte.version}</version>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>

        <!-- JSON Processing -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <!-- Logging -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>postgresql</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
```

### 3. Spring Boot 메인 클래스

#### **SensorSpringApplication.java**
```java
package com.sensor.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableJpaAuditing
@EnableCaching
@EnableAsync
@EnableScheduling
public class SensorSpringApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(SensorSpringApplication.class, args);
    }
}
```

---

## ⚙️ 설정 파일

### 1. application.yml (메인 설정)
```yaml
spring:
  profiles:
    active: dev
  
  application:
    name: sensor-spring-app
  
  datasource:
    url: jdbc:postgresql://localhost:5432/sensor_db
    username: postgres
    password: password
    driver-class-name: org.postgresql.Driver
    
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
        
  redis:
    host: localhost
    port: 6379
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
    encoding: UTF-8
    mode: HTML

server:
  port: 8080
  servlet:
    context-path: /
    
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always

logging:
  level:
    com.sensor.spring: DEBUG
    org.springframework.web: DEBUG
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
```

### 2. application-dev.yml (개발 환경)
```yaml
spring:
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: create-drop
      
  h2:
    console:
      enabled: true
      
logging:
  level:
    com.sensor.spring: DEBUG
    org.springframework.web: DEBUG
```

### 3. application-prod.yml (운영 환경)
```yaml
spring:
  jpa:
    show-sql: false
    hibernate:
      ddl-auto: validate
      
logging:
  level:
    com.sensor.spring: INFO
    org.springframework.web: WARN
```

---

## 🗄️ 데이터 모델

### 1. JPA 엔티티

#### **SensorReading.java**
```java
package com.sensor.spring.entity;

import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;

@Entity
@Table(name = "sensor_readings")
@EntityListeners(AuditingEntityListener.class)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SensorReading {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotNull
    @Column(name = "device_id", nullable = false)
    private String deviceId;
    
    @Column(name = "node_id")
    private Integer nodeId;
    
    @Column(name = "temperature", precision = 5, scale = 2)
    private Double temperature;
    
    @Column(name = "humidity", precision = 5, scale = 2)
    private Double humidity;
    
    @Column(name = "longitude", precision = 10, scale = 6)
    private Double longitude;
    
    @Column(name = "latitude", precision = 10, scale = 6)
    private Double latitude;
    
    @Column(name = "recorded_time")
    private LocalDateTime recordedTime;
    
    @Column(name = "received_time")
    private LocalDateTime receivedTime;
    
    @CreatedDate
    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;
    
    @PrePersist
    protected void onCreate() {
        if (receivedTime == null) {
            receivedTime = LocalDateTime.now();
        }
    }
}
```

#### **Device.java**
```java
package com.sensor.spring.entity;

import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;

@Entity
@Table(name = "devices")
@EntityListeners(AuditingEntityListener.class)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Device {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotNull
    @Column(name = "device_id", unique = true, nullable = false)
    private String deviceId;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "description")
    private String description;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "status")
    private DeviceStatus status;
    
    @Column(name = "last_seen")
    private LocalDateTime lastSeen;
    
    @CreatedDate
    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;
    
    public enum DeviceStatus {
        ACTIVE, INACTIVE, ERROR, MAINTENANCE
    }
}
```

---

**계속...**
