<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yongwan's tech note</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 25 May 2025 08:21:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yongwan's tech note</title>
            <url>https://velog.velcdn.com/images/yongwan_tech/profile/8a1ab146-a22d-4ae0-bc69-9afda6dfc1c0/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yongwan's tech note. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yongwan_tech" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[트러블 슈팅 - 순환 참조(circular references)]]></title>
            <link>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EC%88%9C%ED%99%98-%EC%B0%B8%EC%A1%B0circular-references</link>
            <guid>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EC%88%9C%ED%99%98-%EC%B0%B8%EC%A1%B0circular-references</guid>
            <pubDate>Sun, 25 May 2025 08:21:42 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 마주한 에러 및 버그를 해결하는 과정을 기록하기 위해서 작성되었습니다.</p>
<hr>
<p>&amp;nbsp&amp;nbsp&amp;nbsp &#39;10장. OAuth2로 로그인/로그아웃 구현하기&#39;의 코드를 실행했는데 다음과 같은 컴파일 에러가 뜨며 앱이 실행되지 않았다. </p>
<blockquote>
<p>The dependencies of some of the beans in the application context form a cycle:
┌─────┐
&amp;nbsp&amp;nbsp| &amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp webOAuthSecurityConfig defined in file [C:\Users\...]
↑  &amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp     ↓
&amp;nbsp&amp;nbsp| &amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbspuserService defined in file [C:\Users\...]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.</p>
</blockquote>
<p>&amp;nbsp&amp;nbsp&amp;nbsp <strong>circular references</strong>에 대해 검색해보니, 두 개 이상의 Bean이 서로를 참조하는 <strong>&#39;순환 참조&#39;</strong> 를 의미한다고 한다. 나의 경우에는 webOAuthSecurityConfig가 userService를 참조하고, 동시에 userService가 webOAuthSecurityConfig를 참조하는 상황이 벌어진 것이다. </p>
<p>&amp;nbsp&amp;nbsp&amp;nbsp 순환 참조가 발생했을 때 앱이 실행되지 않는 이유는 의존성을 주입하는 과정에서 무한 루프에 걸려 무한정으로 대기하기 때문이다. A가 B에 의존하는 경우에는 B의 빈이 생성되고 난 다음에 A의 빈이 생성된다. 그런데 A와 B가 동시에 서로 의존하는 경우에는 A와 B가 서로 먼저 생성되기를 무한히 기다리게 된다. </p>
<p>&amp;nbsp&amp;nbsp&amp;nbsp webOAuthSecurityConfig와 userService가 서로 어떻게 참조하고 있는지 확인해보기로 했다.</p>
<p>&amp;nbsp&amp;nbsp&amp;nbsp 다음은 클래스 WebOAuthSecurityConfig의 코드 중 일부이다. 클래스 UserService에 의존하고 있음을 알 수 있다. </p>
<pre><code>@RequiredArgsConstructor
@Configuration
public class WebOAuthSecurityConfig {

    private final OAuth2UserCustomService oAuth2UserCustomService;
    private final TokenProvider tokenProvider;
    private final RefreshTokenRepository refreshTokenRepository;
    private final UserService userService;

// *** 생략 *** //

    @Bean
    public OAuth2SuccessHandler oAuth2SuccessHandler() {
        return new OAuth2SuccessHandler(
                tokenProvider, refreshTokenRepository,
                oAuth2AuthorizationRequestBasedOnCookieRepository(),
                userService
        );
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }</code></pre><p>&amp;nbsp&amp;nbsp&amp;nbsp 다음은 클래스 UserService의 코드이다.</p>
<pre><code>@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public Long save(AddUserRequest request){
        return userRepository.save(User.builder()
                .email(request.getEmail())
                .password(bCryptPasswordEncoder.encode(request.getPassword()))
                .build()).getId();
    }

// *** 생략 *** //

}
</code></pre><p>&amp;nbsp&amp;nbsp&amp;nbsp 클래스 UserService는 클래스 WebOAuthSecurityConfig에 직접적으로 의존하고 있지 않는 듯 보인다. 그렇다면 왜 두 클래스 사이에 순환참조가 발생했던 것일까?</p>
<br>

<p>&amp;nbsp&amp;nbsp&amp;nbsp 두 클래스가 공통적으로 지니고 있는 클래스 BCryptPasswordEncoder에 눈길이 갔다. 클래스 WebOAuthSecurityConfig에서는 BCryptPasswordEncoder의 생성자를 선언했고, 클래스 UserService는 BCryptPasswordEncoder를 참조하고 있다. 혹시 이 클래스를 매개로 하여 두 클래스 간에 순환참조가 발생했던 것이 아닐까? </p>
<p>&amp;nbsp&amp;nbsp&amp;nbsp 그래서 클래스 WebOAuthSecurityConfig에 있는 BCryptPasswordEncoder의 생성자를 없애보고 앱을 실행해보았다. 그랬더니 다음과 같은 컴파일 에러가 발생했다.</p>
<blockquote>
<p>Parameter 1 of constructor in MyCompany.MyBlog.service.UserService required a bean of type &#39;org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder&#39; that could not be found.</p>
</blockquote>
<p>&amp;nbsp&amp;nbsp&amp;nbsp 클래스 UserService에서 참조하고 있는 빈인 BCryptPasswordEncoder을 찾을 수 없다고 한다. 클래스 UserService는 클래스 WebOAuthSecurityConfig에서 생성자를 통해 생성된 빈인 BCryptPasswordEncoder를 참조하고 있었던 것이다. UserService는 BCryptPasswordEncoder를 사용하기 위해 WebOAuthSecurityConfig를 참조해야만 했고, WebOAuthSecurityConfig는 UserService를 명시적으로 참조하고 있는 관계로 두 클래스 사이에 순환참조 관계가 형성되었던 것이다. </p>
<p>&amp;nbsp&amp;nbsp&amp;nbsp 교재의 코드가 올라와있는 깃허브에서는 클래스 UserService를 다음과 같이 수정함으로써 순환참조를 방지했다.</p>
<pre><code>@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;

    public Long save(AddUserRequest request){
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        return userRepository.save(User.builder()
                .email(request.getEmail())
                .password(encoder.encode(request.getPassword()))
                .build()).getId();
    }

// *** 생략 *** //

}</code></pre><p>&amp;nbsp&amp;nbsp&amp;nbsp BCryptPasswordEncoder의 생성자를 save라는 메서드의 내부에서 선언함으로써 WebOAuthSecurityConfig에서 선언한 BCryptPasswordEncoder의 생성자를 참조하지 않는 전략을 사용했다. </p>
<br>

<p>&amp;nbsp&amp;nbsp&amp;nbsp 순환 참조를 방지하기 위한 또다른 아이디어가 떠올라서 한번 실행해보기로 하였다.</p>
<p>&amp;nbsp&amp;nbsp&amp;nbsp 바로 BCryptPasswordEncoder를 WebOAuthSecurityConfig에서 생성하지 않고 별도의 클래스로 빼놓는 것이다. BCryptPasswordEncoder는 패스워드를 암호화하는 기능을 가지고 있다. 이 기능은 회원가입을 할 때 뿐만 아니라 추후에 비밀번호를 변경할때에도 사용될 기능이다. 그래서 이 기능을 담당하는 클래스를 별도로 만들면 순환 참조도 발생하지 않을 것이고 확장성도 용이하다. </p>
<pre><code>@RequiredArgsConstructor
@Component
public class PasswordEncoder {

    public String encode (String password){
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder.encode(password);
    }
}</code></pre><pre><code>@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public Long save(AddUserRequest request){
        return userRepository.save(User.builder()
                .email(request.getEmail())
                .password(passwordEncoder.encode(request.getPassword()))
                .build()).getId();
    }

