<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>It's easy,    if you try</title>
    <link>https://sohee-dev.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 2 Jun 2026 06:40:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>s5he2</managingEditor>
    <image>
      <title>It's easy,    if you try</title>
      <url>https://tistory1.daumcdn.net/tistory/4532556/attach/ec6d15ba70c943218ae623d4c31a2bf4</url>
      <link>https://sohee-dev.tistory.com</link>
    </image>
    <item>
      <title>[SpringBoot] 스프링부트와 JPA 활용 섹션 7 - 변경 감지와 병합</title>
      <link>https://sohee-dev.tistory.com/155</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;명심&lt;/b&gt;&lt;br /&gt;API를 만들때는 엔티티를 반환하면 안된다 ! (정보유출, API 스펙 변화)&lt;/blockquote&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;html 파일&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;lt;head th:replace=&quot;fragments/header :: header&quot;&amp;gt;&lt;/span&gt; : jsp의 include 와 같다.&amp;nbsp;&lt;br /&gt;fragments 폴더 내의 header.html 파일 내의 th:fragment=&quot;header&quot;를 가져다 include 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;타임리프 기본설정&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1678406836464&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
    thymeleaf:
      prefix: classpath:/templates/
      suffix: .html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반환한 문자&lt;/span&gt;&lt;span&gt;( &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;home &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;과 스프링부트 설정 &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;prefix &lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;suffix &lt;/span&gt;&lt;span&gt;정보를 사용해서 렌더링할 뷰&lt;/span&gt;&lt;span&gt;( &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;html &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;를 찾는다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;타임리프 layout 스타일&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Include-style layouts&lt;/li&gt;
&lt;li&gt;Hierarchical-style layouts&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;타임리프 문법&lt;/b&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;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;&amp;lt;td th:text=&quot;${member.address?.city}&quot;&amp;gt;&amp;lt;/td&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
?를 붙이면 null을 무시한다.&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;tr th:each=&quot;item : ${items}&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
Controller에서 model.addAttribute(&quot;items&quot;, items); 를 통해 담아온 items를 하나씩 꺼내 사용&lt;/li&gt;
&lt;/ul&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;@GetMapping(&quot;items/{itemId}/edit&quot;) 이때 itemId는 @PathVariable(&quot;itemId&quot;) Long itemId &amp;lt;- 와 같은 형태의 파라미터로 받아와 사용한다.&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;return &quot;redirect:/orders&quot;; // 리다이랙트&lt;/code&gt;&lt;/pre&gt;
기본적으로 http 로 리다이랙트됨. (설정 할 수 있는지 ? )&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Error Validation&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;컨트롤러 함수에서@Valid + BindingResult result 를 파라미터로 받아오기 -&amp;gt; 오류를 result에 담는다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;result.hasErrors()&amp;nbsp;&lt;/li&gt;
&lt;li&gt;html 파일에서 th:class=&quot;${#fields.hasErrors('name')}&quot; 설정하면됨.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;폼 객체 vs 엔티티 직접 사용&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;요구사항이 정말 단순할 때는 폼 객체( MemberForm ) 없이 엔티티( Member )를 직접 등록과 수정 화면에서 사용해도 된다. 하지만 화면 요구사항이 복잡해지기 시작하면, 엔티티에 화면을 처리하기 위한 기능이 점점 증가한다. 결과적으로 엔티티는 점점 화면에 종속적으로 변하고, 이렇게 화면 기능 때문에 지저분해진 엔티티는 결국 유지보수하기 어려워진다. &lt;br /&gt;실무에서 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다. 화면이나 API에 맞는 폼 객체나 DTO를 사용하자. 그래서 화면이나 API 요구사항을 이것들로 처리하고, 엔티티는 최대한 순수하게 유지하자.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1678407591235&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;container&quot;&amp;gt;
	&amp;lt;div th:replace=&quot;fragments/bodyHeader :: bodyHeader&quot;/&amp;gt;
	&amp;lt;form role=&quot;form&quot; action=&quot;/members/new&quot; th:object=&quot;${memberForm}&quot; method=&quot;post&quot;&amp;gt;
		&amp;lt;div class=&quot;form-group&quot;&amp;gt;
            &amp;lt;label th:for=&quot;name&quot;&amp;gt;이름&amp;lt;/label&amp;gt;
            &amp;lt;input type=&quot;text&quot; th:field=&quot;*{name}&quot; class=&quot;form-control&quot; placeholder=&quot;이름을 입력하세요&quot; th:class=&quot;${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'&quot;&amp;gt;
            &amp;lt;p th:if=&quot;${#fields.hasErrors('name')}&quot; th:errors=&quot;*{name}&quot;&amp;gt;Incorrect date&amp;lt;/p&amp;gt;
		&amp;lt;/div&amp;gt;
        
		&amp;lt;div class=&quot;form-group&quot;&amp;gt;
			&amp;lt;label th:for=&quot;city&quot;&amp;gt;도시&amp;lt;/label&amp;gt;
			&amp;lt;input type=&quot;text&quot; th:field=&quot;*{city}&quot; class=&quot;form-control&quot; placeholder=&quot;도시를 입력하세요&quot;&amp;gt; 
        &amp;lt;/div&amp;gt;
		
        &amp;lt;div class=&quot;form-group&quot;&amp;gt;
			&amp;lt;label th:for=&quot;street&quot;&amp;gt;거리&amp;lt;/label&amp;gt;
			&amp;lt;input type=&quot;text&quot; th:field=&quot;*{street}&quot; class=&quot;form-control&quot; placeholder=&quot;거리를 입력하세요&quot;&amp;gt;
		&amp;lt;/div&amp;gt;

		&amp;lt;div class=&quot;form-group&quot;&amp;gt;
			&amp;lt;label th:for=&quot;zipcode&quot;&amp;gt;우편번호&amp;lt;/label&amp;gt;
			&amp;lt;input type=&quot;text&quot; th:field=&quot;*{zipcode}&quot; class=&quot;form-control&quot; placeholder=&quot;우편번호를 입력하세요&quot;&amp;gt;
        &amp;lt;/div&amp;gt;
        
        &amp;lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&amp;gt;Submit&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
	&amp;lt;br/&amp;gt;
	&amp;lt;div th:replace=&quot;fragments/footer :: footer&quot; /&amp;gt;
&amp;lt;/div&amp;gt; &amp;lt;!-- /container --&amp;gt;&lt;/code&gt;&lt;/pre&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;변경 감지와 병합(merge) - 준영속 엔티티를 수정하는 2가지 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;준영속 엔티티&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다&lt;/span&gt;&lt;span&gt;.&lt;br /&gt;(&lt;/span&gt;&lt;span&gt;여기서는 &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;itemService.saveItem(book) &lt;/span&gt;&lt;span&gt;에서 수정을 시도하는 &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;Book &lt;/span&gt;&lt;span&gt;객체다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;Book &lt;/span&gt;&lt;span&gt;객체는 이미 &lt;/span&gt;&lt;span&gt;DB &lt;/span&gt;&lt;span&gt;에 한번 저장되어서 식별자가 존재한다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span&gt;이렇게 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다&lt;/span&gt;&lt;span&gt;.) &lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;변경 감지로 준영속 엔티티 수정하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1678414514158&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
    Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다.
	findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다. 
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법&lt;br /&gt;트랜잭션 안에서 엔티티를 다시 조회&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;변경할 값 선택 -&amp;gt; 트랜잭션 커밋 시점에 변경 감지&lt;/span&gt;&lt;span&gt;(Dirty Checking)가&lt;/span&gt;&lt;span&gt; 동작해서 데이터베이스에 &lt;/span&gt;&lt;span&gt;UPDATE SQL &lt;/span&gt;&lt;span&gt;실행 &lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;size16&quot;&gt;&lt;b&gt;병합은 모든 속성을 변경하기 때문에&amp;nbsp; 병합시 값이 없으면 null이 들어갈 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1678420153768&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 
	Item mergeItem = em.merge(itemParam);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwjQq/btr2ZzGzUCQ/upK1VbyDuwvVe3wogBmZMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwjQq/btr2ZzGzUCQ/upK1VbyDuwvVe3wogBmZMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwjQq/btr2ZzGzUCQ/upK1VbyDuwvVe3wogBmZMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmwjQq%2Fbtr2ZzGzUCQ%2FupK1VbyDuwvVe3wogBmZMK%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;1586&quot; height=&quot;710&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;병합 동작 방식 &lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #242424;&quot;&gt;merge()&lt;/span&gt;&lt;span&gt;를실행한다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;파라미터로 넘어온 준영속 엔티티의 식별자 값으로 &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;차 캐시에서 엔티티를 조회한다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;2-1. &lt;/span&gt;&lt;span&gt;만약 &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고&lt;/span&gt;&lt;span&gt;, 1&lt;/span&gt;&lt;span&gt;차 캐시에 저장한다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;조회한 영속 엔티티&lt;/span&gt;&lt;span&gt;( &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;mergeMember &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;에 &lt;/span&gt;&lt;span style=&quot;color: #242424;&quot;&gt;member &lt;/span&gt;&lt;span&gt;엔티티의 값을 채워 넣는다&lt;/span&gt;&lt;span&gt;. (member &lt;/span&gt;&lt;span&gt;엔티티의 모든 &lt;/span&gt;&lt;span&gt;바뀐다&lt;/span&gt;&lt;span&gt;.) &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;값을 &lt;/span&gt;&lt;span&gt;mergeMember&lt;/span&gt;&lt;span&gt;에 밀어 넣는다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span&gt;이때 &lt;/span&gt;&lt;span&gt;mergeMember&lt;/span&gt;&lt;span&gt;의 &lt;/span&gt;&lt;span&gt;&amp;ldquo;&lt;/span&gt;&lt;span&gt;회원&lt;/span&gt;&lt;span&gt;1&amp;rdquo;&lt;/span&gt;&lt;span&gt;이라는 이름이 &lt;/span&gt;&lt;span&gt;&amp;ldquo;&lt;/span&gt;&lt;span&gt;회원명변경&lt;/span&gt;&lt;span&gt;&amp;rdquo;&lt;/span&gt;&lt;span&gt;으로 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;영속 상태인 &lt;/span&gt;&lt;span&gt;mergeMember&lt;/span&gt;&lt;span&gt;를 반환한다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;병합시 동작 방식을 간단히 정리 &lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다&lt;/span&gt;&lt;span&gt;.(&lt;/span&gt;&lt;span&gt;병합한다&lt;/span&gt;&lt;span&gt;.) &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 &lt;/span&gt;&lt;span&gt;UPDATE SQL&lt;/span&gt;&lt;span&gt;이 실행 &lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;병합을 사용하면서 null이 업데이트 되는 문제를 해결하려면&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;변경 폼 화면에서 모든 데이터를 항상 유지해야 한다&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;. 이는 변경가능한 데이터만을 주로 노출하는 실무에서 오히려 번거롭다. 이를 해결하기 위해서는 엔티티 변경시에 항상 변경감지를 사용하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컨트롤러에서 어설프게 엔티티를 생성하지 마세요.&lt;br /&gt;트랜잭션이 있는 서비스 계층에 식별자( id )와 변경할 데이터를 명확하게 전달하세요.(파라미터 or dto) &lt;br /&gt;트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하세요. &lt;br /&gt;트랜잭션 커밋 시점에 변경 감지가 실행됩니다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1678421458286&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class ItemService {
	
    private final ItemRepository itemRepository;
    
    /**
    * 영속성 컨텍스트가 자동 변경
    */
    @Transactional
    public void updateItem(Long id, String name, int price, int stockQuantity)
    { // 식별자와 변경할 데이터 명확히 전달 받기
        Item item = itemRepository.findOne(id); // 영속 상태 엔티티 조회
        item.setName(name); // 엔티티 데이터 직접 변경
        item.setPrice(price);
        item.setStockQuantity(stockQuantity);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 서비스 계층&lt;/p&gt;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/155</guid>
      <comments>https://sohee-dev.tistory.com/155#entry155comment</comments>
      <pubDate>Wed, 8 Mar 2023 23:45:37 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] 스프링부트와 JPA 활용 섹션 5, 6 - 도메인 개발 / JPA 동적 쿼리 (JPQL, JPA Criteria)</title>
      <link>https://sohee-dev.tistory.com/154</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주문 도메인 개발&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주문 엔티티 개발&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;생성 메서드&lt;span&gt;&amp;nbsp;및&amp;nbsp;&lt;/span&gt;&lt;/span&gt;비즈니스 로직 작성&lt;/li&gt;
&lt;li&gt;@NoArgsConstructor(access = AccessLevel.PROTECTED) : 외부에서 set 할 수 없도록 하는 옵션&lt;/li&gt;
&lt;li&gt;주문 검색 기능 개발 (아래 참고)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주문 리포지토리 개발&lt;/li&gt;
&lt;li&gt;주문 서비스 개발
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new Order(); 등의 각각의 set은 지양하고 createOrder 함수를 생성하는것이 유지보수 측면에서 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주문 기능 테스트&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고 : 주문 서비스의 주문과 주문 취소 메서드를 보면 비즈니스 로직 대부분이 엔티티에 있다. 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다. 이처럼 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴(&lt;a href=&quot;https://martinfowler.com/eaaCatalog/domainModel.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://martinfowler.com/eaaCatalog/domainModel.html&lt;/a&gt;)이라 한다. 반대로 엔티티에는 비즈니스 로직이 거의 없고, 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을 트랜잭션 스크립트 패턴(&lt;a href=&quot;https://martinfowler.com/eaaCatalog/transactionScript.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://martinfowler.com/eaaCatalog/transactionScript.html&lt;/a&gt;)이라 한다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주문 검색 기능 개발 (JPA 동적 쿼리)&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OrderSearch 클래스 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건인 memberName, orderStatus 속성 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리포지토리에서 검색 함수(findAll) 생성&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) JPQL로 처리(번거롭고, 버그가 발생할 수 있음)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1677758927925&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;Order&amp;gt; findAllByString(OrderSearch orderSearch) {
    //language=JPAQL
    String jpql = &quot;select o From Order o join o.member m&quot;;
    boolean isFirstCondition = true;
      
	//주문 상태 검색
	if (orderSearch.getOrderStatus() != null) {
        if (isFirstCondition) {
            jpql += &quot; where&quot;;
            isFirstCondition = false;
        } else {
            jpql += &quot; and&quot;;
	    }
        jpql += &quot; o.status = :status&quot;;
    }
	
    //회원 이름 검색
	if (StringUtils.hasText(orderSearch.getMemberName())) {
    	if (isFirstCondition) {
            jpql += &quot; where&quot;;
            isFirstCondition = false;
        } else {
            jpql += &quot; and&quot;;
	    }
        jpql += &quot; m.name like :name&quot;;
    }
      
   	TypedQuery&amp;lt;Order&amp;gt; query = em.createQuery(jpql, Order.class) .setMaxResults(1000); //최대 1000건
    if (orderSearch.getOrderStatus() != null) {
        query = query.setParameter(&quot;status&quot;, orderSearch.getOrderStatus());
    }
    
    if (StringUtils.hasText(orderSearch.getMemberName())) {
        query = query.setParameter(&quot;name&quot;, orderSearch.getMemberName());
    }
    return query.getResultList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) JPA Criteria로 처리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1677759348501&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;Order&amp;gt; findAllByCriteria(OrderSearch orderSearch) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery&amp;lt;Order&amp;gt; cq = cb.createQuery(Order.class);
    Root&amp;lt;Order&amp;gt; o = cq.from(Order.class);
    Join&amp;lt;Order, Member&amp;gt; m = o.join(&quot;member&quot;, JoinType.INNER); //회원과 조인
    List&amp;lt;Predicate&amp;gt; criteria = new ArrayList&amp;lt;&amp;gt;();
    //주문 상태 검색
    if (orderSearch.getOrderStatus() != null) {
          Predicate status = cb.equal(o.get(&quot;status&quot;),
		  orderSearch.getOrderStatus());
          criteria.add(status);
    }

    //회원 이름 검색
    if (StringUtils.hasText(orderSearch.getMemberName())) {
          Predicate name = cb.like(m.&amp;lt;String&amp;gt;get(&quot;name&quot;), &quot;%&quot; +
						  orderSearch.getMemberName() + &quot;%&quot;);
          criteria.add(name);
	}
    
    cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));

	TypedQuery&amp;lt;Order&amp;gt; query = em.createQuery(cq).setMaxResults(1000); //최대 1000건
    return query.getResultList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;JPA Criteria는 JPA 표준 스펙이지만 실무에서 사용하기에 너무 복잡하다. 결국 다른 대안이 필요하다. 많은 개발자가 비슷한 고민을 했지만, 가장 멋진 해결책은 Querydsl이 제시했다. Querydsl 소개장에서 간단히 언급하겠다. 지금은 이대로 진행하자.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JPA 더티 채킹&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;orderItem.setStatus(OrderStatus.CANCEL);&lt;/li&gt;
&lt;li&gt;getItem().addStock(count):&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 엔티티의 상태나 수량 등 속성이 변하는 함수를 작성하면 JPA가 변화를 감지하고 DB에 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;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/154</guid>
      <comments>https://sohee-dev.tistory.com/154#entry154comment</comments>
      <pubDate>Thu, 2 Mar 2023 21:22:34 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] 스프링부트와 JPA 활용 섹션 3, 4 - 어플리케이션 아키텍처/ 개발순서 / 엔티티, 리포지토리, 서비스, 테스트 작성</title>
      <link>https://sohee-dev.tistory.com/153</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1) 어플리케이션 아키텍처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;계층형 구조 사용&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EfFRM/btrZV8cI195/SWSyvUKDf2CkyQPpHyTEAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EfFRM/btrZV8cI195/SWSyvUKDf2CkyQPpHyTEAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EfFRM/btrZV8cI195/SWSyvUKDf2CkyQPpHyTEAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEfFRM%2FbtrZV8cI195%2FSWSyvUKDf2CkyQPpHyTEAk%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;1568&quot; height=&quot;462&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;controller, web: 웹 계층 (컨트롤러는 repository에 접근가능, 단방향)&lt;/li&gt;
&lt;li&gt;service: 비즈니스 로직, 트랜잭션 처리&lt;/li&gt;
&lt;li&gt;repository: JPA를 직접 사용, 엔티티 매니저 사용&lt;/li&gt;
&lt;li&gt;domain: 엔티티가 모여있는 계층, 모든 계층에서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패키지 구조&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;domain&lt;/li&gt;
&lt;li&gt;exception&lt;/li&gt;
&lt;li&gt;repository&lt;/li&gt;
&lt;li&gt;service&lt;/li&gt;
&lt;li&gt;web&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발순서&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서비스, 리포지토리 계층 개발&lt;/li&gt;
&lt;li&gt;테스트 케이스 작성&lt;/li&gt;
&lt;li&gt;웹계층 적용&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 개발
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 개발 (엔티티 클래스 작성은 섹션 2 참고 (&lt;a href=&quot;https://sohee-dev.tistory.com/152&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sohee-dev.tistory.com/152&lt;/a&gt;))&lt;/li&gt;
&lt;li&gt;리포지토리 개발&lt;/li&gt;
&lt;li&gt;서비스 개발&lt;/li&gt;
&lt;li&gt;기능 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;화면과 기능 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;API 개발
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ajax 통신 , 안드로이드같은 네이티브 통신시에도 필요&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2) 엔티티 클래스 개발&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(&lt;/span&gt;&lt;a href=&quot;https://sohee-dev.tistory.com/152&quot;&gt;https://sohee-dev.tistory.com/152&lt;/a&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;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex&amp;gt; item 엔티티가 stockQuantity(수량) 속성을 가질 때, 수량증가(addStock) - 이는 데이터를 가지고 있는 곳에서 비즈니스 메소드를 작성한 것.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;setter를 통해 외부에서 변경하는 것이 아니기 떄문에 변경 포인트를 명시해둘 수 있고, 관리가 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3) 리포지토리 개발&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB에 직접 접근&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;@Repository : 스프링컨테이너에 리포지토리를 스프링 빈으로 등록&lt;/li&gt;
&lt;li&gt;@PersistenceContext , EntityManager: &lt;br /&gt;javax.persistence.*;&lt;br /&gt;스프링이 엔티티매니저를 생성하여 주입해준다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@RequiredArgsConstructor + final 필드 EntityManager 로 대체 가능 (일관성 있는 코드 작성에 유용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;em.createQuery(&quot;JPQL문법&quot;, 반환형식);&lt;br /&gt;sql은 테이블을 기준으로, JPQL은 엔티티를 기준으로 쿼리 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4) 서비스 개발&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;@Service&lt;/li&gt;
&lt;li&gt;@Transactional : 데이터 변경 로직이 있다면 꼭 명시해야함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;javax와 springframework (권장)에서 제공&lt;/li&gt;
&lt;li&gt;@Transactional(readOnly = true): 조회할때 성능 최적화&lt;br /&gt;class에 readOnly=true 옵션을 주고, 쓰기 기능을 하는 함수 위에 @Transactional (readOnly=false (default임))을 명시해도됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Autowired
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필드 인잭션 : 간편하지만, 테스트 시에 불편함 (변경 불가능), 변경 불가능 명시를 위해 final&amp;nbsp; (그림 1)&lt;/li&gt;
&lt;li&gt;setter 인잭션 : 변경이 가능하다, 변경이 가능해서 오히려 위험 (그림 2)&lt;/li&gt;
&lt;li&gt;생성자 인잭션 : 중간에 변경 불가능, 테스트시에도 좋음 (그림 3)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@RequiredArgsConstructor : 롬복 문법으로, final이 있는 필드에 대해서 생성자를 만들어줌.&lt;/li&gt;
&lt;li&gt;@NoArgsConstructor: 기본 생성자만 생성&lt;/li&gt;
&lt;li&gt;@AllArgsConstructor: 전체 변수를 생성하는 생성자를 만들어준다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;옵션 access = AccessLevel.PROTECTED : 외부에서 set 할 수 없도록 하는 옵션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중복 검증 등의 로직 추가 (EXCEPTION)&lt;br /&gt;throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);&lt;br /&gt;멀티스레드의 환경을 고려(동시접근)하여, 테이블에 유니크 조건 제약을 두는 등 한번더 검증하기.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dq6sK/btrZJV7lFRV/EG5qX6Y3ke21jldkzPYnN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dq6sK/btrZJV7lFRV/EG5qX6Y3ke21jldkzPYnN0/img.png&quot; data-alt=&quot;그림1 (필드 인잭션)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dq6sK/btrZJV7lFRV/EG5qX6Y3ke21jldkzPYnN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDq6sK%2FbtrZJV7lFRV%2FEG5qX6Y3ke21jldkzPYnN0%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;1254&quot; height=&quot;124&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1 (필드 인잭션)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmcbKg/btrZJ2ZQBxe/9bMpwin4oim1giSepHfXQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmcbKg/btrZJ2ZQBxe/9bMpwin4oim1giSepHfXQk/img.png&quot; data-alt=&quot;그림2 (세터 인잭션)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmcbKg/btrZJ2ZQBxe/9bMpwin4oim1giSepHfXQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmcbKg%2FbtrZJ2ZQBxe%2F9bMpwin4oim1giSepHfXQk%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;1524&quot; height=&quot;250&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2 (세터 인잭션)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn2dGf/btrZYTfpFL1/Aw5pmQ2RJJg93k3YYpGdw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn2dGf/btrZYTfpFL1/Aw5pmQ2RJJg93k3YYpGdw0/img.png&quot; data-alt=&quot;그림3 (생성자 인잭션)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn2dGf/btrZYTfpFL1/Aw5pmQ2RJJg93k3YYpGdw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn2dGf%2FbtrZYTfpFL1%2FAw5pmQ2RJJg93k3YYpGdw0%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;1268&quot; height=&quot;262&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3 (생성자 인잭션)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5) 기능 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 테스트 요구사항 파악&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 테스트 코드 작성&lt;br /&gt;&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;command + shift + T : 테스트 클래스 생성 (JUnit4)&lt;/li&gt;
&lt;li&gt;class에 명시&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SpringBootTest&lt;/li&gt;
&lt;li&gt;@Transactional&lt;/li&gt;
&lt;li&gt;@RunWith(SpringRunner.class)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테스트 함수 위에 명시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Test
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Test(expected = IllegalStateException.class) : 예외가 발생하면 테스트 성공 &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;fail(&quot;실패 메시지&quot;);&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-1) 메모리 DB 사용하기&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;테스트는 케이스 격리된 환경에서 실행하고, 끝나면 데이터를 초기화하는 것이 좋다. 그런 면에서 메모리 DB를 사용하는 것이 가장 이상적이다. 추가로 테스트 케이스를 위한 스프링 환경과, 일반적으로 애플리케이션을 실행하는 환경은 보통 다르므로 설정 파일을 다르게 사용하자. 다음과 같이 간단하게 테스트용 설정 파일을 추가하면 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 폴더 안에 application.yml 파일을 복사 붙여넣기해서 사용하면 이위치 파일을 먼저 참조한다.&lt;/p&gt;
&lt;pre id=&quot;code_1676804127442&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
#  datasource:
#    url: jdbc:h2:mem:testdb
#    username: sa
#    password:
#    driver-class-name: org.h2.Driver
#  jpa:
#    hibernate:
#      ddl-auto: create
#    properties:
#  hibernate:
#        show_sql: true
#    format_sql: true
#open-in-view: false

logging.level:
org.hibernate.SQL: debug
#  org.hibernate.type: trace&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;스프링 부트는 datasource 설정이 없으면, 기본적으로 메모리 DB를 사용하고, driver-class도 현재 등록된 라이브러리를 보고 찾아준다. 추가로 ddl-auto 도 create-drop 모드로 동작한다. 따라서 데이터소스나, JPA 관련된 별도의 추가 설정을 하지 않아도 된다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;create-drop 모드 : 어플리케이션이 종료될때 table을 drop&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/153</guid>
      <comments>https://sohee-dev.tistory.com/153#entry153comment</comments>
      <pubDate>Sun, 19 Feb 2023 20:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] 스프링부트와 JPA 활용 섹션 2 - 도메인 분석 설계 / Entity 클래스 작성</title>
      <link>https://sohee-dev.tistory.com/152</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인 분석 설계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 요구사항 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 동작하는 화면을 보며 기능을 설계한다&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 도메인 모델과 테이블 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RDB는 다대다 관계를 가질 수 없어서 매핑테이블을 가져야함. (1:N / N:1)&lt;/li&gt;
