Spring MVC로 RSS 피드 API 만들기

의존 추가 Spring에서 RSS 지원은 ROME이라는 프레임워크를 기반으로 한다. <dependency> <groupId>com.rometools</groupId> <artifactId>rome</artifactId> <version>1.10.0</version> </dependency> 피드 구현 AbstractRssFeedView 를 구현해야된다. 2가지 메서드를 오버라이드해야된다. buildFeedMetadata는 해당 피드에 대한 정보를 반환하고, buildFeedItems는 피드에 있는 아이템들을 반환한다. public class PostRssView extends AbstractRssFeedView { @Override protected void buildFeedMetadata(Map<String, Object> model, Channel feed, HttpServletRequest request) { feed.setTitle("Baeldung RSS Feed"); feed.setDescription("Learn how to program in Java"); feed.setLink("http://www.baeldung.com"); } @Override protected List<Item> buildFeedItems(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) { Item entryOne = new Item(); entryOne....

2024-09-15 · 1 min · 115 words

Spring MVC 동작 과정

스프링 @MVC 스프링은 DispatcherServlet과 7가지 전략을 기반으로 한 MVC 프레임워크를 제공한다. 애노테이션을 중심으로 한 새로운 MVC의 확장 기능은 @MVC라는 별칭으로도 불린다. 스프링 웹 기술과 스프링 MVC 엔터프라이즈 애플리케이션의 가장 앞단에서 클라이언트 시스템과 연동하는 책임을 맡고 있는 것이 웹 프레젠테이션 계층이다. Java의 웹 프레젠테이션 계층의 기술과 프레임워크는 매우 다양하여 선택의 폭이 넓다. 스프링은 기본적으로 웹 계층과 다른 계층을 깔끔하게 분리해서 스프링 애플리케이션이지만 웹 계층을 다른 기술로 대체하더라도 아무런 문제가 없게 만들 수 있다....

2024-09-15 · 5 min · 856 words

Spring JPA로 논리적 삭제 사용하기

이전에 사용했던 @Where 어노테이션과 @SQLDelete 어노테이션을 사용하면 된다. @Entity @Table(name = "table_product") @SQLDelete(sql = "UPDATE table_product SET deleted = true WHERE id=?") @Where(clause = "deleted=false") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private double price; private boolean deleted = Boolean.FALSE; // setter getter method } @SQLDelete 도 실제로 DB에 쿼리를 보낼 때 적용하게 되므로, 영속성 컨텍스트에 남아 있는 엔티티는 findAll() 같은 메서드로 조회가 되니 유의해야 된다....

2024-09-15 · 1 min · 76 words

Spring Framework에서 멀티 모듈 사용해보기

이 글은 Spring Framework에서 멀티 모듈을 적용하면서 겪은 트러블 슈팅들을 정리한 글이다. 모듈 및 패키지 구조 크게 api, client, core라는 모듈로 나뉘어지고, api와 client는 core 모듈의 의존한 상태다. 이후 소개할 트러블 슈팅들은 api와 core 사이의 관계로 설명하겠다. api 모듈과 core 모듈은 아래의 디렉터리 구조를 가지고 있다. 아래는 main 메서드가 있는 클래스의 코드다. @SpringBootApplication public class HyNoticeApplication { public static void main(String[] args) { SpringApplication.run(HyNoticeApplication.class); } } 트러블 슈팅 Parameter 0 of constructor in hynotice....

2024-09-15 · 1 min · 208 words

Spring data JPA 사용시 @Transactional을 붙이지 않으면 영속성 컨텍스트가 어떻게 동작할까

동작방식을 이해하기 위해서 JpaRepository의 기본 구현체를 확인해볼 필요가 있었다. 기본 구현체인 SimpleJpaRepository 코드를 확인해보니, class 레벨에 @Transactional(readOnly = true)가 설정되어 있고, 쓰기 메서드에 각각 @Transactional이 명시되어 있었다. 따라서, 기본적으로 각 메서드 호출마다 영속성 컨텍스트가 별개로 만들어져서 엔티티가 관리된다. 참고 자료 https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html https://www.baeldung.com/jpa-hibernate-persistence-context

2024-09-15 · 1 min · 42 words

Spring Custom Conditions

배경 config 클래스 중에 Condition 인터페이스의 구현체가 있었는데, 어떤 용도인지 이해하지 못했다. Custom Conditions 정의 조건부로 빈을 등록할 때 @Conditional 애노테이션을 사용할 수 있다. 이 애노테이션에서 사용할 조건을 클래스로 만들 수 있다. class Java8Condition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return JavaVersion.getJavaVersion().equals(JavaVersion.EIGHT); } } 사용 예 @Service @Conditional(Java8Condition.class) public class Java8DependedService { // ... } 참고 자료 https://www.baeldung.com/spring-conditional-annotations

2024-09-15 · 1 min · 63 words

Spring boot는 정적 파일을 어떻게 처리하나

Spring boot의 경우 ResourceHttpRequestHandler 라는 정적 리소스를 처리하는 핸들러가 따로있다. 기본적으로 /static, /public, /resources, /META-INF/resources 디렉토리에 있는 정적 컨텐츠를 처리하고 있다. https://www.baeldung.com/spring-mvc-static-resources https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java

2024-09-15 · 1 min · 23 words

Spring Boot 개발 환경에서 TestContainers 사용하기

목표 TestContainers를 통해 개발 환경에서 Spring 애플리케이션 실행 시 mariaDB 컨테이너를 실행하도록 한다. TestContainers를 통해 이미 mariaDB 컨테이너가 실행 중이라면 재사용하도록 한다. 의존성 추가 일반적으로 TestContainers는 테스트 환경에 사용하지만, Spring 애플리케이션이 dev 프로파일로 실행될 때 사용하는 것이 목표여서 아래와 같이 의존성을 추가한다. mariaDB를 사용하기 위한 JDBC Driver도 함께 의존성 추가한다. dependencies { //... implementation("org.mariadb.jdbc:mariadb-java-client") implementation("org.springframework.boot:spring-boot-testcontainers") implementation("org.testcontainers:mariadb") //... } 컨테이너 자동 실행 Spring Boot 3.1부터 Container 클래스를 Bean 등록하면, 자동으로 컨테이너를 실행해준다....

2024-09-15 · 1 min · 131 words

Spring Boot Web Starter에서 validation 애노테이션 없어짐

배경 Spring Boot에서 @NotBlank를 사용했는데 존재하지 않는 애노테이션으로 인식했다. Spring Boot의 웹 스타터에 validation api를 구현한 하이버네이트 컴포넌트가 존재한다고 알고 있었는데, 의문이었다. 원인 및 해결 Spring Boot 2.3 부터 웹 스타터에서 validation 스타터가 분리되었다. 아래와 같이 직접 의존을 추가해줘야 된다. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> dependencies { ... implementation 'org.springframework.boot:spring-boot-starter-validation' } 참고 자료 https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#validation-starter-no-longer-included-in-web-starters https://stackoverflow.com/questions/48614773/spring-boot-validation-annotations-valid-and-notblank-not-working

2024-09-15 · 1 min · 54 words

Spring Batch 시작하기

목표 Spring Batch의 구조를 이해한다. Spring Batch 사용법을 이해한다. Spring Batch 구조 스프링 배치는 Application, Batch Core, Batch Infrastructure 3가지 컴포넌트를 가진다. Application: 개발자가 작성하는 모든 배치 job과 사용자 정의 코드가 있다. Batch Core: 배치 작업을 시작하고 제어하는 데 필요한 핵심 런타임 클래스가 포함되어 있다. JobLauncher, Job, Step 구현체가 존재한다. Batch Infratstructure: 애플리케이션 개발자와 배치 코어에서 사용하는 클래스들이 포함되어 있다. ItemReader, ItemWriter RetryTemplate 하나의 Job에는 여러 개의 Step이 있고, Step에는 ItemReader, ItemProcessor, ItemWriter를 하나씩 가지고 있다....

2024-09-15 · 3 min · 452 words

Spring AOP 시작하기

목표 AOP가 무엇인지 이해한다. Spring AOP를 사용하는 방법을 이해한다. Spring AOP의 내부 구조를 이해한다. AOP(Aspect-Oriented Programming) Cross-Cutting Concern concern: 기능에 따라 분리한 시스템의 동작 concern의 2가지 종류 core concern: 주요 요구사항에 대한 기능 cross-cutting concern: 보조되는 또는 시스템 전반적인 요구 사항 예시: 로깅, 보안, 데이터 전송 등 aspect: cross-cutting concern을 모듈화한 것 AOP cross-cutting concern을 분리하여 모듈성을 높이는 것을 목표로하는 프로그래밍 패러다임 새로운 동작이 필요하면, 기존 코드에 추가하지 않고 분리된 새로운 코드를 선언할 수 있다....

2024-09-15 · 3 min · 503 words

sort 기준 변경

sort() 함수의 key 파라미터를 통해서 정렬 기준을 변경할 수 있다. 이 파라미터는 람다를 파라미터로 받는다. 람다는 요소를 파라미터로 받고, 우선 순위가 높은 정렬 기준을 앞으로 오도록 하는 튜플을 반환하면 된다. 역순으로 정렬하고 싶다면 -x[0] 와 같이 두면 역순으로 정렬된다. temp.sort(key=lambda x : (x[0], x[1])) # '-'부호를 이용해서 역순으로 가능 좀 더 복잡한 비교 연산이 필요한 경우 함수를 사용해야된다. functools 모듈을 불러와서 functools.cmp_to_key() 의 파라미터로 자신이 커스텀한 함수를 주면 된다. 왼쪽의 값이 먼저 오고싶다면 -1을, 오른쪽 값이 먼저 오고싶다면 1을, 같다면 0을 반환하면된다....

2024-09-15 · 1 min · 122 words

socket io란

socket.io란? WebSocket 등으로 양방향 통신을 가능하게 해주는 라이브러리다. 2가지 주요 기능으로 분류할 수 있다. Node.js 서버를 위한 라이브러리 클라이언트의 JavaScript 라이브러리 socket.io는 JavaScript뿐만아니라 Java, C++, Swift 등의 다양한 언어를 지원해준다. socket.io의 주요 기능 HTTP long-polling fallback: 브라우저가 WebSocket 연결에 실패할 경우 long polling 방식으로 통신을 하도록 만들어준다. Automatic reconnection: heartbeat 매커니즘으로 연결 상태를 확인하고, 연결이 끊겼을 때 자동으로 재연결한다. Broadcasting: 서버에서 연결되어 있는 모든 클라이언트에게 이벤드를 보낼 수 있다. 기본 API 3000번 포트를 사용하는 Node....

2024-09-15 · 1 min · 189 words

Sociable Unit Test vs Integration Test

테스트하고자 하는 객체가 다른 객체에 의존하고 있으면 그것은 단위 테스트가 아닌가? Sociable Unit Test와 Integeration Test는 궁극적으로 테스트의 목표가 다르다. Integration Test는 2개이상의 모듈이 있을 때, 각 모듈의 정확성보다는 제대로 협력하는지를 테스트한다. 따라서 Integration Test는 모든 경우를 모두 테스트할 필요는 없다. Sociable Unit Test는 모든 경우의 수에 제대로 동작하는지 테스트를 한다. 종속되는 대상을 이미 테스트하고 있다면, 굳이 독립적인 테스트를 만들 필요가 없다. 이미 종속되는 테스트를 완료했으며, 만약 너가 TDD를 하고있다면 테스트를 실패하는 원인을 찾기 어렵다는 점도 없을 것이다....

2024-09-15 · 1 min · 81 words

snake case 필드를 camel case로 역직렬화 하기

목표 아래와 같이 snake case로 필드가 존재하는 json을 camel case로 역직렬화 하는 방법이 필요했다. { "first_name": "Jackie", "last_name": "Chan" } 방법1: @JsonProperty 사용 @JsonProperty 어노테이션을 사용하면, 해당 필드와 매핑되는 json 프로퍼티의 이름을 지정할 수 있다. public class UserWithPropertyNames { @JsonProperty("first_name") private String firstName; @JsonProperty("last_name") private String lastName; // standard getters and setters } 방법2: @JsonNaming 사용 @JsonNaming을 클래스 위에 명시하면 현재 클래스의 역직렬화 이름 전략을 설정할 수 있다. 아래와 같이 명시하면 클래스의 필드를 json의 snake case로 찾아서 매핑한다....

2024-09-15 · 1 min · 98 words

setter 메서드로 변경 추적이 되지 않는 문제 해결

Room room = Room.builder() .id(1L) .game(game) .tags(tags) .build(); room.join(user); roomRepository.save(room); 위와 같은 코드가 있는데 roomRepository.getById(1L) 해도 user가 비어있다. @Transactional어노테이션을 붙이니 해결이 됐는데 원인을 모르겠다. 현재 추정: 트랜잭션과 변경 감지 roomRepository.save()를 호출하면 트랜잭션을 기준으로 저장되는 것으로 추정된다. 따라서 위의 코드는 @Transactional 이 없기 때문에 트랜잭션에 모아둔 커밋이 없기 때문에 단순하게 Room Entity 자체만 저장이 되는것같다. (users는 연관관계의 주인이 아니기때문에 저장되지 않는다.) JpaRepository의 코드를 보기로 했다. JpaRepository의 기본 구현체는 SimpleJpaRepository 로 이 부분에 save 메서드가 구현되어 있었다....

2024-09-15 · 1 min · 175 words

Routing

목표 SvelteKit의 라우팅 방법을 이해한다. SvelteKit의 라우팅 SvelteKit은 파일 시스템 기반으로 라우트를 구성한다. 원하는 URL 경로와 일치하는 디렉토리를 코드에 정의하면 된다. src/routes: root 경로 src/routes/: /about 경로의 라우팅 src/routes/blog/[slug]: slug라는 파라미터를 받으면서, /blogs/[slug] 경로의 라우팅 파라미터를 받으려면 동적 라우팅이 필요한데, prerender=true 에서는 사용할 수 없다. SvelteKit이 아닌 https://github.com/jorgegorka/svelte-router 같은 별도의 라우터를 사용해야된다. 모든 route 디렉토리에는 하나 이상의 route 파일이 필요하다. 이는 + prefix로 구분한다. +page, +error, +layout 등 +page +page.svelte 해당 경로의 페이지를 정의한다....

2024-09-15 · 1 min · 142 words

RestAssured로 Rest docs 사용하기

MockMvc로 사용중인 Spring rest docs를 RestAssured로 바꿔보자. 의존 추가 testImplementation 'org.springframework.restdocs:spring-restdocs-restassured' 테스트 코드 private RequestSpecification spec; @BeforeEach public void setUp(RestDocumentationContextProvider restDocumentation) { this.spec = new RequestSpecBuilder() .addFilter(documentationConfiguration(restDocumentation)) .build(); } body 내용을 예쁘게 표시하고 싶다면 아래와 같이 구성하면된다. @BeforeEach protected void setUp(final RestDocumentationContextProvider restDocumentation) throws Exception { this.specification = new RequestSpecBuilder() .addFilter(documentationConfiguration(restDocumentation).operationPreprocessors() .withResponseDefaults(prettyPrint())) .build() ; rest api 호출 RestAssured.given(this.spec) .accept("application/json") .filter(document("index")) .when().get("/") .then().assertThat().statusCode(is(200)); 필드 설명을 추가하고 싶다면 아래와 같이 필터를 지정하면 된다....

2024-09-15 · 1 min · 86 words

REST DOCS가 빌드 시 자동 생성되도록 gradle build 수정하기

의존 추가 없이 구현이 가능했다. asciidoctor에서 최종 문서가 저장되는 위치를 설정하는 방법이 적혀있어서 참고했다. outputDir를 통해 자신이 원히는 경로를 지정할 수 있다. jar 파일을 만들기 전에 build/resources/main/static/docs 경로에 rest docs 파일을 추가하여 수동으로 문서를 커밋하지 않아도 빌드시에 만들어지도록 구현했다. // build.gradle asciidoctor { attributes 'snippets': snippetsDir inputs.dir snippetsDir outputDir "build/resources/main/static/docs" dependsOn test } bootJar { dependsOn asciidoctor } 참고 자료 https://asciidoctor.github.io/asciidoctor-gradle-plugin/development-3.x/user-guide/

2024-09-15 · 1 min · 61 words

ResourceBundleMessageSource 파헤치기

목표 ResourceBundleMessageSource가 다국어 값을 가져오는 과정을 이해한다. ResourceBundleMessageSource에 기본 Locale을 설정하는 방법을 이해한다. ResourceBundleMessageSource Spring Boot에서 다국어를 처리할 때 기본적으로 사용하는 MessageSource 인터페이스의 구현체 액세스한 ResourceBundle 인스턴스와 각 메시지에 대해 생성한 MessageFormat을 캐싱한다. MessageSource Spring에서 만든 메시지를 매개 변수화 및 i18n화 할 때 사용하는 전략 인터페이스 아래 3가지의 메소드가 존재한다. ResourceBundle Java에서 만든 Locale 별로 리소스(예: 문자열)가 필요한 경우 이를 번들로 묶어서 관리해주는 클래스 이를 상속해서 property file로부터 각 Locale 별로 문자열을 관리하는 PropertyResourBundle도 존재한다....

2024-09-15 · 1 min · 161 words