// *** 생략 *** //

}</code></pre><p>&amp;nbsp&amp;nbsp&amp;nbsp 이와 같은 방식으로 코드를 수정하고 앱을 실행시켰더니 잘 되었다!</p>
<hr>
<p>참고 자료
<a href="https://curiousjinan.tistory.com/entry/spring-circular-references">https://curiousjinan.tistory.com/entry/spring-circular-references</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바 개념 정리 - Optional 클래스]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-Optional-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@yongwan_tech/%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-Optional-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Thu, 22 May 2025 08:32:32 GMT</pubDate>
            <description><![CDATA[<pre><code>publc interface UserRepository extends JapRepository&lt;User, Long&gt; {
    Optional&lt;User&gt; findByEmail(String email);
}</code></pre><p>&nbsp;&nbsp;&nbsp;&nbsp;리포지토리에서 JPA를 이용하여 데이터베이스에 접근하는 코드를 작성할 때에, 메서드가 반환하는 데이터의 타입은 Optional&lt;T&gt;로 지정되어있다. Optional이란 어떤 클래스이며 이 경우에 Optional 타입의 데이터를 반환 받는 이유에 대해 정리하기 위하여 이 포스팅을 작성하였다.</p>
<br>

<blockquote>
</blockquote>
<p>Optional is primarily intended for use as a method return type where there is a clear need to represent &quot;no result,&quot; and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;자바에서 공식으로 제공하는 Optional 타입에 대한 설명은 이와 같다. Optional은 반환(return)하고자 하는 값이 존재하지 않는 경우에 그 사실을 명확하게 표기하여 에러를 일으키지 않기 위한 용도로 사용하는 타입이다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;메서드에서 반환한 값을 참조하려고 했지만 그 값이 존재하지 않는(null) 경우에는 NullPointerException이라는 예외가 발생한다. NullPointerException은 존재하지 않는 값을 참조할 때 발생하는 예외이다. 이 예외가 발생하면 컴파일 에러 문구가 뜨면서 프로그램이 비정상적으로 종료되는 문제가 발생할 수 있다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;이러한 문제를 예방하려는 목적으로 만들어진 타입이 바로 Optional이다. NullPointerException이 자주 발생하리라 예상되는 메서드의 반환값을 받는 변수의 타입을 Optional로 지정하면 NullPointerException을 좀 더 안정적으로 대응할 수 있도록 한다. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;Optional은 null이 올 수 있는 값을 감싸는(wrapper) 클래스이기 때문에 이러한 대응이 가능하다. Optional 타입으로 반환이 된 객체는 원래 객체의 타입 그대로 반환이 되는 것이 아니라 Optional 타입으로 감싸진 채로 반환이 된다. 반환되는 객체가 존재하지 않는 null일 경우에는 null인 채로 그대로 반환이 되는 것이 아니라 Optional에 감싸진 채로 반환이 된다. null인 채로 반환이 되었을 때 그 사실을 모르고 해당 값을 참조하게 되면 NullPointerException이 발생하는 것이다. 하지만 Optional로 감싸진 null은 Optional에 들어있는 메서드들을 통해 별도의 예외 처리를 할 수 있다. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;Optional을 통해 NullPointerException을 안정적으로 대응하는 예시는 다음과 같다. </p>
<pre><code>@RequiredArgsConstructor
@Service
public class UserDetailService implements UserDetailsService{

    private final UserRepository userRepository;

    @Override
    public User loadUserByUsername(String email){
        return userRepository.findByEmail(email).orElseThrow(
        () -&gt; new IlleagalArgumentException(&quot;해당 이메일로 가입한 회원이 존재하지 않습니다.&quot;));
    }
}</code></pre><p>&nbsp;&nbsp;&nbsp;&nbsp;이 포스팅의 맨 처음에 제시한 리포지토리 UserRepository에 등록된 JPA 메서드 findByEmail()로 데이터베이스에 접근하여 데이터를 찾으려고 시도했지만, 매개변수로 입력받은 이메일 주소로 가입된 회원이 데이터베이스에 존재하지 않을 수도 있다. 그런 경우에 findByEmail()은 null을 반환할 것이다. 하지만 findByEmail()의 반환값의 타입은 User가 아니라 Optional&lt;User&gt;이다. Optional은 정상적인 User 객체가 반환되지 않고 null이 반환되었을 때에 어떻게 처리할지에 대한 메서드를 추가로 작성하지 않으면 IDE에서 빨간 밑줄이 생긴다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;이 때 작성한 메서드는 orElseThrow()이다. orElseThrow()는 Optional로 감싸진 반환값이 null일 경우에 NullPointerException 대신에 어떤 예외 처리를 할 지에 대해 설정하는 메서드이다. 위의 코드에서는 IlleagalArgumentException 예외 처리를 함과 동시에 &quot;해당 이메일로 가입한 회원이 존재하지 않습니다.&quot;라는 메시지 로그를 띄우는 것으로 해결했다. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;orElseThrow()대신에 쓸 수 있는 메서드는 다음과 같다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;○ orElse() : 매개변수로 객체를 받는다. 반환값이 null일 경우, null 대신 매개변수에 입력된 객체를 반환한다. 이 메서드의 매개변수에 객체 대신 메서드를 입력할 경우, null값을 반환하는 것과 상관 없이 매개변수로 입력된 메서드가 일단 실행되므로 주의할 것.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;○ orElseGet() : 매개변수로 메서드를 받는다. 반환값이 null일 경우, null을 반환하는 대신 매개변수에 입력된 메서드가 실행된다. 반환값이 null이 아닐 경우, 매개변수에 입력된 메서드는 아예 실행되지 않는다. </p>
<br>

<p>&nbsp;&nbsp;&nbsp;&nbsp;NullPointerException은 자주 발생하는 예외이자 어디에서 발생했는지 발견하기 어려운 예외이다. Optional타입을 적절하게 사용하면 NullPointerException으로 발생할 피해를 미연에 방지할 수 있는 효과를 얻을 수 있다. </p>
<hr>
<p>참고자료</p>
<p><a href="https://mangkyu.tistory.com/70">https://mangkyu.tistory.com/70</a>
<a href="https://mangkyu.tistory.com/203">https://mangkyu.tistory.com/203</a>
<a href="https://www.elancer.co.kr/blog/detail/265">https://www.elancer.co.kr/blog/detail/265</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트러블 슈팅 - Status expected:<201> but was:<404>]]></title>
            <link>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-Status-expected201-but-was404</link>
            <guid>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-Status-expected201-but-was404</guid>
            <pubDate>Tue, 20 May 2025 09:19:41 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 마주한 에러 및 버그를 해결하는 과정을 기록하기 위해서 작성되었습니다.</p>
<hr>
<blockquote>
<p>java.lang.AssertionError: Status expected:&lt;201&gt; but was:&lt;404&gt;
Expected :201
Actual   :404</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yongwan_tech/post/5d168417-302d-4367-ab4c-0b16e3e60b44/image.png" alt=""></p>
<p>&nbsp;&nbsp;&nbsp;&#39;9장. JWT로 로그인/로그아웃 구현하기&#39;에서, 리프레쉬 토큰을 받아서 새로운 엑세스 토큰을 발급하는 API의 테스트 코드를 실행시켰을 때에 마주한 에러였다. 
&nbsp;&nbsp;&nbsp;스테이터스 코드가 404가 나왔다는 것은 존재하지 않는 자원의 url에 접근을 시도했다는 의미다. 그래서 url 주소의 오타를 가장 먼저 확인했다.</p>
<h4 id="가설-1-컨트롤러에-적은-url과-테스트코드에서-적은-url이-서로-일치하지-않는다">가설 1. 컨트롤러에 적은 url과 테스트코드에서 적은 url이 서로 일치하지 않는다.</h4>
<p>&nbsp;&nbsp;&nbsp;컨트롤러(TokenApiController.java)에 적힌 url은 &quot;/api/token&quot;이었다. </p>
<pre><code>@RequiredArgsConstructor
@RestController
public class TokenApiController {

    private final TokenService tokenService;

    @PostMapping(&quot;/api/token&quot;)
    public ResponseEntity&lt;CreateAccessTokenResponse&gt; createNewAccessToken(
            @RequestBody CreateAccessTokenRequest request) {
        String newAccessToken = tokenService.createNewAccessToken(request.getRefreshToken());
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(new CreateAccessTokenResponse(newAccessToken));
    }
}</code></pre><p>&nbsp;&nbsp;&nbsp;테스트코드(TokenApiController.java)에 적힌 url 역시 &quot;/api/token&quot;으로 컨트롤러의 url과 일치했다. </p>
<pre><code>@SpringBootTest
@AutoConfigureMockMvc
public class TokenApiController {
   // 생략 //

    @DisplayName(&quot;createNewAccessToken: 새로운 엑세스 토큰을 발급한다.&quot;)
    @Test
    public void createdNewaccessToken() throws Exception{
        // given
        final String url = &quot;/api/token&quot;;

        User testUser = userRepository.save(User.builder()
                .email(&quot;user@gmail.com&quot;)
                .password(&quot;test&quot;)
                .build());
   // 생략 //

}</code></pre><p>&nbsp;&nbsp;&nbsp;따라서 가설 1은 에러의 원인이 아니었다.</p>
<h4 id="가설-2-스프링-시큐리티의-설정으로-인해-인증-및-인가가-필요한-url에-접근할-수-없게-되었다">가설 2. 스프링 시큐리티의 설정으로 인해 인증 및 인가가 필요한 url에 접근할 수 없게 되었다.</h4>
<p>&nbsp;&nbsp;&nbsp;WebSecurityConfig.java에서 별도의 인증 및 인가 없이 접근할 수 있는 url들과 인증을 해야만 접근할 수 있는 url들을 설정해주었다. 그래서 로그인과 회원가입 따위의 url을 제외한 모든 url은 테스트 환경의 모킹된 MVC구조에서조차 접근할 수 없게 된 것이 아닐까 하는 생각이들었다. 
&nbsp;&nbsp;&nbsp;그래서 이전에 작성한 블로그 글을 작성하고 수정하는 url에 접속하여 테스트를 돌리는 테스트 코드(BlogApiControllerTest.java)를 다시 실행시켜보았다. 이 가설이 맞다면 이 테스트 코드에서도 통과가 되지 않아야 할 것이다.</p>
<p><img src="https://velog.velcdn.com/images/yongwan_tech/post/fed4e26f-2eeb-4746-9dbd-2b1ca520124e/image.png" alt=""></p>
<p>&nbsp;&nbsp;&nbsp;이 테스트 코드의 모든 테스트가 무사히 통과가 된 것을 보아하니, 스프링 시큐리티의 설정 때문은 아닌 것으로 보인다. 가설 2는 에러의 원인이 아니었다.</p>
<br>

<p>&nbsp;&nbsp;&nbsp;이 두가지 가설 외에 내가 떠올릴 수 있는 가설은 더이상 없었다. 하염없이 코드만을 쳐다보고 있던 도중, 문뜩 에러가 난 테스트 코드의 클래스 이름에 노란색 밑줄이 쳐져 있는 것이 눈에 들어왔다.</p>
<p><img src="https://velog.velcdn.com/images/yongwan_tech/post/f0dfa77a-e5c8-45b5-9882-a949a5a22c54/image.png" alt=""></p>
<blockquote>
<p>Test class name &#39;TokenApiController&#39; doesn&#39;t match regex &#39;[A-Z][A-Za-z\d]*Test(s|Case)?|Test[A-Z][A-Za-z\d]*|IT(.*)|(.*)IT(Case)?&#39;</p>
</blockquote>
<p>&nbsp;&nbsp;&nbsp;TokenApiController라는 클래스 명이 이러한 정규식과 일치하지 않는다는 의미인 듯 보였다. 그러고보니 다른 테스트 코드를 돌리기 위한 클래스의 이름은 모두 &quot;~~Test&quot;로 끝났다. 그런데 이 클래스는 그렇지 않았다. 
&nbsp;&nbsp;&nbsp;그래서 클래스 이름을 TokenApiControllerTest로 변경하고 다시 테스트를 돌려보았다. </p>
<p><img src="https://velog.velcdn.com/images/yongwan_tech/post/3dedaae5-ceb3-4311-9a2c-890f4a5c6213/image.png" alt=""></p>
<p>&nbsp;&nbsp;&nbsp;클래스 이름만 바꿔주었을 뿐인데 테스트가 통과되었다! </p>
<p>&nbsp;&nbsp;&nbsp;아무래도 스프링 부트는 많은 기능들이 자동으로 동작한다. 클래스와 변수들을 자동으로 감지해서 동작하기 위해서는 클래스와 변수 이름들을 규칙에 맞게 꼼꼼히 지어야 할 것이다. 테스트 코드의 클래스 이름도 이 경우에 속할 줄은 전혀 몰랐다. 다음부터는 이름을 지을 때에 좀 더 주의해야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트러블 슈팅 - Column 'id' is duplicated in mapping for entity ]]></title>
            <link>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-Column-id-is-duplicated-in-mapping-for-entity</link>
            <guid>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-Column-id-is-duplicated-in-mapping-for-entity</guid>
            <pubDate>Mon, 19 May 2025 03:40:19 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 마주한 에러 및 버그를 해결하는 과정을 기록하기 위해서 작성되었습니다.</p>
<br>

<p>&nbsp;&nbsp;&nbsp;&#39;8장. 스프링 시큐리티로 로그인/로그아웃, 회원가입 구현하기&#39;에서 로그인, 로그아웃 기능을 구현하는 중, 다음과 같은 컴파일 에러를 마주하게 되었다.</p>
<blockquote>
<p>org.springframework.beans.factory.BeanCreationException: Error creating bean with name &#39;entityManagerFactory&#39; defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: <strong>Column &#39;id&#39; is duplicated in mapping for entity &#39;MyCompany.MyBlog.domain.User&#39;</strong> (use &#39;@Column(insertable=false, updatable=false)&#39; <strong>when mapping multiple properties to the same column</strong>)</p>
</blockquote>
<p>&nbsp;&nbsp;&nbsp;회원의 정보가 담긴 엔티티 User를 생성할 때에 id 행이 중복되었고, 같은 행에 다수의 속성이 매핑되었다는 의미 같았다. 엔티티 User의 코드를 다시 한번 살펴보았다. </p>
<h4 id="가설-1-한-엔티티에서-id라고-이름-붙여진-곳이-두-곳-이상이다">가설 1. 한 엔티티에서 id라고 이름 붙여진 곳이 두 곳 이상이다.</h4>
<p>&nbsp;&nbsp;&nbsp;코드를 다시 살펴보니, 참 어이 없는 실수였다.</p>
<pre><code>@Table(name=&quot;users&quot;)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, updatable = false)
    private Long id;

    @Column(name = &quot;id&quot;, nullable = false, unique = true)
    private String email;

    @Column(name = &quot;password&quot;)
    private String password;

    @Builder
    public User (String email, String password, String auth){
        this.email = email;
        this.password = password;
    }