&lt;li&gt;공통속성을 가진경우 상속 구조로 표현&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ppMWB/btrZCpTBsc3/kHJ3g4KIHmgQLa4AMX2XdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ppMWB/btrZCpTBsc3/kHJ3g4KIHmgQLa4AMX2XdK/img.png&quot; data-alt=&quot;도메인 설계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ppMWB/btrZCpTBsc3/kHJ3g4KIHmgQLa4AMX2XdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FppMWB%2FbtrZCpTBsc3%2FkHJ3g4KIHmgQLa4AMX2XdK%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;1172&quot; height=&quot;468&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도메인 설계&lt;/figcaption&gt;
&lt;/figure&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;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ii1QP/btrZzaw2d4r/CuV6eWDwdgJRCgXKHCKrXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ii1QP/btrZzaw2d4r/CuV6eWDwdgJRCgXKHCKrXK/img.png&quot; data-alt=&quot;엔티티 분석&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ii1QP/btrZzaw2d4r/CuV6eWDwdgJRCgXKHCKrXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIi1QP%2FbtrZzaw2d4r%2FCuV6eWDwdgJRCgXKHCKrXK%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;1188&quot; height=&quot;748&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;엔티티 분석&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때, Address 는 임베디드 타입 - 회원과 배송에서 사용됨.&lt;/li&gt;
&lt;li&gt;테이블 분석 (테이블에 맞는 형태로 작성 - PK/ FK 지정, 네이밍 등)&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;1018&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1o3Bd/btrZAHgjSDt/cGIQrLR76ozDgE1x7W9r60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1o3Bd/btrZAHgjSDt/cGIQrLR76ozDgE1x7W9r60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1o3Bd/btrZAHgjSDt/cGIQrLR76ozDgE1x7W9r60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1o3Bd%2FbtrZAHgjSDt%2FcGIQrLR76ozDgE1x7W9r60%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;1190&quot; height=&quot;1018&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;1018&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;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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/947Qm/btrZy9Y8L4U/VONG7q3q9qFxWhmpzqVkR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/947Qm/btrZy9Y8L4U/VONG7q3q9qFxWhmpzqVkR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/947Qm/btrZy9Y8L4U/VONG7q3q9qFxWhmpzqVkR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F947Qm%2FbtrZy9Y8L4U%2FVONG7q3q9qFxWhmpzqVkR1%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;771&quot; height=&quot;224&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&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;/li&gt;
&lt;li&gt;@ManyToMany 는 사용 지양&lt;br /&gt;중간 테이블( CATEGORY_ITEM )에 컬럼을 추가할 수 없고, 세밀하게 쿼리를 실행하기 어렵기 때문에 실무에서 사용하기에는 한계가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1676601170858&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ManyToMany
@JoinTable(name = &quot;category_item&quot;,
	joinColumns = @JoinColumn(name = &quot;category_id&quot;),
	inverseJoinColumns = @JoinColumn(name = &quot;item_id&quot;))
