1. 테스트 코드 작성
JUnit과 Mockito를 사용하여 유닛 테스트와 통합 테스트 작성
MockMvc : Spring MVC 테스트를 위한 클래스입니다. MockMvc를 사용하면 실제 서블릿 컨테이너를 사용하지 않고도 컨트롤러 테스트를 수행할 수 있습니다.
Mockito : 목 객체를 생성하고 관리하는 데 사용되는 라이브러리입니다. 여기서 @Mock
어노테이션을 사용하여 목 객체를 생성하고, @InjectMocks
를 사용하여 해당 목 객체를 주입합니다.
JUnit : Java용 테스트 프레임워크입니다. @Test
어노테이션을 사용하여 테스트 메서드를 정의합니다.
Copy import org . junit . jupiter . api . Test ;
import org . mockito . InjectMocks ;
import org . mockito . Mock ;
import org . mockito . MockitoAnnotations ;
import org . springframework . http . MediaType ;
import org . springframework . test . web . servlet . MockMvc ;
import org . springframework . test . web . servlet . setup . MockMvcBuilders ;
import java . time . LocalDateTime ;
import java . util . Collections ;
import static org . mockito . Mockito . when ;
import static org . springframework . test . web . servlet . request . MockMvcRequestBuilders . get ;
import static org . springframework . test . web . servlet . result . MockMvcResultMatchers . content ;
import static org . springframework . test . web . servlet . result . MockMvcResultMatchers . status ;
class GlucoseControllerTest {
private MockMvc mockMvc;
@ Mock
private GlucoseService glucoseService;
@ InjectMocks
private GlucoseController glucoseController;
@ Test
void getRecentGlucoseReadings () throws Exception {
// Mockito 초기화
MockitoAnnotations . openMocks ( this );
// MockMvc 설정
this . mockMvc = MockMvcBuilders . standaloneSetup (glucoseController) . build ();
// Mock 데이터 생성
Glucose glucose = new Glucose() ;
glucose . setId ( 1L );
glucose . setSensor ( "Sensor1" );
glucose . setType ( "Random" );
glucose . setData ( "150" );
glucose . setTimestamp ( LocalDateTime . now ());
// Mock 동작 정의
when( glucoseService . getRecentGlucoseReadings()) . thenReturn ( Collections . singletonList (glucose));
// MockMvc를 사용한 테스트 요청 및 검증
mockMvc . perform ( get( "/api/glucose/recent" )
. contentType ( MediaType . APPLICATION_JSON ))
. andExpect ( status() . isOk ())
.andExpect(content().json("[{'id': 1, 'sensor': 'Sensor1', 'type': 'Random', 'data': '150', 'timestamp': '2023-06-21T12:34:56'}]"));
}
}
2. 영속성 전이 (Cascade Types)
영속성 전이 (Cascade Type)는 엔티티 간의 관계를 설정할 때 부모 엔티티의 상태 변화가 자식 엔티티에 전이되도록 설정하는 것입니다. 이를 통해 부모 엔티티를 저장하거나 삭제할 때 자식 엔티티도 함께 처리할 수 있습니다.
CascadeType.PERSIST
: 부모 엔티티가 저장될 때 자식 엔티티도 함께 저장됩니다.
CascadeType.MERGE
: 부모 엔티티가 병합될 때 자식 엔티티도 함께 병합됩니다.
CascadeType.REMOVE
: 부모 엔티티가 삭제될 때 자식 엔티티도 함께 삭제됩니다.
CascadeType.REFRESH
: 부모 엔티티가 새로 고침될 때 자식 엔티티도 함께 새로 고침됩니다.
CascadeType.DETACH
: 부모 엔티티가 분리될 때 자식 엔티티도 함께 분리됩니다.
CascadeType.ALL
: 모든 작업(PERSIST, MERGE, REMOVE, REFRESH, DETACH)이 자식 엔티티에 전파됩니다.
Copy import jakarta . persistence . * ;
import lombok . Getter ;
import lombok . Setter ;
import java . util . Set ;
@ Entity
@ Getter
@ Setter
public class Pet {
@ Id
@ GeneratedValue (strategy = GenerationType . IDENTITY )
private Long id;
private String name;
private int age;
private double weight;
@ ManyToOne
@ JoinColumn (name = "species_id" )
private Species species;
private String photo;
@ ManyToMany (cascade = CascadeType . ALL )
@ JoinTable (
name = "pet_disease" ,
joinColumns = @ JoinColumn (name = "pet_id" ) ,
inverseJoinColumns = @ JoinColumn (name = "disease_id" )
)
private Set < Disease > diseases;
}
3. 롬복 어노테이션 추가
롬복 (Lombok) 라이브러리는 자바에서 보일러플레이트 코드를 줄이는 데 유용합니다. 롬복 어노테이션을 사용하면 getter, setter, toString, equals, hashCode 등의 메서드를 자동으로 생성할 수 있습니다.
롬복 어노테이션 종류:
@Getter
: 모든 필드에 대한 getter 메서드를 생성합니다.
@Setter
: 모든 필드에 대한 setter 메서드를 생성합니다.
@ToString
: 모든 필드를 포함하는 toString 메서드를 생성합니다.
@EqualsAndHashCode
: equals 및 hashCode 메서드를 생성합니다.
@Data
: @Getter
, @Setter
, @ToString
, @EqualsAndHashCode
, @RequiredArgsConstructor
를 한 번에 적용합니다.
4. Redis 캐시 추가
Redis 는 인메모리 데이터 구조 저장소로, 고속의 데이터 읽기 및 쓰기 성능을 제공합니다. Redis를 캐시로 사용하여 데이터베이스 부하를 줄이고 애플리케이션 성능을 개선할 수 있습니다. Docker를 통해 Redis를 설정하고 Spring Boot에서 Redis 캐시를 사용합니다.
예시: Redis 설정 및 캐시 적용
Copy # docker-compose.yml
version : '3.8'
services :
redis :
image : 'redis:latest'
ports :
- '6379:6379'
Copy import org . springframework . cache . annotation . EnableCaching ;
import org . springframework . context . annotation . Bean ;
import org . springframework . context . annotation . Configuration ;
import org . springframework . data . redis . connection . RedisConnectionFactory ;
import org . springframework . data . redis . core . RedisTemplate ;
import org . springframework . data . redis . serializer . GenericJackson2JsonRedisSerializer ;
import org . springframework . data . redis . serializer . StringRedisSerializer ;
@ Configuration
@ EnableCaching
public class RedisConfig {
@ Bean
public RedisTemplate < String , Object > redisTemplate ( RedisConnectionFactory redisConnectionFactory) {
RedisTemplate < String , Object > redisTemplate = new RedisTemplate <>();
redisTemplate . setConnectionFactory (redisConnectionFactory);
redisTemplate . setKeySerializer ( new StringRedisSerializer() );
redisTemplate . setValueSerializer ( new GenericJackson2JsonRedisSerializer() );
return redisTemplate;
}
}
Copy import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . cache . annotation . Cacheable ;
import org . springframework . stereotype . Service ;
import org . springframework . transaction . annotation . Transactional ;
import java . time . LocalDateTime ;
import java . util . List ;
@ Service
public class GlucoseService {
@ Autowired
private GlucoseRepository glucoseRepository;
@ Cacheable (value = "glucoseCache" , key = "'glucoseData'" )
@ Transactional (readOnly = true )
public List < Glucose > getRecentGlucoseReadings () {
LocalDateTime oneDayAgo = LocalDateTime . now () . minusDays ( 1 );
return glucoseRepository . findByTimestampAfter (oneDayAgo);
}
}
5. 트랜잭션 관리
트랜잭션 관리 는 데이터베이스 작업의 일관성을 유지하고 데이터 무결성을 보장하는 데 중요합니다.
Spring의 @Transactional
어노테이션을 사용하여 트랜잭션을 관리할 수 있습니다. 트랜잭션 전파 방식과 격리 수준을 설정하여 데이터 일관성을 유지합니다.
Propagation.REQUIRED
: 현재 트랜잭션이 존재하면 해당 트랜잭션 내에서 실행되고, 존재하지 않으면 새로운 트랜잭션을 생성합니다.
Propagation.REQUIRES_NEW
: 항상 새로운 트랜잭션을 생성합니다.
Isolation.DEFAULT
: 기본 트랜잭션 격리 수준을 사용합니다.
Copy import org . springframework . beans . factory . annotation . Autowired ;
import org . springframework . stereotype . Service ;
import org . springframework . transaction . annotation . Propagation ;
import org . springframework . transaction . annotation . Transactional ;
@ Service
public class PetService {
@ Autowired
private PetRepository petRepository;
@ Transactional (propagation = Propagation . REQUIRED )
public Pet savePet ( Pet pet) {
return petRepository . save (pet);
}
@ Transactional (readOnly = true )
public Pet findById ( Long id) {
return petRepository . findById (id) . orElse ( null );
}
@ Transactional (propagation = Propagation . REQUIRES_NEW )
public void deleteById ( Long id) {
petRepository . deleteById (id);
}
}
Last updated 4 months ago