// ... 생략 ... //

}
</code></pre><p>&nbsp;&nbsp;&nbsp;id와 이메일 정보가 담긴 행 모두 이름이 id로 되어있었다. 이름은 같은데 속성이 다른 행을 만드려고 하니 컴파일 에러가 발생했던 것이다. 
&nbsp;&nbsp;&nbsp;다음과 같이 코드를 수정했다.</p>
<pre><code>@Table(name=&quot;users&quot;)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, updatable = false)
    private Long id;

    @Column(name = &quot;email&quot;, nullable = false, unique = true)
    private String email;

    @Column(name = &quot;password&quot;)
    private String password;

    @Builder
    public User (String email, String password, String auth){
        this.email = email;
        this.password = password;
    }

// ... 생략 ... //

}</code></pre><p>&nbsp;&nbsp;&nbsp;코드를 수정하고 나니 컴파일 에러 없이 서버가 정상적으로 실행되었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 개념 정리7 - MVC모델, DTO]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC7-MVC%EB%AA%A8%EB%8D%B8-DTO</link>
            <guid>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC7-MVC%EB%AA%A8%EB%8D%B8-DTO</guid>
            <pubDate>Sun, 18 May 2025 10:03:48 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 핵심 개념들을 정리하기 위해 작성하였습니다.</p>
<h2 id="7장-블로그-화면-구성하기">7장. 블로그 화면 구성하기</h2>
<p>&nbsp;&nbsp;&nbsp;백엔드의 계층은 프레젠테이션 계층(컨트롤러), 비즈니스 계층(서비스), 퍼시스턴스 계층(리포지토리)로 나뉘어서 볼 수 있다. 하지만 이러한 3계층 구조는 서버 내부 로직에 한정된 설계 원칙이다. 이번 장에서는 타임리프를 이용하여 사용자가 실제로 이용하는 화면인 UI까지 구현하려고 한다. 프론트엔드를 포함한 프로그램 전체적인 관점에서 봤을 때에는 MVC 모델로 계층을 나누는 것이 더 적절하다.</p>
<p><strong>□ MVC 모델</strong>
&nbsp;&nbsp;&nbsp;MVC 모델은 한 프로그램의 전체 구조를 <span style='background-color:yellow'>Model, View, Controller</span>라는 세가지 계층으로 나누어서 설명하는 모델이다. 이렇게 나눈 관점으로 프로그램을 보면 <span style='background-color:yellow'>보다 UI와 프론트엔드 중심으로</span> 프로그램의 구조를 이해할 수 있다. </p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ View</strong>
&nbsp;&nbsp;&nbsp; 사용자의 입력과 결과 출력을 담당하는 계층이다. 이 계층에서 사용자는 데이터를 요청할 수 있고 요청받은 데이터를 확인할 수 있다. 다시 말해 사용자에게 직접적으로 보여지는 화면을 의미한다. 타임리프로 구현하고자 하는 것이 바로 View 계층이다. </p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ Model</strong>
&nbsp;&nbsp;&nbsp; 데이터를 처리하는 모든 시스템과 로직을 담당하는 계층이다.</p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ Controller</strong>
&nbsp;&nbsp;&nbsp; View와 Model을 서로 연결시키는 계층이다. View에서 받은 요청은 Controller를 통해 Model로 전달되고, Model에서 내놓은 결과값은 Contorller를 통해 View로 전달되어 사용자에게 보여진다. </p>
<br>

<p>&nbsp;&nbsp;&nbsp;서버가 클라이언트에게 전달하는 정보는 여러가지가 있다. 이 때 중요한 것은 클라이언트가 필요로 하는 정보만 줘야 하며, 클라이언트에게 보여지거나 외부에 노출되면 곤란한 정보에 대해서는 감춰야 한다는 점이다. 그렇다고 해서 모든 경우에 대응하는 엔티티(Entity)를 만들기에는 비효율적이다. 그래서 등장한 개념이 <span style='background-color:yellow'>DTO</span>이다.</p>
<p><strong>□ DTO (Data Transfer Object)</strong>
&nbsp;&nbsp;&nbsp; DTO란 오로지 <span style='background-color:yellow'>데이터 전송을 목적으로만 설계된 객체</span>를 의미한다. DTO에는 엔티티가 담고 있는 정보들 중에서 전송에 필요한 데이터들만 담고 있으며 전달되어서는 안되는 정보들은 담지 않고 있다. 정보를 전달할 때에는 엔티티 자체를 전달하는 것이 아니라 이렇게해서 생성된 DTO를 보내면 된다. 
&nbsp;&nbsp;&nbsp; DTO를 사용했을 때의 장점은 다음과 같다.</p>
<p>&nbsp;&nbsp;&nbsp;○ 보안이 향상된다.
&nbsp;&nbsp;&nbsp; 민감한 정보를 담고 있는 엔티티의 경우, 민감한 정보만 빼고 필요한 정보만 보낼 수 있기 때문이다. 또한 엔티티 전체를 공개하게 되면 비즈니스 로직이 유출될 수 있는데, 대신 DTO를 보내면 그러한 위험을 방지할 수 있다.</p>
<p>&nbsp;&nbsp;&nbsp;○ API 설계를 유연하게 할 수 있다.
&nbsp;&nbsp;&nbsp; 한 개의 엔티티로도 여러가지 응답에 대응할 수 있다. 여러개의 엔티티를 만드는 대신 여러개의 DTO를 만들면 되기 때문이다.</p>
<p>&nbsp;&nbsp;&nbsp;○ 유지 보수가 쉬워진다.
&nbsp;&nbsp;&nbsp; 엔티티가 변경되어도 전체 코드에 큰 영향을 미치지 않는다.</p>
<p>&nbsp;&nbsp;&nbsp;○ 순환 참조 문제를 예방할 수 있다.
&nbsp;&nbsp;&nbsp; DTO와 엔티티는 양방향으로 참조하지 않는다. 그래서 JSON 직렬화가 무한히 벌어지는 순환 참조가 발생하는 것을 막을 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트러블 슈팅 - 게시글 생성 날짜가 표시되지 않을 때]]></title>
            <link>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%83%9D%EC%84%B1-%EB%82%A0%EC%A7%9C%EA%B0%80-%ED%91%9C%EC%8B%9C%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@yongwan_tech/%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EA%B2%8C%EC%8B%9C%EA%B8%80-%EC%83%9D%EC%84%B1-%EB%82%A0%EC%A7%9C%EA%B0%80-%ED%91%9C%EC%8B%9C%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Sun, 18 May 2025 08:13:46 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 마주한 에러 및 버그를 해결하는 과정을 기록하기 위해서 작성되었습니다.</p>