private List&amp;lt;Item&amp;gt; items = new ArrayList&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엔티티 클래스 개발&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실무에서는 Getter는 열어두고, Setter는 닫아두는 것이 좋다.&lt;/li&gt;
&lt;li&gt;Setter의 경우 변경 지점이 명확하도록 변경을 위한 비즈니스 메서드를 별도로 제공해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;domain 폴더 생성&lt;/li&gt;
&lt;li&gt;엔티티 클래스 생성 (@Entity)&lt;/li&gt;
&lt;li&gt;임베디드 클래스 생성 (@Embadedable , @Embeded)&lt;br /&gt;임베디드 타입은 사용자가 직접 정의한 값 타입이다. (객체지향적, 응집력 상승)&lt;/li&gt;
&lt;li&gt;추상 클래스 생성 (카테고리에 속함 - 공통속성을 가진경우 상속 구조로 표현)&lt;br /&gt;자식들은 부모의 속성을 가지면서 자신의 고유 속성을 추가.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;enum 클래스 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1676597575588&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum OrderStatus {
	ORDER, CANCEL
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Enumerated(EnumType.STRING) : enum 타입을 숫자가 아닌 지정해둔 string으로 테이블에 저장.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어노테이션&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;@Id : 엔티티 식별자&lt;/li&gt;
&lt;li&gt;@GeneratedValue: @Id와 같이 붙인다.&lt;/li&gt;
&lt;li&gt;@Column(name=&quot;&quot;) : 칼럼명 지정&lt;/li&gt;
&lt;li&gt;@OneToMany(mappedBy = &quot;member&quot;) : 연관관계의 주인이 아닌곳에 지정, member 칼럼에 매핑&amp;nbsp;&lt;/li&gt;
&lt;li&gt;@ManyToOne(fetch = FetchType.LAZY):&amp;nbsp; 연관관계의 주인, referencedColumnName 속성을 생략하면 자동으로 일대다 관계를 맺고있는 엔티티의 PK와 매핑이 된다.&lt;/li&gt;
&lt;li&gt;@JoinColumn(name=&quot;&quot;) : 테이블에 name으로 매핑해서 칼럼명 저장&lt;/li&gt;
&lt;li&gt;추상 클래스 관련 어노테이션&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Inheritance(strategy = InheritanceType.SINGLE_TABLE): 상속받은 객체의 칼럼들 모두 하나의 테이블의 속성으로 지정&lt;/li&gt;
&lt;li&gt;@DiscriminatorColumn(name = &quot;dtype&quot;): &lt;span style=&quot;background-color: #ffffff;&quot;&gt;부모 클래스에 선언,&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; 하위 클래스를 구분한기위한 용도이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;@OneToOne(): 1:1 관계에서는 액세스를 더 많이 하는 곳에 FK를 둔다. (FK가 아닌곳에 mappedBy 지정)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;fetch = FetchType.LAZY 옵션&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;지연 로딩 (반대는 즉시 로딩 FetchType.EAGER) : 예를 들어 Team이라는 객체에 지연로딩 옵션을 걸어두면 , getTeam을 할때에는 쿼리가 나가지 않고 Team의 프록시 객체가 호출된다. getTeam().getName() 을 하는 경우, 실제로 쿼리가 DB에 조회된다. 만약 관계를 맺고 있는 다른 객체(ex: Member)와 비즈니스 로직상 동시에 사용되는 경우가 더 많다면, 즉시 로딩을 사용하는 것이 좋다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;즉시 로딩 : select를 두번 사용하지 않고, join으로 한번에 조회해온다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;cascade = CascadeType.ALL 옵션&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@OneToMany나 @ManyToOne 에 옵션으로 줄 수 있는 값&lt;/li&gt;
&lt;li&gt;Entity의 상태 변화를 전파시키는 옵션&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Transient&lt;/li&gt;
&lt;li&gt;Persistent&lt;/li&gt;
&lt;li&gt;Detached&lt;/li&gt;
&lt;li&gt;Removed&lt;/li&gt;
&lt;li&gt;ALL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cascade 옵션이란 @OneToMany 나 @ManyToOne에 옵션으로 줄 수 있는 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity의 상태 변화를 전파시키는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Entity의 상태 변화가 있으면 연관되어 있는(ex. @OneToMany, @ManyToOne) Entity에도 상태 변화를 전이시키는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cascade 옵션이란 @OneToMany 나 @ManyToOne에 옵션으로 줄 수 있는 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Entity의 상태 변화를 전파시키는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Entity의 상태 변화가 있으면 연관되어 있는(ex. @OneToMany, @ManyToOne) Entity에도 상태 변화를 전이시키는 옵션이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;엔티티 설계시 주의점&lt;/b&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉시로딩( EAGER )은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.&lt;/li&gt;
&lt;li&gt;실무에서 모든 연관관계는 지연로딩( LAZY )으로 설정해야 한다.&lt;/li&gt;
&lt;li&gt;연관된 엔티티를 함께 DB에서 조회해야 하면, fetch join 또는 엔티티 그래프 기능을 사용한다.&lt;/li&gt;
&lt;li&gt;@XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;컬렉션은 필드에서 초기화 하자.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컬렉션은 필드에서 바로 초기화 하는 것이 안전하다. null 문제에서 안전하다.&lt;/li&gt;
&lt;li&gt;하이버네이트는 엔티티를 영속화 할 때, 컬랙션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다. 만약 getOrders() 처럼 임의의 메서드에서 컬력션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다. 따라서 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.&lt;/li&gt;
&lt;li&gt;필드에서 초기화하지 않은 경우는 아래와 같다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1676601426962&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Member member = new Member();
System.out.println(member.getOrders().getClass());
em.persist(member); // 컬랙션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경
System.out.println(member.getOrders().getClass());

//출력 결과
class java.util.ArrayList
class org.hibernate.collection.internal.PersistentBag&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;하이버네이트 네이밍 전략
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;ImplicitNamingStrategy : 사용자에 의해 명시적으로 명명되거나 (@Colume 등) ImplicitNamingStrategy@Table&lt;span&gt;&amp;nbsp;&lt;/span&gt;계약을 통해 Hibernate에 의해 암시적으로 결정된다.&lt;/li&gt;
&lt;li&gt;PhysicalNamingStrategy : 현재 스프링부트에서 디폴트로 사용하는 전략이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;PhysicalNamingStrategy :&amp;nbsp;&lt;br /&gt;all dots are replaced by underscores and camel casing is replaced by underscores as well. By default, all table names are generated in lower case, but it is possible to override that flag if your schema requires it.&lt;br /&gt;&lt;br /&gt;If you prefer to use Hibernate 5&amp;rsquo;s default instead, set the following property:&lt;br /&gt;&lt;b&gt;spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl&lt;/b&gt;&lt;/blockquote&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;size16&quot;&gt;&lt;b&gt;private LocalDateTime orderDate;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;java8부터 제공하는 날짜객체&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://ict-nroo.tistory.com/132&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ict-nroo.tistory.com/132&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 김영한 님 강의자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#naming&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#naming&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.1.3.RELEASE/reference/htmlsingle/#howto-configure-hibernate-naming-strategy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-boot/docs/2.1.3.RELEASE/reference/htmlsingle/#howto-configure-hibernate-naming-strategy&lt;/a&gt;&lt;/p&gt;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/152</guid>
      <comments>https://sohee-dev.tistory.com/152#entry152comment</comments>
      <pubDate>Wed, 15 Feb 2023 23:05:37 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 입문 강의 섹션 5, 6, 7 - 웰컴페이지/ DB 접근기술/ AOP</title>
      <link>https://sohee-dev.tistory.com/151</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;웰컴페이지 우선순위&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 스프링 컨테이너에서 관련 컨트롤러를 찾고, 없으면 static에서 찾기 때문에&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;@GetMapping(&quot;/&quot;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;static 내의 웰컴페이지로 설정한 파일&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;html&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675692942533&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;/members/new&quot; method=&quot;post&quot;&amp;gt;
	&amp;lt;div class=&quot;form-group&quot;&amp;gt;
    	&amp;lt;label for=&quot;name&quot;&amp;gt;이름&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot; placeholder=&quot;이름을 입력하세요&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;&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;action url로 controller의 @PostMapping으로 매핑된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;thymeleaf&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1675694371214&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;container&quot;&amp;gt;
	&amp;lt;div&amp;gt;
    	&amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
        	&amp;lt;th&amp;gt;#&amp;lt;/th&amp;gt;
            &amp;lt;th&amp;gt;이름&amp;lt;/th&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
        &amp;lt;tr th:each=&quot;member : ${members}&quot;&amp;gt;
        	&amp;lt;td th:text=&quot;${member.id}&quot;&amp;gt;&amp;lt;/td&amp;gt;
            &amp;lt;td th:text=&quot;${member.name}&quot;&amp;gt;&amp;lt;/td&amp;gt;
		&amp;lt;/tr&amp;gt;
        &amp;lt;/tbody&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th: 로 시작하는게 타임리프 문법&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;스프링 DB 접근 기술&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. H2 DB를 이용한 스프링 데이터 엑세스&lt;br /&gt;&lt;/b&gt;개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. JDBC&lt;br /&gt;&lt;/b&gt;과거에 사용하던 방식&lt;b&gt;&lt;/b&gt;&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;size16&quot;&gt;&lt;b&gt;@SpringBootTest: &lt;/b&gt;스프링 컨테이너와 테스트를 함께 실행한다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;@Transactional :&lt;/b&gt; 테스트 시작 전에 트랜잭션을 시작하고, 테스트가 끝나면 rollback 해준다. (다음 테스트에 영향을 주지 않는다)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스프링 JdbcTemplate&lt;/b&gt;&lt;b&gt;&lt;/b&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;/li&gt;
&lt;li&gt;순수 Jdbc와 동일한 환경설정을 하면 된다.&lt;/li&gt;
&lt;li&gt;스프링 Jdbctemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드르 대부분 제거해주지만, SQL은 직접 작성해야한다.&lt;br /&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;생성자로 의존성 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1675903463153&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class SpringConfig {

    private final JdbcTemplate jdbcTemplate;

    public SpringConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}&lt;/code&gt;&lt;/pre&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;JPA&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 반복코드와 기본적은 SQL을 모두 만들어서 실행해준다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SQL과 데이터 중심 -&amp;gt; 객체 중심의 설계&lt;/li&gt;
&lt;li&gt;개발생산성 향상&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;build.gradle 에 라이브러리 추가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675903918185&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    //implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* spring-boot-starter-data-jpa&lt;/b&gt;는 내부에 jdbc 관련 라이브러리를 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;resources/application.properties에 JPA 설정 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675904220825&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none&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;show-sql : JPA가 생성하는 SQL을 호출한다.&lt;/li&gt;
&lt;li&gt;ddl-auto: none인 경우 테이블 자동 생성 기능을 끈다. (create로 설정 시 엔티티 정보를 바탕으로 JPA가 테이블도 직접 생성)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) JPA 엔티티 매핑&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Entity : 클래스 위에 선언&lt;/li&gt;
&lt;li&gt;@Id : 구분자 위에 선언&lt;/li&gt;
&lt;li&gt;@GeneratedValue(strategy = GenerationType.IDENTITY) : Id 어노테이션 옆에 선언&lt;/li&gt;
&lt;li&gt;getter, setter 있어야함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) JPA 회원 리포지토리&lt;/p&gt;
&lt;pre id=&quot;code_1675907611527&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements MemberRepository {
    private final EntityManager em;
    
    public JpaMemberRepository(EntityManager em) {
    	this.em = em;
    }
    
    public Member save(Member member) {
    	em.persist(member); // 데이터 저장
    	return member;
    }
    
    public Optional&amp;lt;Member&amp;gt; findById(Long id) {
    	Member member = em.find(Member.class, id); //id로 select
    	return Optional.ofNullable(member); // null이어도 객체로 감싸서 반환
    }
    
    public List&amp;lt;Member&amp;gt; findAll() {
    	return em.createQuery(&quot;select m from Member m&quot;, Member.class)
    	.getResultList();
    }
    
    public Optional&amp;lt;Member&amp;gt; findByName(String name) {
    	List&amp;lt;Member&amp;gt; result = em.createQuery(&quot;select m from Member m where m.name = :name&quot;, Member.class)
		.setParameter(&quot;name&quot;, name)
		.getResultList();
       
		return result.stream().findAny(); // 찾은 요소들 중 가장 먼저 탐색되는 요소 리턴
        //findFirst : 스트림에서 순서가 가장 앞에 있는 요소 
        
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 서비스 계층에 트랜잭션 추가&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;@Transactional : org.springframework.transaction.annotation.Transactional&lt;/li&gt;
&lt;li&gt;런타임 예외 발생 시 롤백.&lt;/li&gt;
&lt;li&gt;JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.&lt;/li&gt;
&lt;/ul&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;스프링 데이터 JPA&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트 + JPA 기반 위에 더해지는 프레임워크&lt;br /&gt;개발 코드들이 확연히 줄어든다. -&amp;gt; 개발자는 핵심 비즈니스 로직을 개발하는데 집중할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 리포지토리를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1675916086900&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface SpringDataJpaMemberRepository extends JpaRepository&amp;lt;Member,
Long&amp;gt;, MemberRepository {
	Optional&amp;lt;Member&amp;gt; findByName(String name);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 스프링 데이터 JPA 회원 리포지토리를 사용하도록 스프링 설정을 변경한다.&lt;/p&gt;
&lt;pre id=&quot;code_1675916140883&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링&amp;nbsp;데이터&amp;nbsp;JPA가&amp;nbsp;SpringDataJpaMemberRepository&amp;nbsp;를&amp;nbsp;스프링&amp;nbsp;빈으로&amp;nbsp;자동&amp;nbsp;등록해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;1548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4W0lZ/btrYzKlAHBN/Lal6fsC4Da1VqWZqZL7vZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4W0lZ/btrYzKlAHBN/Lal6fsC4Da1VqWZqZL7vZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4W0lZ/btrYzKlAHBN/Lal6fsC4Da1VqWZqZL7vZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4W0lZ%2FbtrYzKlAHBN%2FLal6fsC4Da1VqWZqZL7vZk%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;1139&quot; height=&quot;1548&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;1548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터페이스를 통한 기본적인 CRUD&lt;/li&gt;
&lt;li&gt;메서드 이름 만으로 조회 기능 제공 (ex: findByName())&lt;/li&gt;
&lt;li&gt;페이징 기능 자동 제공&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 JPA + 스프링 데이터 JPA를 기본으로 사용&lt;br /&gt;복잡한 동적 쿼리는 Querydsl 라이브러리 또는 JPA가 제공하는 네이티브 쿼리 또는 JdbcTemplate.&lt;br /&gt;Querydsl은 자바코드로 쿼리를 작성할 수 있고, 동적 쿼리를 편리하게 작성 가능.&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;AOP&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) AOP란 ?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Aspect Oriented Programming&lt;span&gt;의 약자로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;관점 지향 프로그래밍을 의미한다.&lt;br /&gt;AOP를 통해 Aspect를 모듈화할 수 있다. (= 코드를 수정하지 않으면서 공통 기능의 구현을 추가하는 것)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) AOP가 필요한 상황은 ?&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;&lt;b&gt;공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;회원 가입 시간, 회원 조회 시간을 측정하고 싶을 때 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caFjVw/btrYIrrimko/IWh2v92o1boK30Y0lea4r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caFjVw/btrYIrrimko/IWh2v92o1boK30Y0lea4r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caFjVw/btrYIrrimko/IWh2v92o1boK30Y0lea4r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaFjVw%2FbtrYIrrimko%2FIWh2v92o1boK30Y0lea4r1%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;1125&quot; height=&quot;557&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;557&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbvRra/btrYEiIzfFl/Ka7LYxrjQ4DMxMXpNKi3K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbvRra/btrYEiIzfFl/Ka7LYxrjQ4DMxMXpNKi3K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbvRra/btrYEiIzfFl/Ka7LYxrjQ4DMxMXpNKi3K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbvRra%2FbtrYEiIzfFl%2FKa7LYxrjQ4DMxMXpNKi3K1%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;934&quot; height=&quot;476&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) AOP 구현 방법&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;컴파일: 자바 파일을 클래스 파일로 만들 때, 바이트코드를 조작하여 적용된 바이트코드를 생성&lt;/li&gt;
&lt;li&gt;로드 타임: 컴파일은 원래 클래스대로 하고, 클래스를 로딩하는 시점에 끼워서 넣는다.&lt;/li&gt;
&lt;li&gt;런타임: A라는 클래스를 빈으로 만들때, A라는 타입의 프록시 빈을 감싸서 만든 후에, 프록시 빈이 클래스 중간에 코드를 추가해서 넣는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 런타임 방법을 많이 이용한다. 스프링 AOP는 프록시 객체를 자동으로 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) AOP 적용 전 vs 후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적용 전 회원 조회 시간 측정 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675921139274&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package hello.hellospring.service;

