• 스프링 데이터 Spring Data
: 기본적인 데이터 저장의 특수한 속성을 유지하면서 데이터에 액세스하는 친숙하고 일관된 스프링 기반 프로그래밍 모델을 제공하는 것
https://spring.io/projects/spring-data
6.1 엔티티 정의
스프링 데이터는 다양한 복잡성(추상성) 수준에서 스프링 부트 애플리케이션이 사용할 수 있는 다양한 메커니즘과 데이터 엑세스 옵션을 제공
1단계) 적용 가능한 데이터를 처리하는 데 사용할 도메인 클래스를 정의
2단계) 데이터 사용 범위, 클라이언트가 사용하는 외부 API, DB 종류를 고려하여 DB, 추상화 수준 정하기
• 도메인 클래스: 그 연관성과 중요성이 다른 데이터와 독립적인 기본 도메인 엔티티
→ 도메인 클래스는 다른 엔티티와 연결되지 않은 때에도 단독으로 존재하고 그 자체로 의미있다는 뜻
보통 이런 작업을 할 때 템플릿, repository 중 하나 사용
*DDD Domain-Driven Design 도메인 주도 설계
*롬복(Lombok): 자바 클래스를 만들때 자주 사용되는 getter/setter나 toString 등의 코드를 어노테이션으로 대체해서 선언하고 java 코드를 컴파일 할 때 그에 맞는 코드를 생성해주는 것이다.
6.2 템플릿 지원
스프링데이터는 다양한 데이터 타입에 Operations 타입의 인터페이스(예: MongoOperations, RedisOperations, CassandraOperations)를 정의함
• Template 클래스에 Operations 인터페이스가 구현됨
• 템플릿은 일종의 SPI(Service Provider Interface 서비스 제공 인터페이스)
- 일반적으로는 매번 반복되는 단계를 거쳐야 함
→ 일반적인 패턴의 데이터 액세스에서는 repository가 더 좋은 옵션이 됨
- repository가 템플릿을 기반으로 하기 때문에 추상화를 높이더라도 잃을게 없음 (?????)
6.3 저장소 지원
• Repository 인터페이스
- Repository 인터페이스로부터 그 외 모든 유형의 스프링데이터 repository 인터페이스가 파생됨
- findAll(), findById(), count(), delete(), deleteAll() 등과 같은 유용한 상위 수준 함수를 지정함
6.5 레디스로 템플릿 기반 서비스 생성하기
• Redis
: 일반적으로 서비스 내 인스턴스 간에 상태를 공유, 캐싱과 서비스 간 메시지를 중개하기 위해 인메모리 repository로 사용하는 데이터베이스
6.5.1 프로젝트 초기화하기
6.5.2 레디스 서비스 개발하기
도메인 클래스 정의하기
package com.thehecklers.sburredis;
import java.time.Instant;
import org.springframework.data.annotation.Id;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown=true)
public class Aircraft {
@Id
private Long id;
private String callsign, squawk, reg, flightno, route, type, category;
private int altitude, heading, speed;
@JsonProperty("vert_rate")
private int vertRate;
@JsonProperty("selected_altitude")
private int selectedAltitude;
private double lat, lon, barometer;
@JsonProperty("polar_distance")
private double polarDistance;
@JsonProperty("polar_bearing")
private double polarBearing;
@JsonProperty("is_adsb")
private boolean isADSB;
@JsonProperty("is_on_ground")
private boolean isOnGround;
@JsonProperty("last_seen_time")
private Instant lastSeenTime;
@JsonProperty("pos_update_time")
private Instant posUpdateTime;
@JsonProperty("bds40_seen_time")
private Instant bds40SeenTime;
public String getLastSeenTime() {
return lastSeenTime.toString();
}
public void setLastSeenTime(String lastSeenTime) {
if(null != lastSeenTime) {
this.lastSeenTime = Instant.parse(lastSeenTime);
} else {
this.lastSeenTime = Instant.ofEpochSecond(0);
}
}
public String getPosUpdateTime() {
return posUpdateTime.toString();
}
public void setPosUpdateTime(String posUpdateTime) {
if(null != posUpdateTime) {
this.posUpdateTime = Instant.parse(posUpdateTime);
} else {
this.posUpdateTime = Instant.ofEpochSecond(0);
}
}
public String getBds40SeenTime() {
return bds40SeenTime.toString();
}
public void setBds40SeenTime(String bds40SeenTime) {
if(null != bds40SeenTime) {
this.bds40SeenTime = Instant.parse(bds40SeenTime);
} else {
this.bds40SeenTime = Instant.ofEpochSecond(0);
}
}
}
- @Data:: 롬복에서 게터, 세터, equals(), hashCode(), toString() 메서드를 생성해 데이터 클래스를 만듦
- @NoArgsConstructor:: 롬복에 매개변수가 없는 생성자를 만들도록 지시
- @AllArgsConstructor:: 롬복에 각 멤버 변수의 매개변수가 있는 생성자를 만들도록 지시하고 모든 멤버 변수에 인수를 제공
- @JsonIgnoreProperties(ignoreUnknown = true):: JSON응답필드 중에서 클래스에 상응하는 멤버 변수가 없는 경우, Jackson 역직렬화 메커니즘이 이를 무시하도록 함
- @Id:: 어노테이션이 달린 멤버 변수가 DB 항목/레코드의 고유식별자를 가지도록 지정
- @JsonProperty(" "):: 한 멤버 변수를 다른 이름이 붙은 JSON 필드와 연결
(+) Instant 클래스 참고 https://sujl95.tistory.com/85
템플릿 지원 추가하기
• RedisTemplate 클래스 - RedisAccessor 클래스를 상속해 RedisOperations 인터페이스를 구현함
- RedisOperations → 레디스가 상호작용하는데 필요한 기능을 지정해줌
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.thehecklers.sburredis.Aircraft;
@SpringBootApplication
public class SburRedisApplication {
@Bean
public RedisOperations<String, Aircraft>
redisOperations(RedisConnectionFactory factory){
Jackson2JsonRedisSerializer<Aircraft> serializer = new Jackson2JsonRedisSerializer<Aircraft>(Aircraft.class);
RedisTemplate<String, Aircraft> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setDefaultSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
return template;
}
public static void main(String[] args) {
SpringApplication.run(SburRedisApplication.class, args);
}
}
SburRedisApplication.java
package com.thehecklers.sburredis;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
@EnableScheduling
@Component
public class PlaneFinderPoller {
private WebClient client =
WebClient.create("http://localhost:7634/aircraft"); //PlaneFinder 서비스에 의해 노풀되는 객체의 엔드포인트를 가리키도록
private final RedisConnectionFactory connectionFactory;
private final RedisOperations<String, Aircraft> redisOperations;
//Redis ConntctionFactory - 레디스 의존성을 추가했으므로 스프링 부트의 '자동설정'에 의해 제공됨
PlaneFinderPoller(RedisConnectionFactory connectionFactory,
RedisOperations<String, Aircraft> redisOperations){
this.connectionFactory = connectionFactory;
this.redisOperations = redisOperations;
}
@Scheduled(fixedRate=1000)
private void pollPlanes() {
connectionFactory.getConnection().serverCommands().flushDb(); //flushDb(): 존재하는 모든 키를 지움
client.get()
.retrieve()
.bodyToFlux(Aircraft.class)
.filter(plane -> !plane.getReg().isEmpty())
.toStream()
.forEach(ac ->
redisOperations.opsForValue().set(ac.getReg(), ac));
redisOperations.opsForValue()
.getOperations()
.keys("*") //모든 키 조회
.forEach(ac ->
System.out.println(redisOperations.opsForValue().get(ac))); //각각의 키로 해당 항공기 값을 조회한 다음 출력
}
}
PlaneFinderPoller.java
6.6 템플릿에서 repository로 변환하기
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.thehecklers.sburredis.Aircraft;
@SpringBootApplication
public class SburRedisApplication {
/*
* @Bean public RedisOperations<String, Aircraft>
* redisOperations(RedisConnectionFactory factory){
* Jackson2JsonRedisSerializer<Aircraft> serializer = new
* Jackson2JsonRedisSerializer<Aircraft>(Aircraft.class);
*
* RedisTemplate<String, Aircraft> template = new RedisTemplate<>();
* template.setConnectionFactory(factory);
* template.setDefaultSerializer(serializer); template.setKeySerializer(new
* StringRedisSerializer());
*
* return template; }
*/
public static void main(String[] args) {
SpringApplication.run(SburRedisApplication.class, args);
}
}
SburRedisApplication.java
package com.thehecklers.sburredis;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
@EnableScheduling
@Component
public class PlaneFinderPoller {
private WebClient client =
WebClient.create("http://localhost:7634/aircraft"); //PlaneFinder 서비스에 의해 노풀되는 객체의 엔드포인트를 가리키도록
private final RedisConnectionFactory connectionFactory;
// private final RedisOperations<String, Aircraft> redisOperations; 1) 멤버변수 제거, 아래로 교체
private final AircraftRepository repository;
//Redis ConntctionFactory - 레디스 의존성을 추가했으므로 스프링 부트의 '자동설정'에 의해 제공됨
/*
* PlaneFinderPoller(RedisConnectionFactory connectionFactory,
* RedisOperations<String, Aircraft> redisOperations){ this.connectionFactory =
* connectionFactory; this.redisOperations = redisOperations; }
*/
PlaneFinderPoller(RedisConnectionFactory connectionFactory, AircraftRepository repository){
this.connectionFactory = connectionFactory;
this.repository = repository;
}
@Scheduled(fixedRate=1000)
private void pollPlanes() {
connectionFactory.getConnection().serverCommands().flushDb(); //flushDb(): 존재하는 모든 키를 지움
client.get()
.retrieve()
.bodyToFlux(Aircraft.class)
.filter(plane -> !plane.getReg().isEmpty())
.toStream()
.forEach(repository::save);
/*.forEach(ac ->
redisOperations.opsForValue().set(ac.getReg(), ac));*/
/*
* redisOperations.opsForValue() .getOperations() .keys("*") //모든 키 조회
* .forEach(ac -> System.out.println(redisOperations.opsForValue().get(ac)));
* //각각의 키로 해당 항공기 값을 조회한 다음 출력
*/
repository.findAll().forEach(System.out::println);
}
}
PlaneFinderPoller.java
package com.thehecklers.sburredis;
import org.springframework.data.repository.CrudRepository;
public interface AircraftRepository extends CrudRepository<Aircraft, Long> {
}
AircraftRepository.java
레디스에 값 추가하는 방법..?
책 불친절..
6.7 JPA로 repository 기반 서비스 만들기
package com.example7;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Aircraft {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String callsign, squawk, reg, flightno, route, type, category;
private int altitude, heading, speed;
@JsonProperty("vert_rate")
private int verRate;
@JsonProperty("selected_altitude")
private int selectedAltitude;
private double lat, lon, barometer;
@JsonProperty("polar_distance")
private double polarDistance;
@JsonProperty("polar_bearing")
private double polarBearing;
@JsonProperty("is_adsb")
private boolean isADSB;
@JsonProperty("is_on_ground")
private boolean isOnGround;
@JsonProperty("last_seen_time")
private Instant lastSeenTime;
@JsonProperty("pos_update_time")
private Instant posUpdateTime;
@JsonProperty("bds40_seen_time")
private Instant bds40SeenTime;
}
package com.example7;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
@EnableScheduling
@Component
@RequiredArgsConstructor
public class PlaneFinderPoller {
@NonNull //final이면 굳이 안붙여도 될듯..
private final AircraftRepository repository;
private WebClient client = WebClient.create("http://localhost:7634/aircraft");
@Scheduled(fixedRate = 1000)
private void pollPlanes(){
repository.deleteAll();
client.get()
.retrieve()
.bodyToFlux(Aircraft.class)
.filter(plane -> !plane.getReg().isEmpty())
.toStream()
.forEach(repository::save);
repository.findAll().forEach(System.out::println);
}
}
PlaneFinderPoller.java
- @GeneratedValue:: 식별자(Identifier)가 기본 데이터베이스 엔진에 의해 생성될 것을 알려줌
- @EnableScheduling:: 스프링 스케줄러를 사용하기 위해서 Spring Boot 실행 파일에 선언해야 함
- @Scheduled:: 스케줄러 설정을 적용할 메소드에 사용
- @RequiredArgsConstructor:: 초기화 되지 않은 final 필드나 @NonNull이 붙은 필드에 대해 생성자를 생성해줌
(+) 스프링 스케줄러 참고.. https://king-ja.tistory.com/81
6장 공부하면서 느낀점 ..
이 책은 spring boot + α 의 지식이 있어야 .. 이해가 된다..
JPA 부분이 있길래 열심히 보려했는데
내용 보면 .. JPA 공부를 따로 하는게 나을것 같아서 (따라해서 도움이 안될것 같음..)
6장은 여기까지 ..
어노테이션들만 알아두면 될듯 ..!!!
'Study > Spring Boot' 카테고리의 다른 글
5. 애플리케이션 설정과 검사 (0) | 2023.09.17 |
---|---|
4. 데이터베이스 액세스 (0) | 2023.09.10 |
3. REST API (0) | 2023.09.03 |