<br>

<p>&nbsp;&nbsp;&nbsp;&#39;7장. 블로그 화면 구성하기&#39;에서 생성한 게시글을 타임리프라는 뷰 컨트롤러에 표시하는 작업을 하고 있을 때였다. 게시글은 성공적으로 생성되었지만, 게시글을 생성한 시각이 &#39;Posted on null&#39;로 표기되는 버그가 발생하였다.</p>
<p><img src="https://velog.velcdn.com/images/yongwan_tech/post/e0bf2943-44e2-477c-b613-f234532cc7e4/image.png" alt=""></p>
<h4 id="가설-1-타임리프에서-게시글-생성-시각-데이터를-불러오지-못했다">가설 1. 타임리프에서 게시글 생성 시각 데이터를 불러오지 못했다.</h4>
<p>&nbsp;&nbsp;&nbsp;우선 게시글 생성 시간 데이터가 데이터베이스에 저장이 제대로 되어있는지부터 확인해보기로 했다. 결론부터 말하자면 이 가설은 버그의 원인으로 옳지 않았다.</p>
<p><img src="https://velog.velcdn.com/images/yongwan_tech/post/d3216201-4088-4467-852e-bd038030ff4e/image.png" alt=""></p>
<p>&nbsp;&nbsp;&nbsp;1, 2, 3번 id의 게시글은 SQL문을 이용해 앱을 실행할 때부터 자동으로 생성되어있던 게시글들이다. 4번 id의 게시글이 앱을 실행하고 나서 내가 직접 생성한 게시글이다. SQL문으로 게시글을 생성할 때에는 생성 날짜가 잘 입력되었지만, 앱에서 게시글을 생성할 때에는 생성 날짜가 데이터베이스에 아예 입력되어있지 않았다. 타임리프에서 생성 날짜를 불러오지 못한 것이 아니라 애초에 생성 날짜가 데이터베이스에 저장되어있지 않았던 것이다. </p>
<br>

<h4 id="가설-2-게시글-엔티티를-데이터베이스에-저장하는-로직에서-게시글-생성-시각-데이터가-누락되었다">가설 2. 게시글 엔티티를 데이터베이스에 저장하는 로직에서 게시글 생성 시각 데이터가 누락되었다.</h4>
<p>&nbsp;&nbsp;&nbsp;게시글 엔티티 코드를 살펴보았다.</p>
<pre><code>@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, updatable = false)
    private Long id;

    @Column(name = &quot;title&quot;, nullable = false)
    private String title;

    @Column(name = &quot;Content&quot;, nullable = false)
    private String content;

    @CreatedDate
    @Column(name = &quot;created_at&quot;)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;

    @Builder
    public Article(String title, String content){
        this.title = title;
        this.content = content;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}</code></pre><p>&nbsp;&nbsp;&nbsp;엔티티가 생성될 때 생성 시간을 저장하는 어노테이션 @CreateDate와 엔티티가 수정될 때 수정 시간을 저장하는 어노테이션 @LastModifiedDate를 모두 제대로 입력하였다. 엔티티가 생성되거나 수정될 때에는 생성 시간 정보를 담은 필드값 createdAt과 수정 시간 정보를 담은 필드값 updatedAt이 자동으로 입력되어야 할 것이다. </p>
<pre><code>@EnableJpaAuditing
@SpringBootApplication
public class MyBlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBlogApplication.class, args);
    }

}</code></pre><p>&nbsp;&nbsp;&nbsp;애플리케이션을 수행하는 메인 클래스에도 어노테이션 @EnableJpaAuditing을 작성해줌으로써 @CreateDate과 @LastModifiedDate이 동작할 수 있게 해주었다. 하지만 필드값 createdAt과 updatedAt에는 아무런 데이터도 입력되지 않은 채 엔티티가 생성되고 데이터베이스로 넘겨진 것을 보아하니, 어떤 이유에서인지 어노테이션 @CreateDate과 @LastModifiedDate이 동작하지 않은 모양이다. 
&nbsp;&nbsp;&nbsp;그래서 엔티티의 생성자와 엔티티를 수정하는 메서드에 createdAt과 updatedAt의 입력값을 롬복 에너테이션이 아니라 직접 작성한 코드를 통해서 입력받는 것으로 수정했다. </p>
<pre><code>@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {

// ... 생략 ... //

    @Builder
    public Article(String title, String content){
        this.title = title;
        this.content = content;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
        this.updatedAt = LocalDateTime.now();
    }
}
</code></pre><p>&nbsp;&nbsp;&nbsp;코드를 수정한 다음 앱을 다시 실행시키고 새로운 글을 작성해보았다.
<img src="https://velog.velcdn.com/images/yongwan_tech/post/598f993d-fe0a-47da-9944-cc198c402fb2/image.png" alt=""></p>
<p>&nbsp;&nbsp;&nbsp;생성 시각이 제대로 표시되었다.</p>
<p><img src="https://velog.velcdn.com/images/yongwan_tech/post/6ed9580e-0ca7-4518-89b9-e82b28fb2a22/image.png" alt=""></p>
<p>&nbsp;&nbsp;&nbsp;데이터베이스에도 생성 시각과 수정 시각이 제대로 저장된 것을 확인할 수 있었다.</p>
<br>

<hr>
<br>

<p>&nbsp;&nbsp;&nbsp;그렇다면 롬복 에너테이션 @CreateDate와 @LastModifiedDate이 동작하지 않았던 이유는 무엇이었을까? 혹시라도 내가 미처 작성하지 못한 코드가 있지 않은지 살펴보기 위해 이 교재의 코드들이 모두 올라와있는 깃허브에서 코드를 천천히 살펴보았다. 
&nbsp;&nbsp;&nbsp; 그런데 게시글 엔티티 코드에서 교재에서는 보지 못했던 코드를 발견했다. 바로 어노테이션 <strong>@EntityListeners(AuditingEntityListener.class)</strong>이었다. </p>
<pre><code>@EntityListeners(AuditingEntityListener.class)
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;, updatable = false)
    private Long id;

    @Column(name = &quot;title&quot;, nullable = false)
    private String title;

    @Column(name = &quot;content&quot;, nullable = false)
    private String content;

    @CreatedDate
    @Column(name = &quot;created_at&quot;)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;


    @Builder
    public Article(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}</code></pre><p>&nbsp;&nbsp;&nbsp;@CreateDate와 @LastModifiedDate이 동작하기 위해서는 앱을 구동시키는 메인 클래스에 @EnableJpaAuditing을 작성해주는 것과 동시에 엔티티 클래스에서도 @EntityListeners(AuditingEntityListener.class)를 작성해줘야 했던 것이다.
&nbsp;&nbsp;&nbsp;과연 이 어노테이션의 정체는 무엇인지 알아보았다.</p>
<p>&nbsp;&nbsp;&nbsp;@EntityListeners란, 엔티티의 생성·수정·소멸 주기 이벤트를 자동으로 감지해서 이벤트가 발생하기 전후에 특정한 기능을 자동으로 수행하게 하는 어노테이션이다. 주로 생성일과 수정일을 자동으로 설정하거나, 삭제 전에 데이터를 백업하는 등의 역할을 수행할 때에 해당 어노테이션을 사용한다.
&nbsp;&nbsp;&nbsp;위의 코드의 경우에는 어노테이션의 소괄호 안에 AuditingEntityListener.class가 들어있다. 이 클래스는 Audting 기술을 지원하는 클래스이다. Auditing 기술이란 시간과 관련해서 자동으로 값을 넣어주는 기술을 의미한다. @EntityListeners(AuditingEntityListener.class)의 의미를 정리하면, 시간과 관련된 값을 자동으로 넣어주는 기능을 엔티티의 생명주기마다 자동으로 실행시켜달라는 의미인 것이다.</p>
<br>
참고 자료
https://webcoding-start.tistory.com/53
Chat GPT




]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 개념 정리6 - API, RESTful API]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC6-API-RESTful-API-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC6-API-RESTful-API-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Sat, 17 May 2025 03:21:24 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 핵심 개념들을 정리하기 위해 작성하였습니다.</p>
<h2 id="6장-블로그-기획하고-api-만들기">6장. 블로그 기획하고 API 만들기</h2>
<p>&nbsp;&nbsp;&nbsp;클라이언트와 서버가 서로 데이터를 주고받을 수 있는 이유는 <span style="background-color:yellow">API</span>를 통해서 서로 통신하고 있기 때문이다. 마찬가지로 서로 다른 프로그램끼리 데이터를 주고받을때에도 API가 사용된다. 이처럼 API는 서비스를 제공하기 위해서 반드시 필요하다. </p>
<p><strong>□ API (Application Programming Interface)</strong></p>
<p>&nbsp;&nbsp;&nbsp;API는 <span style="background-color:yellow">서로 다른 어플리케이션들끼리 데이터를 주고 받을 수 있게 해주는 통신 규칙</span>을 의미한다. 클라이언트가 서버에 데이터를 요청하는 것, 그리고 서버에서 클라이언트에 데이터를 제공하는 것, 그리고 서로 다른 프로그램끼리 데이터를 주고 받는 것 모두 API를 통해서 이루어진다. </p>
<p><strong>□ RESTful API</strong>
&nbsp;&nbsp;&nbsp;RESTful API는 REST 원칙에 따라서 설계된 API를 의미한다. REST는 Representational State Transfer의 약자로, <span style="background-color:yellow">자원을 이름으로 구분하여 주고받는 스타일</span>을 의미한다. </p>
<p>&nbsp;&nbsp;&nbsp;REST의 핵심 원칙은 다음과 같다. </p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ 자원(Resource) 기반 설계</strong>
&nbsp;&nbsp;&nbsp; API는 각각의 자원(데이터)이 어디에 저장되어있는지 나타내는 URL(Uniform Resource Locator)을 중심으로 통신을 한다. REST한 URL은 <span style="background-color:yellow">데이터를 나타내는 경로 또는 자원의 이름만으로</span> 구성되어야 한다. 예를 들면 다음과 같다.</p>
<div> &nbsp;&nbsp;&nbsp; RESTful함 : /users/11 </div>
<div> &nbsp;&nbsp;&nbsp; RESTful하지 않음 : /getUserInfo/11 </div>
&nbsp;&nbsp;&nbsp; 두번째 API는 데이터를 나타내는 경로 또는 데이터의 이름 외에 동사가 들어가 있으므로 REST하지 않다. 