@Transactional
public class MemberService {
    /**
    * 회원가입
    */
    public Long join(Member member) {
    	long start = System.currentTimeMillis();
    	try {
            validateDuplicateMember(member); //중복 회원 검증
            memberRepository.save(member);
            return member.getId();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;join &quot; + timeMs + &quot;ms&quot;);
        }
    }
    
    /**
    * 전체 회원 조회
    */
    public List&amp;lt;Member&amp;gt; findMembers() {
        long start = System.currentTimeMillis();
        try {
        	return memberRepository.findAll();
        } finally {
        	long finish = System.currentTimeMillis();
        	long timeMs = finish - start;
        	System.out.println(&quot;findMembers &quot; + timeMs + &quot;ms&quot;);
        }
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제점이 발생한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;시간을 측정하는 로직은 공통 관심 사항이나, 핵심 비즈니스 로직과 섞이게 되어 유지보수가 어렵다.&lt;/li&gt;
&lt;li&gt;시간을 측정하는 로직을 별도의 공통 로직으로 만들기 매우 어렵다.&lt;/li&gt;
&lt;li&gt;시간을 측정하는 로직을 변경할 때, 모든 로직을 찾아가면서 변경해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AOP 적용 후 회원 조회 시간 측정 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 관심 사항과 핵심 관심 사항 분리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qHt69/btrYIOzPmjP/gHsbjTSEroMXuuuPe6nlk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qHt69/btrYIOzPmjP/gHsbjTSEroMXuuuPe6nlk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qHt69/btrYIOzPmjP/gHsbjTSEroMXuuuPe6nlk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqHt69%2FbtrYIOzPmjP%2FgHsbjTSEroMXuuuPe6nlk0%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;1139&quot; height=&quot;590&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1675921678014&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package hello.hellospring.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class TimeTraceAop {

    @Around(&quot;execution(* hello.hellospring..*(..))&quot;)
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println(&quot;START: &quot; + joinPoint.toString());
        try {
	        return joinPoint.proceed();
        } finally {
    	    long finish = System.currentTimeMillis();
        	long timeMs = finish - start;
        	System.out.println(&quot;END: &quot; + joinPoint.toString()+ &quot; &quot; + timeMs + &quot;ms&quot;);
        }
    }
}&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;@Aspect: 공통 관심 사항을 모듈로 분리&lt;/li&gt;
&lt;li&gt;@Around
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;excution을 사용하여 적용 범위를 지정 가능&lt;br /&gt;hello.hellospring 패키지 하위 모든 클래스에 적용&lt;/li&gt;
&lt;li&gt;@annotation(어노테이션명)&lt;br /&gt;어노테이션명이 달린 메소드가 실행되면 aspect의 around가 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ProceedingJoinPoint / proceed() : proceed() 메소드를 사용해서 실제 대상 객체의 메소드를 호출하는 것.&lt;/li&gt;
&lt;li&gt;@Retention(RetentionPolicy.RUNTIME): default는 CLASS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9TQQF/btrYBxTWYMl/eVAK9UNW9I1k6ecFrbyMFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9TQQF/btrYBxTWYMl/eVAK9UNW9I1k6ecFrbyMFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9TQQF/btrYBxTWYMl/eVAK9UNW9I1k6ecFrbyMFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9TQQF%2FbtrYBxTWYMl%2FeVAK9UNW9I1k6ecFrbyMFk%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;1139&quot; height=&quot;590&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;590&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;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://devlog-wjdrbs96.tistory.com/398&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Spring] AOP(Aspect Oriented Programming)란 무엇일까?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;김영한 스프링 MVC 입문 강의 자료&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;display: inline !important;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/151</guid>
      <comments>https://sohee-dev.tistory.com/151#entry151comment</comments>
      <pubDate>Tue, 7 Feb 2023 00:25:35 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 입문 강의 섹션 3 - 웹 어플리케이션 계층 구조 / 도메인, 리포지토리, 테스트케이스 작성</title>
      <link>https://sohee-dev.tistory.com/149</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비즈니스 요구사항 정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 웹 어플리케이션 계층 구조&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnhbhU/btrWEQfeu8V/VzYsN6KCXus1eQB3n2kAu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnhbhU/btrWEQfeu8V/VzYsN6KCXus1eQB3n2kAu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnhbhU/btrWEQfeu8V/VzYsN6KCXus1eQB3n2kAu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnhbhU%2FbtrWEQfeu8V%2FVzYsN6KCXus1eQB3n2kAu0%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;1004&quot; height=&quot;342&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨트롤러: 웹 MVC의 컨트롤러 역할&lt;/li&gt;
