<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>아직은 NULL NULL 합니다</title>
    <link>https://lets-go-it.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 11 Apr 2026 01:04:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>is낫널</managingEditor>
    <image>
      <title>아직은 NULL NULL 합니다</title>
      <url>https://tistory1.daumcdn.net/tistory/6628413/attach/74a69408b8074aed8bcd9e646170bfe5</url>
      <link>https://lets-go-it.tistory.com</link>
    </image>
    <item>
      <title>[Spring Security] CurrentUser 구현체 + ArgumentResolver</title>
      <link>https://lets-go-it.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리하게 된 계기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 팔로우 기능을 개발하면서 팔로우 추가 파라미터를 받는 FollowRequestDTO 를 작성할 때&amp;nbsp;&lt;br /&gt;아직 Spring Security 가 구현안된 상태여서 TODO 로 아래처럼 적어둔 상태였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761748731273&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ...

@Getter
@NoArgsConstructor
public class FollowRequestDTO {

    // TODO : JWT 도입 후 토큰 정보에서 로그인 사용자 ID 뽑아올 예정
    private String fromUserId;
    private String toUserId;

    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Follow 기능을 어느정도 완성한 후 follow branch 에서 main 으로 PR 을 올렸는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 리뷰를 받게 되면서 CurrentUser 구현체가 있다는 걸 알게 되어서 이번 기회에 정리해보려고 한다!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUdQCU/dJMcaa4DCv8/mgnXWNSrW0x25Bw4o8vMW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUdQCU/dJMcaa4DCv8/mgnXWNSrW0x25Bw4o8vMW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUdQCU/dJMcaa4DCv8/mgnXWNSrW0x25Bw4o8vMW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUdQCU%2FdJMcaa4DCv8%2FmgnXWNSrW0x25Bw4o8vMW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;646&quot; height=&quot;305&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 TokenUtil 이라는 파일을 따로 두고 JWT 를 직접 파싱하는 방식으로 아래 코드처럼 구현을 해서 사용자의 정보를 가져왔었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761751347641&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Claims getClaimsFromToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(Constants.SIGN_WITH)
                    .parseClaimsJws(token)
                    .getBody();

        } catch (ExpiredJwtException | MalformedJwtException | SignatureException | UnsupportedJwtException | IllegalArgumentException e) {
            logger.error(&quot;getClaimsFromToken Exception | &quot;, e);
            return null;
        }
    }

    private Claims generateClaims(User.UserInfoDTO user) {
        Claims claims = new DefaultClaims();

        try {
            User.TokenClaimsDTO tokenClaimsDTO = User.TokenClaimsDTO.builder()
                    .userId(user.getUserId())
                    .userName(user.getUserName())
                    .department(user.getDepartment())
                    .auth(user.getUserStatus())
                    .joinStatus(user.getJoinStatus())
                    .build();

            claims.put(&quot;userId&quot;, tokenClaimsDTO.getUserId());
            claims.put(&quot;user&quot;, objectMapper.writeValueAsString(tokenClaimsDTO));

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return claims;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이와 같은 방식은 요청이 들어올 때마다 직접 JWT 를 해석해서 필요한 정보를 뽑는 방식으로&amp;nbsp;&lt;br /&gt;Security 없이 토큰만 검증했던 것이었다. 허허&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;몰랐다면 이제 알면되지 ~!&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 마인드로 빠르게 CurrentUser 구현체가 왜 나오게 되었는지 에 대해 파악을 해보자 !&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CurrentUser 란 ?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@CurrentUser 는 현재 로그인한 사용자 정보를 가져오기 위한 커스텀 구현체이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 말 그대로 커스텀 구현체 이기 때문에 꼭 CurrentUser 라는 이름이 아닐수도 있지만 &lt;br /&gt;대체적으로 CurrentUser 로 통일해서 사용하는 것 같아서 나 또한 헷갈리지 않게 그대로 이름을 사용해보려 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761751914799&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/profile&quot;)
public ResponseEntity&amp;lt;UserResponse&amp;gt; getProfile(@CurrentUser CustomUserDetails user) {
    return ResponseEntity.ok(userService.getProfile(user.getId()));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 위와 같이 단순히 컨트롤러 파라미터에 &quot;현재 인증된 사용자 정보&quot; 를 자동으로 주입해주는 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 CurrentUser 구현체를 만든 이유는 SecurityContextHolder 안에 저장된 인증 객체(Authentication) 를 꺼내는 일을 직접 안해도 되게끔 하기 위해서이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로그인 유저 정보의 저장 위치&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 로그인 시 인증 객체(Authentication)를 &lt;b&gt;SecurityContextHolder&lt;/b&gt;에 보관한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체 안에는 Principal (주체, 즉 로그인한 사용자)이 들어있고 &lt;br /&gt;JWT 기반 인증이라면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Principal 는 UserDetails 인터페이스를 상속받은 CustomUserDetails 객체일 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761752288870&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CustomUserDetails implements UserDetails {
    private Long id;
    private String username;
    private String role;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;그렇다면 여기서 @CurrentUser 어노테이션이 없다면 어떻게 로그인 정보를 가져오게 될까?&lt;/blockquote&gt;
&lt;pre id=&quot;code_1761752674306&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/profile&quot;)
public ResponseEntity&amp;lt;UserResponse&amp;gt; getProfile() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
    return ResponseEntity.ok(userService.getProfile(user.getId()));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 반복되는 코드가 형성되게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 @CurrentUser 는 그저 표식으로 사용될 뿐이고 이제 ArguementResolver 를 통해 @CurrentUser 와 같은 표식을 구분하여 특정 파라미터를 보고 실제 객체를 찾아 넣어주는 로직을 수행하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;argument-resolver&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Argument Resolver 란 ?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 메서드의 파라미터(argument) 에 알맞은 값을 찾아서 넣어주는 객체이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 ArgumentResolver 를 만들기 위해서는 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;클래스가 &lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;HandlerMethodArgumentResolver 를 구현해야 한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start; background-color: #ffffff;&quot;&gt;구현해야할 메서드는 총 2개로 supportsParameter() 와 resolveArgument() 가 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start; background-color: #ffffff;&quot;&gt;supportsParameter() 는 인자로 들어온 파라미터를 처리 할 수 있는지 아닌지를 boolean 타입으로 판단해주며&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start; background-color: #ffffff;&quot;&gt;resolveArgument() 는 &lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;supportsParameter() 에서 true 로 처리된 Resolver 가 해당 메서드를 실행해서 &lt;br /&gt;그 파라미터에 들어갈 실제 객체를 반환한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start; background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;로직으로 본다면 아래와 같다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761753736711&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.security.resolver;

import com.example.security.annotation.CurrentUser;
import com.example.security.auth.CustomUserDetails;
import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class)
                &amp;amp;&amp;amp; CustomUserDetails.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(
            MethodParameter parameter,
            ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest,
            WebDataBinderFactory binderFactory) {

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null || authentication.getPrincipal().equals(&quot;anonymousUser&quot;)) {
            return null; // 인증되지 않은 사용자
        }

        return authentication.getPrincipal(); // CustomUserDetails 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt; WebMvcConfigurer에 Resolver 등록 &lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1761753879836&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.config;

import com.example.security.resolver.CurrentUserArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

    private final CurrentUserArgumentResolver currentUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List&amp;lt;HandlerMethodArgumentResolver&amp;gt; resolvers) {
        resolvers.add(currentUserArgumentResolver);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;이후 해당 Resolver 를&amp;nbsp;WebMvcConfigurer 에 등록을 해줌으로써&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;스프링 MVC 가 컨트롤러의 파라미터를 처리할 때 내가 만든 Resolver 를 인식하게끔 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;그리고 대망의 CurrentUser 의 세팅은 이렇다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;@CurrentUser 어노테이션 정의&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1761753930115&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.security.annotation;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentUser {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 Argument Resolver 내부에서 &lt;br /&gt;Authentication authentication = SecurityContextHolder.getContext().getAuthentication() 를 통해&amp;nbsp;&lt;br /&gt;사용자 로그인 정보를 가져옴으로써 Security 의 이해도가 높아지는 동시에 코드를 효율적으로 짤 수 있는 방법을 알게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;참고&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@uiurihappy/Spring-Argument-Resolver-%EC%A0%81%EC%9A%A9%ED%95%98%EC%97%AC-%EC%9C%A0%EC%A0%80-%EC%A0%95%EB%B3%B4-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring] Argument Resolver 적용하여 유저 정보 불러오기&lt;/a&gt;&lt;/p&gt;</description>
      <category>Project/Team</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/73</guid>
      <comments>https://lets-go-it.tistory.com/73#entry73comment</comments>
      <pubDate>Wed, 29 Oct 2025 23:34:01 +0900</pubDate>
    </item>
    <item>
      <title>[JPA + QueryDSL] 이해하기</title>
      <link>https://lets-go-it.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서론&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글에서는 JPA 와 QueryDSL 에 대한 개념보단 프로젝트를 진행하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마주한 문제와 알게된 내용을 기록하기 위해 작성하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPA (Java Persistence API)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 객체(클래스) 와 데이터베이스 테이블을 자동으로 매핑해주는 ORM 표준 인터페이스이다.&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인터페이스 이기 때문에 Hibernate, OpenJPA 등이 JPA를 구현한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;ORM (Object-Relational Mapping)&amp;nbsp;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션의 Class 와 RDB (Relational Database) 의 테이블을 매핑(연결) 한다는 뜻이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로는 어플리케이션의 객체를 RDB 테이블에 자동으로 영속화 해주는 것이라고 보면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kLjT6/dJMcaj1Bi7s/T23noqsELkJHwfq1oC3kF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kLjT6/dJMcaj1Bi7s/T23noqsELkJHwfq1oC3kF1/img.png&quot; data-alt=&quot;출처 : 구글검색&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kLjT6/dJMcaj1Bi7s/T23noqsELkJHwfq1oC3kF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkLjT6%2FdJMcaj1Bi7s%2FT23noqsELkJHwfq1oC3kF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;209&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : 구글검색&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;QueryDSL 을 적용하게 된 계기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 를 통해서 쿼리를 작성하려다가, 디테일한 쿼리를 작성해야하는 것이 필요해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 기반으로 되어 가독성이 떨어지는 @Query 어노테이션의 JPQL 만 작성하는 것보다 &lt;br /&gt;QueryDSL 이라는 빌더를 사용하는 것이 복잡한 쿼리를 작성하는 데 가독성이 높을 것 같아 사용하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;QueryDSL&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;Java에서 안전하고 가독성 있는 쿼리를 작성할 수 있게 해주는 프레임워크다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;QueryDSL 은 이러한 특징을 갖고 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;타입 안정성&lt;/b&gt; : 컴파일 시점에 쿼리의 타입을 검증하여 런타임 에러 방지&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가독성&lt;/b&gt; : 코드 형태로 쿼리를 작성하므로 가독성이 높아지고 유지보수가 용이하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동적 쿼리 지원&lt;/b&gt; : 조건에 따른 동적 쿼리를 유연하게 생성할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통합성&lt;/b&gt; : JPA, Hibernate, SQL 과 쉽게 통합 가능하다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 직접 QueryDSL 을 사용하면서 이해해보도록 하자!&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;팔로우 리스트 조회하는 쿼리&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1761479296191&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select
    f.id
     , f.from_user_id as user_id
     , u.email
     , u.nickname
     , IF(targetF.id is not null
              and targetF.deleted_at is null, TRUE, FALSE) as isFollowingBack
    -- , avatar_file_path
from follows f join user u
                    on f.from_user_id = u.id
               left join follows targetF
                         on f.from_user_id = targetF.to_user_id
                             and f.to_user_id = targetF.from_user_id
where f.to_user_id = 'userId' -- 로그인한 사용자의 id(pk) : 임시 데이터
  and f.deleted_at is null
#   and u.nickname like '%유저닉네임%' -- 임시 데이터 
order by f.created_at desc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 쿼리를 QueryDSL 로 표현했을 때&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체코드로는 아래와 같다. 위의 실제 쿼리와 QueryDSL 의 구문은 대체로 유사하여 이해하는데 어렵지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 처음보는 구문도 있기에 한번 하나씩 뜯어보도록 하자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762158295451&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;FollowSearchListResponseDTO&amp;gt; findFollowerList(String userId, @Nullable String keyword, @Nullable Long lastId, int size) {
    // follows 와 user 조인
    // keyword 가 존재시 nickname Like 검색 조건 추가
    return jpaQueryFactory
            .select(Projections.constructor(FollowSearchListResponseDTO.class,
                    qFollows.id,
                    qFollows.fromUserId.as(&quot;userId&quot;),
                    qUser.email,
                    qUser.nickname,
                    Expressions.booleanTemplate(
                            &quot;CASE WHEN {0} IS NOT NULL AND {1} IS NULL &quot; +
                                    &quot;THEN TRUE ELSE FALSE END&quot;,
                            targetF.id,
                            targetF.deletedAt
                    ).as(&quot;isFollowingBack&quot;)
                    ))
            .from(qFollows)
            .join(qUser).on(qFollows.fromUserId.eq(qUser.id))
            .leftJoin(targetF)
                .on(qFollows.fromUserId.eq(targetF.toUserId)
                .and(qFollows.toUserId.eq(targetF.fromUserId)))
            .where(qFollows.toUserId.eq(userId)
                    .and(qFollows.deletedAt.isNull())
                    .and(keywordCondition(keyword))
                    .and(cursorCondition(lastId))
                    )
            .orderBy(qFollows.createdAt.desc())
            .limit(size)
            .fetch();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;QFollows 라는 Q클래스가 이미&amp;nbsp; 생성되어 있고 주 테이블이기 때문에 객체를 생성한 상태에서&amp;nbsp;&lt;br /&gt;같은 테이블끼리 Join 이 필요할 때&amp;nbsp; 또 다른 Q클래스를 생성해야 할 경우&amp;nbsp;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;전역 변수로 만들었기 때문에 위 코드에는 생성과정이 보이지 않아, 따로 넣어주었다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1761479439114&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 기본 인스턴스를 사용하여 Q클래스 사용 
private final QFollows qFollows = QFollows.follows;
// 원하는 별칭(&quot;targetF&quot;) 으로 Q클래스 사용  
private final QFollows targetF = new QFollows(&quot;targetF&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CASE WHEN THEN 을 표현할 경우
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Expressions.booleanTemplate(&quot;SQL&amp;nbsp;표현식&quot;,&amp;nbsp;파라미터들...)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;복잡하거나 &lt;b&gt;QueryDSL에서 직접 지원하지 않는 SQL 구문을 그대로 표현할 때&lt;/b&gt; 사용하는 &lt;br /&gt;&lt;b&gt;SQL 템플릿 기반 Boolean 표현식&lt;/b&gt;&amp;nbsp;생성 메서드이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;아래 코드에서 {0} 과 {1} 은 &lt;b&gt;placeholder&lt;/b&gt;로, &lt;br /&gt;뒤에 오는 파라미터를 SQL로 치환해주는데 순서대로 0에는 targetF.id 가 들어가고 &lt;br /&gt;1 에는 targetF.deletedAt 이 들어가게 된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1761479548072&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.booleanTemplate(
    &quot;CASE WHEN {0} IS NOT NULL AND {1} IS NULL THEN TRUE ELSE FALSE END&quot;,
    targetF.id,
    targetF.deletedAt
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;QueryDSL 을 DTO 로 결과를 매핑하는 방식&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Projections.constructor() : 생성자를 통해 매핑 (생성자 기반이라 불변객체가 가능하여 Builder 패턴에 적합함)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Projections. fields() : 필드 이름으로 매핑&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Projections.bean() : Setter 로 매핑&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPA 의 더티체킹 (Dirty Checking)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 는 DB 에서 엔티티를 조회하거나 저장 하는 시점에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 엔티티의 @Id 의 값을 알게 된다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티를 생성 할 때 @Id 값을 DB 에서 받아오고&amp;nbsp;&lt;/li&gt;
&lt;li&gt;조회 시 쿼리 결과에서 id 컬럼을 함께 읽어 오기 때문이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에서 JPA 는 영속성 컨텍스트(1차 캐시) 에 등록을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이 부분을 깨닫게 된 시점은 아래 QueryDSL 의 수정 부분의 동작을 이해하게 되었을 때이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761485235653&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
    public void addFollow(String fromUserId, String toUserId) {
        Follows existing = jpaQueryFactory
                .selectFrom(qFollows)
                .where(qFollows.fromUserId.eq(fromUserId)
                        .and(qFollows.toUserId.eq(toUserId))
                        .and(qFollows.deletedAt.isNotNull()))
                .fetchOne();

        // 객체가 비어있다면 첫 팔로우 추가
        if(ObjectUtils.isEmpty(existing)) {
            jpaQueryFactory
                    .insert(qFollows)
                    .columns(qFollows.fromUserId, qFollows.toUserId)
                    .execute();
        } else if(existing.getDeletedAt() != null) {
            existing.restore();
        }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요하게 봐야할 부분은 existing.restore() 메서드를 호출하는 구간이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구간은 deletedAt 컬럼이 null 이냐 아니냐 를 통해서&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 팔로우 했다가 언팔한 사람을 다시 팔로우 하는 시점인지&lt;/li&gt;
&lt;li&gt;새로운 사람을 팔로우하는 시점인지&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 파악하는 곳이었고, deletedAt 이 null 이 아닐 경우 전자 시점에 해당하여&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 생성된 row 에서 deletedAt 의 컬럼 데이터만 null 로 바꾸어주면 다시 팔로우 하게 되게 하는 쿼리문이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본래라면 아래코드와 같이 다시 수정 쿼리를 날려야 할 것 같지만, JPA 의 더티 체킹 이라는 기능으로 인해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;달라진 필드만 update 가 된다는 것을 알게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761485510927&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jpaQueryFactory
            .update(qFollows)
            .setNull(qFollows.deletedAt)
            .where(qFollows.fromUserId.eq(fromUserId)
                    .and(qFollows.toUserId.eq(toUserId))
                    .and(qFollows.deletedAt.isNull()))
            .execute();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 restore 메서드는 Follows 엔티티에 작성된 메서드인 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761485597237&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void restore() {
        this.deletedAt = null;
        this.createdAt = LocalDateTime.now();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 deletedAt 컬럼과 createdAt 컬럼이 변경됨으로써 JPA 의 더티체킹에 걸리게 되면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따로 update 쿼리를 생성할 필요 없이 조회 시 저장하고 있던 id 값을 통해 update 를 자동으로 요청하게 되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dbjh.tistory.com/77&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring JPA] JPA 란 ?&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762154425894&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring JPA] JPA 란?&quot; data-og-description=&quot;이번 글에서는 JPA(Java Persistence API)가 무엇인지 알아보려고한다. JPA는 자바 진영에서 ORM(Object-Relational Mapping) 기술 표준으로 사용되는 인터페이스의 모음이다. 그 말은 즉, 실제적으로 구현된것이&quot; data-og-host=&quot;dbjh.tistory.com&quot; data-og-source-url=&quot;https://dbjh.tistory.com/77&quot; data-og-url=&quot;https://dbjh.tistory.com/77&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/t0RM3/hyZM9aHOJX/DXqucKfklCWQC2UhdqOQb1/img.jpg?width=800&amp;amp;height=800&amp;amp;face=67_318_318_591,https://scrap.kakaocdn.net/dn/b1H6Z4/hyZMUzqpm4/gzx4n49rjYsWfSNbcjyFNK/img.jpg?width=800&amp;amp;height=800&amp;amp;face=67_318_318_591,https://scrap.kakaocdn.net/dn/oOxJW/hyZMJY06RQ/kwS1MKuIlQNlhv33Efbp8k/img.png?width=1999&amp;amp;height=593&amp;amp;face=0_0_1999_593&quot;&gt;&lt;a href=&quot;https://dbjh.tistory.com/77&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dbjh.tistory.com/77&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/t0RM3/hyZM9aHOJX/DXqucKfklCWQC2UhdqOQb1/img.jpg?width=800&amp;amp;height=800&amp;amp;face=67_318_318_591,https://scrap.kakaocdn.net/dn/b1H6Z4/hyZMUzqpm4/gzx4n49rjYsWfSNbcjyFNK/img.jpg?width=800&amp;amp;height=800&amp;amp;face=67_318_318_591,https://scrap.kakaocdn.net/dn/oOxJW/hyZMJY06RQ/kwS1MKuIlQNlhv33Efbp8k/img.png?width=1999&amp;amp;height=593&amp;amp;face=0_0_1999_593');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring JPA] JPA 란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 JPA(Java Persistence API)가 무엇인지 알아보려고한다. JPA는 자바 진영에서 ORM(Object-Relational Mapping) 기술 표준으로 사용되는 인터페이스의 모음이다. 그 말은 즉, 실제적으로 구현된것이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dbjh.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kangth97.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Java Spring Boot] QueryDSL 사용하기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762154450189&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java Spring Boot] QueryDSL 사용하기.&quot; data-og-description=&quot;QueryDSL의 특징QueryDSL은 Java에서 안전하고 가독성 있는 쿼리를 작성할 수 있게 해주는 프레임워크이다.타입 안전성: 컴파일 시점에 쿼리의 타입을 검증하여 런타임 에러 방지가독성: 코드 형태로 &quot; data-og-host=&quot;kangth97.tistory.com&quot; data-og-source-url=&quot;https://kangth97.tistory.com/42&quot; data-og-url=&quot;https://kangth97.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RtRtW/hyZMXQuMIE/5vpMq7IFVhuCkqwn8sCxgk/img.png?width=414&amp;amp;height=482&amp;amp;face=0_0_414_482,https://scrap.kakaocdn.net/dn/ccHVYH/hyZMJq9L7L/lhdPjt2cbXJ9hkpCL9f0qk/img.png?width=414&amp;amp;height=482&amp;amp;face=0_0_414_482,https://scrap.kakaocdn.net/dn/HGIVA/hyZMZngef1/4sWuQEDdrWTiQj0YrBpqVK/img.png?width=414&amp;amp;height=482&amp;amp;face=0_0_414_482&quot;&gt;&lt;a href=&quot;https://kangth97.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kangth97.tistory.com/42&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RtRtW/hyZMXQuMIE/5vpMq7IFVhuCkqwn8sCxgk/img.png?width=414&amp;amp;height=482&amp;amp;face=0_0_414_482,https://scrap.kakaocdn.net/dn/ccHVYH/hyZMJq9L7L/lhdPjt2cbXJ9hkpCL9f0qk/img.png?width=414&amp;amp;height=482&amp;amp;face=0_0_414_482,https://scrap.kakaocdn.net/dn/HGIVA/hyZMZngef1/4sWuQEDdrWTiQj0YrBpqVK/img.png?width=414&amp;amp;height=482&amp;amp;face=0_0_414_482');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java Spring Boot] QueryDSL 사용하기.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;QueryDSL의 특징QueryDSL은 Java에서 안전하고 가독성 있는 쿼리를 작성할 수 있게 해주는 프레임워크이다.타입 안전성: 컴파일 시점에 쿼리의 타입을 검증하여 런타임 에러 방지가독성: 코드 형태로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kangth97.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/Team</category>
      <category>backend</category>
      <category>JPA</category>
      <category>querydsl</category>
      <category>springboot</category>
      <category>더티체킹</category>
      <category>백엔드</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/70</guid>
      <comments>https://lets-go-it.tistory.com/70#entry70comment</comments>
      <pubDate>Sun, 26 Oct 2025 21:10:21 +0900</pubDate>
    </item>
    <item>
      <title>[Git] Branch 와 checkout 의 헤프닝</title>
      <link>https://lets-go-it.tistory.com/68</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&amp;nbsp;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  새로운 브랜치의 변경사항을 commit 하지 않았는데도 main 에 적용된 것처럼 보이는 현상 발생&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사건의 흐름&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Github (원격) 의 main 브랜치에서 새로운 브랜치를 생성함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 로컬로 돌아와 git fetch origin 을 하여 원격에서 생성된 브랜치를 가져옴&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 원격에서 fetch 해온 새로운 브랜치로 checkout 하여 패키지명을 변경함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이 때 새로운 브랜치에서 commit 하지 않은 채 로컬 main 브랜치로 checkout 함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- commit 하지 않고 main 브랜치로 체크아웃했던 이유는 혹시 실수로 main 에 반영된 게 아닐까 싶어 확인차 돌아갔었던 차였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;그런데 새로운 브랜치에서 변경한 사항이 main 에도 똑같이 패키지명이 바뀌어져 있어서 &lt;i&gt;어라? &lt;/i&gt;하며 순간 식은땀이 흘렀다 ㅎ..ㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;왜냐하면 패키지명 바꾸자마자 갑자기 모든 라이브러리들이 인식이 안되는 문제가 발생하고 있었기 때문이다.&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp;[문제 해결 더보기 참고]&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 - gradle project 에서 패키지명 변경하려다 발생한 라이브러리 인식 안되는 문제&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 패키지명이 com.xxx.demo 에서 com.xxx.dev 로 변경하려 했다고 가정&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 리팩토링 기능으로 패키지명을 변경 한 후 settings.gradle 파일에서 rootProject.name 까지 야무지게 dev 로 바꿔둠&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그러나 build.gradle 에서 group = 'com.xxx' 로만 되어있는 것을 'com.xxx.dev' 로 적었더니 라이브러리 인식이&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp;안되는 문제가 발생한 거였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jar 이름은 &amp;lt;rootProject.name&amp;gt;-&amp;lt;version&amp;gt;.jar 로 jar 파일이 만들어지는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 group 을 붙여 결과가 com.xxx.dev: dev:0.0.1-SNAPSHOT 으로 만들어지는 것이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 rootProject.name 구간에 dev 가 들어가는데 group 에 까지 dev 를 넣게 됨으로써 라이브러리가&amp;nbsp;&lt;br /&gt;해당 프로젝트를 찾지 못하는 일이 발생하게 되면서 인식이 안되게 된 것이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;group : 다른 프로젝트가 나를 찾을 때 쓰는 주소(label)&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[fetch / checkout]&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- fetch : 원격 저장소의 최신 정보를 로컬로 동기화&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- checkout : 현재 작업할 브랜치(혹은 커밋) 로 작업 공간 이동&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 (= 헤프닝 ㅋㅋ.)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 간단했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋하지 않은 변경사항은 브랜치 간에 &lt;b&gt;공유&lt;/b&gt;가 되기 때문이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 새로운 브랜치에서 수정했지만 commit 하지 않은 채로 브랜치를 바꾸게 되면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git 에서는 변경된 파일을 staging 안 한 상태이기 때문에 working directory 에 남겨둔 채 그대로 main 브랜치로 공유가 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 main 도 새로운 브랜치의 변경사항처럼 똑같이 변경된 것으로 보이는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하려면&amp;nbsp;새로운 브랜치에서 변경사항을 commit 하고 main 으로 체크아웃하여 돌아오게 되면 &lt;br /&gt;변경 파일들은 새로운 브랜치에만 반영이 된 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;결론은 브랜치의 포인터는 달라도, 파일은 동일한 공간(Working Directory) 을 공유 중이라는 것을 알아두도록 하자!&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project/Team</category>
      <category>backend</category>
      <category>Branch</category>
      <category>checkout</category>
      <category>COMMIT</category>
      <category>fetch</category>
      <category>frontend</category>
      <category>GIT</category>
      <category>github</category>
      <category>개발</category>
      <category>백엔드</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/68</guid>
      <comments>https://lets-go-it.tistory.com/68#entry68comment</comments>
      <pubDate>Wed, 22 Oct 2025 01:32:46 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ:1342 / JAVA] 행운의 문자열</title>
      <link>https://lets-go-it.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1342&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1342&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 요약&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력받은 문자열에 대해서 서로 인접해있는 문자가 같지 않은 문자열이면 &lt;b&gt;행운의 문자열&lt;/b&gt; 이라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 내의 문자를 재배치하여 인접해 있는 문자가 서로 같지 않게 만들어 행운의 문자열이 몇개 나오는지 개수를 세는 문제&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 알고리즘 (접근 방법)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;○&lt;b&gt; 백트래킹 + DFS (재귀)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 해당 문자들을 재배치하기 위해서는 문자열내의 문자가 각각 몇개씩 있는지 셀 수 있는 카운트 배열을 두었다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758815778594&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;char[] strArr = str.toCharArray();
for(char ch : strArr) {
    int x = ch - 'a';     
    count[x]++;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력받은 문자열을 문자 배열로 만들어준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;반복문을 돌리며 해당 문자가 index 에 맞게 카운트 되도록 아스키코드의 번호를 사용하여 계산해주었다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ex. ch 가 a 인 경우 ASCII 번호로 49 이며, 이를 49 - 49 를 하게 되면 0이 되어 a의 개수는 0번 인덱스에 저장된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서로 인접해 있는 문자들이 같지 않아야 한다는 점에서, &lt;br /&gt;재배치할 경우 이미 선택되어 배치된 문자와 겹치지 않는 것이 포인트라고 생각하였다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758815982656&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for(int i = 0; i &amp;lt; 27; i++) {
    if(count[i] == 0) continue; // 알파벳 갯수가 0이면 넘기기

    if(pre != (char)(i + 'a')) {
        count[i]--;  // 해당 인덱스의 알파벳 개수 줄이기
        dfs(idx+1, (char)(i+'a'));
        count[i]++; // 이전값과 다른 값일 경우에만 idx 를 증가시킨다.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;알파벳 abc..개수인 26개만큼 반복문을 돌리며, 탐색을 시작한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;알파벳 개수를 담는 count 배열의 문자 개수가 0이라면 배치가 불가능하므로 넘기는 조건을 넣어주었다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;pre 는 이전 문자를 담는 변수로 ex. i + 'a' &amp;rarr; 0 + 49 &amp;rarr; (char)49 &amp;rarr; 'a' 와 비교하여 같은 문자가 아닌 경우에만 배치를 시작했다.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;다시 count[i]++ 을 해준 이유는 재귀(dfs) 가 끝나면 다시 원상복구하여 다른 문자를 탐색해야 하기 때문에 백트레킹 코드를 넣어준 것이다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;왜 원상복구 해야 하는가? &amp;rarr; ab 라는 입력값이 들어왔을 때 ab 로 배치를 완성하여 행운의 문자열 임을 확인 하고 또 다른 배치가 있는지를 확인하기 위해서 반복문이 돌면서 b로 먼저 시작하는 과정도 고려해야하기 때문이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이 구간이 이해 안된다면 아래 더보기를 보자.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1758816557498&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dfs(0, ' ')
 ├─ i='a' 선택 (count[a]=1) &amp;rarr; dfs(1, 'a')
 │    ├─ i='a' 불가(연속) &amp;rarr; 스킵
 │    └─ i='b' 선택 (count[b]=0) &amp;rarr; dfs(2, 'b')
 │         └─ i='a' 선택 (count[a]=0) &amp;rarr; dfs(3, 'a')
 │              └─ idx==length(3) &amp;rarr; res++ (1개 완성: &quot;aba&quot;)
 │         &amp;larr; 되돌리기 count[a]++  (a를 지움)
 │    &amp;larr; 되돌리기 count[b]++      (b를 지움)
 └─ 되돌리기 count[a]++          (a를 지움)

 └─ i='b' 선택 (count[b]=0) &amp;rarr; dfs(1, 'b')
      └─ i='a' 선택 (count[a]=1) &amp;rarr; dfs(2, 'a')
           ├─ i='a' 불가(연속)
           └─ i='b' 불가(count[b]=0)
           &amp;rarr; 막다른 길 (완성X)
      &amp;larr; 되돌리기 count[a]++
 &amp;larr; 되돌리기 count[b]++&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 최종 코드&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758816580156&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
    // 행운의 문자열
    // 인접해있는 모든 문자가 같지 않은 문자열을 행운의 문자열이라고 한다.
    // aabbbaa -&amp;gt; abababa 행운의 문자열
    // aaab -&amp;gt; baaa -&amp;gt; abaa -&amp;gt; aaba 어떻게 배치해도 인접해있는 문자들이 다수
    static int[] count = new int[27]; // abcdefghij ..
    static int length;
    static int res;
    // 문자열 S 에 나오는 문자를 재배치하면 서로 다른 행운의 문자열이 몇개 나오는지 궁금해졌다
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        String str = br.readLine();
        length = str.length();
        char[] strArr = str.toCharArray();
        for(char ch : strArr) {
            int x = ch - 'a';
            count[x]++;
        }
        dfs(0, ' ');

        System.out.println(res);
    }

    private static void dfs(int idx, char pre) {
        // idx 와 길이가 같아지면 멈추기
        if(idx == length) {
            res++;
            return;
        }

        for(int i = 0; i &amp;lt; 27; i++) {
            if(count[i] == 0) continue; // 알파벳 갯수가 0이면 넘기기

            if(pre != (char)(i + 'a')) {
                count[i]--;  // 해당 인덱스의 알파벳 개수 줄이기
                dfs(idx+1, (char)(i+'a'));
                count[i]++; // 이전값과 다른 값일 경우에만 idx 를 증가시킨다.
            }
        }

    }

}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/BOJ &amp;amp; SWEA</category>
      <category>1342</category>
      <category>BOJ</category>
      <category>dfs</category>
      <category>백준</category>
      <category>백트래킹</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/53</guid>
      <comments>https://lets-go-it.tistory.com/53#entry53comment</comments>
      <pubDate>Fri, 26 Sep 2025 01:11:13 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ: 1005/ JAVA] ACM Craft</title>
      <link>https://lets-go-it.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1005&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1005&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUgUCV/btsQwCBEHf4/v8MISKwzWHr6EDPga4yKC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUgUCV/btsQwCBEHf4/v8MISKwzWHr6EDPga4yKC1/img.png&quot; data-alt=&quot;백준 1005번 문제 사진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUgUCV/btsQwCBEHf4/v8MISKwzWHr6EDPga4yKC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUgUCV%2FbtsQwCBEHf4%2Fv8MISKwzWHr6EDPga4yKC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;339&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;백준 1005번 문제 사진&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. 문제 요약&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특정 건물을 가장 빨리 지을 때까지 걸리는 최소 시간을 알아내는 프로그램&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 주의할 점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최소 시간을 알아내는 프로그램 이라지만, 동시에 건축을 해야하는 건물의 경우&amp;nbsp;&lt;br /&gt;그 건물들이 모두 건축이 완성되어야 다음 건물을 건축할 수 있기 때문에 최소 시간을 그리 신경쓸 필요 없는 듯 하다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 알고리즘 (접근 방법)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;○ 위상 정렬 방식 (Queue)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;정점은 건축물을 말하는 것&lt;/b&gt;이며, &lt;b&gt;진입차수는 해당 건축물과 연결된 이전 건축물과의 간선이 몇개인지를 말하는 것&lt;/b&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1005번 사진에서는 1, 2, 3, 4 건축물들이 모두 정점임을 의미하고, 1번 정점이 3번 정점과 연결되어 있고 이를 간선이라고 표한다.&amp;nbsp;&lt;br /&gt;여기서 3번 정점으로 들어오는 진입차수는 1번과 연결된 간선 1개이다.&amp;nbsp;&lt;br /&gt;또 다른 예로 4번 정점으로 들어오는 진입차수는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- 3번에서 들어오는 간선 1개&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;- 2번에서 들어오는 간선 1개&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 2개의 진입차수를 갖고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 위상 정렬 방식으로 먼저 세팅을 해준다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;진입차수가 0인 모든 정점을 Queue 에 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757750214238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for(int v = 1; v &amp;lt; V+1; v++) {
    if(inDegree[v] == 0) { // 진입차수가 없다면 방문을 해볼까나 
        q.add(v);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Queue 가 공백이 될 때까지 반복 수행&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757750318102&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while(!q.isEmpty()) {
	// ... 로직 
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반복문 내에서 Queue 안에 있는 원소를 꺼내 해당 노드에서 나가는 간선(즉, 진입차수)을 그래프에서 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757750406579&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int curr = q.poll(); // 큐에 담긴 맨앞에꺼 꺼내기 
    List&amp;lt;Integer&amp;gt; list = adjList[curr]; 

    // curr 인접한 정점을 순회하면서 간선 제거
    for(int i = 0; i &amp;lt; list.size(); i++) {
        int next = list.get(i); // 2, 3

        // curr 과 인접한 정점의 진입차수를 하나씩 제거 해주기 
        inDegree[next]--;

        //...다음 로직 
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반복문 내에서 새롭게 진입차수가 0이 된 노드를 Queue 삽입한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1757750509521&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(inDegree[next] == 0) {
    q.add(next);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 반복문 내에서 인접한 정점을 건축하는 시간 (only 해당 정점의 건축시간) 과 해당 정점까지 오는데 걸린 건축시간과 비교하여 누적값을 넣어준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr; 예시 더보기 클릭&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 정점 (curr 변수에 담긴 값) 와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;인접한 정점(list.get(i) 해서 나오는 next 변수에 담긴 값) 을 건축하는 시간과&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그 인접한 정점까지 오는데 걸린 시간을 합한 것이 bt 이다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들자면,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 정점은 3번이며, 인접한 정점은 4번인 상황이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4번 정점까지 오는데 있어서 1 &amp;rarr; 3 &amp;rarr; 4 순으로 왔다면&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;- times[next] = 4번 정점을 짓는 건축시간 10초&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;- result[curr] = 3번 정점 건축물까지 짓는데 걸린 건축시간 110초 (10 + 100)&amp;nbsp;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;총 120 초가 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;result[next] 에 담긴 값은 현재 times[next] 에 담긴 값과 같기에&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 10초 &amp;lt; 120초 비교하여 120 초가 resutl[next] 에 들어가게 되는 구조이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1757750471628&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 차수가 갱신된 건물을 짓는데 걸리는 시간 
int bt = times[next] + result[curr];
result[next] = Math.max(bt, result[next]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. 최종 코드&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1757665593456&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class Main {
	 
	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine()); 
		StringBuilder sb = new StringBuilder();
		for(int t = 0; t &amp;lt; T; t++) {
			
			StringTokenizer st = new StringTokenizer(br.readLine());
			int V = Integer.parseInt(st.nextToken()); // 건물 개수 (정점의 개수) 
			int E = Integer.parseInt(st.nextToken()); // 건설 순서 규칙 개수 (간선의 개수) 
			
			ArrayDeque&amp;lt;Integer&amp;gt; q = new ArrayDeque&amp;lt;&amp;gt;(); 
			int[] times = new int[V+1];  
			List&amp;lt;Integer&amp;gt;[] adjList= new ArrayList[V+1]; 
			int[] inDegree = new int[V+1];
			int[] result = new int[V+1];
			
			st = new StringTokenizer(br.readLine());
			for(int v = 1; v &amp;lt; V+1; v++) {
				int time = Integer.parseInt(st.nextToken());
				times[v] = time; 
				result[v] = time;  
				adjList[v] = new ArrayList&amp;lt;&amp;gt;();				  
			}
			
			for(int e = 0; e &amp;lt; E; e++) {
				st = new StringTokenizer(br.readLine());
				int from = Integer.parseInt(st.nextToken()); // 정점 1
				int to = Integer.parseInt(st.nextToken());   // 정점 2 (진입차수를 받는 정점) 
				
				adjList[from].add(to); 		// 유향 리스트 
				inDegree[to]++; 			// 진입차수 세팅 
			}
			
			// 건축해야할 번호 받기 
			int W = Integer.parseInt(br.readLine());
			
			for(int v = 1; v &amp;lt; V+1; v++) {
				if(inDegree[v] == 0) { // 진입차수가 0 인 정점을 큐에 추가
					q.add(v);
				}
			}
			
			// 큐가 공백이 될때까지 돌리기
			while(!q.isEmpty()) {
				int curr = q.poll(); // 큐에 담긴 정점 꺼내기
				List&amp;lt;Integer&amp;gt; list = adjList[curr];  // 해당 정점과 연결된 다음 정점들 리스트
				
	        	// curr 인접한 정점을 순회하기 
				for(int i = 0; i &amp;lt; list.size(); i++) {
					int next = list.get(i); 
					
                    // 인접한 정점의 진입차수 감소 시키기
					inDegree[next]--;
					
                    // 진입차수가 0이 되었다면, 큐에 담기 
					if(inDegree[next] == 0) {
						q.add(next);
					}
		         	// 차수가 갱신된 건물을 짓는데 걸리는 시간 
					int bt = times[next] + result[curr];
                    // 위에서 계산한 값과 기존에 가지고 있는 값 중 큰 값을 누적 값에 저장
					result[next] = Math.max(bt, result[next]);
				}
				
			}
            
            // 특정 건물 (W) 를 짓는데 걸리는 시간 출력 
			sb.append(result[W]).append(&quot;\n&quot;);
		}
		
		// 결과 출력
		System.out.println(sb);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/BOJ &amp;amp; SWEA</category>
      <category>1005</category>
      <category>BOJ</category>
      <category>백준</category>
      <category>위상정렬</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/44</guid>
      <comments>https://lets-go-it.tistory.com/44#entry44comment</comments>
      <pubDate>Fri, 12 Sep 2025 11:33:51 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ: 7785/ JAVA] 회사에 있는 사람</title>
      <link>https://lets-go-it.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/7785&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/7785&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출퇴근&amp;nbsp;로그를&amp;nbsp;통해&amp;nbsp;현재&amp;nbsp;회사에&amp;nbsp;있는&amp;nbsp;모든&amp;nbsp;사람을&amp;nbsp;구하는&amp;nbsp;프로그램&amp;nbsp;작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 주의할 점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;현재 회사에 있는 사람의 이름을 사전 순의 역순으로 출력해야 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 알고리즘 (접근 방법)&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;○ 첫번째 방식 (HashMap - Array.sort)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. HashMap 을 이용하여, 직원 이름을 Key 값으로 삼고, &quot;enter&quot; 인 상태일 때만 HashMap 에 put 해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기존에 &quot;enter&quot; 로 Map에 저장된 직원이 &quot;leave&quot; 로그로 입력받은 경우, &lt;br /&gt;&amp;nbsp; &amp;nbsp; 직원이름 Key 를 호출하여 해당 직원을 Map 에서 삭제 해주었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &quot;enter&quot; 로 Map 에 저장되어 있는 직원들의 이름이 역순으로 정렬해주기 위해&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; 먼저 Array.sort 방식을 이용하여 이름 사전순으로 정렬을 해주었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 그 후, 정렬된 배열을 거꾸로 출력하는 식으로 문제를 해결하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756020685994&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

// 회사에 있는 사람
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        Map&amp;lt;String, Integer&amp;gt; enterLog = new HashMap&amp;lt;&amp;gt;();
        for(int n = 0; n &amp;lt; N; n++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            String employee = st.nextToken();
            String status = st.nextToken();

            if(status.equals(&quot;enter&quot;)) {
                enterLog.put(employee, n);
            } else {
                if(enterLog.containsKey(employee)) {
                    enterLog.remove(employee);
                }
            }
        }

        String[] arr = new String[enterLog.size()];
        int index = 0;
        for(Map.Entry&amp;lt;String, Integer&amp;gt; log : enterLog.entrySet()) {
            arr[index] = log.getKey();
            index++;
        }

        StringBuilder sb = new StringBuilder();
        Arrays.sort(arr, new Comparator&amp;lt;String&amp;gt;() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });

        for(int i = arr.length-1; i &amp;gt;= 0; i--) {
            sb.append(arr[i]).append(&quot;\n&quot;);
        }

        System.out.println(sb);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;○ 두번째 방식 (TreeSet)&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TreeSet 이라는 자료구조를 자주 안 써봐서 이번 기회를 통해 이해를 해보았다.&amp;nbsp;&lt;br /&gt;먼저 그냥 TreeSet 자료구조를 초기화 하게 될 경우, 데이터 삽입하는 경우에 따라 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;오름차순 형식으로&lt;span&gt; &lt;/span&gt;&lt;/span&gt;저장이 된다.&amp;nbsp;&lt;br /&gt;그러나 해당 문제에서는 역순으로 출력을 하길 원했기 때문에 &lt;br /&gt;처음부터 역순으로 초기화하는 &lt;b&gt;Collections.reverseOrder()&lt;/b&gt; 를 사용하여 내림차순 형식으로 데이터를 저장하게 할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;TreeSet 의 데이터 삽입 형식은 아래 구조를 잘 지켜나가는 이진 탐색 트리 이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오름차순 의 경우&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;왼쪽 노드 &amp;lt; 부모 노드, 부모 노드 &amp;lt; 오른쪽 노드&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내림차순 의 경우&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;왼쪽 노드 &amp;gt; 부모 노드, 부모 노드 &amp;gt; 오른쪽 노드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 추가적으로 보여주자면, &lt;br /&gt;데이터가 순서대로 10 &amp;rarr; 20 &amp;rarr; 5 &amp;rarr;&amp;nbsp; 15 순으로 add 되었을 때 오름차순은 아래와 같이 삽입된다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756021625895&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        10
       /  \
      5    20
          /
        15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 내림차순의 경우 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1756021691167&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        10
       /  \
     20    5
     /
   15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 처음부터 역순으로 저장이 되기 때문에, 우리는 위에서 접근한 첫번째 방식에서 크게 다를 것없이&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역순으로 정렬하는 과정만 빼고 코드를 작성하면 더 간결한 코드로 작성을 할 수 있게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1756021081292&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

// 회사에 있는 사람
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        // TreeSet 을 역순 정렬 Comparator 와 함께 초기화
        Set&amp;lt;String&amp;gt; enterLog = new TreeSet&amp;lt;&amp;gt;(Collections.reverseOrder());

        for(int n = 0; n &amp;lt; N; n++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            String employee = st.nextToken();
            String status = st.nextToken();

            if(&quot;enter&quot;.equals(status)) {
                enterLog.add(employee);
            } else {
                enterLog.remove(employee);
            }
        }

        StringBuilder sb = new StringBuilder();
        for(String employee :enterLog) {
            sb.append(employee).append(&quot;\n&quot;);
        }

        System.out.println(sb);
    }

}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/BOJ &amp;amp; SWEA</category>
      <category>7785</category>
      <category>BOJ</category>
      <category>Java</category>
      <category>백준</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/42</guid>
      <comments>https://lets-go-it.tistory.com/42#entry42comment</comments>
      <pubDate>Sun, 24 Aug 2025 16:49:55 +0900</pubDate>
    </item>
    <item>
      <title>[SWEA:14510/JAVA] 나무 높이</title>
      <link>https://lets-go-it.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AYFofW8qpXYDFAR4&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AYFofW8qpXYDFAR4&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1755957900176&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SW Expert Academy&quot; data-og-description=&quot;SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!&quot; data-og-host=&quot;swexpertacademy.com&quot; data-og-source-url=&quot;https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AYFofW8qpXYDFAR4&quot; data-og-url=&quot;https://swexpertacademy.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oFtX9/hyZC0rYpIr/D1WHEN3vpdoad3KDY4MBG1/img.png?width=600&amp;amp;height=315&amp;amp;face=0_0_600_315,https://scrap.kakaocdn.net/dn/eCbNX/hyZC5mwZpl/OdjTOIiWvXplUmgYcU7Ou0/img.png?width=3378&amp;amp;height=3378&amp;amp;face=0_0_3378_3378,https://scrap.kakaocdn.net/dn/mdWz1/hyZCZT8Yue/p2cqX3fCIiryVCcdsLlaS1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AYFofW8qpXYDFAR4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AYFofW8qpXYDFAR4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oFtX9/hyZC0rYpIr/D1WHEN3vpdoad3KDY4MBG1/img.png?width=600&amp;amp;height=315&amp;amp;face=0_0_600_315,https://scrap.kakaocdn.net/dn/eCbNX/hyZC5mwZpl/OdjTOIiWvXplUmgYcU7Ou0/img.png?width=3378&amp;amp;height=3378&amp;amp;face=0_0_3378_3378,https://scrap.kakaocdn.net/dn/mdWz1/hyZCZT8Yue/p2cqX3fCIiryVCcdsLlaS1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SW Expert Academy&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;swexpertacademy.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 날은 물을 준 나무의 키가 1 자라고, 둘째날은 물을 준 나무의 키가 2 자라고, 셋째날은 물을 준 나무의 키가 1 자라는 식.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홀수 번째 날은 키가 1자라고, 짝수번째 날은 키가 2 자란다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 나무의 키가 가장 키가 컸던 나무의 키와 같아지도록 할 수 있는 최소 날짜 수 구하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ 주의할 점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 날짜 수를 구하기 위해 물을 안주는 날도 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 알고리즘 [접근 방법]&amp;nbsp;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 입력값에서 가장 키가 큰 나무의 키를 저장하는 max 변수가 필요하다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;[max - 현재 나무의 키] 를 하여 모든 나무의 키가 max 변수만큼 자라려면 얼마만큼의 성장량이 필요한지 계산한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;growth 가 0일 경우에는 해당 나무가 키가 가장 큰 나무이기 때문에 반복문을 돌 때 제외한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;성장량의 최소 홀수 day 와 최소 짝수 day 를 각각 totalOddDay, totalEvenDay 에 누적시킨다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;[4, 2, 1] 순서로 나무의 키를 담은 배열이 있다고 가정하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 max 값은 4 이고 각각의 나무 성장량은 0, 2, 3 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0을 제외한 2와 3이 성장을 하려면 각각의 (홀,짝) 이 (0, 1), (1, 1) 이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 성장량 2를 가진 나무는 홀수 day 는 0일, 짝수 day 는 1일을 가지게 되어 &lt;br /&gt;1일차(물X), 2일차(물O) =&amp;gt; 키2 + 물2cm = 4cm 가 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 성장량 3을 가진 나무는 홀수 day 는 1일, 짝수 day 는 1일을 가지게 되어&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1일차(물O), 2일차(물O) =&amp;gt; 키1 + 물1cm + 물2cm = 4cm 가 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 totalOddDay 에 누적하게 되면 성장량 2의 홀수 0, 성장량 3의 홀수 1 을 하여 총 totalOddDay 는 1이 되고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;totalEvenDay 에는 성장량 2의 짝수1, 성장량 3의 짝수 1을 하여 총 totalEvenDay 는 2가 된다.&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 짝수day 가 더 클수록 최소날짜수는 최소가 아니게 되기 때문에, 홀수와 짝수의 균형을 맞춰주기 위해 균형을 맞춰주는 작업을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 총합을 구하는 작업을 홀수누적합이 더 큰 경우, 짝수 누적합이 더 큰 경우, 그리고 같은 경우를 나눠 아래와 같이 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;홀수누적합이 더 클 경우&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;예를 들어,&amp;nbsp; totalOddDay 이 3일, totalEvenDay 2일 일 때&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홀수 day 를 일자까지 표현하려면, totalOddDay * 2 - 1 를 해야함.&lt;br /&gt;(즉 3일인 경우, 1,3,5일을 거쳐 총 5일까지 가야하므로 3*2-1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;짝수 누적합이 더 큰 경우&amp;gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어,&amp;nbsp; totalOddDay 이 2일, totalEvenDay 4일 일 때&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;짝수 day 를 일자까지 표현하려면, totalEvenDay * 2 를 해야함.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;(즉 4일인 경우, 2,4,6,8일을 거쳐 총 8일까지 가야하므로 4*2)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;짝수 누적합 + 홀수 누적합&amp;gt; 인 경우&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, totalOddDay 이 5일, totalEvenDay 5일 일 때,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;totalOddDay + totalEvenDay 를 하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(1,2,3,4,5,6,7,8,9,10일을 거쳐 총 10일까지 가야하므로 5+5)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755957758857&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class SWEA_14510_나무높이 {
	
	public static void main(String[] args) throws NumberFormatException, IOException {

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine());
		for(int t = 1; t &amp;lt;= T; t++) {
			int N = Integer.parseInt(br.readLine());
			int[] trees = new int[N];
			
			// 나무 담기
			int max = Integer.MIN_VALUE;
			StringTokenizer st = new StringTokenizer(br.readLine());
			for(int n = 0; n &amp;lt; N; n++) {
				trees[n] = Integer.parseInt(st.nextToken());
				// 키가 가장 큰 나무 max 에 담기 
				max = Math.max(max, trees[n]); 
			}
			
			int totalOddDay = 0; // 최소홀수값
			int totalEvenDay = 0; // 최소짝수값
			for(int i = 0; i &amp;lt; N; i++) {
				
				// 성장량 기록 
				int growth = max - trees[i];
				if(growth != 0) { // growth 가 0인 경우, 가장 키가 큰 나무이기때문에 제외
					totalOddDay += growth % 2; // 홀수
					totalEvenDay += growth / 2; // 짝수
				}
			}
			
			// 균형을 맞춰주기 위해 작업 
			if(totalEvenDay &amp;gt; totalOddDay) {
				while(Math.abs(totalEvenDay - totalOddDay) &amp;gt; 1) {
					totalEvenDay--; 
					totalOddDay += 2; 
				}
			}
			
			int totalDay = 0;
			if(totalOddDay &amp;gt; totalEvenDay) {
				totalDay = 2 * totalOddDay - 1; 
			} else if(totalEvenDay &amp;gt; totalOddDay) {
				totalDay = 2 * totalEvenDay; 
			} else {
				totalDay = totalOddDay + totalEvenDay;
			}
			
			System.out.println(&quot;#&quot; + t + &quot; &quot; + totalDay);
		}
				
	} // main
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm/BOJ &amp;amp; SWEA</category>
      <category>14510 나무 높이</category>
      <category>Java</category>
      <category>SWEA</category>
      <category>나무 높이</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/41</guid>
      <comments>https://lets-go-it.tistory.com/41#entry41comment</comments>
      <pubDate>Sun, 24 Aug 2025 12:22:50 +0900</pubDate>
    </item>
    <item>
      <title>[SSAFY] 경력자였던 비전공자 SSAFY 14기 1차 합격 후기</title>
      <link>https://lets-go-it.tistory.com/40</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;서론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이번 SSAFY 14기 비전공자 1차 합격 후기 입니다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;지원 절차는 이렇습니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;1. 서류 접수&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. SW 적성진단&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 에세이 접수&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;4. 1차 합격자 발표&lt;/b&gt;&lt;br /&gt;&lt;b&gt;4. 인터뷰&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;5. 최종 합격자 발표&lt;/b&gt;&lt;br /&gt;&lt;b&gt;6. 입과&amp;nbsp;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;이 글을 통해 어떤 생각과 고민을 하며 에세이를 작성했는지&lt;/b&gt;&lt;br /&gt;&lt;b&gt;적성검사는 어떻게 준비하였는지 정리하고자 합니다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt; &lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;누구에게는 &lt;/b&gt;&lt;/span&gt;&lt;b&gt;&quot;딱히 꿀팁이 아닌데&quot;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;라고 생각하실 수 있으며 도움이 되지 않을 수 있습니다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;'그저 이러한 방식도 있구나' 하고 가볍게 읽어주시길 바랍니다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;저는 &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자격증 : 정보처리기사, SQLD &lt;/b&gt;&lt;br /&gt;&lt;b&gt;전공 : 중국어문학전공 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;경력 : 백엔드 개발자 1년 5개월 &lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 경력이 있음에도 어떠한 이유 때문에 SSAFY에 지원하게 될 예비 SSAFY 지원자분들께&lt;br /&gt;참고 사항이 되었으면 하는 바람으로 글을 남기게 되었습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지원 이유 (건너뛰셔도 됩니다 TMI 라 ㅎㅎ ..)&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱 제가 회사에 입사하게 된 이후부터 개발자 취업이 어려워지게 되었다는 소식을 많이 접하였습니다.&amp;nbsp;&lt;br /&gt;저는 SI 사업 위주의 프로젝트도 물론 재밌었으나 깊은 개발은 하기가 어렵다는 생각이 들었습니다.&amp;nbsp;&lt;br /&gt;그리하여 기존 회사와 저의 핏이 들어맞지 않다고 판단하여, 이직을 고려하게 되었을 즈음 퇴사와 환승이직 중 고민을 많이 하였습니다.&amp;nbsp;&lt;br /&gt;그러나 SI 위주의 사업으로 야근이 잦았고 주말출근을 할 때도 있었기에 이직을 준비하기에 쉽지 않았습니다.&amp;nbsp;&lt;br /&gt;물론 이러한 환경에서조차 환승이직을 할 사람들은 어떻게든 하는 것을 보고 저도 가능할 것이라고 생각하고&amp;nbsp;&lt;br /&gt;처음에는 먼저 회사 업무를 하며 코딩테스트 강의를 끊고 지속해본 결과 문제는 3가지였습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;첫번째는, 갑작스러운 사수의 퇴사로 저에게 백엔드 업무가 몰린 문제가 컸습니다.&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;앞선 문제로 인해 야근을 하는 시간이 많아지고 집에 오면 지쳐 잠이 드는 것을 반복하였습니다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;두번째는, 하루 1문제라도 풀자 라는 마인드로 임하여도 알고리즘 실력이 많이 부족하여 촉박한 시간속에 30분내로 문제를 끝내지 못하는 제 모습에 답답함을 느꼈습니다. &lt;br /&gt;게다가 문제를 해결하지 못해 블로그나 다른 사람의 코드를 보고 이해하는 과정에서 1시간.. 더 하면 2시간이나 소요되는 것에 스트레스가 과중되었습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;세번째는, CS 지식이 부족하다는 점이었습니다.&amp;nbsp;&lt;br /&gt;이직 또는 신입사원으로 다시 들어간다고 할 경우 개발자 취업시장이 험난해진 현재 서류 통과는 물론, 이를 넘어서 면접까지 간다 하더라도 기술면접에서 부족한 저의 CS 지식을 보고 줄줄이 탈락할 것이라고 생각하였습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;네.. 서론이 길었네요.&amp;nbsp;&lt;br /&gt;앞선 세가지 문제로 인해, 저는 퇴사를 결심했고 단기간 내에 알고리즘 향상과 CS 지식 및 기본기를 탄탄히 하여&lt;br /&gt;더 높은 곳으로 가고자 SSAFY 지원을 하게 되었습니다.&amp;nbsp;&lt;br /&gt;( 더 자세한게 궁금하시다면, 댓글로 질문주세요!)&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SW 적성 진단 및 에세이 작성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서류접수를 한 모든 지원자는&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;비전공자 일 경우 수추리영역 + CT (Computer Thinking) 시험을 진행하고&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;전공자 일 경우 코딩테스트 를 진행합니다.&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;여기서 저는 비전공자 이기 때문에 수추리 영역 + CT 시험에 대한 이야기를 하겠습니다.&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;수추리 영역 책 추천 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;저는 1번 해커스 SSAFY 책과 3번 해커스 GSAT 으로 공부를 진행하였는데요.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;해커스 GSAT 은 해커스 SSAFY 문제에 비해 훨씬 어렵게 문제가 나와있어서 해커스 SSAFY 책으로 &lt;br /&gt;먼저 감을 잡은 후 해커스 GSAT 문제를 풀며 익혀나갔습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그러나 저는 여기서 해커스 SSAFY 책보다는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;에듀윌 SSAFY 책을 더 추천 드립니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;사실 에듀윌 책은 풀어보진 않았으나 해커스 SSAFY 책을 다 풀고 적성진단 하루 전날 불안한 마음에 서점에 가서&amp;nbsp;&lt;br /&gt;한번 에듀윌 책을 펼쳐 문제 유형을 보았을 때 해커스 SSAFY 책 수준보다 조금 어렵고 Gsat 보다는 쉬운 느낌의 문제였습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 적성진단의 문제는 대외비로 인해 자세한 얘긴 할 수 없으나,&amp;nbsp;&lt;br /&gt;해커스 SSAFY 보단 체감상 어렵게 느껴졌기 때문에 에듀윌SSAFY 책을 권장드립니다.&amp;nbsp;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;1. SSAFY 해커스 통합 기본서&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x4irT/btsPhknyurX/DqwEoBgNb2uJ3CdSyfJwr1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x4irT/btsPhknyurX/DqwEoBgNb2uJ3CdSyfJwr1/img.webp&quot; data-alt=&quot;해커스 SSAFY 통합 기본서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x4irT/btsPhknyurX/DqwEoBgNb2uJ3CdSyfJwr1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx4irT%2FbtsPhknyurX%2FDqwEoBgNb2uJ3CdSyfJwr1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;337&quot; height=&quot;450&quot; data-origin-width=&quot;337&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해커스 SSAFY 통합 기본서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 에듀윌 취업 SSAFY 통합 기본서&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bziVpE/btsPfKOHWKC/p9xi0hNZnkapO9uelK02zk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bziVpE/btsPfKOHWKC/p9xi0hNZnkapO9uelK02zk/img.webp&quot; data-alt=&quot;에듀윌 SSAFY 통합 기본서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bziVpE/btsPfKOHWKC/p9xi0hNZnkapO9uelK02zk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbziVpE%2FbtsPfKOHWKC%2Fp9xi0hNZnkapO9uelK02zk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;343&quot; height=&quot;450&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에듀윌 SSAFY 통합 기본서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 해커스 GSAT 통합기본서&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtD1Ro/btsPgu5pM2q/kbgrT09tMEp5JQNBRW28Lk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtD1Ro/btsPgu5pM2q/kbgrT09tMEp5JQNBRW28Lk/img.jpg&quot; data-alt=&quot;해커스 GSAT&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtD1Ro/btsPgu5pM2q/kbgrT09tMEp5JQNBRW28Lk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtD1Ro%2FbtsPgu5pM2q%2FkbgrT09tMEp5JQNBRW28Lk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;547&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해커스 GSAT&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CT 영역 공부&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트 문제를 풀어보신 분들이라면 어떤 식으로 문제에 접근 해야할지&amp;nbsp;&lt;br /&gt;금방 이해하실 것입니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;b&gt;CT 는 코딩테스트의 문제를 코드로 푸는 것이 아닌 손으로 문제를 푼다고 생각하면 쉬울 것 같습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;그래서 &lt;a href=&quot;https://www.acmicpc.net/step&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;백준&lt;/span&gt;&lt;/a&gt; 이라는 사이트의 기초 문제를 많이 풀어보며 감을 익히는 것을 추천드립니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;또한 해당 SSAFY 지원자 오픈채팅방을 추천드립니다.&lt;br /&gt;&lt;a href=&quot;https://open.kakao.com/o/gQYSpD9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://open.kakao.com/o/gQYSpD9&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;SSAFY 삼성 청년 SW 아카데미 (14기 준비)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;#11기모집인원수가비번 #청년 #소프트웨어 #ssafy #싸피 #SSAFY #14기&quot; data-og-host=&quot;open.kakao.com&quot; data-og-source-url=&quot;https://open.kakao.com/o/gQYSpD9&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dixBXt/hyZjEi8vpN/GxVGhMycVu823xKLJE7SfK/img.png?width=800&amp;amp;height=400&amp;amp;face=112_196_184_268&quot; data-og-url=&quot;https://open.kakao.com/o/gQYSpD9&quot;&gt;&lt;a href=&quot;https://open.kakao.com/o/gQYSpD9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://open.kakao.com/o/gQYSpD9&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dixBXt/hyZjEi8vpN/GxVGhMycVu823xKLJE7SfK/img.png?width=800&amp;amp;height=400&amp;amp;face=112_196_184_268');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SSAFY 삼성 청년 SW 아카데미 (14기 준비)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;#11기모집인원수가비번 #청년 #소프트웨어 #ssafy #싸피 #SSAFY #14기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;open.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오픈채팅방에는 SSAFY 의 이전 기수 선배들이 예비 SSAFY 인들을 위해 정성스럽게 정보를 공유해주시고 계십니다.&amp;nbsp;&lt;br /&gt;여기에서 CT 문제를 어떻게 접근하면 좋을지, 어떤 문제를 풀며 감을 잡을지 등을 알 수 있어서 좋았습니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;저는 1차 적성 공부 기간이 2주였습니다. &lt;br /&gt;그런데 비전공자에 수학 머리가 없던 저는 2주만으론 턱없이 부족함을 느꼈습니다.&lt;br /&gt;짧은 시간내에 수추리영역을 풀어야 하기때문에 이전으로 돌아간다면 한달 정도 준비를 진행 할 것 같습니다. &lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;에세이 작성 (시간 절약 방법)&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저는 이 에세이 접수를 거의 하루5시간 만에 작성하여 제출했던 것 같습니다.&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;결론부터 말씀드리자면, ChatGPT 같은 생성형 AI 를 잘 활용하는 것이 시간 절약 팁 입니다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 때 당시 SW 적성진단 시험을 치루고 에세이 작성할 기간이 일주일정도 남았었습니다.&amp;nbsp;&lt;br /&gt;그러나 해당 주에는 팀에서 진행하던 프로젝트가 끝나는 날이어서 야근이 끊이지 않았으며&lt;br /&gt;금요일에는 저의 마지막 근무로 인한 회식을 진행함으로써&amp;nbsp;&lt;br /&gt;에세이를 작성할 시간이 거의 없었습니다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;먼저 SSAFY 에세이를 쓰기 전 제가 생각하는 주의사항에 대해 언급해보겠습니다!&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;1. SSAFY 에세이는 회사 자소서 와는 다르다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. SSAFY 는 교육생을 뽑는 자리이다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 그러나 SSAFY 는 취업할 확률이 높은 교육생을 뽑는다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;저는 이 3가지를 주의하면서 에세이를 아래 항목을 기반으로 접근하였습니다.&amp;nbsp;&lt;br /&gt;&lt;b&gt;1. A 라는 &lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;문제를&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b&gt; B 라는 방식으로 해결했으나, 이러한 점이 부족했다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 부족한 점을 메꾸기 위해 SSAFY 에서 어떠한 것을 채워나가고 싶다.&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그리고 경력자로써 &lt;span style=&quot;color: #333333;&quot;&gt;가장 중요한 &lt;/span&gt;퇴사하고 SSAFY 에 지원한 이유를 설명하기 위해 노력했습니다.&amp;nbsp;&lt;br /&gt;사실 제가 생각한 SSAFY 는 SSAFY 가 없어도 취업할 수 있는 사람보다 SSAFY 가 꼭 필요하여 절실한 사람을 뽑는다 였습니다. &lt;br /&gt;그래서 어떻게든 SSAFY 만의 특징과 저의 부족한 점을 엮으려 하였습니다. &lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;서론 -&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 경험한 문제&lt;br /&gt;(SI 사업으로 기능을 급하게 개발 -&amp;gt; 검증 부족 -&amp;gt; 과거에 부족했던 검증으로 인해 수정하면서 현재의 시간을 잡아먹음) 언급&amp;nbsp;&lt;br /&gt;&lt;b&gt;본론 -&lt;/b&gt; &lt;br /&gt;1. 해당 문제를 어떻게 해결했는지 대략적으로 언급함 (프로세스 이해 인지 후 해당 기능에 대한 의도 파악 및 모르는 것 바로 묻기)&amp;nbsp;&lt;br /&gt;2. 1번 문제를 해결하면서 부족한 점을 파악하고 퇴사한 이유(=SSAFY 지원이유)를 언급&amp;nbsp;&lt;br /&gt;(속도가 우선시 됨으로써 설계적인 측면의 부족함을 느낌 -&amp;gt; 개발자로서의 정체성 찾고픔)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결론 - &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본론에서 언급한 이유와 엮어 SSAFY 에서 어떠한 특징을 활용하여 도움이 될 것 같아 지원함 으로 마무리&amp;nbsp;&lt;br /&gt;(SSAFY 만의 특징인 특화 및 기업연계 프로젝트 언급 -&amp;gt; 개발자로서의 방향성과 기준을 세울 수 있어서 지원)&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이렇게 어느정도 틀을 잡은 후 생성형 AI 를 통해 문장을 매끄럽게 만들어달라.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하면 저의 부족한 글쓰기 능력을 아주 잘 보완해줍니다 ㅎㅎ..&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;제가 사용한 ChatGpt 의 프롬프트 예시를 한번 적어보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 나는 지금 ssafy 지원하기 위한 에세이를 작성하고 있어. 그런데 ssafy 에서는 왜 자소서 라고 안하고, 에세이라고 했을까? 그거 에 대한 포인트를 찾아서 글을 쓰고 싶어.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 챗 지피티가 어느정도 SSAFY 에세이 포인트를 언급하면서&amp;nbsp; 이 대화를 기억하게 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. 먼저 싸피의 에세이는 자기성찰, 동기가 중요해. 또한 싸피의 인재상은 SW 의 개념과 원리를 이해하고 규칙을 찾아 문제를 해결하는 인재(논리적 사고), 열정과 도전정신으로 교육에 적극 참여하는 인재(열정), 지속적으로 학습하고 교육에 몰두하여 목표를 성취하는 인재(학습의지) 야. 나는 대충 이러한 형태로 틀을 잡을 예정이야. 백엔드 개발자로 1년 5개월을 일하면서 내게 있어 어려웠던 경험을 말하고 그 경험을 어떻게 해결했다. 그러나 이러한 점이 부족해서 싸피에 어떠한 이유때문에 지원하려 한다. 이렇게 말이야. 틀 한번 잡아줘볼래 ?&lt;br /&gt;=&amp;gt; SSAFY 의 인재상을 기억하게 만들고, 틀을 잡아주면 그 틀을 기반으로 빠르게 작성할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 어느정도 틀이 잡히면 이제부터 본인이 어떠한 에세이를 만들어 갈 것인지 분위기를 잡아나가면 될 것 같습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;추가로 에세이 도입부 를 인상 깊게 남기면 더욱 눈에 띈다고 생각하여 &lt;/b&gt;제가 작성한 에세이를 GPT&amp;nbsp;&amp;nbsp;에게 붙여넣고 &lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;rdquo;강렬하고 인상적인 도입부로 요약해서 여러개 예시 보여달라&amp;ldquo; 하여 &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;ldquo;문제 생기면 나중에 고치지 뭐&amp;rdquo; &lt;/b&gt;같은 정말 도발적인 에세이 도입부를 완성한 것 같습니다 &lt;b&gt;  &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마지막으로..&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;저는 수추리영역에서 시간 내에 문제를 많이 풀지못하였지만 CT 는 대체로 다 풀어서 냈을 정도로 쉽게 느껴졌던 것 같습니다. &lt;br /&gt;제 개인적인 생각으로는 CT 와 에세이 에서 점수를 얻지 않았을까.. 합니다! &lt;br /&gt;&lt;br /&gt;그래서 에세이 부분에서 제가 어떻게 작성했는지 최대한 내용을 담아보려 노력했는데 팁이 되었으면 좋겠습니다 &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blLSBu/btsPj46FPMd/ZvlWVkMKa9EVNhP4S8CZB1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blLSBu/btsPj46FPMd/ZvlWVkMKa9EVNhP4S8CZB1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blLSBu/btsPj46FPMd/ZvlWVkMKa9EVNhP4S8CZB1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblLSBu%2FbtsPj46FPMd%2FZvlWVkMKa9EVNhP4S8CZB1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;273&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;마지막으로 1차 합격한 사진을 남기며 다음 싸피 15기 분들도 합격하셨으면 좋겠습니다. &lt;br /&gt;읽어주셔서 감사합니다 :)&lt;/p&gt;</description>
      <category>사회인 준비생/SSAFY</category>
      <category>14기</category>
      <category>15기</category>
      <category>1차</category>
      <category>SSAFY</category>
      <category>sw적성진단</category>
      <category>경력자</category>
      <category>비전공자</category>
      <category>시간절약</category>
      <category>싸피</category>
      <category>에세이</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/40</guid>
      <comments>https://lets-go-it.tistory.com/40#entry40comment</comments>
      <pubDate>Thu, 3 Jul 2025 18:50:45 +0900</pubDate>
    </item>
    <item>
      <title>[책: IT 세계의 괴물들] 소프트웨어에 대해</title>
      <link>https://lets-go-it.tistory.com/32</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서론&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테마별로 책에 대한 기록 중이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테마 1이었던 반도체에 대해서는 &lt;a href=&quot;https://lets-go-it.tistory.com/31&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;을 읽어보도록 하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 테마2인 소프트웨어이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 본론으로 들어가보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소프트웨어&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OS (Operating System : 운영체제)&lt;br /&gt;CPU, GPU 같은 반도체들에게 작업절차를 안내한다.&amp;nbsp;&lt;br /&gt;자세하게는 작업 스케줄링과 리소스를 관리한다.&amp;nbsp;&lt;br /&gt;OS 는 커널(kernel) 을 통해 작업절차를 전달한다.&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;그래서 순차적인 흐름은 어떠한 어플리케이션에서 시스템 호출이 들어오면, OS 는 커널을 통해 작업절차를 안내하는 형식이다.&amp;nbsp;&lt;/span&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;OS 에는 커널말고도 Device Driver, Memory management 라는 게 있다.&amp;nbsp;&lt;br /&gt;- Device Driver 는 키보드, 마우스, USB 등 외부 입출력 장치와 연결 및 통신을 제어해준다.&lt;br /&gt;- Memory Management 는 시스템 전체 메모리 사용량을 모니터링하며,&amp;nbsp;&lt;br /&gt;&amp;nbsp; 메모리가 부족하면 일부를 SSD 에 분배했다가 램에 공간이 생기면 다시 로드를 한다.&lt;br /&gt;&amp;nbsp; 또한, 다른 프로그램이 할당되지 않은 메모리를 마음대로 사용할 수 없게 접근을 막는다.&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;시스템 호출 (System Call) : 어플리케이션이 OS 에 특정 서비스에 대한 요청을 하는 것이다.&amp;nbsp;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;예를 들어, PPT 어플리케이션에서 사용자가 저장버튼을 눌렀다고 가정했을 때&lt;br /&gt;1. PPT 어플리케이션은 OS 에게로 시스템 콜을 보낸다.&amp;nbsp;&lt;br /&gt;2. OS 는 커널을 통해 반도체에 작업절차를 전달한다.&amp;nbsp;&lt;br /&gt;3. CPU 는 메모리에서 작업절차를 검색하고 수행하는데, 이 때 CPU 는 PPT 와 같은 어플리케이션, OS 등의 작업절차를 직접 수행한다.&lt;br /&gt;4. RAM 은 CPU 에서 작업 중인 데이터를 SSD 에 저장한다.&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;커널 (Kernal)&amp;nbsp;&lt;br /&gt;커널은 위에서 언급했듯이 OS 가 CPU 나 GPU, RAM 에게 작업절차를 전달할 때 사용이 되기도 하지만&amp;nbsp;&lt;br /&gt;커널 스케줄러를 통해 반도체의 리소스들을 관리한다.&amp;nbsp;&lt;br /&gt;또한 어느 한 프로그램이 자원(CPU, GPU, RAM 등) 을 독점하지 못하게 다른 프로그램에도 자원을 할당하고 어떤 프로그램이 조용하면 대기상태로 만든다.&lt;/li&gt;
&lt;li&gt;BIOS / UEFI&lt;br /&gt;컴퓨터가 켜지면 가장 먼저 동작해서 시스템 HW 를 초기화 시키고 부트로더를 실행시키는 펌웨어이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;부트로더&amp;nbsp;&lt;br /&gt;컴퓨터가 켜지면 BIOS / UEFI 에 의해서 실행이 된 후 OS 를 SSD 에서 RAM 으로 로딩하는 펌웨어이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;부팅&lt;br /&gt;사용자가 컴퓨터를 부팅하게 되면 BIOS 기상나팔이 울리고 ROM 에서 부트로더가 OS 를 싣고 출발한다.&amp;nbsp;&lt;br /&gt;그리고 부트로더가 OS 를 SSD 에 내려주고 SSD 에서 OS 는 RAM 으로 로딩시켜준다.&amp;nbsp;&lt;br /&gt;이러한 과정을 부팅이라고 한다.&amp;nbsp;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;펌웨어 : 특정 하드웨어 장치에 포함된 영구적인 소프트웨어이다.&amp;nbsp;&lt;br /&gt;ROM : 한번 기록된 정보를 읽을 수만 있는 메모리 반도체로 비휘발성 메모리이다.&amp;nbsp;&lt;br /&gt;- 여기서 RAM 으로 로딩되는 이유로 OS 는 아주 중요하기 때문에 RAM 의 메모리 공간을 가장 먼저 확보해야하기 때문이다.&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 77px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 37.4805%; height: 20px;&quot; colspan=&quot;2&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 43.0621%; height: 20px;&quot;&gt;정의&lt;/td&gt;
&lt;td style=&quot;width: 19.4574%;&quot;&gt;예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;td style=&quot;width: 14.8449%; height: 40px;&quot; rowspan=&quot;2&quot;&gt;프로그램&lt;/td&gt;
&lt;td style=&quot;width: 22.6356%; height: 23px;&quot;&gt;시스템 소프트웨어&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 43.0621%; height: 23px;&quot;&gt;- 어플리케이션이 원활히 운영될 수 있도록 컴퓨터의 리소스를 관리한다.&amp;nbsp;&lt;br /&gt;- 각종 장치와 프로그램을 지원/제어하는 필수 소프트웨어&lt;/td&gt;
&lt;td style=&quot;width: 19.4574%;&quot;&gt;OS&lt;br /&gt;펌웨어&lt;br /&gt;미들웨어&amp;nbsp;&lt;br /&gt;드라이버&lt;br /&gt;소프트웨어 플랫폼 등&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 22.6356%; height: 17px;&quot;&gt;어플리케이션 소프트웨어&lt;/td&gt;
&lt;td style=&quot;width: 43.0621%; height: 17px;&quot;&gt;- 유저가 특정 목적을 위해 직접 사용한다.&lt;br /&gt;- 특정 작업을 실행하면 컴퓨터의 리소스들이 이를 수행하도록 지시하는 소프트웨어&amp;nbsp;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 19.4574%;&quot;&gt;게임&lt;br /&gt;영상/음악 재생&lt;br /&gt;문서 작성&lt;br /&gt;웹 브라우저&lt;br /&gt;주식투자&amp;nbsp;&lt;br /&gt;금융거래&lt;br /&gt;코딩 IDE&amp;nbsp;&lt;br /&gt;영상편집&lt;br /&gt;등&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 37.4805%; height: 17px;&quot; colspan=&quot;2&quot;&gt;데이터&lt;/td&gt;
&lt;td style=&quot;width: 43.0621%; height: 17px;&quot;&gt;유저가 프로그램을 사용하면서 저장, 파생되는 정보&lt;/td&gt;
&lt;td style=&quot;width: 19.4574%;&quot;&gt;게임 세이브 파일&lt;br /&gt;인터넷 쿠키&lt;br /&gt;GPS 메타데이터 등&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1Byte : 소프트웨어를 표현하는 일반적인 단위인 8bit&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기계어 : 2진수(바이너리) 로 구성된 컴퓨터 CPU 가 이해하고 실행할 수 있는 언어&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Bit Flip&amp;nbsp;&lt;br /&gt;메모리 상의 하나 혹은 그 이상의 비트가 0 에서 1이나 1에서 0 으로 바뀌는 현상이다.&amp;nbsp;&lt;br /&gt;이는 메모리의 정보가 실제와 다르게 저장되어 발생할 수 있다.&amp;nbsp;&lt;br /&gt;일반적으로 Bit Flip 은 반도체 자체나 트랜지스터의 결함, 전파장애, 고온 고습 등에 의해 발생한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C 언어&amp;nbsp;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;최초 1972 년에 출시된 언어&lt;/li&gt;
&lt;li&gt;포인터&amp;nbsp;&lt;br /&gt;다른 변수의 메모리 주소를 저장하는 변수&lt;br /&gt;메모리 위치를 직접 조작할 수 있기에 효율적이고 유연한 프로그래밍 가능&amp;nbsp;&lt;br /&gt;이를 통해 메모리를 할당하고 해제한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;메모리 사이즈가 작다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;컴파일 언어 (Compiled Language)&amp;nbsp;&lt;br /&gt;밑의 빌드를 통해 인간이 읽을 수 있는 코드를 기계가 읽을 수 있는 코드와 실행 파일로 만드는 언어를 말한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;빌드 순서
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;컴파일러를 통해 어셈블리 코드가 된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;어셈블리코드는 다시 어셈블러를 거쳐서 오브젝트 코드가 된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;오브젝트 코드는 링커를 통해 필요한 실행 파일의 형태를 갖춘다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이 모든작업을 빌드라고 한다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Heap&amp;nbsp;&lt;br /&gt;데이터 구조와 변수 저장을 위해 동적으로 할당되는 RAM 의 메모리 영역이다.&amp;nbsp;&lt;br /&gt;이 때, 메모리 영역을 잘 사용하지 않으면&lt;br /&gt;RAM 내 할당된 메모리 블록 범위를 넘어서 메모리에 액세스 하려고 할 때 메모리 오버플로우 라는 에러가 발생할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;컴파일 (Compile)&amp;nbsp;&lt;br /&gt;프로그래밍 언어로 작성된 소스 코드를 기계어로 변환하는 과정을 말한다.&amp;nbsp;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;컴파일러(Compiler) : 소스코드를 읽어 기계어로 변환하고, 이를 실행할 수 있는 실행파일을 생성하는 빌드의 절차중 하나.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Science</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/32</guid>
      <comments>https://lets-go-it.tistory.com/32#entry32comment</comments>
      <pubDate>Sun, 16 Mar 2025 18:30:32 +0900</pubDate>
    </item>
    <item>
      <title>[책:  IT 세계의 괴물들] 반도체에 대해</title>
      <link>https://lets-go-it.tistory.com/31</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;서론&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_KakaoTalk_20250309_175440466.jpg&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M5LaV/btsMELIT8vX/164AFVEU96k2MwYintKDUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M5LaV/btsMELIT8vX/164AFVEU96k2MwYintKDUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M5LaV/btsMELIT8vX/164AFVEU96k2MwYintKDUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM5LaV%2FbtsMELIT8vX%2F164AFVEU96k2MwYintKDUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;607&quot; data-filename=&quot;edited_edited_KakaoTalk_20250309_175440466.jpg&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비전공자인 백엔드 개발자 2년차로서 CS 지식이 너무 부족하단 것을 아직도 체감하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하다. 책을 사놓고 잘 읽지도 않고 읽고나서도 머릿속에 남지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런 바보같은 나를 위해 읽히기도 쉽고 머리에도 잘 남는 &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000211595468&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;IT 세계의 괴물들&lt;/b&gt;&lt;/a&gt; 이라는 책의 만화책을 사봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(광고 및 협찬 아님 - 협찬 들어오면 좋겠다..IT 관련해서 들어오면 더 좋겠다 ..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 만화책이 생각보다 많은 내용을 포함하고 있어서 한번 정리를 해놔야겠다는 생각에 기록해본다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 읽자마자 최대한 책을 보지 않고 기록해볼 것이기 때문에 틀린 내용을 적을 수도 있다.&amp;nbsp;&lt;br /&gt;또한 엄청 대충 기록할 것이기 때문에 추가적인 사항은 해당 책을 사보자 ^^ (다시 한번 말하지만 협찬 및 광고아님)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반도체&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;트랜지스터&lt;/b&gt;&lt;br /&gt;전류가 흐르거나, 흐르지 않게 하여 0과 1의 2가지 경우의 수를 표현하는 컴퓨터의 세포다.&amp;nbsp;&lt;br /&gt;이것은 컴퓨터가 0과 1만 알아들을 수 있게 근본원리이다.&amp;nbsp;&lt;br /&gt;0 과 1로만 경우의 수를 표현하여 이진법을 사용한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모스펫&lt;/b&gt;&lt;br /&gt;트랜지스터의 변환형으로 이 모스펫이 수십~수백억개가 모여 컴퓨터의 CPU(혹은 프로세서) 가 된다.&amp;nbsp;&lt;br /&gt;그리고 모스펫이 결합된 패키지를 IC(Integrated Circuit : 직접회로)&amp;nbsp; 라고 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;나노 공정&amp;nbsp;&lt;/b&gt;&lt;br /&gt;트랜지스터를 IC에 많이 넣으면 넣을 수록 IC 의 연산 성능이 빠르게 발전한다.&amp;nbsp;&lt;br /&gt;즉, IC 의 회로 선폭을 가늘게 만들어 트랜지스터를 많이 넣을 수 있다.&lt;br /&gt;이 때 회로의 선폭 크기 단위가 나노단위로 만들어지고 있다. (연도가 지날수록 IC 의 선폭이 더 줄어들고 있음)&lt;br /&gt;2022년을 기준으로 IC 선폭은 3나노미터까지 가늘게 만들고 있다고 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;큐비트&amp;nbsp;&lt;/b&gt;&lt;br /&gt;만약 IC 의 선폭이 1나노미터를 넘어 원자보다 더 작아지게 된다고 가정했을 때 &lt;br /&gt;미시세계에서의 물리법칙을 따라야한다.&lt;br /&gt;양자역학에서는 원자처럼 아주 작은 입자들이 동시에 여러형태를 가진다고 한다.&amp;nbsp;&lt;br /&gt;이를 비유하자면 '&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8A%88%EB%A2%B0%EB%94%A9%EA%B1%B0%EC%9D%98_%EA%B3%A0%EC%96%91%EC%9D%B4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;슈뢰딩거의 고양이'&lt;/a&gt; 라는데, 이 고양이는 상자에서 살아있거나 죽어있는 2가지 형태를 동시에 가진다고 한다.&amp;nbsp;&lt;br /&gt;양자컴퓨팅에서 트랜지스터의 역할을 하는게 큐비트 인데,&amp;nbsp;&lt;br /&gt;큐비트는 0이면서도 1인 상태를 동시에 가질 수 있어서 트랜지스터보다 훨씬 강력하고 빠른 계산이 가능하다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팹리스&amp;lt;Fabless: Fabrication(생산) + less&amp;gt;&lt;/b&gt;&lt;br /&gt;IC 를 설계만 하여 생산은 위탁하는 회사로 대표적인 예는 애플이 있다. &lt;br /&gt;(삼성은 IC 의 설계와 생산을 모두 하는 종합반도체 회사라고 한다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파운더리&amp;nbsp;&lt;/b&gt;&lt;br /&gt;팹리스업체에서 위탁받아, IC 를 생산만 하는 회사로 대표적인 예는 TSMC 가 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;EUV 노광장비&lt;/b&gt;&lt;br /&gt;n나노공정 중에는 설계된 회로를 빛으로 웨이퍼에 상을 투사하는 노광작업이 필요하다고 한다.&lt;br /&gt;이 때 사용되는 빛이 EUV 라서 EUV 노광장비가 필요하다.&amp;nbsp;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;b&gt;EUV&lt;/b&gt; 란 ?&amp;nbsp;&lt;br /&gt;인간 가시범위를 넘어간 극자외선이다.&lt;/div&gt;
&lt;/div&gt;
그래서 파운더리 업체들은 EUV 노광장비를 개발하는 회사에서 EUV 노광장비를 사기 위해 경쟁한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU&lt;/b&gt;&amp;nbsp;&lt;br /&gt;CPU 는 심장과도 같다.&amp;nbsp;&lt;br /&gt;CPU 의 클럭주기(Clock cycle)는 심장이고&amp;nbsp;&lt;br /&gt;CPU 의 클럭속도(Clock Speed) 심장박동 속도로 예시를 들어보자.&amp;nbsp;&lt;br /&gt;클럭주기는 CPU 의 연산을 위해 진동(심장박동)을 할 때마다 전기신호를 모스펫에게 전달한다.&lt;br /&gt;클럭속도는 1초에 심장박동(클럭주기)가 몇 번 뛰는지 측정하고 헤르츠(Hz) 로 표시한다.&amp;nbsp;&lt;br /&gt;즉, 클럭속도가 빠를 수록 더 많은 연산과 작업을 진행할 수 있다.&amp;nbsp;&lt;br /&gt;ex) 1초에 43억번 심장박동이 뛴다면 속도는 4.3GHz 이다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CPU 의 코어&lt;/b&gt;&amp;nbsp;&lt;br /&gt;CPU 는 코어를 가지고 있으며 코어에는 컨트롤 유닛이 있다.&amp;nbsp;&lt;br /&gt;이 컨트롤 유닛은 할당받은 어플리케이션을 2진수로 해독한 뒤 명령을 일련의 작업으로 변환해서&lt;br /&gt;모스펫들이 On/Off 를 수행할 수 있도록 전달한다.&amp;nbsp;&lt;br /&gt;만약, 싱글코어일 경우, 여러 어플리케이션이 컨트롤 유닛에 할당되면 &lt;br /&gt;여러 어플리케이션을 돌아가면서 작업해야한다.&amp;nbsp;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;이 때 돌아가면서 작업하는 것을 &lt;a href=&quot;https://lets-go-it.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;컨텍스트 스위칭&lt;/b&gt;&lt;/a&gt;이라고 표현한다. 하지만 코어가 한 작업에 리소스를 집중할 수 없기에 성능이 떨어지게 된다. 그래서 코어는 많으면 많을 수록 여러 어플리케이션을 작동시키기에 좋다.&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GPU&lt;/b&gt;&lt;br /&gt;CPU 의 단점은 그래픽과 관련된 작업은 상당히 느리다는 점이다.&amp;nbsp;&lt;br /&gt;그래서 등장하게 된 것이 GPU.&amp;nbsp;&lt;br /&gt;GPU 는 수천개의 코어가 있기 때문에 그래픽이나 비주얼 작업할 때 병렬 작업에 최적화된 GPU 가 성능이 좋다.&amp;nbsp;&lt;br /&gt;그렇기에 CPU 와 GPU 가 협업해서 게임로직과 데이터 흐름을 제어하고, 물리엔진과 AI 를 관리하는 것은 CPU 가, 3D 모델, 텍스처 등 데이터를 GPU 로 보내면 GPU 는 받은 데이터를 처리하고 이미지를 생성하는 등 그래픽 렌더링과 비주얼 데이터를 처리한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RAM&amp;nbsp;&lt;/b&gt;&lt;br /&gt;CPU 가 필요한 데이터나 프로그램을 RAM 에게 요청하면 SSD 에서 데이터를 가져와서 보관해둔다.&lt;br /&gt;그러면 CPU 는 SSD 까지 갈 필요 없이 RAM 이 보관하고 있는 데이터에 빠르게 접근 할 수 있게 된다.&amp;nbsp;&lt;br /&gt;그래서 RAM 의 크기가 클수록 보관하는 용량이 많아지기 때문에 SSD 에 가는 횟수는 줄어들게 되므로&amp;nbsp;&lt;br /&gt;컴퓨터의 성능은 더 좋아지게 된다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시&lt;/b&gt;&amp;nbsp;&lt;br /&gt;CPU 내에는 캐시라는 것이 있는데&lt;br /&gt;웹 페이지, 쿠키, 최근 열었던 문서 같이 더 자주 접근해야 하는 것은 캐시라는 메모리에 저장해둔다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;휘발성 메모리&amp;nbsp;&lt;/b&gt;&lt;br /&gt;컴퓨터가 꺼지면 메모리가 날아가는 것을 휘발성 메모리라고 한다.&lt;br /&gt;RAM 과 캐시가 여기에 속한다.&amp;nbsp;&lt;br /&gt;이 때 CPU 는 특정 데이터를 저장할 때 먼저 RAM 에 그 데이터를 쓰고 RAM 이 다시 SSD 에 저장한다.&lt;br /&gt;(조금더 책을 읽어보니 정확히는 RAM 이 직접적으로 움직이는 게 아닌 OS 에 의해서 데이터가 RAM 에서 SSD 로 옮겨진다고 한다.)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비휘발성 메모리&amp;nbsp;&lt;/b&gt;&lt;br /&gt;SSD 는 전원이 꺼져도 저장된 프로그램이나 데이터가 휘발되지 않는다.&amp;nbsp;&lt;br /&gt;이유는 Flash Memory 의 특성 때문이라는데, Flash Memory 에는 특수한 기술이 적용되어 있다.&amp;nbsp;&lt;br /&gt;작은 전하가 고립된 게이트에 저장되어 전원이 차단되어도 데이터를 유지할 수 있다고 한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;끝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 기억이 잘 안나서 한번 더 다시 보면서 기록했다..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 반복하면서 읽어야 할 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Science</category>
      <category>CS지식</category>
      <category>it세계의 괴물들</category>
      <category>개발자</category>
      <category>도서</category>
      <category>독서</category>
      <category>반도체</category>
      <category>백엔드</category>
      <category>비전공자</category>
      <category>운영체제</category>
      <author>is낫널</author>
      <guid isPermaLink="true">https://lets-go-it.tistory.com/31</guid>
      <comments>https://lets-go-it.tistory.com/31#entry31comment</comments>
      <pubDate>Sun, 9 Mar 2025 17:56:19 +0900</pubDate>
    </item>
  </channel>
</rss>