<p>&nbsp;&nbsp;&nbsp; 자원 기반 설계를 했을 때의 장점은 다음과 같다.</p>
<p>&nbsp;&nbsp;&nbsp; 1. 경로와 메서드만 보고도 API의 역할을 유추할 수 있다. 가독성이 높아진다.
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2. 일관성 있게 개발을 할 수 있다. 동사를 허용하게 되면 같은 역할을 하는 API라도 개발자마다 서로 다른 이름을 붙여 혼동을 주는 상황이 발생한다. 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3. 확장성과 재사용성이 높다. 나중에 기능이 추가되거나 변경되었을 때 URL을 크게 바꾸지 않아도 된다. 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;4. 문서 자동화 도구를 사용할 때에 용이하다. Swagger, OpenAPI 등 API의 기능을 문서로 정리해주는 프로그램들은 REST한 API들을 더 잘 분석해주고 정리해준다. </p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ HTTP 메서드(Method) 활용</strong>
&nbsp;&nbsp;&nbsp; 자원에 접근할 때에는 HTTP 메서드를 사용하여 접근하여야 한다. 자주 사용되는 HTTP 메서드의 종류는 다음과 같다.</p>
<p>&nbsp;&nbsp;&nbsp; <strong>1. GET</strong>
&nbsp;&nbsp;&nbsp;&nbsp; 자원을 <span style="background-color:yellow">조회</span>할 때에 주로 사용되는 메서드이다. 바디 없이 쿼리 파라미터 또는 경로를 통해서만 요청한다. <span style="background-color:yellow">캐시</span> 기능이 담겨있기 때문에 GET 메서드로 자원을 조회하는 것이 성능면으로 유리하다.
&nbsp;&nbsp;&nbsp;&nbsp; GET 메서드로도 데이터를 전송할 수 있지만 이는 금기시된다. <span style="background-color:yellow">대부분의 프로그램은 GET 메서드가 요청될 경우 서버에 아무런 변화가 발생하지 않는다고 전제</span>하고 있기 때문이다. 한번의 클릭 만으로도 서버에 변화가 생긴다면 이는 안전한 요청이라고 볼 수 없다.</p>
<p>&nbsp;&nbsp;&nbsp; <strong>2. POST</strong>
&nbsp;&nbsp;&nbsp;&nbsp; 서버에 새로운 자원을 <span style="background-color:yellow">생성</span>하기 위해서 주로 사용되는 메서드이다. 생성할 자원을 <span style="background-color:yellow">바디(body)</span>에 담아서 JSON 형식으로 요청한다. 생성된 자원에 대해서는 식별자(ID)가 발급된다. 주로 /users, /articles와 같은 컬렉션 자원에 대해서 요청된다. 
&nbsp;&nbsp;&nbsp;&nbsp; POST 메서드로도 데이터를 조회할 수 있지만 이 역시 금기시된다. 무슨 데이터를 조회했는지 URL로 확인할 수 없기 때문에 불편함이 생긴다. POST 메서드의 바디에 담긴 데이터가 서버에 변화를 줄지도 모른다는 위험도 있다. 무엇보다도 POST 메서드에는 캐시 기능이 없기 때문에 조회 목적으로 POST 메서드를 사용할 경우에 성능이 저하된다. </p>
<p>&nbsp;&nbsp;&nbsp; <strong>3. PUT</strong>
&nbsp;&nbsp;&nbsp;&nbsp; 리소스를 <span style="background-color:yellow">통째로 변경</span>하기 위해서 사용되는 메서드이다. 변경하고자 하는 자원의 <span style="background-color:yellow">모든 필드</span>가 바디(body)에 JSON 형식으로 담겨있어야 한다. 그렇지 않으면 누락된 필드의 내용이 PUT 메서드를 실행할 시에 사라질 수 있기에 주의해야 한다. 한편 PUT 메서드는 같은 요청을 여러번 보내도 결과가 같은 <span style="background-color:yellow">멱등성</span>이라는 특징을 지니고 있다. 사용자 정보를 전체 수정하거나 설정 초기화를 할 때에 주로 사용된다. </p>
<p>&nbsp;&nbsp;&nbsp; <strong>4. PATCH</strong>
&nbsp;&nbsp;&nbsp;&nbsp; 리소스를 <span style="background-color:yellow">일부분만 변경</span>하기 위해서 사용되는 메서드이다. 변경하고자 하는 자원의 일부분의 필드만 변경하고자 할 때 주로 사용된다. PATCH 메서드 역시 멱등성을 지니고 있지만 서버마다 다를 수 있다. 비밀번호만 변경한다거나 프로필 이미지만 수정할 때에 주로 사용된다. </p>
<p>&nbsp;&nbsp;&nbsp; <strong>5. DELETE</strong>
&nbsp;&nbsp;&nbsp;&nbsp; 리소스를 <span style="background-color:yellow">삭제</span>할 때에 사용되는 메서드이다. 역시 멱등성을 지니고 있다. 응답으로 보통 204 No Content나 200 OK를 반환한다. </p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ 무상태성 (Stateless)</strong>
&nbsp;&nbsp;&nbsp;&nbsp;무상태성이란 1)<span style="background-color:yellow">서버는 클라이언트의 상태를 저장하지 않고</span>, 2) <span style="background-color:yellow">각 요청은 이전 요청에 의존하지 않고 독립적이어야 한다</span>는 규칙이다. 권한이 있는 유저만 게시글을 작성할 수 있거나 이전에 어떤 상품을 구매한 사람에게만 혜택을 주는 것 같이 클라이언트의 상태와 과거 행적을 알아야하는 경우도 존재한다. 이럴때는 JWT 토큰 발급 등의 방법으로 클라이언트의 상태를 클라이언트가 기억하도록 만든다. 
&nbsp;&nbsp;&nbsp;&nbsp;서버는 들어온 요청에 대해 이 요청을 보낸 클라이언트가 어떤 사람인지 전혀 기억하지 않는다. 서버는 같은 요청이 들어오면 같은 응답을 반환할 뿐이다. 서버가 클라이언트의 상태를 저장하지 않았을 때의 장점은 여러가지가 있다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;1. 서버 확장과 서버 분산이 쉬워진다.
&nbsp;&nbsp;&nbsp;&nbsp;서버는 클라이언트의 상태를 기억하지 않기 때문에, 클라이언트는 아무 서버에 가서도 동일한 결과값을 얻을 수 있다. 그래서 서버를 확장하거나 요청들을 분산시켜 처리하는 작업이 용이해진다. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;2. 장애 복구가 쉽다.
&nbsp;&nbsp;&nbsp;&nbsp;서버가 클라이언트의 상태를 저장하고 있을 경우, 해당 서버가 다운되면 저장되어있던 클라이언트의 상태도 같이 다운된다. 하지만 그렇지 않은 경우, 서버가 다운되어도 상태는 클라이언트에게 있기 때문에 문제가 되지 않는다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;3. 캐시(Cache) 기능을 사용할 수 있다.
&nbsp;&nbsp;&nbsp;&nbsp;무상태성을 유지함으로써 캐시(Cache) 기능을 사용하여 성능을 올릴 수 있다. 어떤 클라이언트가 요청하든지 간에 같은 요청에 대해서는 같은 결과가 늘 보장되기 때문에, 자주 오는 요청에 대해서는 캐시를 하여 더 빠르게 응답할 수 있다. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;4. 유지 보수에 용이하다.
&nbsp;&nbsp;&nbsp;&nbsp;이 API는 언제나 같은 역할만을 수행하니까 코드도 간단해지고 API 문서화도 명확하게 할 수 있다.</p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ 계층화(Layered System)</strong>
&nbsp;&nbsp;&nbsp;&nbsp;계층화란 <span style="background-color:yellow">API의 구조를 여러가지 계층으로 나누어서 설계하는 것</span>을 의미한다. RESTful API는 역할 별로 계층이 나뉘어있고, 각 계층은 자신이 맡은 역할만 수행한다. 계층들은 서로가 무슨 역할을 하는지 알 필요가 없고, 서로의 역할에 얽매여있지 않다.
&nbsp;&nbsp;&nbsp;&nbsp;<span style="background-color:yellow">서로 독립적인 계층</span>으로 나누어서 API를 설계했을 때의 장점은 다음과 같다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;1. 보안이 향상된다.
&nbsp;&nbsp;&nbsp;&nbsp; 인증, 인가와 같이 보안이 중요한 로직을 별도의 계층으로 따로 분리하여, 외부에 노출되는 계층과 별개로 관리할 수 있다. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;2. 확장성이 용이하다.
&nbsp;&nbsp;&nbsp;&nbsp; 트래픽이 몰릴 경우, 트래픽이 몰리는 계층만 확장시키면 된다. </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;3. 유지 보수가 용이하다.
&nbsp;&nbsp;&nbsp;&nbsp; 수정이 필요한 계층만 수정하면 된다. 서로의 계층은 독립적이므로 한 계층이 수정된다고 해서 다른 계층에 큰 영향이 가지 않는다.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;4. 캐시 기능을 사용할 수 있다. 
&nbsp;&nbsp;&nbsp;&nbsp; 각 계층에서 캐시 기능을 사용할 수 있어서 성능이 향상된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 개념 정리5 - ORM, JPA, 엔티티, 영속성 컨텍스트]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC5-ORM-JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC5-ORM-JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Fri, 16 May 2025 04:55:15 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 핵심 개념들을 정리하기 위해 작성하였습니다.</p>
<h2 id="5장-데이터베이스-조작이-편해지는-orm">5장. 데이터베이스 조작이 편해지는 ORM</h2>
<p>&nbsp;&nbsp;&nbsp;클라이언트가 요청한 데이터를 전달하기 위해서는 데이터베이스에 접근해야 한다. 그런데 <span style='background-color: yellow'>ORM</span>이라는 프로그래밍 기법 덕분에 SQL 언어를 쓰지 않고도 자바 언어로만 데이터베이스에 접근할 수 있게 되었다.</p>
<p><strong>□ ORM (Object Relational Mapping)</strong>
&nbsp;&nbsp;&nbsp;ORM은 <span style='background-color: yellow'>자바 언어만 가지고도 데이터베이스에 접근할 수 있도록 하는 프로그래밍 기법</span>이다. 데이터베이스의 데이터와 자바의 <span style='background-color: yellow'>객체</span> 서로 연결짓는 방식으로 ORM을 실현한다. </p>
<p>&nbsp;&nbsp;&nbsp;ORM의 장점은 다음과 같다.
&nbsp;&nbsp;&nbsp;○ 데이터베이스에 접근하기 위해 SQL 언어를 따로 사용할 필요가 없다.
&nbsp;&nbsp;&nbsp;○ 객체 지향적으로 코드를 작성할 수 있기 때문에 비즈니스 로직을 개발하는 데에만 집중할 수 있다.
&nbsp;&nbsp;&nbsp;○ 객체가 어떤 데이터와 상응하는지 명확하기 때문에 유지보수할 때에 용이하다. 
&nbsp;&nbsp;&nbsp;○ 데이터베이스 관리 프로그램을 바꾸더라도 기존의 코드를 거의 변경하지 않아도 된다.</p>
<p>&nbsp;&nbsp;&nbsp;한편 단점은 다음과 같다.
&nbsp;&nbsp;&nbsp;○ 프로젝트가 복잡해질수록 사용 난이도도 올라간다.
&nbsp;&nbsp;&nbsp;○ 복잡한 쿼리는 ORM으로 작동시킬 수 없다.</p>
<br>