&lt;li&gt;서비스: 핵심 비즈니스 로직 구현&lt;/li&gt;
&lt;li&gt;리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리&lt;/li&gt;
&lt;li&gt;도메인: 비즈니스 도메인 객체 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;데이터 저장소가 선정되지 않았을땐, 우선 Repository를 인터페이스로 생성하여 추후에 구현 클래스를 변경할 수 있도록 설계한다.&lt;/blockquote&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;1. 도메인 , 리포지토리 작성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⭐️ &lt;span style=&quot;background-color: #dddddd;&quot;&gt;Optional&lt;/span&gt;&lt;/b&gt;&amp;nbsp;Java8에 도입됨. null 처리하는 방법 중 하나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용방법 예시)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optional&amp;lt;Member&amp;gt; member : Optional 객체 선언&lt;/li&gt;
&lt;li&gt;Optional.ofNullable(store.get(id)); : null이어도 감싸서 반환&lt;/li&gt;
&lt;li&gt;Optional객체.ifPresent : null이 아니면 (값이 있으면)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 테스트케이스 작성 (리포지토리)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Junit 테스트 프레임워크로 테스트를 실행 : 반복 실행에 유리하다.&lt;/li&gt;
&lt;li&gt;test폴더에 작성&lt;/li&gt;
&lt;li&gt;@Test 어노테이션을 함수위에 선언&lt;/li&gt;
&lt;li&gt;Assertions.assertEquals : 두개의 파라미터가 동일한지 검증 (동일하지 않으면 테스트 실패)&lt;/li&gt;
&lt;li&gt;Assertions는 static으로 선언해두면 더욱 간략한 코드 작성이 가능하다.&lt;/li&gt;
&lt;li&gt;@AfterEach : 각각의 테스트가 끝날때마다 실행하고 싶은 함수 위에 선언&lt;br /&gt;테스트가 끝날때마다 데이터를 clear해주는 것이 좋다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 서비스 클래스 작성&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서비스 클래스의 함수명은 비즈니스적으로, 리포지토리는 좀더 기계적으로 작성하면 좋다.&lt;br /&gt;DI한 repository를 작성(외부에서 repository를 넣어준다, 아래 사진 참고)하면 같은 메모리 repository를 사용할 수 있다.&amp;nbsp;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 테스트케이스 작성 (서비스)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스에서 command + shift + T : create New Test (클래스의 테스트 껍데기 코드를 만들어줌)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;테스트 함수는 한글로 작성해도 괜찮다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수 내에 주석 활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;//given : 주어진 데이터로&lt;/li&gt;
&lt;li&gt;//when : 이걸 실행했을 때&lt;/li&gt;
&lt;li&gt;//then : 검증이 되어야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;fail(&quot;&quot;);&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Exception e&amp;nbsp; = assertThrows(Exception.class, () -&amp;gt; Exception 예외가 발생했을 때)&lt;br /&gt;Exception이 발생하지 않으면 테스트 실패함.&amp;nbsp;&lt;br /&gt;e.getMessage&lt;/li&gt;
&lt;li&gt;Repository 초기화를 @BeforeEach에서 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z59Nb/btrWCE8iaj9/3kz0XIasBZzGHvwbjKWjSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z59Nb/btrWCE8iaj9/3kz0XIasBZzGHvwbjKWjSK/img.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;274&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.384%; margin-right: 10px;&quot; data-widthpercent=&quot;57.05&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z59Nb/btrWCE8iaj9/3kz0XIasBZzGHvwbjKWjSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ59Nb%2FbtrWCE8iaj9%2F3kz0XIasBZzGHvwbjKWjSK%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;828&quot; height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cniE94/btrWDnrkG8U/C418fcozoSkHBSXMWkpBH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cniE94/btrWDnrkG8U/C418fcozoSkHBSXMWkpBH0/img.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;356&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;42.95&quot; style=&quot;width: 42.4532%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cniE94/btrWDnrkG8U/C418fcozoSkHBSXMWkpBH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcniE94%2FbtrWDnrkG8U%2FC418fcozoSkHBSXMWkpBH0%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;810&quot; height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;같은 메모리를 갖는 repository&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/149</guid>
      <comments>https://sohee-dev.tistory.com/149#entry149comment</comments>
      <pubDate>Thu, 19 Jan 2023 00:18:11 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링 입문 강의 섹션 1,2 - 프로젝트 초기 세팅 / 라이브러리 / 웹 개발 기초(구조)</title>
      <link>https://sohee-dev.tistory.com/147</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트 초기셋팅 (springboot)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;build.gradle &lt;/b&gt;&amp;gt; repositories&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- mavenCentral : 메이븐 공개 사이트에서 관련 레포지토리를 다운로드 받아라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.gitignore&lt;/b&gt; : 기본적으로 셋팅 되어있음&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;size16&quot;&gt;스프링부트는 tomcat 서버 설치 안해도 된당&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 system.out.println 대신 log 사용해야한다 !! (로그 레벨별로 관리하기 위해)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 관련&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;java에서 테스트 코드 작성시 주로 junit을 이용한다. (최근에 junit5가 나옴) - 테스트 프레임워크&lt;/li&gt;