<p>&nbsp;&nbsp;&nbsp;ORM에도 여러가지 종류가 있다. 그 중에서 자바에서 표준으로 쓰이는 것은 <span style='background-color: yellow'>JPA</span>이다. </p>
<p><strong>□ JPA (Java Persistence API)</strong>
&nbsp;&nbsp;&nbsp;JPA는 자바에서 관계형 데이터베이스를 사용하는 <span style='background-color: yellow'>방식을 정의한 인터페이스</span>이다. </p>
<p><strong>□ 하이버네이트 (Hibernate)</strong>
&nbsp;&nbsp;&nbsp;JPA는 인터페이스므로 <span style='background-color: yellow'>구현체</span>가 필요하다. 하이버네이트가 바로 JPA를 실제로 동작하도록 구현한 ORM 프레임워크이다. 하이버네이트에서는 내부적으로 JDBC API를 사용한다.</p>
<br>

<p>&nbsp;&nbsp;&nbsp;JPA와 하이버네이트가 객체를 통해 데이터베이스에 접근할 때에는 엔티티, 엔티티 메니저, 그리고 영속성 컨텍스트가 사용된다.</p>
<p><strong>□ 엔티티 (Entity)</strong>
&nbsp;&nbsp;&nbsp;엔티티는 <span style='background-color: yellow'>데이터베이스의 테이블에 상응하는 객체</span>이다. 엔티티는 데이터베이스에 쿼리를 실행하는 기능을 지니고 있기 때문에 다른 객체들과 차별점을 두어 엔티티라는 별도의 이름으로 부른다. </p>
<p><strong>□ 엔티티 매니저 (Entity Manager)</strong>
&nbsp;&nbsp;&nbsp;엔티티 매니저는 <span style='background-color: yellow'>엔티티들을 관리하는 역할</span>을 한다. 엔티티 매니저는 엔티티 매니저 팩토리에서 생성된다.
&nbsp;&nbsp;&nbsp;같은 요청이 여러곳에서 동시에 들어올 때 엔티티 매니저 팩토리는 각 요청에 대응할 수 있는 엔티티 매니저들을 생성한다. 이때 생성되는 엔티티 매니저들은 실제 엔티티 매니저가 아니라 프록시(가짜) 엔티티 매니저들이다. 왜냐하면 스프링부트에서는 빈을 하나만 생성해서 공유하는 방식으로 동작하기 때문이다. 생성된 프록시 엔티티 매니저는 자신이 맡은 요청에 따라서 엔티티들을 생성하고 저장한다. </p>
<p><strong>□ 영속성 컨텍스트 (Persistence Context)</strong>
&nbsp;&nbsp;&nbsp;영속성 컨텍스트는 <span style='background-color: yellow'>엔티티를 관리하는 가상의 공간</span>이다. 엔티티 매니저가 생성한 엔티티는 영속성 컨텍스트에 저장된다.
&nbsp;&nbsp;&nbsp;영속성 컨텍스트라는 별도의 가상 공간을 만들어서 엔티티를 관리하는 이유는 영속성 컨텍스트가 다음과 같이 <span style='background-color: yellow'>데이터베이스에 접근하는 부담을 덜어주는 기능들<span>을 맡아 성능을 올려주기 때문이다. </p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ 1차 캐시</strong>
&nbsp;&nbsp;&nbsp;영속성 컨텍스트에는 1차 캐시라는 임시 저장 공간이 있다. 엔티티를 조회할 때에 가장 먼저 1차 캐시를 조회한다. 만약 1차 캐시에 엔티티가 존재한다면 데이터베이스에 접근하지 않고도 결과값을 반환할 수 있다. 1차 캐시에 엔티티가 존재하지 않는다면 데이터베이스에서 조회한 데이터를 1차 캐시에 저장해둔다. 자주 조회하는 엔티티는 1차 캐시 덕분에 빠른 속도로 조회할 수 있다.</p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ 쓰기 지연</strong>
&nbsp;&nbsp;&nbsp;영속성 컨텍스트는 쿼리들을 어느정도 모아둔 다음에 한꺼번에 데이터베이스에 전송한다. 모아뒀던 쿼리들을 한꺼번에 보내는 것을 트랜잭션을 커밋한다고 표현한다. 데이터베이스에 접근하는 회수를 줄임으로써 부담을 줄이려는 목적이다.</p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ 변경 감지</strong>
&nbsp;&nbsp;&nbsp;트랜잭션을 커밋하면 1차 캐시에 있는 엔티티와 데이터베이스에 있는 데이터를 비교하여, 변경 사항이 감지되었을 경우에 변경된 값을 자동으로 데이터베이스에 반영한다. 쓰기 지연과 마찬가지로 데이터베이스에 접근하는 회수를 줄임으로써 부담을 줄이려는 목적이다.</p>
<p>&nbsp;&nbsp;&nbsp;<strong>○ 지연 로딩</strong>
&nbsp;&nbsp;&nbsp;쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하지 않고 필요할 때에만 로딩한다.</p>
<p>&nbsp;&nbsp;&nbsp;엔티티는 영속성 컨텍스트와 어떤 관계를 맺고 있느냐에 따라서 네가지 상태로 나뉜다. </p>
<ol>
<li>비영속 상태 : 엔티티가 영속성 컨텍스트와 전혀 관련이 없는 상태이다.</li>
<li>관리 상태 : 엔티티가 영속성 컨텍스트에서 관리되고 있는 상태이다. </li>
<li>분리 상태 : 엔티티가 영속성 컨텍스트로부터 분리된 상태이다.</li>
<li>삭제 상태 : 엔티티가 영속성 컨텍스트와 데이터베이스에서 삭제된 상태이다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 개념 정리4 - JUnit, Given-When-Then]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC4-JUnit-Given-When-Then</link>
            <guid>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC4-JUnit-Given-When-Then</guid>
            <pubDate>Thu, 15 May 2025 09:27:04 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 핵심 개념들을 정리하기 위해 작성하였습니다.</p>
<h2 id="4장-스프링-부트-3와-테스트">4장. 스프링 부트 3와 테스트</h2>
<p>&nbsp;&nbsp;&nbsp;작성한 코드가 제대로 동작하는지 확인하기 위해서 앱 전체를 실행하는 것은 비효율적이다. 그래서 스프링 부트에서는 <span style='background-color: yellow'>JUnit</span>이라는 프레임워크를 이용하여 코드를 테스트한다.</p>
<p><strong>□ JUnit</strong>
&nbsp;&nbsp;&nbsp;JUnit이란 자바 언어로 <span style='background-color: yellow'>단위 테스트</span>를 할 수있는 프레임워크이다. 단위 테스트란 작은 단위로 코드가 제대로 작동하는지 검증하는 것을 의미한다. 주로 메서드 단위로 테스트를 진행한다. 메서드들은 각각 독립적으로 테스트된다. 또한 한 개의 메서드라도 목표한 결과가 나오지 않으면 전체 테스트가 실패한 것으로 표기된다. 사용 방법이 단순하고 즉각적이고 직관적으로 피드백을 제공하기 때문에 널리 쓰인다. </p>
<br>
&nbsp;&nbsp;&nbsp;JUnit에서 메서드 앞에서 주로 사용되는 에너테이션들은 다음과 같다.

<p><strong>□ @DisplayName(&quot;내용&quot;)</strong>
&nbsp;&nbsp;&nbsp;콘솔창에서 어떤 테스트를 진행했는지 표기해준다.</p>
<p><strong>□ @Test</strong>
&nbsp;&nbsp;&nbsp;해당 메서드를 테스트한다.</p>
<p><strong>□ @BeforeAll</strong>
&nbsp;&nbsp;&nbsp;전체 테스트를 맨 처음 시작할 때에 한 번만 해당 메서드를 테스트한다. 이때 반드시 static이라는 키워드를 써줘야 한다. </p>
<p><strong>□ @BeforeEach</strong>
&nbsp;&nbsp;&nbsp;에너테이션 @Test가 붙은 메서드들을 테스트를 시작하기 전마다 해당 메서드를 매번 테스트한다.</p>
<p><strong>□ @AfterAll</strong>
&nbsp;&nbsp;&nbsp;전체 테스트의 맨 마지막에 한 번만 해당 메서드를 테스트한다. 이때 반드시 static이라는 키워드를 써줘야 한다.</p>
<p><strong>□ @AfterEach</strong>
&nbsp;&nbsp;&nbsp;에너테이션 @Test가 붙은 메서드들의 테스트가 끝날때마다 해당 메서드를 매번 테스트한다.</p>
<p>&nbsp;&nbsp;&nbsp;한편 테스트할 메서드들이 모여있는 클래스 앞에서 주로 사용되는 에너테이션들은 다음과 같다.</p>
<p><strong>□ @SpringBootTest</strong>
&nbsp;&nbsp;&nbsp;클래스 이름이 OOOTest로 되어있을 경우, 에너테이션 @SpringBootApplication이 붙은 클래스 중에서 이름이 OOO으로 되어있는 클래스를 찾은 뒤, OOO 클래스의 빈을 생성하고, 테스트용 애플리케이션 컨텍스트를 생성한다.</p>
<p><strong>□ @AutoConfigureMockMvc</strong>
&nbsp;&nbsp;&nbsp;애플리케이션을 서버에 배포하지 않고도 가상으로 MVC 환경을 만들어서 테스트할 수 있게 한다. 이 에너테이션은 컨트롤러(프레젠테이션 계층)을 테스트할 때에 사용된다. </p>
<br>

<p>&nbsp;&nbsp;&nbsp;메서드를 테스트할때 작성하는 코드에는 일련의 패턴이 있다. 이때 사용되는 패턴을 <span style="background-color:yellow">given-when-then</span> 패턴이라고 부른다.</p>
<p><strong>□ given</strong>
&nbsp;&nbsp;&nbsp;메서드를 테스트하기 위해 필요한 준비 작업을 하는 단계이다. 컨트롤러를 테스트할 경우, 변수를 미리 지정하거나 레포지토리를 통해 데이터베이스에 데이터를 저장해두는 작업이 given에 속한다. </p>
<p><strong>□ when</strong>
&nbsp;&nbsp;&nbsp;테스트할 기능을 작성하는 단계이다. 컨트롤러를 테스트할 경우, 어떤 url을 받는지, 그리고 결과물을 어떤 형태로 반환할 것인지를 설정한다.</p>
<p><strong>□ then</strong>
&nbsp;&nbsp;&nbsp;when 단계에서 나온 결과물과 given 단계에서 미리 지정한 값들이 일치하는지 확인하는 단계다. 서로 일치하면 테스트는 통과되고, 그렇지 않으면 테스트는 통과되지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 개념 정리3 - 프레젠테이션 계층, 비즈니스 계층, 퍼시스턴스 계층]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC3-%ED%94%84%EB%A0%88%EC%A0%A0%ED%85%8C%EC%9D%B4%EC%85%98-%EA%B3%84%EC%B8%B5-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EA%B3%84%EC%B8%B5-%ED%8D%BC%EC%8B%9C%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B3%84%EC%B8%B5</link>
            <guid>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC3-%ED%94%84%EB%A0%88%EC%A0%A0%ED%85%8C%EC%9D%B4%EC%85%98-%EA%B3%84%EC%B8%B5-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EA%B3%84%EC%B8%B5-%ED%8D%BC%EC%8B%9C%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B3%84%EC%B8%B5</guid>
            <pubDate>Thu, 15 May 2025 06:00:26 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 핵심 개념들을 정리하기 위해 작성하였습니다.</p>
<h2 id="3장-스프링-부트-3-구조-이해하기">3장. 스프링 부트 3 구조 이해하기</h2>
<p>&nbsp;&nbsp;&nbsp;스프링 부트는 <mark style = "backgournd-color:yellow">계층</mark>이라고 불리우는 구성 요소들이 서로 통신을 주고받는 모습으로 작동한다. 스프링 부트의 세가지 계층은 다음과 같다.</p>
<p><strong>□ 1. 프레젠테이션 계층</strong>
&nbsp;&nbsp;&nbsp;프레젠테이션 계층이란 <mark>클라이언트의 HTTP 요청과 비즈니스 계층에 있는 메서드를 서로 연결시키는 역할</mark>을 하는 계층이다. 스프링 부트에서는 <mark>@Controller</mark> 또는 <mark>@RestController</mark>라는 어노테이션으로 이 계층을 구분한다. </p>
<p><strong>□ 2. 비즈니스 계층</strong>
&nbsp;&nbsp;&nbsp;비즈니스 계층이란 클라이언트의 요청을 직접적으로 처리하는 메서드들이 들어있는 계층이다. 즉, <mark>비즈니스 로직을 처리</mark>하는 계층이다. 스프링 부트에서는 <mark>@Service</mark>라는 어노테이션으로 이 계층을 구분한다.</p>
<p><strong>□ 3. 퍼시스턴스 계층</strong>
&nbsp;&nbsp;&nbsp;퍼시스턴스 계층이란 <mark>데이터베이스에 접근하는 역할</mark>을 하는 계층이다. 스프링 부트에서는 <mark>@Repository</mark>라는 어노테이션으로 이 계층을 구분한다.</p>
<br>
&nbsp;&nbsp;&nbsp; 스프링 부트가 클라이언트의 HTTP 요청을 처리하는 구체적인 방식은 다음과 같다. 

<ol>
<li>클라이언트가 HTTP 요청을 <mark>톰캣(WAS)</mark>에 보낸다.</li>
<li>톰캣에 들어온 HTTP 요청을 스프링 부트의 <mark>디스패쳐 서블릿</mark>이 분석하여, 어느 컨트롤러(프레젠테이션 계층)에 보낼지 결정한다.</li>
<li>HTTP 요청을 받은 <mark>컨트롤러</mark>가 해당 요청을 처리할 수 있는 <mark>서비스(비즈니스 계층)</mark>의 메서드에 전달한다.</li>
<li>메서드는 <mark>리포지토리(퍼시스턴스 계층)</mark>을 거쳐 <mark>데이터베이스</mark>에서 필요한 데이터를 가져온다.</li>
<li><mark>뷰 리졸버</mark>가 메서드가 반환한 결과물을 HTML이나 JSON 따위의 형식으로 변환시켜 보여준다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 개념 정리2 - 제어의 역전, 의존성 주입, AOP, 이식 가능한 서비스 추상화]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC2</link>
            <guid>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC2</guid>
            <pubDate>Tue, 13 May 2025 14:13:00 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 핵심 개념들을 정리하기 위해 작성하였습니다.</p>