&lt;li&gt;assertj: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리&lt;/li&gt;
&lt;li&gt;spring-test: 스프링 통합 테스트 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;View 환경설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src&amp;gt;resources&amp;gt;static&amp;gt; index.html : default welcome page&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring.io &amp;gt; projects &amp;gt; spring Boot &amp;gt; Learn tab &amp;gt; 버전 선택 &amp;gt; Spring Boot Feature &amp;gt; index.html 검색 &amp;gt; Welcome Page 검색하면 정의 알 수 있음.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;템플릿엔진(Thymeleaf)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 jsp, php 도 템플릿 엔진 -&amp;gt; 서버 동작을해서 화면을 동적으로 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절대경로를 복사해서 붙여넣기해도 페이지의 껍데기를 확인할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 템플릿엔진 종류로는 FreeMarker, Groovy, Mustache가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czDPvj/btrV4ts00WG/QkXhkJGtmfS2UblWwzmxT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czDPvj/btrV4ts00WG/QkXhkJGtmfS2UblWwzmxT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czDPvj/btrV4ts00WG/QkXhkJGtmfS2UblWwzmxT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczDPvj%2FbtrV4ts00WG%2FQkXhkJGtmfS2UblWwzmxT0%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;914&quot; height=&quot;472&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;return &quot;hello&quot;; : hello.html을 렌더링해라&lt;/li&gt;
&lt;li&gt;viewResolver: 화면을 찾아서 처리 resources:templates/{viewName}.html&lt;/li&gt;
&lt;li&gt;참고: spring-boot-devtools 라이브러리 사용시 파일을 컴파일만 해주면 서버 재시작 없이 View 파일 변경이 가능하다.&lt;/li&gt;
&lt;/ul&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;size16&quot;&gt;맥 기준: 터미널에서 프로젝트 폴더 접속 &amp;gt;&amp;nbsp; ./gradlew build &amp;gt; cd build/libs &amp;gt; java -jar {jar 파일명}&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;./gradlew clean : build 폴더가 사라짐&lt;/li&gt;
&lt;/ul&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;스프링 웹 개발 기초&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정적 콘텐츠&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/static 폴더에서 제공&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcDh8c/btrV68gFIVm/EYRJL4MQ04a6XTdr0lUXZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcDh8c/btrV68gFIVm/EYRJL4MQ04a6XTdr0lUXZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcDh8c/btrV68gFIVm/EYRJL4MQ04a6XTdr0lUXZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcDh8c%2FbtrV68gFIVm%2FEYRJL4MQ04a6XTdr0lUXZK%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;1050&quot; height=&quot;524&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVC와 템플릿 엔진&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;model / view / controller 로 구분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view는 화면을 그리는 것에만 집중&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/on18i/btrV7dhTGuJ/plg326oCOzAWTWvTpCNqEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/on18i/btrV7dhTGuJ/plg326oCOzAWTWvTpCNqEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/on18i/btrV7dhTGuJ/plg326oCOzAWTWvTpCNqEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fon18i%2FbtrV7dhTGuJ%2Fplg326oCOzAWTWvTpCNqEK%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;1930&quot; height=&quot;986&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;986&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;API&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿엔진과는 다른 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ResponseBody 필수 !! (return값이 응답 body 값으로 셋팅됨.)&lt;br /&gt;html 태그 없이 return값이 그대로 내려감&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 return하면 ? json 형태(키+값)로 내려감.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qonpj/btrV6J2rvBH/B94K4mfHX9vt66wkrDO8gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qonpj/btrV6J2rvBH/B94K4mfHX9vt66wkrDO8gk/img.png&quot; data-alt=&quot;@ResponseBody 사용 원리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qonpj/btrV6J2rvBH/B94K4mfHX9vt66wkrDO8gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQonpj%2FbtrV6J2rvBH%2FB94K4mfHX9vt66wkrDO8gk%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;934&quot; height=&quot;470&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;@ResponseBody 사용 원리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP의 BODY에 문자 내용을 직접 반환&lt;/li&gt;
&lt;li&gt;viewResolver 대신 HttpMessageConverter가 동작&lt;/li&gt;
&lt;li&gt;기본 문자처리 : StringHttpMessageConverter&lt;/li&gt;
&lt;li&gt;기본 객체처리 : MappingJackson2HttpMessageConverter&lt;/li&gt;
&lt;li&gt;참고: HTTP Accept 헤더(클라이언트가 받고싶어하는 형식)와 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 HttpMessageConverter가 선택된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&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;command + shift + enter : 자동완성&lt;/p&gt;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/147</guid>
      <comments>https://sohee-dev.tistory.com/147#entry147comment</comments>
      <pubDate>Thu, 12 Jan 2023 21:45:16 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] 스프링부트와 JPA 활용 섹션 1- 라이브러리 의존과 Thymeleaf</title>
      <link>https://sohee-dev.tistory.com/146</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;목표&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스프링부트 프로젝트에서 사용되는 라이브러리의 의존성에 대해 알아보자&lt;/li&gt;