<h2 id="2장-스프링-부트-3-시작하기">2장. 스프링 부트 3 시작하기</h2>
<p>&nbsp;&nbsp;웹 애플리케이션을 만들기 위한 도구들은 크게 라이브러리와 프레임워크로 나뉘어진다고 앞 장에서 이야기한 바 있다. 스프링(Spring)과 스프링 부트(Spring boot)는 가장 널리 쓰이는 프레임워크들 중 하나이다.</p>
<p><strong>□ 스프링 (Spring)</strong>
&nbsp;&nbsp;&nbsp;스프링은 대규모의 복잡한 데이터를 처리하는 애플리케이션인 엔터프라이즈 애플리케이션을 개발하기 위해 만들어진 프레임워크이다. 엔터프라이즈 애플리케이션을 개발하려면 <mark>좋은 서버 성능, 안정성, 그리고 높은 수준의 보안</mark>이 필요하다. 스프링은 이 세가지를 모두 제공해준다. 그래서 스프링으로 엔터프라이즈 애플리케이션을 개발하면 <mark>온전히 기능 개발에만 집중할 수 있다</mark>.</p>
<p><strong>□ 스프링 부트 (Spring boot)</strong>
&nbsp;&nbsp;&nbsp;스프링 부트는 스프링을 좀 더 쉽게 사용할 수 있도록 개정한 프레임워크이다. 기존의 스프링은 설정이 너무 복잡하다는 문제점을 가지고 있었다. 스프링 부트는 스프링의 각종 설정을 쉽고 빠르게 할 수 있다. 스프링과 차별화되는 스프링 부트의 특징들은 다음과 같다.
&nbsp;&nbsp;&nbsp;○ 개발 환경을 자동으로 설정해준다.
&nbsp;&nbsp;&nbsp;스프링 코어와 스프링 MVC의 모든 기능을 자동으로 로드해주므로 개발 환경을 수동으로 구성할 필요가 없다.
&nbsp;&nbsp;&nbsp;○ 자체적으로 WAS(Web Application Server, 웹 애플리케이션 서버)가 내장되어있다.
&nbsp;&nbsp;&nbsp;톰캣, 제티, 언더토우 같은 WAS가 내장되어 있으므로 WAS를 별도로 설치할 필요가 없다.
&nbsp;&nbsp;&nbsp;○ XML을 사용하지 않는다. Java 언어로 모든 것을 작성할 수 있다.
&nbsp;&nbsp;&nbsp;○ 인메모리 데이터베이스를 지원한다.
&nbsp;&nbsp;&nbsp;○ JAR를 이용해서 자바 옵션만으로도 배포가 가능하다.
<br></p>
<p>&nbsp;&nbsp;&nbsp;스프링은 다음과 같은 4가지 핵심 개념들에 근거하여 구동된다.</p>
<p><strong>□ 1. 제어의 역전(IoC, Inversion of Control)</strong>
&nbsp;&nbsp;&nbsp;제어의 역전이란 <mark>객체의 생성과 관리</mark>를 개발자가 직접 하는 것이 아니라 <mark>스프링 컨테이너라는 클래스에서 대신 해주는</mark> 것을 의미한다. 객체가 필요할 때에는 객체의 생성자를 사용하지 않고 선언만 해주어도 스프링 컨테이너에서 자동으로 객체의 생성주기를 관리해준다.
&nbsp;&nbsp;&nbsp;제어의 역전을 사용하는 이유는 클래스 간의 <mark>결합도가 약해져서</mark> 프로그램을 유지 및 확장 하기에 용이해지기 때문이다. 클래스A에서 클래스B의 객체가 필요할 때에 클래스A에서 클래스B의 객체를 직접 생성하는 것을 강한 결합도라고 한다. 결합도가 강할 경우에는 클래스B가 교체될 때에 클래스 A에서 변경해야할 코드들이 많아진다. 그러나 인터페이스를 통해서 클래스A가 클래스B의 객체를 직접 생성하지 않는 것을 약한 결합도라고 한다. 이 경우 클래스B가 교체되더라도 클래스A의 코드에 미치는 영향이 줄어든다. </p>
<p><strong>□ 2. 의존성 주입(DI, Dependency Injection)</strong>
&nbsp;&nbsp;&nbsp;의존성 주입이란 제어의 역전을 구현하기 위한 기술이다. 객체를 선언하고 난 곳에 <mark>@Autowired</mark>라는 에너테이션을 사용하면 그 객체를 필요로 하는 클래스에 스프링 컨테이너가 대신 만들어준 객체를 주입한다. 이 때 스프링 컨테이너가 대신 생성하고 관리하는 객체를 빈(Bean)이라고 부른다. </p>
<p><strong>□ 3. 관점 지향 프로그래밍(AOP, Aspect Oriented Programming)</strong>
&nbsp;&nbsp;&nbsp;관점 지향 프로그래밍은 <mark>핵심 기능과 부가 기능을 따로 나누어서 보는 관점</mark>을 통해서 프로그래밍을 한다는 의미이다. 로그 관리나 데이터베이스 접근 등의 기능들은 비즈니스 로직에서 핵심은 아니지만 공통적으로 쓰이는 기능들이다. 이러한 기능들을 따로 분리하여 <mark>모듈화</mark>하여 각각 비즈니스 로직에서 필요로할 때에 그대로 가져다가 쓸 수 있다.
&nbsp;&nbsp;&nbsp;관점 지향 프로그래밍은 개발자가 핵심 기능을 개발하는데에 집중할 수 있도록 하고, 또한 프로그램을 확장하고 변경할때에도 유연하게 대처할 수 있다는 장점이 있다. </p>
<p><strong>□ 4. 이식 가능한 서비스 추상화(PSA, Portable Service Abstraction)</strong>
&nbsp;&nbsp;&nbsp;이식 가능한 서비스 추상화란 <mark>인터페이스</mark>를 제공함으로써 <mark>기능을 다른 기술로 교체하더라도 동일하게 작동하도록 하는 것</mark>을 의미한다. 덕분에 코드를 변경하지 않아도 데이터베이스나 WAS 등을 다른 것으로 교체할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 기본 개념 정리1 - 클라이언트, 서버, RDB, NoSQL, IP, Port, 라이브러리, 프레임워크]]></title>
            <link>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC1</link>
            <guid>https://velog.io/@yongwan_tech/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC1</guid>
            <pubDate>Tue, 13 May 2025 11:01:26 GMT</pubDate>
            <description><![CDATA[<p>이 포스팅은 신선영 저, 『스프링 부트 3 백엔드 개발자 되기』(골든래빗, 2023)를 공부하면서 핵심 개념들을 정리하기 위해 작성하였습니다.</p>
<h2 id="1장-자바-백엔드-개발자가-알아두면-좋은-지식">1장. 자바 백엔드 개발자가 알아두면 좋은 지식</h2>
<p>   &nbsp;&nbsp;우리가 사용하는 웹 애플리케이션의 동작은 <mark>클라이언트와 서버가 서로 정보를 주고 받는 행위</mark>로 설명된다.</p>
<p>   <strong>□ 클라이언트</strong>
   &nbsp;&nbsp;클라이언트는 서버에 데이터를 <mark>요청</mark>하는 프로그램이다. 웹 애플리케이션에서 클라이언트는 웹 브라우저이다. </p>
<p>   <strong>□ 서버</strong>
   &nbsp;&nbsp;서버는 클라이언트로부터 요청받은 데이터를 <mark>처리</mark>하거나 클라이언트에게 데이터를 <mark>전달</mark>하는 주체이다.
   <br></p>
<p>   &nbsp;&nbsp;한편 클라이언트에게 넘겨줄 데이터들이 한 데 저장되어있는 곳이 있다. 그 곳이 바로 <mark>데이터베이스</mark>이다. 서버는 데이터베이스에 접근해서 데이터를 탐색하여 클라이언트에게 넘겨준다.</p>
<p>   &nbsp;&nbsp; 데이터베이스는 크게 <mark>RDB(관계형 데이터베이스)</mark>와 <mark>NoSQL</mark>로 나뉘어진다.</p>
<p>   <strong>□ RDB(Relational Database, 관계형 데이터베이스)</strong>
   &nbsp;&nbsp;관계형 데이터베이스는 데이터를 행과 열로 이루어진 <mark>표(Table, 테이블)</mark>로 변환하여 다루는 데이터베이스이다. 각각의 행은 절대로 중복되지 않는 값인 <mark>기본키(Primary key)</mark>를 통해서 구분된다. 또한 여러 표들이 서로 관계를 맺기도 한다. 관계형 데이터베이스를 관리하는 시스템 중 유명한 것들은 오라클, MySQL, PostgreSQL 등이 있다. </p>
<p>   <strong>□ NoSQL(Not only SQL)</strong>
   &nbsp;&nbsp;NoSQL은 <mark>관계형 데이터베이스 이외의 데이터베이스들</mark>을 가리킨다. SQL(Structured Query Language)은 데이터 검색을 하기 위해 사용되는 프로그래밍 언어인데, 관계형 데이터베이스를 관리할 때에 주로 쓰인다. 물론 NoSQL에서도 SQL을 지원하는 경우가 많다. 하지만 관계형 데이터베이스와는 다른 방식으로 데이터를 다룬다는 점을 강조하기 위해서 Not only SQL의 약자인 NoSQL이라는 이름으로 불린다. NoSQL을 관리하는 시스템 중 유명한 것들은 Redis, MongoDB 등이 있다. 
   <br></p>
<p>   &nbsp;&nbsp;데이터를 주고 받는 행위는 서로 떨어져있는 기기 및 프로그램들 사이에서 이루어진다. 그래서 데이터를 정확한 곳에 전달하기 위해서는 <mark>각기 다른 기기 및 프로그램들을 구분할 수 있는 표식</mark>이 필요하다. <mark>아이피(IP)</mark>와 <mark>포트(Port)</mark>가 그 역할을 한다.
   <strong>□ 아이피(IP)</strong>
   &nbsp;&nbsp;아이피는 인터넷 상에서 <mark>컴퓨터 또는 기기들을 서로 구분</mark>하기 위한 주소이다. 이를 통해서 서버에 접근할 수 있다. 
   <strong>□ 포트(Port)</strong>
   &nbsp;&nbsp;서버에 접근한 다음에 서버를 이용하기 위해서는 아이피 주소만 알아서는 안된다. 포트 번호까지 알고 있어야 한다. 왜냐하면 한 서버에 여러가지 서비스를 제공하고 있을 수도 있기 때문이다. 포트란 <mark>한 서버 내에서 운용되고 있는 서비스들을 서로 구분</mark>하기 위한 번호이다. 
   <br></p>
<p>   &nbsp;&nbsp;그렇다면 위와 같은 동작들을 구현해내는 웹 애플리케이션은 무엇으로 만드는가? 이 세상에는 웹 애플리케이션을 만드는데에 유용한 도구들이 많다. 이 도구들의 종류를 크게 둘로 나눠보면 <mark>라이브러리</mark>와 <mark>프레임워크</mark>로 나뉜다. 
   <strong>□ 라이브러리</strong>
   &nbsp;&nbsp;라이브러리는 애플리케이션 개발에 유용하게 쓰이는 <mark>각종 메서드들과 클래스들을 한 데 모아놓은 것</mark>이다. 개발에 필요한 코드들을 처음부터 작성할 필요 없이 라이브러리에 저장된 코드들을 가져다가 사용하기만 하면 된다.
   <strong>□ 프레임워크</strong>
   &nbsp;&nbsp;프레임워크는 <mark>애플리케이션 개발의 구조와 순서를 미리 정해놓은 틀</mark>을 의미한다. 프레임워크를 이용해 애플리케이션을 개발하면 애플리케이션의 구조를 처음부터 구상할 필요 없이 기능 개발에만 집중할 수 있다. </p>
]]></description>
        </item>
    </channel>
</rss>