&lt;li&gt;Thymeleaf가 무엇인지 알아보자&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스프링부트 라이브러리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 라이브러리 의존 관계 확인하는 법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 의존 ? &lt;a href=&quot;https://goodmean.tistory.com/114&quot;&gt;Gradle 이란? (라이브러리 의존성) (tistory.com)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에 해당 프로젝트 폴더로 이동 &amp;gt; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;./gradlew dependencises&lt;/span&gt; : 의존 관계를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 라이브러리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring mvc&lt;/li&gt;
&lt;li&gt;spring orm&lt;/li&gt;
&lt;li&gt;JPA, 하이버네이트&lt;/li&gt;
&lt;li&gt;스프링 데이터 JPA
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 데이터 JPA는 스프링과 JPA를 먼저 이해하고 사용해야 하는 응용기술이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기타 라이브러리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;H2 데이터베이스 클라이언트&lt;/li&gt;
&lt;li&gt;커넥션 풀: 부트 기본은 HikariCP&lt;/li&gt;
&lt;li&gt;WEB(thymeleaf)&lt;/li&gt;
&lt;li&gt;로깅 SLF4J &amp;amp; LogBack&lt;/li&gt;
&lt;li&gt;테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 가이드 : &lt;a href=&quot;https://spring.io/guides&quot;&gt;https://spring.io/guides&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트 메뉴얼 : &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-template-engines&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-template-engines&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;springboot-devtools&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋팅 확인 : restartMain&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build &amp;gt; recompile '파일명' 누르면 새로고침만으로 바뀜&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu4sfQ/btrVkecsiYu/TuK922MOs3CpfS11KyJyWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu4sfQ/btrVkecsiYu/TuK922MOs3CpfS11KyJyWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu4sfQ/btrVkecsiYu/TuK922MOs3CpfS11KyJyWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu4sfQ%2FbtrVkecsiYu%2FTuK922MOs3CpfS11KyJyWk%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;1192&quot; height=&quot;1252&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 탭에서 Gradle &amp;gt; Dependencies 를 통해 라이브러리 의존성 확인 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;spring-boot-stater-web&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- embedded tomcat을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- spring-webmvc 의존&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;thymeleaf&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;spring-boot-starter-data-jpa&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceVhjW/btrVkXnYOCP/aG22GM2B8kHPWQu78NnK2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceVhjW/btrVkXnYOCP/aG22GM2B8kHPWQu78NnK2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceVhjW/btrVkXnYOCP/aG22GM2B8kHPWQu78NnK2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceVhjW%2FbtrVkXnYOCP%2FaG22GM2B8kHPWQu78NnK2k%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;779&quot; height=&quot;213&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Thymeleaf&lt;/b&gt; (&lt;a href=&quot;https://www.thymeleaf.org/&quot;&gt;https://www.thymeleaf.org/&lt;/a&gt;)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바에서 server-side 렌더링을 지원해주는 Java Template Engine&lt;/li&gt;
&lt;li&gt;유지 관리가 수월한 템플릿을 작성하도록 지원&lt;/li&gt;
&lt;li&gt;HTML이 브라우저에 올바르게 표시될 수 있고, 정적 프로토타입으로도 작동하여 더 강력한 협업을 가능하게 한다.&lt;/li&gt;
&lt;li&gt;Spring Framework용 모듈, 즐겨 사용하는 도구와의 통합 및 자체 기능을 플러그인할 수 있는 기능을 통해 HTML5 JVM 웹 개발에 이상적으로 작동한다.&lt;/li&gt;
&lt;li&gt;다양한 웹 개발 프레임워크에서 작성할 수 있다.&lt;/li&gt;
&lt;li&gt;Springboot에서 사용하기 위해서는 Maven이나 Gradle같은 빌드 툴을 이용한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Natural Template&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thymeleaf로 작성된 HTML 템플릿은 여전히 HTML처럼 보이고 작동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1673271117342&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;table&amp;gt;
  &amp;lt;thead&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;th th:text=&quot;#{msgs.headers.name}&quot;&amp;gt;Name&amp;lt;/th&amp;gt;
      &amp;lt;th th:text=&quot;#{msgs.headers.price}&quot;&amp;gt;Price&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
    &amp;lt;tr th:each=&quot;prod: ${allProducts}&quot;&amp;gt;
      &amp;lt;td th:text=&quot;${prod.name}&quot;&amp;gt;Oranges&amp;lt;/td&amp;gt;
      &amp;lt;td th:text=&quot;${#numbers.formatDecimal(prod.price, 1, 2)}&quot;&amp;gt;0.99&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&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;Thymeleaf가 제공해주는 Template&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;XML&lt;/li&gt;
&lt;li&gt;TEXT&lt;/li&gt;
&lt;li&gt;Javascript&lt;/li&gt;
&lt;li&gt;Css&lt;/li&gt;
&lt;li&gt;Raw&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>스프링</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/146</guid>
      <comments>https://sohee-dev.tistory.com/146#entry146comment</comments>
      <pubDate>Thu, 5 Jan 2023 00:10:01 +0900</pubDate>
    </item>
    <item>
      <title>[백준/boj] 20061: 모노미노도미노 2 (JAVA) / 구현 / 시뮬레이션</title>
      <link>https://sohee-dev.tistory.com/145</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/submit/20061/50513201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제&lt;/a&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1665673075783&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;로그인&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/submit/20061/50513201&quot; data-og-url=&quot;https://www.acmicpc.net/login?next=%2Fsubmit%2F20061&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sc9wY/hyP9uPKOci/g1YJsANIqrsuN9ZnmrqHjk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/submit/20061/50513201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/submit/20061/50513201&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sc9wY/hyP9uPKOci/g1YJsANIqrsuN9ZnmrqHjk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&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;로그인&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;pre id=&quot;code_1665673005058&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
	static int N;
	static List&amp;lt;Block&amp;gt; blocks;
	static Block nowB;
	static boolean map[][];
	static List&amp;lt;Integer&amp;gt; removeList;
	static int point;
	
	static class Block {
		int t;
		int x1;
		int y1;
		int x2;
		int y2;
		
		Block(int t, int x1, int y1, int x2, int y2) {
			this.t = t;
			this.x1 = x1;
			this.y1 = y1;
			this.x2 = x2;
			this.y2 = y2;
		}
	}
	
	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		blocks = new ArrayList&amp;lt;&amp;gt;();
		map = new boolean[10][10];
		
		int t,x,y;
		for(int i=0; i&amp;lt;N; i++) {
			st = new StringTokenizer(br.readLine());
			t = Integer.parseInt(st.nextToken());
			x = Integer.parseInt(st.nextToken());
			y = Integer.parseInt(st.nextToken());
			switch(t) {
			case 1:
				blocks.add(new Block(t, x, y, -1, -1));
				break;
			case 2:
				blocks.add(new Block(t, x, y, x, y+1));
				break;
			case 3:
				blocks.add(new Block(t, x, y, x+1, y));
				break;
			default:
				break;
			}
		}
		
		for(int i=0; i&amp;lt;N; i++) {
			nowB = blocks.get(i);
			moveG();
			moveB();
			checkPoint();
			checkPastel();
		}
		
		System.out.println(point);
		System.out.println(cntTile());
	}

	private static void checkPastel() {
		// G
		int x = 5;
		for(int y=0; y&amp;lt;=3; y++) {
			if(map[x][y]) {
				removeList.add(9); break;
			}
		}
		x = 4;
		for(int y=0; y&amp;lt;=3; y++) {
			if(map[x][y]) {
				removeList.add(8); break;
			}
		}
		removeG();
		removeList.clear();
		// B
		int y = 5;
		for(x=0; x&amp;lt;=3; x++) {
			if(map[x][y]) {
				removeList.add(9); break;
			}
		}
		y = 4;
		for(x=0; x&amp;lt;=3; x++) {
			if(map[x][y]) {
				removeList.add(8); break;
			}
		}
		removeB();
		removeList.clear();
	}

	private static int cntTile() {
		int cnt = 0; 
		for(int i=0; i&amp;lt;10; i++) {
			for(int j=0; j&amp;lt;10; j++) {
				if(map[i][j]) cnt++;
			}
		}
		return cnt;
	}

	private static void checkPoint() {
		removeList = new ArrayList&amp;lt;Integer&amp;gt;();
		boolean pFlag = true;
		// G
		for(int x=9; x&amp;gt;=3; x--) {
			pFlag = true;
			for(int y=0; y&amp;lt;=3; y++) {
				if(!map[x][y]) pFlag = false;
			}
			if(pFlag) removeList.add(x);
		}
		removeG();
		point += removeList.size();
		removeList.clear();
		// B
		for(int y=9; y&amp;gt;=3; y--) {
			pFlag = true;
			for(int x=0; x&amp;lt;=3; x++) {
				if(!map[x][y]) pFlag = false;
			}
			if(pFlag) removeList.add(y);
		}
		removeB();
		point += removeList.size();
		removeList.clear();
	}

	private static void removeB() {
		int num = 0;
		for(int y : removeList) {
			y += num;
			for(int j=y; j&amp;gt;3; j--) {
				for(int x=0; x&amp;lt;=3; x++) {
					map[x][j] = map[x][j-1];
				}
			}
			num++;
		}
	}

	private static void removeG() {
		int num = 0;
		for(int x : removeList) {
			x+=num;
			for(int i=x; i&amp;gt;3; i--) {
				for(int y=0; y&amp;lt;=3; y++) {
					map[i][y] = map[i-1][y];
				}
			}
			num++;
		}
	}

	private static void moveG() {
		int row = 4;
		if(nowB.t == 1) {
			for(int x=4; x&amp;lt;=9; x++) {
				if(!map[x][nowB.y1])
					row = x;
				else
					break;
			}
			map[row][nowB.y1] = true;
		}
		if(nowB.t == 2) {
			for(int x=4; x&amp;lt;=9; x++) {
				if(!map[x][nowB.y1] &amp;amp;&amp;amp; !map[x][nowB.y2])
					row = x;
				else
					break;
			}
			map[row][nowB.y1] = true;
			map[row][nowB.y2] = true;
		}
		if(nowB.t == 3) {
			for(int x=4; x&amp;lt;=9; x++) {
				if(!map[x][nowB.y1])
					row = x;
				else
					break;
			}
			map[row][nowB.y1] = true;
			map[row-1][nowB.y1] = true;
		}
	}
	
	private static void moveB() {
		int col = 4;
		if(nowB.t == 1) {
			for(int y=4; y&amp;lt;=9; y++) {
				if(!map[nowB.x1][y])
					col = y;
				else
					break;
			}
			map[nowB.x1][col] = true;
		}
		if(nowB.t == 2) {
			for(int y=4; y&amp;lt;=9; y++) {
				if(!map[nowB.x1][y]) 
					col = y;
				else
					break;
			}
			map[nowB.x1][col] = true;
			map[nowB.x1][col-1] = true;
		}
		if(nowB.t == 3) {
			for(int y=4; y&amp;lt;=9; y++) {
				if(!map[nowB.x1][y] &amp;amp;&amp;amp; !map[nowB.x2][y])
					col = y;
				else
					break;
			}
			map[nowB.x1][col] = true;
			map[nowB.x2][col] = true;
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초록색은 행을 위주로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파란색은 열을 위주로 하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;t가 1,2,3 인 경우마다 다르게 시뮬레이션 되도록 했다..&lt;/p&gt;</description>
      <category>알고리즘/자바(Java)</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/145</guid>
      <comments>https://sohee-dev.tistory.com/145#entry145comment</comments>
      <pubDate>Thu, 13 Oct 2022 23:57:56 +0900</pubDate>
    </item>
    <item>
      <title>[백준/boj] 19237: 어른 상어 (JAVA) / 구현 / 시뮬레이션</title>
      <link>https://sohee-dev.tistory.com/144</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/19237&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제&lt;/a&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1665587106662&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;19237번: 어른 상어&quot; data-og-description=&quot;첫 줄에는 N, M, k가&amp;nbsp;주어진다. (2&amp;nbsp;&amp;le; N&amp;nbsp;&amp;le; 20, 2&amp;nbsp;&amp;le; M&amp;nbsp;&amp;le; N2, 1&amp;nbsp;&amp;le; k&amp;nbsp;&amp;le; 1,000) 그 다음 줄부터 N개의 줄에 걸쳐 격자의 모습이 주어진다. 0은 빈칸이고, 0이 아닌 수 x는 x번 상어가 들어있는 칸을 의미&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/19237&quot; data-og-url=&quot;https://www.acmicpc.net/problem/19237&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgq85w/hyP8bcujSA/TFF16Rhi5EYN56yVcHYUQK/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480,https://scrap.kakaocdn.net/dn/cE5WD0/hyP8adAHBJ/bceRNrOBqAkOKuhuPMXXOK/img.jpg?width=1802&amp;amp;height=716&amp;amp;face=0_0_1802_716,https://scrap.kakaocdn.net/dn/cooroe/hyP727F4hk/tLrVaIcPAF5ZwRrZbLNH01/img.jpg?width=1804&amp;amp;height=714&amp;amp;face=0_0_1804_714&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/19237&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/19237&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgq85w/hyP8bcujSA/TFF16Rhi5EYN56yVcHYUQK/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480,https://scrap.kakaocdn.net/dn/cE5WD0/hyP8adAHBJ/bceRNrOBqAkOKuhuPMXXOK/img.jpg?width=1802&amp;amp;height=716&amp;amp;face=0_0_1802_716,https://scrap.kakaocdn.net/dn/cooroe/hyP727F4hk/tLrVaIcPAF5ZwRrZbLNH01/img.jpg?width=1804&amp;amp;height=714&amp;amp;face=0_0_1804_714');&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;19237번: 어른 상어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫 줄에는 N, M, k가&amp;nbsp;주어진다. (2&amp;nbsp;&amp;le; N&amp;nbsp;&amp;le; 20, 2&amp;nbsp;&amp;le; M&amp;nbsp;&amp;le; N2, 1&amp;nbsp;&amp;le; k&amp;nbsp;&amp;le; 1,000) 그 다음 줄부터 N개의 줄에 걸쳐 격자의 모습이 주어진다. 0은 빈칸이고, 0이 아닌 수 x는 x번 상어가 들어있는 칸을 의미&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;pre id=&quot;code_1665587120739&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
	static ArrayList&amp;lt;Shark&amp;gt;[][] map; // 상어의 위치 
	static ArrayList&amp;lt;Smell&amp;gt;[][] smellMap; // 상어의 냄새 정보
	static Map&amp;lt;Integer, int[][]&amp;gt; orders; // 상어의 이동 우선순위 &amp;lt;상어번호, 상하좌우에따른 이동순위 int[4]&amp;gt;
	static int N; // 격자 범위 
	static int M; // 상어 숫자 
	static int k; // 냄새가 사라지기까지의 횟수
	static int[] dx = {0, -1, 1, 0, 0}; // 상하좌우
	static int[] dy = {0, 0, 0, -1, 1}; 

	static class Shark {
		int sNum; // 상어 번호
		int x; 
		int y;
		int dir; // 현재 방향
		boolean isMoved; // 이동 여부
		
		Shark(int sNum, int x, int y, int dir, boolean isMoved) {
			this.sNum = sNum;
			this.x = x;
			this.y = y;
			this.dir = dir;
			this.isMoved = isMoved;
		}
	}
	
	static class Smell {
		int sNum; // 상어 번호
		int k; // 남은 횟수 
		
		Smell(int sNum, int k) {
			this.sNum = sNum;
			this.k = k;
		}
	}
	
	public static void main(String[] args) throws Exception {
		int answer = 0; // 1번 상어만 남기까지 걸리는 시간 
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		k = Integer.parseInt(st.nextToken());
		map = new ArrayList[N][N];
		smellMap = new ArrayList[N][N];
		orders = new HashMap&amp;lt;&amp;gt;();
		List&amp;lt;Shark&amp;gt; sharks = new ArrayList&amp;lt;&amp;gt;();
		for(int i=0; i&amp;lt;N; i++) {
			st = new StringTokenizer(br.readLine());
			for(int j=0; j&amp;lt;N; j++) {
				map[i][j] = new ArrayList&amp;lt;&amp;gt;();
				smellMap[i][j] = new ArrayList&amp;lt;&amp;gt;();
				int sNum = Integer.parseInt(st.nextToken());
				if(sNum &amp;gt; 0) {
					sharks.add(new Shark(sNum, i, j, 0, false));
				}
			}
		}
		
		int[] tempDirs = new int[M+1];
		st = new StringTokenizer(br.readLine()); // 상어의 방향
		for(int i=1; i&amp;lt;=M; i++) {
			tempDirs[i] = Integer.parseInt(st.nextToken());
		}
		
		for(Shark shark: sharks) {
			shark.dir = tempDirs[shark.sNum];
			map[shark.x][shark.y].add(shark);
		}
		
		
		for(int i=1; i&amp;lt;=M; i++) { // 상어마다 			
			int[][] tempOrderArr = new int[5][5]; // 0~4
			for(int j=1; j&amp;lt;=4; j++) { // 방향에 따른 (상하좌우)
				st = new StringTokenizer(br.readLine());
				for(int k=1; k&amp;lt;=4; k++) { // 우선순위 4개 
					tempOrderArr[j][k] = Integer.parseInt(st.nextToken());
				}
			}
			orders.put(i, tempOrderArr);
		}
		
		while(remainSharkCnt() != 1) {
			answer++;
			if(answer &amp;gt; 1000) {
				answer = -1; break;
			}
			
			// 1. 이동하기  
			moveShark();
			// 2. 겹치는 상어 내쫓기 
			outShark();
			// 3. k 줄이기 
			reduceK();
		}
		
		System.out.println(answer);
	}

	private static void outShark() {
		for(int i=0; i&amp;lt;N; i++) {
			for(int j=0; j&amp;lt;N; j++) {
				int minNum = Integer.MAX_VALUE;
				for(int s=map[i][j].size()-1; s&amp;gt;=0; s--) {
					minNum = Math.min(minNum , map[i][j].get(s).sNum); 
					map[i][j].get(s).isMoved = false;
				}
				
				for(int s=map[i][j].size()-1; s&amp;gt;=0; s--) {
					if(map[i][j].get(s).sNum != minNum) map[i][j].remove(s);
				}
			}
		}
	}

	private static void reduceK() {
		for(int i=0; i&amp;lt;N; i++) {
			for(int j=0; j&amp;lt;N; j++) {
				for(int s=smellMap[i][j].size()-1; s&amp;gt;=0; s--) {
					Smell smell = smellMap[i][j].get(s);
					if(smell.k &amp;gt; 0) smell.k -= 1;
					if(smell.k == 0) smellMap[i][j].remove(s);
				}
			}
		}
	}

	private static void moveShark() {
		// 이동 
		for(int i=0; i&amp;lt;N; i++) {
			for(int j=0; j&amp;lt;N; j++) {
				for(int s=map[i][j].size()-1; s&amp;gt;=0; s--) {
					Shark shark = map[i][j].get(s);
					if(shark.isMoved) continue;
					int nx; 
					int ny;
					// 우선순위 중 ! 인접한 칸 중 아무 냄새 없는 칸
					int[][] dirs = orders.get(shark.sNum);
					for(int dir : dirs[shark.dir]) {
						if(dir == 0) continue;
						nx = shark.x + dx[dir];
						ny = shark.y + dy[dir];
						if(!isRange(nx, ny) || smellMap[nx][ny].size() &amp;gt; 0) continue;
						shark.x = nx;
						shark.y = ny;
						shark.dir = dir;
						shark.isMoved = true;
						map[i][j].remove(s);
						map[nx][ny].add(shark);
						smellMap[i][j].add(new Smell(shark.sNum, k));
						break;
					}
					
					if(!shark.isMoved) { // 자신의 냄새가 있는 곳으로 
						bp: for(int dir : dirs[shark.dir]) {
							if(dir == 0) continue;
							nx = shark.x + dx[dir];
							ny = shark.y + dy[dir];
							if(!isRange(nx,ny)) continue;
							for(Smell smell : smellMap[nx][ny]) {
								if(smell.sNum == shark.sNum) {
									shark.x = nx;
									shark.y = ny;
									shark.dir = dir;
									shark.isMoved = true;
									map[i][j].remove(s);
									map[nx][ny].add(shark);
									smellMap[i][j].add(new Smell(shark.sNum, k));
									break bp;
								}
							}
						}
					}
				}
			}
		}
	}

	private static boolean isRange(int nx, int ny) {
		return nx &amp;gt;= N || nx &amp;lt;0 || ny &amp;gt;= N || ny &amp;lt;0 ? false: true;
	}

	private static int remainSharkCnt() {
		int cnt = 0; 
		for(int i=0; i&amp;lt;N; i++) {
			for(int j=0; j&amp;lt;N; j++) {
				if(map[i][j].size() != 0) cnt+= map[i][j].size();
			}
		}
		return cnt;
	}
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘/자바(Java)</category>
      <category>Java</category>
      <category>구현</category>
      <category>시뮬레이션</category>
      <author>s5he2</author>
      <guid isPermaLink="true">https://sohee-dev.tistory.com/144</guid>
      <comments>https://sohee-dev.tistory.com/144#entry144comment</comments>
      <pubDate>Thu, 13 Oct 2022 00:05:26 +0900</pubDate>
    </item>
  </channel>
</rss>