<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발은 꽃</title>
    <link>https://it-mesung.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 28 May 2026 01:15:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>메성</managingEditor>
    <image>
      <title>개발은 꽃</title>
      <url>https://tistory1.daumcdn.net/tistory/3456971/attach/d0fdefa30d9f4be786c3bd930ad30c8e</url>
      <link>https://it-mesung.tistory.com</link>
    </image>
    <item>
      <title>[Spring Boot JPA] 회원 기능 개발</title>
      <link>https://it-mesung.tistory.com/211</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1645105692139&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;실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의&quot; data-og-description=&quot;실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강&quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxjUkZ/hyNrMFRNoo/NroeHLezVT81Uekc0DYBb0/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/rvBwF/hyNrMy5sZh/v61UfewFnBEIOQoh9ROTl0/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/zegxG/hyNrLmD8qb/kNTjCLnXquW4frJgxKfXtk/img.png?width=263&amp;amp;height=350&amp;amp;face=0_0_263_350&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxjUkZ/hyNrMFRNoo/NroeHLezVT81Uekc0DYBb0/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/rvBwF/hyNrMy5sZh/v61UfewFnBEIOQoh9ROTl0/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/zegxG/hyNrLmD8qb/kNTjCLnXquW4frJgxKfXtk/img.png?width=263&amp;amp;height=350&amp;amp;face=0_0_263_350');&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;실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;회원 기능&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;회원 등록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;회원 조회&lt;/span&gt;&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;span&gt;회원 등록&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Getter @Setter
public class MemberForm {
​
 &amp;nbsp; &amp;nbsp;@NotEmpty(message = &quot;회원 이름은 필수입니다. &quot;)
 &amp;nbsp; &amp;nbsp;private String name;
​
 &amp;nbsp; &amp;nbsp;private String city;
 &amp;nbsp; &amp;nbsp;private String street;
 &amp;nbsp; &amp;nbsp;private String zipcode;
}
​
​
//Controller
@PostMapping(&quot;/members/new&quot;)
public String create(@Valid MemberForm memberForm, BindingResult result) {
​
 &amp;nbsp;if(result.hasErrors()) {
 &amp;nbsp; &amp;nbsp;return &quot;members/createMemberForm&quot;;
  }
​
 &amp;nbsp;Address address = new Address(memberForm.getCity(), memberForm.getStreet(), memberForm.getZipcode());
 &amp;nbsp;Member member = new Member();
 &amp;nbsp;member.setName(memberForm.getName());
 &amp;nbsp;member.setAddress(address);
​
 &amp;nbsp;memberService.join(member);
 &amp;nbsp;return &quot;redirect:/&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;javax.validation을 통해서 쉽게 validation check가 가능&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;@NotEmpty, @Valid&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;스프링 2.3부터는 따로 라이브러리를 추가해야한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645105278542&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-validation'&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;BindingResult&lt;/span&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645105340216&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.validation.BindingResult;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@Vaild와 함께 BindingResult를 파라미터로 가지고 있으면, @Valid에 체크한 오류가 BindingResult에 담겨서 Controller가 실행이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Controller에서는 BindingResult의 hasErrors 메소드를 통해서 분기 처리가 가능하고, view 단에서 오류에 관련된 내용을 들고 가게 된다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645105400838&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;style&amp;gt;
.fieldError {
border-color: #bd2130;
}
&amp;lt;/style&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;​&lt;/code&gt;&lt;/pre&gt;
&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;hasErrors에 필드값 name이 있으면 해당 분기를 타게되고 input에 관련된 이벤트를 실행하게 된다. 추가로 th:errors로 인해 @NotEmpty에 정의한 메시지가 출력되게 된다.&lt;/span&gt;&lt;span&gt;&lt;/span&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;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주의해야할 점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;화면에서 주고받을 데이터(DTO)와 Entity의 필드값이 같다고 하더라도 별개의 클래스를 만들어 놓는 것이 좋다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;둘의 성격이 다르기 때문에!&lt;/span&gt;&lt;/b&gt;&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;span&gt;회원 목록&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;Entity와 DTO&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;API를 만들 때 데이터를 넘기는 경우 Entity를 넘기면(외부로 반환) 절대 안 되고 DTO를 넘겨야한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;만약, Member Entity에 새로운 필드를 추가하게 되면, API 스펙이 변경이 되어 큰 이슈가 발생될 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Entity는 순수하게 유지하는 것이 좋다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;화면에서 사용하는 데이터는 DTO를 따로 만들어 사용하는 것이 좋다.&lt;/span&gt;&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;span&gt;회원 Repository 개발&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Repository
public class MemberRepository {
​
 &amp;nbsp; &amp;nbsp;@PersistenceContext
 &amp;nbsp; &amp;nbsp;private EntityManager em;
​
 &amp;nbsp; &amp;nbsp;public void save(Member member) {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;em.persist(member);
 &amp;nbsp;  }
​
 &amp;nbsp; &amp;nbsp;public Member findOne(Long id) {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;return em.find(Member.class, id);
 &amp;nbsp;  }
​
 &amp;nbsp; &amp;nbsp;//jpql 사용
 &amp;nbsp; &amp;nbsp;public List&amp;lt;Member&amp;gt; findAll() {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;return em.createQuery(&quot;select m from Member m&quot;, Member.class)
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;  .getResultList();
 &amp;nbsp;  }
​
 &amp;nbsp; &amp;nbsp;//jpql 사용
 &amp;nbsp; &amp;nbsp;public List&amp;lt;Member&amp;gt; findByName(String name) {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;return em.createQuery(&quot;select m from Member m where m.name = :name&quot;, Member.class)
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;  .setParameter(&quot;name&quot;, name)
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;  .getResultList();
 &amp;nbsp;  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;@Repository&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Spring Bean으로 등록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@Component가 내장되어 있어 ComponentScan 시 스프링 빈으로 등록이 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@PersistenceContext&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;해당 어노테이션이 있으면 스프링이 EntityManager를 주입해준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;em.persist(member);&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;EntityManager에 의해 member Entity를 영속성 컨텍스트에 넣어주고 트랜잭션이 커밋되는 시점에 member를 저장하게 한다.(insert)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;em.find(Member.class, id);&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;단건 조회로서, 첫번째는 타입이고 두번째는 key값을 넣어주면 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;em.createQuery(&quot;select m from Member m&quot;, Member.class)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; .getResultList();&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;jpql을 사용한 것으로 쿼리문과 거의 비슷하나 from 절에 Member는 테이블이 아닌 Entity이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;첫번째 파라미터는 쿼리, 두번째 파라미터는 from 절에서 사용하는 Entity를 넣어준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;회원 서비스 개발&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Service
@Transactional(readOnly = true)
public class MemberService {
​
 &amp;nbsp; &amp;nbsp;@Autowired
 &amp;nbsp; &amp;nbsp;private MemberRepository memberRepository;
​
 &amp;nbsp; &amp;nbsp;/**
 &amp;nbsp; &amp;nbsp; * 회원 가입
 &amp;nbsp; &amp;nbsp; */
    @Transactional
 &amp;nbsp; &amp;nbsp;public Long join(Member member) {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;validateDuplicateMember(member); &amp;nbsp; &amp;nbsp;//중복 회원 검증
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;memberRepository.save(member);
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;return member.getId(); &amp;nbsp;//@GeneratedValue에 의해 id를 저장하지 않아도 값이 들어감
 &amp;nbsp;  }
​
 &amp;nbsp; &amp;nbsp;private void validateDuplicateMember(Member member) {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;List&amp;lt;Member&amp;gt; findMembers = memberRepository.findByName(member.getName());
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;//EXCEPTION
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;if(!findMembers.isEmpty()) {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
 &amp;nbsp; &amp;nbsp; &amp;nbsp;  }
 &amp;nbsp;  }
​
 &amp;nbsp; &amp;nbsp;/**
 &amp;nbsp; &amp;nbsp; * 회원 전체 조회
 &amp;nbsp; &amp;nbsp; */
 &amp;nbsp; &amp;nbsp;public List&amp;lt;Member&amp;gt; findMembers() {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;return memberRepository.findAll();
 &amp;nbsp;  }
​
 &amp;nbsp; &amp;nbsp;/**
 &amp;nbsp; &amp;nbsp; * 회원 조회
 &amp;nbsp; &amp;nbsp; */
 &amp;nbsp; &amp;nbsp;public Member findOne(Long id) {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;return memberRepository.findOne(id);
 &amp;nbsp;  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;@Transactional(readOnly = true)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;메소드 단계에서 readOnly 조건을 주게 되면, DB에게 해당 메소드에서 실행하는 로직은 단순히 읽기만 하니 많은 리소스를 사용하지 말라라는 의미를 줄 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Class 단계에서 Transactional을 사용하면 클래스 내에 있는 메소드에 모두 적용이 된다. 만약 트랜잭션의 조건을 변경하고 싶으면 join 메소드처럼 @Transactional을 걸어놓으면 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@Transactional&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;데이터 변경 시 어떤 이슈에 의해 중간에 오류가 나면 모두 rollback 처리할 수 있도록 해주는 어노테이션이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;데이터 변경 작업이 있으면 무조건 @Transactional을 줘야한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;validateDuplicateMember()&lt;/span&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;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이런 일을 대비하기 위해서 Member의 Name을 DB에서 유니크한 제약 조건을 걸어두는 것이 좋다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@Autowired&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;스프링에 의해서 인스턴스의 주입을 받을 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;해당 소스에서는 필드 인젝션 처리한 예이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Setter Injection&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;필드 인젝션 말고 세터 인젝션 방법도 있는데, 해당 방법을 사용하게 되면 테스트를 짤 때 Mock 객체를 넣어줌으로 원활하게 테스트가 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645105996041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberService {
 private MemberRepository memberRepository;
 
 @Autowired
 public void setMemberRepository(MemberRepository memberRepository) {
   this.memberRepository = memberRepository;
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;하지만 Setter를 사용하게 되면 MemberService가 뜨고 중간에 Setter에 의해서 Repository가 변경되는 경우가 발생할 수도 있다. 그러므로 가장 좋은 방법은 MemberSerivce의 생성자에 인젝션 처리를 하는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;생성자 Injection&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106085384&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberService {
	private final MemberRepository memberRepository; //final을 사용하면 생성자 시점에 repository를 정의해주지 않으면 오류가 발생하여 컴파일 시점 체크가 가능하다.
 
    @Autowired //없어도 됨.
    public MemberService(MemberRepository memberRepository) {
    	this.memberRepository = memberRepository;  
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;MemberService가 생성되는 동시에 Repository가 주입을 받으므로 중간에 Repository를 변경할 수가 없다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;그리고 테스트 케이스 작성할 때 MemberSerivce를 생성해야하는데 이 때, MemberService가 어떤 Repository를 의존하고 있는지 명확하게 알 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;추가로 요즘 스프링에서는 생성자가 하나만 있는 경우 @Autowired가 없어도 스프링이 인젝션 처리를 해준다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;또한, MemberRepository에 final을 사용하면 생성자 시점에 repository를 정의해주지 않으면 오류가 발생하여 컴파일 시점 체크가 가능하다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;롬복 사용 Injection&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106152136&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public class MemberService { 
    private final MemberRepository memberRepository; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;@RequiredArgsConstructor를 사용하게 되면 final 필드를 생성자의 파라미터로 갖게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;위 방법이 가장 많이 사용되는 인젝션 방법이고 생성자 Injection과 동일하다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;또한, 스프링 부트(Spring Data JPA)를 사용하면 Repository의 @PersistenceContext를 @Autowired로 줄 수 있어 Repository의 EntityManager를 아래와 같이 인젝션 처리가 가능하다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106189236&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class MemberRepository {
  	@AutoWired
    private EntityManager em;
  	
  	public MemberRepository(EntityManager em) {
      this.em = em;
    }
}

//아래로 변경 가능
@RequiredArgsConstructor
public class MemberRepository {
    private final EntityManager em;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;회원 기능 테스트&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;회원 가입을 성공해야한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;회원 가입 시 같은 이름이면 예외를 발생시킨다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;@Transactional이 테스트 케이스에 존재하게 되면 기본적으로 Rollback 처리를 한다.&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;이로 인해서 Repository.save하는 경우 console에 insert는 안보이게 되는데, 만약 insert 하는 것을 보고 싶다면 EntityManager를 테스트 케이스에서 주입 받은 후 flush 처리를 하면 된다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106316396&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional  //rollback이 가능
public class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;
  	@Autowired EntityManager em;

    @Test
    public void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName(&quot;Lim&quot;);

        //when
        Long savedId = memberService.join(member);

        //then
      	em.flush();	//member가 들어간 영속성 컨텍스트가 쿼리로 DB에 반영(강제로 DB에 반영)
        assertEquals(member, memberRepository.findOne(savedId));
    }
}​&lt;/code&gt;&lt;/pre&gt;
&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;쿼리에 반영 후 Transactional에 의해서 마지막에 롤백을 하게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;만약 DB에 넣고 싶다면 해당 메소드에 Rollback(false)를 입력해주면 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106347256&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@Rollback(false)
public void 회원가입() throws Exception {
      
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Exception 처리 테스트&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106376209&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void 중복_회원_예외() throws Exception {
  //given
  Member member1 = new Member();
  member1.setName(&quot;Lim1&quot;);
  Member member2 = new Member();
  member2.setName(&quot;Lim1&quot;);

  //when
  memberService.join(member1);
  try {
    memberService.join(member2); //예외가 발생해야 한다.
  } catch (IllegalStateException e) {
    return;
  }

  //then
  fail(&quot;에외가 발생해야 한다.&quot;);   //해당 라인을 타게되면 테스트 실패
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;MemberService.join에서 IllegalStateException 예외가 발생하면 catch를 하고 return을 함으로써 fail() 메소드를 안 타게 되어 테스트가 성공하게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;하지만 코드의 복잡성을 줄이기 위해 아래와 같이 작성할 수 있다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;@Test(expected = Exception 종류)를 함으로써 코드를 줄일 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106402951&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test(expected = IllegalStateException.class)
public void 중복_회원_예외() throws Exception {
  //given
  Member member1 = new Member();
  member1.setName(&quot;Lim1&quot;);
  Member member2 = new Member();
  member2.setName(&quot;Lim1&quot;);

  //when
  memberService.join(member1);
  memberService.join(member2); //예외가 발생해야 한다.

  //then
  fail(&quot;에외가 발생해야 한다.&quot;);   //해당 라인을 타게되면 테스트 실패
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;전체 소스 확인&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106583493&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional  //rollback이 가능
public class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {
        //given
        Member member = new Member();
        member.setName(&quot;Lim&quot;);

        //when
        Long savedId = memberService.join(member);

        //then
        assertEquals(member, memberRepository.findOne(savedId));
    }

    @Test(expected = IllegalStateException.class)
    public void 중복_회원_예외() throws Exception {
        //given
        Member member1 = new Member();
        member1.setName(&quot;Lim1&quot;);
        Member member2 = new Member();
        member2.setName(&quot;Lim1&quot;);

        //when
        memberService.join(member1);
        memberService.join(member2); //예외가 발생해야 한다.

        //then
        fail(&quot;에외가 발생해야 한다.&quot;);   //해당 라인을 타게되면 테스트 실패
    }
}​&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@RunWith(SpringRunner.class)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Junit 실행 시 스프링이랑 같이 엮어서 실행하겠다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@SpringBootTest&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;스프링 부트를 띄운 상태에서 테스트하겠다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;해당 어노테이션이 없으면 @Autowired가 모두 실패 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;스프링 컨테이너 안에서 테스트를 돌리는 것으로 보면 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@Transactional&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;테스트가 끝나면 모두 Rollback 처리를 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;메모리 DB 사용&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;실제 사용하는 DB가 아닌 테스트용으로 잠깐 사용할 DB를 메모리를 통해 사용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;test 패키지 밑에 resources의 패키지를 추가하여 test 할 시에는 메모리 DB를 탈 수 있도록 할 수 있다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;운영 소스인 java 및 resources와 테스트 소스인 test 및 resources가 있을 때 test의 경우 test 밑에 있는 resources의 yml을 바라보게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;test용 resources/application.yml&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1645106661314&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    url: jdbc:h2:mem:test
    username: sa
    password:
      driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
#        show_sql: true
        format_sql: true
      
logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type: trace
server:
  port: 8083​&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;h2의 경우 h2에서 제공해주는 메모리 DB url을 설정함으로써, 운영 DB 접속 여부에 상관없이 메모리 DB를 활용하여 테스트를 진행할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;하지만 스프링 부트의 경우 아래 application.yml처럼 모두 주석처리 해줘도 된다. 스프링 부트가 기본적으로 메모리 DB를 제공해준다.&lt;/span&gt;&lt;/b&gt;&lt;/span&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;
&lt;pre id=&quot;code_1645106691566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#spring:
#  datasource:
#    url: jdbc:h2:mem:test
#    username: sa
#    password:
#      driver-class-name: org.h2.Driver
#
#  jpa:
#    hibernate:
#      ddl-auto: create
#    properties:
#      hibernate:
##        show_sql: true
#        format_sql: true

logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type: trace
server:
  port: 8083​&lt;/code&gt;&lt;/pre&gt;
&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;메모리 DB를 사용하게 되면 마지막에 모두 drop처리를 한다.&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/Spring Boot JPA</category>
      <category>JPA</category>
      <category>junit</category>
      <category>Spring</category>
      <category>springboot</category>
      <category>test</category>
      <category>transactional</category>
      <category>김영한</category>
      <category>인프런</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/211</guid>
      <comments>https://it-mesung.tistory.com/211#entry211comment</comments>
      <pubDate>Thu, 17 Feb 2022 23:05:51 +0900</pubDate>
    </item>
    <item>
      <title>[이슈] 스프링 빈 순환 참조 (Error Msg: The dependencies of some of the beans in the application context form a cycle)</title>
      <link>https://it-mesung.tistory.com/210</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 빈을 참조하는데 A와 B가 서로 참조하는 경우 스프링 빈 순환 참조 문제가 발생할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| userServiceImpl defined in URL [jar:file:/app.jar!/BOOT-INF/classes!/com/mesung/domain/service/impl/userServiceImpl.class]
&amp;uarr;     &amp;darr;
| orderServiceImpl (field private com.meusng.domain.service.userService com.meusng.domain.service.orderServiceImpl.userService)
└─────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위 로그처럼 userServiceImpl과 orderServiceImpl에서 서로 참조를 하게 되면, 스프링은 어떤 걸 먼저 생성해야 하는지 모르기 때문에 에러를 뱉어내게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;그래서 임시방편으로 @RequiredArgsConstructor를 주석 처리 후 직접 생성자를 만들어서 순환 참조가 발생되는 빈(orderServiceImpl)에 @Lazy 어노테이션을 줌으로써 지연 로딩을 통해 해결할 수 있다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class OrderSerivceImpl implements OrderService {
    private final UserService userService;
  public UserServiceImpl(@Lazy UserService userService) {
    this.userService = userService;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>기타</category>
      <category>@Lazy</category>
      <category>Spring</category>
      <category>빈 순환 참조</category>
      <category>스프링</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/210</guid>
      <comments>https://it-mesung.tistory.com/210#entry210comment</comments>
      <pubDate>Wed, 20 Oct 2021 14:48:20 +0900</pubDate>
    </item>
    <item>
      <title>[Java8] Optional</title>
      <link>https://it-mesung.tistory.com/208</link>
      <description>&lt;figure id=&quot;og_1632832481085&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;더 자바, Java 8 - 인프런 | 강의&quot; data-og-description=&quot;자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이 &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/the-java-java8&quot; data-og-url=&quot;https://www.inflearn.com/course/the-java-java8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcBqcQ/hyLLA1QVXg/tA8G4I0j6QngbkyIbRoT20/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cbOJ3A/hyLLNmAi3a/dBie23K4HcrmZHcScef4hK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/br8v2K/hyLLLCilKr/sUYidU6NACLOjxslSm2gEK/img.png?width=706&amp;amp;height=441&amp;amp;face=0_0_706_441&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-java8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/the-java-java8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcBqcQ/hyLLA1QVXg/tA8G4I0j6QngbkyIbRoT20/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cbOJ3A/hyLLNmAi3a/dBie23K4HcrmZHcScef4hK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/br8v2K/hyLLLCilKr/sUYidU6NACLOjxslSm2gEK/img.png?width=706&amp;amp;height=441&amp;amp;face=0_0_706_441');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;더 자바, Java 8 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Optional은 언제 사용되는 것인가?&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;/ul&gt;
&lt;pre id=&quot;code_1632831135257&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class App {
  public static void main(String[] args) {

    OnlineClass spring_boot = new OnlineClass(1, &quot;spring boot&quot;, true);

    Duration studyDuration = spring_boot.getProgress().getStudyDuration();
    //출력을 하면 결과는 어떻게 될까?
    System.out.println(studyDuration);

  }
}

public class OnlineClass {

    private Integer id;
    private String title;
    private boolean closed;
    public Progress progress;

    public OnlineClass(Integer id, String title, boolean closed) {
        this.id = id;
        this.title = title;
        this.closed = closed;
    }

    public Integer getId() {
        return id;
    }

    ...

    public Progress getProgress() {
        return progress;
    }

    public void setProgress(Progress progress) {
        this.progress = progress;
    }
}

public class Progress {

    private Duration studyDuration;
    private boolean finished;

    public Duration getStudyDuration() {
        return studyDuration;
    }

    public void setStudyDuration(Duration studyDuration) {
        this.studyDuration = studyDuration;
    }
}&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;main을 보면 OnlineClass를 생성 후 OnlineClass에 있는 Progress 인스턴스가 의존하고 있는 Duration을 호출하려는 로직을 가지고 있다.&lt;/li&gt;
&lt;li&gt;하지만 결과는 'NullPointerException'이 나타난다.&lt;/li&gt;
&lt;li&gt;이유는 당연하다. 현재 OnlineClass의 Progress는 Null이기 때문이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;그렇다면 이런 이슈를 방지 하기 위해 우리는 기존에 어떻게 체크를 해왔는가?&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Optional 전 해결방안(조건문 활용)&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;
&lt;pre id=&quot;code_1632831216491&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
  ...
    
  OnlineClass spring_boot = new OnlineClass(1, &quot;spring boot&quot;, true);
  Progress progress = spring_boot.getProgress();
  if (progress != null) {
    System.out.println(progress.getStudyDuration());
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;보시는 바와 같이 OnlineClass에서 Progress를 가져온 후 조건문을 통해 null 체크하는 방식을 사용해왔다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;하지만 &lt;b&gt;이런 방식은 개발자 이슈가 나타날 수 있는 경우&lt;/b&gt;이다. 즉, 사람이기에 null 체크하는 부분을 잊을 수 있다는 것이다.&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Optional 전 해결방안(throw Exception)&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;
&lt;pre id=&quot;code_1632831283401&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OnlineClass {
  ...
    
  public Progress getProgress() {
    if (this.progress == null) {
      throw new IllegalStateException();
    }
    return progress;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;getProgress()할 때 미리 null 체크를 하여 Exception을 던지는 방법이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하지만 위 같은 방법을 보면 로직 처리 하는 부분에 Exception을 던지도록 하였는데, 이 방법은 그리 좋은 방법이 아니다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그 이유는, Exception을 발생하도록 하게 되면 Java에서는 Stack Trace를 찍게 된다. 즉, 해당 Exception이 발생하기 까지의 어떤 콜 스택을 거쳐서 Exception이 발생했는지에 대해 찍게 된다. 결과적으로 리소스를 사용하게 된다는 것이다.&lt;/li&gt;
&lt;li&gt;그러므로 정말 필요한 경우에믄 Exception을 사용해야된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Optional 사용 해결방안&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632831407319&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OnlineClass {
  ...
    
  public Optional&amp;lt;Progress&amp;gt; getProgress() {
  	//progress가 null일 경우 optional.empty()를 반환
    return Optional.ofNullable(progress);
  }
}

public static void main(String[] args) {
  ...
    
  OnlineClass spring_boot = new OnlineClass(1, &quot;spring boot&quot;, true);
  spring_boot.getProgress()
                .ifPresent(duration -&amp;gt; {
                    System.out.println(duration.getStudyDuration());
                });
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Optional 사용 시 주의사항&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;리턴값으로만 쓰기를 권장한다.&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;메소드 매개변수 타입으로 쓰지 말아라&lt;/b&gt;&lt;/span&gt;(매개변수 타입으로 Null이 들어올 수가 있다.)&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632831479047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void setProgress(Optional&amp;lt;Progress&amp;gt; progress) {
  progress.ifPresent(p -&amp;gt; this.progress = p);
}
//progress 매개변수가 Null로 들어올 경우 위 로직은 다시 NullPoineException이 발생한다. 

//만약 정상적으로 실행하고 싶다면 아래와 같이 한번 더 Null 체크를 해줘야 한다.
if(progress != null) {
    progress.ifPresent(p -&amp;gt; this.progress = p);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말아라.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional을 리턴하는 메소드에서 null을 리턴하지 말아라.&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;굳이 빈값을 리턴하고 싶으면 null을 리턴하지 말고 Optinal.empty()를 리턴하도록 해라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Primitive 용 Optional은 따로 존재한다.&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OptionalDouble, OptionalInt, ...&lt;/li&gt;
&lt;li&gt;만약 Optional.of(10)을 하게되면 Optional 안에서 박싱, 언박싱을 진행하므로 성능상 이슈가 있을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Optional 주요 사용 메소드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632899615121&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {

  List&amp;lt;OnlineClass&amp;gt; springClasses = new ArrayList&amp;lt;&amp;gt;();
  springClasses.add(new OnlineClass(1, &quot;spring boot&quot;, true));
  springClasses.add(new OnlineClass(2, &quot;spring data jpa&quot;, true));
  springClasses.add(new OnlineClass(3, &quot;spring mvc&quot;, false));
  springClasses.add(new OnlineClass(4, &quot;spring core&quot;, false));
  springClasses.add(new OnlineClass(5, &quot;rest api development&quot;, false));
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional of()와 ofNullable()&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;of(); -&amp;gt; of 메소드 안에 파라미터가 Null이 아님을 확신할 때 사용. 만약 파라미터가 Null이면 NullPointerException 발생&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;ofNullable(); -&amp;gt; ofNullable 메소드 안에 파라미터가 Null일수도 있을 경우 사용.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional 값 여부 확인&lt;/b&gt;&lt;/span&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;i&gt;isPresent(); -&amp;gt; Optoinal의 값이 있는지 판단(true/false 반환)(Null이면 NPE)&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;isEmpty(); (Java 11 부터 제공) &lt;b&gt;&lt;i&gt;-&amp;gt; true/false 반환(&lt;b&gt;&lt;i&gt;Null이면 NPE)&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632899521064&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {

  ...

  //isPresent
  Optional&amp;lt;OnlineClass&amp;gt; spring = springClasses.stream()
    .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;spring&quot;))
    .findFirst();
  boolean present = spring.isPresent();
  System.out.println(present);	//true

  //isEmpty
  Optional&amp;lt;OnlineClass&amp;gt; jpa = springClasses.stream()
    .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;jpa&quot;))
    .findFirst();
	boolean present2 = jpa.isEmpty();
  System.out.println(present2);	//true
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional 값 호출&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;i&gt;get(); -&amp;gt; Optional 값 가져옴(Null이면 NPE).&lt;/i&gt;&lt;/b&gt;&lt;/span&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;pre id=&quot;code_1632831689202&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
  ...
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
  			.filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;spring&quot;))
		  	.findFirst();

  //get을 통해 Optional의 값을 호출할 수 있음
  OnlineClass onlineClass = optional.get();
  System.out.println(onlineClass.getTitle());	//spring boot
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;위 소스 처럼 &lt;b&gt;get()을 통해 Optional의 값을 호출&lt;/b&gt;할 수 있으나, &lt;b&gt;만약 Optional이 없는 경우&lt;/b&gt;에는 어떻게 될까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632831734232&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
  ...
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
          .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;jpa&quot;))
          .findFirst();

  //get을 통해 Optional의 값을 호출할 수 있음
  OnlineClass onlineClass = optional.get();
  System.out.println(onlineClass.getTitle());	//NoSuchElementException
}​&lt;/code&gt;&lt;/pre&gt;
&amp;nbsp;&lt;/li&gt;
&lt;li&gt;현재 '&lt;b&gt;jpa&lt;/b&gt;'로 시작되는 타이틀은 존재하지 않는다. 이런 경우에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;NoSuchElementException&lt;/b&gt;&lt;/span&gt;으로 &lt;b&gt;RuntimeException&lt;/b&gt;이 발생하게 된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;i&gt;결과적으로 get()이란 메소드를 사용하는 것은 크게 권장하지 않는다.&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional 값 있는 경우 해당 값 사용&lt;/b&gt;&lt;/span&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;i&gt;ifPresent(); -&amp;gt; 값이 있을 경우에만 해당 메소드 로직 수행(&lt;b&gt;&lt;i&gt;Null이면 NPE)&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 사용한 get() 메소드 대신 ifPresent()를 사용해보자.&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1633007171449&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
  ...
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
                  .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;spring&quot;))
                  .findFirst();
  optional.ifPresent(onlineClass -&amp;gt; System.out.println(onlineClass.getTitle()));
  //결과 : spring boot

  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
                  .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;jpa&quot;))
                  .findFirst();
  optional.ifPresent(onlineClass -&amp;gt; System.out.println(onlineClass.getTitle()));
  //결과 : 빈값
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;이 처럼 Optional의 값을 사용하고 싶을 때는 ifPresent를 사용하는 것이 효율적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional 값 있는 경우 가져오고, 없는 경우 정의한 값 리턴&lt;/b&gt;&lt;/span&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;i&gt;orElse(); -&amp;gt; (상수로 이미 만들어져있는 것을 사용할 때 사용하는 것을 권장)(&lt;b&gt;&lt;i&gt;Null이면 NPE)&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632832068279&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;jpa&quot;))
                .findFirst();

  //orElse : 값이 있는 경우 해당 값을 가져오고, 없는 경우 orElse 매개변수에 있는 값을 리턴한다.
  OnlineClass onlineClass = optional.orElse(createNewClass());
  System.out.println(onlineClass.getTitle())
}

private static OnlineClass createNewClass() {
  System.out.println(&quot;creating new class&quot;);
  return new OnlineClass(6, &quot;New Class&quot;, false);
}
//결과
//creating new class
//New Class&lt;/code&gt;&lt;/pre&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;
&lt;pre id=&quot;code_1632832089597&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;spring&quot;))
                .findFirst();

  //orElse : 값이 있는 경우 해당 값을 가져오고, 없는 경우 orElse 매개변수에 있는 값을 리턴한다.
  OnlineClass onlineClass = optional.orElse(createNewClass());
  System.out.println(onlineClass.getTitle())
}

private static OnlineClass createNewClass() {
  System.out.println(&quot;creating new class&quot;);
  return new OnlineClass(6, &quot;New Class&quot;, false);
}
//결과
//creating new class
//spring boot&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;b&gt;여기서 주의해야할 점은, 분명 값이 있는데도 불구하고 새롭게 생성하는 클래스의 로직을 타는 것을 볼 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;즉, 값이 있든 없든 orElse()에 속해있는 로직은 무조건 탄다.&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;'create new class' 출력&amp;nbsp;&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;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional 값 있는 경우 가져오고, 없는 경우 로직 처리&lt;/b&gt;&lt;/span&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;i&gt;orElseGet();&amp;nbsp; -&amp;gt; (동적으로 작업을 해서 만들어야할 경우 사용하는 것을 권장)&lt;b&gt;&lt;i&gt;(&lt;b&gt;&lt;i&gt;Null이면 NPE)&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;orElse()를 보면 뭔가 찝찝하다.. 값이 있다면 새롭게 생성하는 클래스를 호출하기 싫은데.. 이럴 때 &lt;span style=&quot;color: #ee2323;&quot;&gt;orElseGet()을 사용하면 된다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;spring&quot;))
                .findFirst();

  //orElseGet : 값이 있는 경우 해당 값을 가져오고, 없는 경우에만 orElseGet 매개변수에 있는 값을 리턴한다.
  OnlineClass onlineClass = optional.orElseGet(createNewClass());
  System.out.println(onlineClass.getTitle())
}

private static OnlineClass createNewClass() {
  System.out.println(&quot;creating new class&quot;);
  return new OnlineClass(6, &quot;New Class&quot;, false);
}
//결과
//spring boot&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional 값 있는 경우 가져오고 없는 경우 예외 처리&lt;/b&gt;&lt;/span&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;i&gt;orElseThrow();&lt;/i&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;또한, 클래스를 새롭게 생성하지 않고 Exception을 날려줄 수도 있다.&lt;b&gt;&lt;i&gt;(&lt;b&gt;&lt;i&gt;Null이면 NPE)&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;jpa&quot;))
                .findFirst();

  //람다식
  OnlineClass onlineClass = optional.orElseThrow(() -&amp;gt; {
    return new IllegalArgumentException();
  });

  //메소드 레퍼런스
   OnlineClass onlineClass = optional.orElseThrow(IllegalArgumentException::new)
     System.out.println(onlineClass.getTitle())
}

private static OnlineClass createNewClass() {
  System.out.println(&quot;creating new class&quot;);
  return new OnlineClass(6, &quot;New Class&quot;, false);
}
&lt;/code&gt;&lt;/pre&gt;
&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;&lt;b&gt;결과적으로&amp;nbsp; isPresent() 와 get()은 사용하지 않는 것이 좋다.&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;b&gt;isPresent(); 와 get(); 대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;b&gt;ifPresent, orElseGet, orElseThrow등을 사용하라&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;b&gt;&lt;i&gt;이유는 get(); isPresent()를 통해서&amp;nbsp; 로직을 구성할 시&amp;nbsp; 코드가 길어지는 것을 볼 수 있다.&lt;/i&gt;&lt;/b&gt;&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632899026253&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;spring&quot;))
                .findFirst();

//isPresent()와 get() 사용
if(optional.isPresent()) {
  OnlineClass onlineClass = optional.get();
} else {
  OnlineClass onlineClass = createNewClass();
}
//orElseGet() 사용
OnlineClass onlineClass = optional.orElseGet(createNewClass());&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;Java9에서는 ifPresentOrElse();를 통해 ifPresent와 orElseGet을 동시에 사용할 수 있다.&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;Optional에 있는 값 필터링&lt;/span&gt;&lt;/b&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;i&gt;filter();&lt;/i&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optional의 filter() 메소드를 통해 Optional의 조건 값을 줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  ...
  springClasses.add(new OnlineClass(5, &quot;rest api development&quot;, false));

  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
    .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;rest&quot;))
    .findFirst();

  //filter를 통해 optional 안에 있는 값의 조건을 줄 수 있다.
  Optional&amp;lt;OnlineClass&amp;gt; optional1 = optional.filter(onlineClass -&amp;gt; onlineClass.isClosed());
  System.out.println(optional1.isEmpty());
}

//결과 : true&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;Optional에 있는 값 변환&lt;/b&gt;&lt;/span&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;i&gt;map();&lt;/i&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optional의 mpa() 메소드를 통해 Optional의 리턴값을 변환할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  ...
  springClasses.add(new OnlineClass(5, &quot;rest api development&quot;, false));

  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
    .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;rest&quot;))
    .findFirst();

  //map을 통해 getId()를 호출할 시 Optional&amp;lt;Integer&amp;gt; 형태로 리턴이 된다.
  Optional&amp;lt;Integer&amp;gt; integer = optional.map(onlineClass -&amp;gt; onlineClass.getId());
  System.out.println(integer.isPresent());
}

//결과 : true&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;flatMap();&lt;/i&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;map으로 추출 시 Optional&amp;lt;&amp;gt;이 아닌 Opitonal을 한번 더 감싼 Optional&amp;lt;Optional&amp;lt;&amp;gt;&amp;gt;이라면 flatMap을 통해 한번에 Optional&amp;lt;&amp;gt;로 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class OnlineClass {
  public Optional&amp;lt;Progress&amp;gt; getProgress() {
    return Optional.ofNullable(progress);
  }
}

public static void main(String [] args) {
  ...
  springClasses.add(new OnlineClass(5, &quot;rest api development&quot;, false));

  Optional&amp;lt;OnlineClass&amp;gt; optional = springClasses.stream()
    .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;rest&quot;))
    .findFirst();

  //map으로 표현하면 아래와 같다.
  Optional&amp;lt;Optional&amp;lt;Progress&amp;gt;&amp;gt; progress1 = optional.map(onlineClass -&amp;gt; onlineClass.getProgress());
  Optional&amp;lt;Progress&amp;gt; progress2 = progress1.orElse(Optional.empty());

  //flatMap을 사용할 시 쌍으로 되어있는 Optional을 풀 수 있다.
  Optional&amp;lt;Progress&amp;gt; progress = optional.flatMap(onlineClass -&amp;gt; onlineClass.getProgress());
}&lt;/code&gt;&lt;/pre&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;i&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Stream의 flatMap()&lt;/b&gt;&lt;/span&gt;을 사용하는 경우는 input은 하나인데 output이 여러개일 경우 사용하는 것으로서 Optional과는 다르다.&lt;/i&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ex. input은 하나인데, list1과 list2가 output 되는 경우, flatMap을 사용하여 list1과 list2를 하나의 list로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java/Java8</category>
      <category>flatMap</category>
      <category>ifPresent</category>
      <category>java</category>
      <category>java8</category>
      <category>optional</category>
      <category>자바</category>
      <category>자바8</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/208</guid>
      <comments>https://it-mesung.tistory.com/208#entry208comment</comments>
      <pubDate>Tue, 28 Sep 2021 21:37:51 +0900</pubDate>
    </item>
    <item>
      <title>[Java8] Stream API</title>
      <link>https://it-mesung.tistory.com/207</link>
      <description>&lt;figure id=&quot;og_1628925733720&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;더 자바, Java 8 - 인프런 | 강의&quot; data-og-description=&quot;자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이 &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/the-java-java8/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/the-java-java8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8Ukpe/hyLerxL4wr/0o54EQwYU8lAKTrK9wme1k/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/ppDkn/hyLerYN87D/JI52xOkRVl1k5OBfQOKkwk/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/lbeWe/hyLeFQkBzR/GSgAcUxPcmnldo75TVa2o1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-java8/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/the-java-java8/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8Ukpe/hyLerxL4wr/0o54EQwYU8lAKTrK9wme1k/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/ppDkn/hyLerYN87D/JI52xOkRVl1k5OBfQOKkwk/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/lbeWe/hyLeFQkBzR/GSgAcUxPcmnldo75TVa2o1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;더 자바, Java 8 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1632871772159&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;&amp;gt;();
names.add(&quot;mesung&quot;);
names.add(&quot;ms&quot;);
names.add(&quot;11st&quot;);

Stream&amp;lt;String&amp;gt; stringStream = names.stream().map(String::toUpperCase);
//대문자로 변경하는 로직을 태워도 names의 근본 값들은 변경되지 않는다.&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;무제한일 수도 있다. 단 Short Circuti 메소드를 사용해서 몇개의 리스트만 보겠다라고 제한을 들 수도 있다.)&lt;/li&gt;
&lt;li&gt;스트림에는 중간 연산와 종료 연산가 있으며, 중간 단계의 경우 근본적으로 lazy하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;&amp;gt;();
names.add(&quot;mesung&quot;);
names.add(&quot;ms&quot;);
names.add(&quot;11st&quot;);

names.stream()
  .map(s -&amp;gt; {
    System.out.println(s);
    return s.toUpperCase(Locale.ROOT);
  });

System.out.println(&quot;==========&quot;);

//결과
//==========&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;lazy한다는 의미는 중간 연산의 경우 종료 연산을 만날 때까지는 실행하지 않고 정의만 한 것이다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;즉, 스트림 파이프 라인을 정의해야 한다! -&amp;gt; 중간 연산와 종료 연산의 조합(무조건 종료 연산이 있어야한다.)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public static void main(String[] args) {
  List&amp;lt;String&amp;gt; names = new ArrayList&amp;lt;&amp;gt;();
  names.add(&quot;mesung&quot;);
  names.add(&quot;ms&quot;);
  names.add(&quot;11st&quot;);

  List&amp;lt;String&amp;gt; collect = names.stream()
    .map(s -&amp;gt; {
      System.out.println(s);
      return s.toUpperCase(Locale.ROOT);
    })
    .collect(Collectors.toList());
  collect.forEach(System.out::println);

  System.out.println(&quot;==========&quot;);
}

//결과
//mesung
//ms
//11st
//MESUNG
//MS
//11ST
//==========&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;손쉽게 병렬 처리를 할 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스트림 파이프라인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 또는 다수의 중간 연산과 한개의 종료 연산으로 구성되어 있다.&lt;/li&gt;
&lt;li&gt;스트림의 데이터 소스는 반드시 종료 여난이 실행될 때만 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중간 연산&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Stream을 리턴한다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;filter, map, limit, skip, sorted&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;종료 연산&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Stream을 리턴하지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;collect, allMatch, count, forEach, min, max&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스트림 사용 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;직접 풀어보시고 아래 답변 확인하시면 도움이 될 겁니다.&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public class App {

    public static void main(String[] args) {

        List&amp;lt;OnlineClass&amp;gt; springClasses = new ArrayList&amp;lt;&amp;gt;();
        springClasses.add(new OnlineClass(1, &quot;spring boot&quot;, true));
        springClasses.add(new OnlineClass(2, &quot;spring data jpa&quot;, true));
        springClasses.add(new OnlineClass(3, &quot;spring mvc&quot;, false));
        springClasses.add(new OnlineClass(4, &quot;spring core&quot;, false));
        springClasses.add(new OnlineClass(5, &quot;rest api development&quot;, false));

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;spring 으로 시작하는 수업&quot;);

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;close 되지 않은 수업&quot;);

        System.out.println(&quot;=========메서드 레퍼런스 사용==========&quot;);
        System.out.println(&quot;close 되지 않은 수업&quot;);

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;수업 이름만 모아서 스트림 만들기&quot;);



        List&amp;lt;OnlineClass&amp;gt; javaClasses = new ArrayList&amp;lt;&amp;gt;();
        javaClasses.add(new OnlineClass(6, &quot;The Java, Test&quot;, true));
        javaClasses.add(new OnlineClass(7, &quot;The Java, Code manipulation&quot;, true));
        javaClasses.add(new OnlineClass(8, &quot;The Java, 8 to 11&quot;, false));

        List&amp;lt;List&amp;lt;OnlineClass&amp;gt;&amp;gt; keesunEvents = new ArrayList&amp;lt;&amp;gt;();
        keesunEvents.add(springClasses);
        keesunEvents.add(javaClasses);

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;두 수업 목록에 들어있는 모든 수업 아이디 출력&quot;);
        // TODO

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;10부터 1씩 증가하는 무제한 스트림 중에서 앞에 10개 빼고 최대 10개 까지만&quot;);
        // TODO

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;자바 수업 중에 Test가 들어있는 수업이 있는지 확인&quot;);
        // TODO

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;스프링 수업 중에 제목에 spring이 들어간 것만 모아서 List로 만들기&quot;);
        // TODO

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제 답변&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628925569522&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class App {
    public static void main(String[] args) {

        List&amp;lt;OnlineClass&amp;gt; springClasses = new ArrayList&amp;lt;&amp;gt;();
        springClasses.add(new OnlineClass(1, &quot;spring boot&quot;, true));
        springClasses.add(new OnlineClass(2, &quot;spring data jpa&quot;, true));
        springClasses.add(new OnlineClass(3, &quot;spring mvc&quot;, false));
        springClasses.add(new OnlineClass(4, &quot;spring core&quot;, false));
        springClasses.add(new OnlineClass(5, &quot;rest api development&quot;, false));

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;spring 으로 시작하는 수업&quot;);
        springClasses.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().startsWith(&quot;spring&quot;))
                .forEach(onlineClass -&amp;gt; System.out.println(onlineClass.getId()));

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;close 되지 않은 수업&quot;);
        springClasses.stream()
                .filter(onlineClass -&amp;gt; !onlineClass.isClosed())
                .forEach(onlineClass -&amp;gt; System.out.println(onlineClass.getId()));
        System.out.println(&quot;=========메서드 레퍼런스 사용==========&quot;);
        springClasses.stream()
                .filter(Predicate.not(OnlineClass::isClosed))
                .forEach(onlineClass -&amp;gt; System.out.println(onlineClass.getId()));

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;수업 이름만 모아서 스트림 만들기&quot;);
        springClasses.stream()
                .map(onlineClass -&amp;gt; onlineClass.getTitle())
                .forEach(System.out::println);


        List&amp;lt;OnlineClass&amp;gt; javaClasses = new ArrayList&amp;lt;&amp;gt;();
        javaClasses.add(new OnlineClass(6, &quot;The Java, Test&quot;, true));
        javaClasses.add(new OnlineClass(7, &quot;The Java, Code manipulation&quot;, true));
        javaClasses.add(new OnlineClass(8, &quot;The Java, 8 to 11&quot;, false));

        List&amp;lt;List&amp;lt;OnlineClass&amp;gt;&amp;gt; keesunEvents = new ArrayList&amp;lt;&amp;gt;();
        keesunEvents.add(springClasses);
        keesunEvents.add(javaClasses);

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;두 수업 목록에 들어있는 모든 수업 아이디 출력&quot;);
        //me
        keesunEvents.stream()
                .flatMap(list -&amp;gt; list.stream())
                .forEach(onlineClass -&amp;gt; System.out.println(onlineClass.getId()));

        //keesun
        keesunEvents.stream()
                .flatMap(Collection::stream)
                .forEach(onlineClass -&amp;gt; System.out.println(onlineClass.getId()));


        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;10부터 1씩 증가하는 무제한 스트림 중에서 앞에 10개 빼고 최대 10개 까지만&quot;);
        Stream.iterate(10, i -&amp;gt; i + 1)
                .skip(10)
                .limit(10)
                .forEach(System.out::println);

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;자바 수업 중에 Test가 들어있는 수업이 있는지 확인&quot;);
        //me
        keesunEvents.stream()
                .flatMap(list -&amp;gt; list.stream())
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().contains(&quot;Java&quot;))
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().contains(&quot;Test&quot;))
                .map(onlineClass -&amp;gt; onlineClass.getTitle())
                .forEach(System.out::println);

        //keesun
        boolean test = javaClasses.stream()
                .anyMatch(onlineClass -&amp;gt; onlineClass.getTitle().contains(&quot;Test&quot;));
        System.out.println(test);

        System.out.println(&quot;===================&quot;);
        System.out.println(&quot;스프링 수업 중에 제목에 spring이 들어간 것만 모아서 List로 만들기&quot;);
        //me
        List&amp;lt;OnlineClass&amp;gt; springClass = springClasses.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().contains(&quot;spring&quot;))
                .collect(Collectors.toList());
        springClass.forEach(spring -&amp;gt; System.out.println(spring.getTitle()));

        //keesun
        List&amp;lt;String&amp;gt; springTitle = springClass.stream()
                .filter(onlineClass -&amp;gt; onlineClass.getTitle().contains(&quot;spring&quot;))
                .map(onlineClass -&amp;gt; onlineClass.getTitle())
                .collect(Collectors.toList());
        springTitle.forEach(System.out::println);

    }
}&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;b&gt;flatMap : 다중 리스트를 단일 리스트로 변경할 수 있는 중간 단계&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Java/Java8</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/207</guid>
      <comments>https://it-mesung.tistory.com/207#entry207comment</comments>
      <pubDate>Sat, 14 Aug 2021 16:22:24 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot JPA] 테이블 설계 및 Entity 개발 시 주의사항</title>
      <link>https://it-mesung.tistory.com/206</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1628919585874&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;실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의&quot; data-og-description=&quot;실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., 본 &quot; data-og-host=&quot;www.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iGHlG/hyLes4oWKw/t65Wz24r5XRph68mHWXnlk/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/bpiijl/hyLezCuNtf/WiUFrLrvyPepLbCQARuonk/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/EF1Fi/hyLerxFau5/ESu1Pm0g9hnvBf5cN2cI21/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iGHlG/hyLes4oWKw/t65Wz24r5XRph68mHWXnlk/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/bpiijl/hyLezCuNtf/WiUFrLrvyPepLbCQARuonk/img.png?width=768&amp;amp;height=500&amp;amp;face=513_86_648_233,https://scrap.kakaocdn.net/dn/EF1Fi/hyLerxFau5/ESu1Pm0g9hnvBf5cN2cI21/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., 본&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;1:N 관계에서는 N 쪽이 무조건 외래키가 존재한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일대다, 다대일 양방향 관계에서는 연관관계의 주인&lt;/b&gt;을 정해야하는데, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;외래키가 있는 쪽을 연관관계의 주인&lt;/b&gt;&lt;/span&gt;으로 정하는 것이 좋다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Ex. 주문과 회원일 경우 주문쪽이 연관관계의 주인&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;연관관계의 주인쪽에 값을 세팅해야지만 값이 변경된다.&lt;/li&gt;
&lt;li&gt;연관관계의 주인을 FK로 정해야하는데 그 이유는, 연관관계를 맺고 있는 두 테이블(회원과 주문)의 수정작업이 있을 때, FK(주문 테이블쪽의 회원ID)를 수정해야지만 유지보수 측면에서 우수하다. &lt;i&gt;즉, 주문 테이블의 회원번호가 변경되었으니 회원의 회원번호도 변경된다라는 것을 자연스럽게 인식할 수 있다.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;회원과 주문사이의 연관관계가 맺어있을 경우 연관관계의 주인은 주문의 회원번호가 되고, 회원의 주문관련컬럼은 단순히 매핑만 되는 것으로 회원쪽 Entity에 매핑 여부를 기입한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1628919143488&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Member {
  //Member 1 : Order N | 이쪽 orderList는 단순히 Order에게 매핑만 된다. orderList가 변경되어도 Order의 FK는 변경되지 않는다.
  @OneToMany(mappedBy = &quot;member&quot;)  
    private List&amp;lt;Order&amp;gt; orderList = new ArrayList&amp;lt;&amp;gt;();
}

public class Order {
  //Order N : Member 1 | 해당 값의 변경이 일어날 시 member의 id값도 변경이 일어난다. 
  @ManyToOne  
  @JoinColumn(name = &quot;member_id&quot;) //FK
  private Member member;
}&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;b&gt;일대일 관계인 경우 FK는 보통 access가 많은 쪽으로 정하면 된다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그렇다면 연관관계의 주인은 FK가 있는 곳으로 잡으면 된다.&lt;/li&gt;
&lt;li&gt;Ex. 주문과 배달의 경우 주문쪽이 access가 많고 해당에 FK를 줄 것이므로 연관관계의 주인은 주문이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Entity 개발&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;주요 어노테이션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;JPA 내장 타입 세팅&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Embeddable : JPA에 내장되어 있는 타입일 경우 / 본 클래스에 기입&lt;/li&gt;
&lt;li&gt;@Embedded : 본 클래스를 의존하는 곳에 기입&lt;/li&gt;
&lt;li&gt;@Embeddable이나 @Embedded 둘 중에 하나만 기입되어져 있으면 JPA 내장 타입으로 정의할 수 있다.&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1628919911269&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Embeddable
@Getter
public class Address {

    private String city;
    private String street;
    private String zipcode;

    protected Address() {}

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

@Entity
@Getter @Setter
public class Delivery {

    @Id @GeneratedValue
    @Column(name = &quot;delivery_id&quot;)
    private Long id;

    @OneToOne(mappedBy = &quot;delivery&quot;, fetch = FetchType.LAZY)
    private Order order;

    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus status;  //READY, COMP
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;상속 관계 전략&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 테이블에 모든 item을 때려 박음&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1628919963225&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;dtype&quot;)
@Getter @Setter
public abstract class Item {

    @Id
    @GeneratedValue
    @Column(name = &quot;item_id&quot;)
    private Long id;

	...
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Inheritance(strategy = InheritanceType.JOINED)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정규화된 스타일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속하고 있는 item 값들의 테이블들이 세팅되는 방식&lt;/li&gt;
&lt;li&gt;Ex. Album, Book, Movie의 테이블이 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Enum 사용시 주의사항&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;Status를 사용할 때 보통 Enum을 사용하는데, Entity에서 EnumStatus를 세팅할 때 주의해야할 점이 있다.&lt;/li&gt;
&lt;li&gt;@Enumerated는 Enum의 타입을 정하는데,&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1628919315046&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum DeliveryStatus {
    READY, COMP
}

@Enumerated(EnumType.STRING) //입력한 그대로 문자타입
private DeliveryStatus status; //READY, COMP

@Enumerated(EnumType.ORDINAL)	//숫자 타입으로 저장됨(default)
private DeliveryStatus status; //READY : 1, COMP : 2&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ORDINAL이 디폴트인데 해당 형태로 등록이 되어있을 때 위 처럼 READY와 COMP 사이에 다른 상태가 들어오면 COMP는 2가 아닌 3으로 변경되어 원하는 로직을 구성 못할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Getter와 Setter&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;실무에서 엔티티의 데이터는 조회할 일이 매우 많으므로 &lt;b&gt;Getter는 열어두는 것이&lt;/b&gt; 일반적으로 좋다.&lt;/li&gt;
&lt;li&gt;하지만 Setter의 경우는 다르다.&lt;/li&gt;
&lt;li&gt;Setter를 호출하게 되면 데이터가 변하므로, Setter를 열어두면 가까운 미래에 엔티티가 변경되는 지점을 찾기가 어려워잔다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그러므로 Setter 대신 변경 지점이 명확하도록 메소드를 별도로 지정하는 것이 좋다. &lt;/b&gt;&lt;i&gt;아래 코드처럼 Setter 대신 addStock과 removeStock 메소드를 Entity내에 만들어 놓는다.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1628920134756&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;dtype&quot;)
@Getter
public abstract class Item {

	...
    
    /**
     * stock 증가
     */
    public void addStock(int quantity) {
        this.stockQuantity += quantity;
    }

    /**
     * stock 감소
     */
    public void removeStock(int quantity) {
        int realStock = this.stockQuantity - quantity;
        if (realStock &amp;lt; 0) {
            throw new NotEnoughStockException(&quot;need more stock&quot;);
        }
        this.stockQuantity = realStock;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Entity의 식별자&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;보통 Entity의 식별자는 Long id;를 사용하는데 객체의 경우 타입이 존재하므로 어떤 객체의 변수인지 쉽게 구분할 수 있다.&lt;/li&gt;
&lt;li&gt;하지만 Table의 경우 타입이 없으므로 @Column을 지정할 때는 객체이름_id로 작성하는 것이 유지보수에 편하다.&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1628919451972&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Id @GeneratedValue
@Column(name = &quot;order_id&quot;)
private Long Id;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;값 타입&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Embeddable
@Getter
public class Address {

    private String city;
    private String street;
    private String zipcode;

    protected Address() {}

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;위 처럼 값 타입의 경우 Setter를 지정하지 않고, 생성자에 값을 모두 초기화해서 변경 불가능한 클래스를 만드는 것이 좋다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;JPA 스펙상 Entity나 임베디드(@Embeddable) 타입은 기본 생성자를 public이나 protected로 두는 것이 좋다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;JPA가 이런 제약(기본 생성자를 만드는 제약)을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플렉션 같은 기술을 사용할 수 있도록 지원해야 하기 때문이다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&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;&lt;b&gt;가급적이면 Setter를 사용하지 말자.&lt;/b&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;/ul&gt;
&lt;/li&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;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;@XToOne인 경우에는 모두 지연 로딩으로 변경해줘야 한다. 기본이 즉시 로딩이므로,&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;즉시 로딩으로 하게 되면 실무에서 매우 위험할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;li id=&quot;code_1628919467474&quot; class=&quot;java hljs&quot; contenteditable=&quot;false&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;//@OneToOne(fetch = FetchType.LAZY)&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;//@ManyToOne(fetch = FetchType.LAZY)&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-class&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;Order&lt;/span&gt; &lt;/span&gt;{ &lt;span class=&quot;hljs-meta&quot;&gt;@ManyToOne&lt;/span&gt;(fetch = FetchType.LAZY) &lt;span class=&quot;hljs-comment&quot;&gt;//Order N : Member 1&lt;/span&gt; &lt;span class=&quot;hljs-meta&quot;&gt;@JoinColumn&lt;/span&gt;(name = &lt;span class=&quot;hljs-string&quot;&gt;&quot;member_id&quot;&lt;/span&gt;) &lt;span class=&quot;hljs-comment&quot;&gt;//FK&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;private&lt;/span&gt; Member member; }&lt;/li&gt;
&lt;li&gt;&lt;b&gt;member의 fetchType을 LAZY로 안할 경우 order 조회하는 순간 order가 member를 찾게된다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;즉, 100개의 order 쿼리를 조회할 때 각각의 쿼리가 100개의 member 쿼리를 조회하게 된다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;성능 똥망&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;
&lt;pre id=&quot;code_1628920319768&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Order&amp;gt; orderList = new ArrayList&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&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;스프링 부트에서는 @Table 및 @Column을 지정하지 않으면 기본 세팅에 따라 변경된다.(SpringPhysicalNamingStrategy)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카멜 케이스 -&amp;gt; 언더 스코어&lt;/li&gt;
&lt;li&gt;점 -&amp;gt; 언더 스코어&lt;/li&gt;
&lt;li&gt;대문자 -&amp;gt; 소문자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;private int orderPrice; //주문 가격
//order_pirce로 변경&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Cascade&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;public class Order {
    @OneToMany(mappedBy = &quot;order&quot;, cascade = CascadeType.ALL)
    private List&amp;lt;OrderItem&amp;gt; orderItemList = new ArrayList&amp;lt;&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;원래는 OrderItem에 먼저 세팅을 하고 Order를 저장해야하는데, Order에 값들을 저장하게되면 Cascade에 의해서 OrderItem에도 값들이 저장되게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Order {
  @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  @JoinColumn(name = &quot;delivery_id&quot;)
  private Delivery delivery;  
}&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;Order 저장 시 Delivery도 자동으로 값 저장이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&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;양방향인 객체들을 저장하려고 할 때 각각의 객체를 생성해서 각각 넣어줘야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1628919484511&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String [] args) {
  Member member = new Member();
  Order order = new Order();
  member.getOrders().add(order);
  order.setMember(member);
}&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1628919495228&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Order {
  ...
  // ====연관관계 메소드====
  public void setMember(Member member) {
    this.member = member;
    member.getOrders().add(this);
  }

  public void addOrderItem(OrderItem orderItem) {
    orderItemList.add(orderItem);
    orderItem.setOrder(this );
  }

  public void setDelivery(Delivery delivery) {
    this.delivery = delivery;
    delivery.setOrder(this);
  }
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/Spring Boot JPA</category>
      <category>entity</category>
      <category>JPA</category>
      <category>김영한</category>
      <category>연관관계</category>
      <category>주인</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/206</guid>
      <comments>https://it-mesung.tistory.com/206#entry206comment</comments>
      <pubDate>Sat, 14 Aug 2021 14:55:23 +0900</pubDate>
    </item>
    <item>
      <title>[Mokito] Mock 객체 Stubbing</title>
      <link>https://it-mesung.tistory.com/205</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Mock 객체 Stubbing&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Mock 객체의 행동이란,&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리턴 값이 있는 메소드는 모두 &lt;i&gt;Null&lt;/i&gt; 을 리턴하고 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Optional 타입인 경우 Optional.empty로 리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Primitive 타입은 모두 Primitive 값을 따르고 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ex. Boolean인 경우 'false' / Integer 혹은 Long인 경우 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Collection의 경우 모두 비어있는 Collection을 가지고 있다.&lt;/li&gt;
&lt;li&gt;Void 메소드의 경우 예외를 던지지 않고 아무 일도 발생하지 않는다.&lt;/li&gt;
&lt;/ul&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;Stubbing이란,&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mock 객체의 행동을 조작하는 것을 말한다.&lt;/li&gt;
&lt;li&gt;Mock객체의 when 메소드를 활용하여 Stubbing을 진행해보자.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ex. 리턴값이 있는 when 메소드 활용 1&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1623582466789&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Mock MemberService memberService
memberService.findById(1L);

//위 메소드를 호출할 때 아래와 같은 인스턴스를 받고 싶다면... Mock객체를 통해서 Stubbing을 하여 받을 수 있다.
Member member = new Member();
member.setId(1L);
member.setEmail(&quot;abc@gmail.com&quot;);

//Mock객체를 Stubbing
Mockito.when(memberService.findById(1L)).thenReturn(Optional.of(member));​&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ex. 리턴값이 있는 when 메소드 활용 2(Service단 로직의 리턴값을 Stubbing을 활용하여 반환해보자.)&lt;/b&gt;&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;studyService.createNewStudy(1L, study)를 호출하여 우리가 선언한 Member 인스턴스의 값이 반환되는 지 확인해보자.&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/121803329-f6e24600-cc7b-11eb-834c-7a95903b36e0.png&quot; alt=&quot;image&quot; /&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;/code&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1623581937207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;assertEquals(&quot;abc@gmail.com&quot;, memberService.findById(1L).get().getEmail());&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그러나 만약 memberService.findById()의 값이 '1L'이 아닌 다른 값이면 stubbing한 Member 인스턴스가 반환되지 않으니 주의해야한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;단, any()를 사용하게 되면 어떤 변수가 들어와도 stubbing한 Member 인스턴스를 반환할 수 있다.&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;assertEquals(&quot;&lt;a href=&quot;mailto:abc@gmail.com&quot;&gt;abc@gmail.com&lt;/a&gt;&quot;, memberService.findById(any()).get().getEmail());&lt;/li&gt;
&lt;li&gt;any() 뿐만 아니라 다양한 ArgumentMatchers 사용 가능&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/ArgumentMatchers.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/ArgumentMatchers.html&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1623582161381&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;mockito-core 3.11.1 javadoc (org.mockito)&quot; data-og-description=&quot;Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 3.11.1 https://javadoc.io/doc/org.mockito/mockito-core/3.11.1 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org&quot; data-og-host=&quot;javadoc.io&quot; data-og-source-url=&quot;https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/ArgumentMatchers.html&quot; data-og-url=&quot;https://javadoc.io/doc/org.mockito/mockito-core/3.11.1/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/ArgumentMatchers.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/ArgumentMatchers.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;mockito-core 3.11.1 javadoc (org.mockito)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 3.11.1 https://javadoc.io/doc/org.mockito/mockito-core/3.11.1 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;javadoc.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Ex. 예외를 던지는 when 메소드 활용 1&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1623582191941&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Mockito.when() 활용
when(memberService.findById(1L)).thenThrow(new RuntimeException());

//Mockito.doThrow() 활용
doThrow(new IllegalAccessError()).when(memberService).validate(1L);
assertThrows(IllegalArgumentException.class, () -&amp;gt; {
  memberService.validate(1L);
});&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ex.&amp;nbsp;리턴값&amp;nbsp;or&amp;nbsp;예외를&amp;nbsp;던지는&amp;nbsp;when&amp;nbsp;메소드&amp;nbsp;활용&amp;nbsp;(stubbing한&amp;nbsp;메소드를&amp;nbsp;반복&amp;nbsp;호출할&amp;nbsp;때의&amp;nbsp;순서에&amp;nbsp;따른&amp;nbsp;결과물을&amp;nbsp;다르게&amp;nbsp;나타낼&amp;nbsp;수&amp;nbsp;있음)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1623582370212&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;when(memberService.findById(any()))
  .thenReturn(Optional.of(member))
  .thenThrow(new RuntimeException())
  .thenReturn(Optional.empty());

//thenReturn
Optional&amp;lt;Member&amp;gt; byId = memberService.findById(1L);
assertEquals(&quot;abc@gmail.com&quot;, byId.get().getEmail());

//thenThrow
assertThrows(RuntimeException.class, () -&amp;gt; {
  memberService.findById(2L);
});

//thenReturn
assertEquals(Optional.empty(), memberService.findById(3L));&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-application-test/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/course/the-java-application-test/dashboard&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623582515395&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.inflearn.com&quot; data-og-source-url=&quot;https://www.inflearn.com/course/the-java-application-test/dashboard&quot; data-og-url=&quot;https://www.inflearn.com/course/the-java-application-test&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dlOqjm/hyKxDMD608/EqoFyT0ugpLIWlvHtKRGI1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cYc2Uh/hyKxKE1Jox/kKF9Mo7cJewcxGkxfiFTjK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/c2I8z9/hyKxOm8If3/Z9VJGFAOt0tTGI6mgZ4FL0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/the-java-application-test/dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.inflearn.com/course/the-java-application-test/dashboard&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dlOqjm/hyKxDMD608/EqoFyT0ugpLIWlvHtKRGI1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cYc2Uh/hyKxKE1Jox/kKF9Mo7cJewcxGkxfiFTjK/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/c2I8z9/hyKxOm8If3/Z9VJGFAOt0tTGI6mgZ4FL0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500');&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;자바 프로그래밍 언어를 사용하고 있거나 공부하고 있는 학생 또는 개발자라면 반드시 알아야 하는 애플리케이션을 테스트하는 다양한 방법을 학습합니다., 그냥 개발자를 넘어 '더 나은 개발&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.inflearn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Test/Mockito</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/205</guid>
      <comments>https://it-mesung.tistory.com/205#entry205comment</comments>
      <pubDate>Sun, 13 Jun 2021 20:05:16 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot 실습] 등록, 수정, 삭제, 조회 화면 만들기</title>
      <link>https://it-mesung.tistory.com/204</link>
      <description>&lt;h2&gt;등록, 수정, 삭제, 조회 화면 만들기&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;게시글 등록 화면 만들기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;게시글 등록 화면을 만들기 위해 오픈소스 부트스트랩을 사용하겠다.&lt;/p&gt;
&lt;p&gt;부트스트랩, 제이쿼리 등 프론트엔드 라이브러리를 사용할 수 있는 방법은 2가지가 있는데, 하나는 외부 CDN을 사용하는 것이고, 하나는 직접 라이브러리를 받아서 사용하는 것이다.&lt;/p&gt;
&lt;p&gt;우리는 외부 CDN을 사용하여 프론트엔드를 구현하겠다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;실무에서는 외부 CDN 방식을 사용하지 않는다. 직접 구현해야지!!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;레이아웃 방식을 사용하여 부트스트랩과 제이쿼리를 index.mustache에 추가해야한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;레이아웃 방식 : 공통 영역을 별도의 파일로 분리하여 필요한 곳에서 가져다 쓰는 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 부트스트랩과 제이쿼리를 머스테치 화면 어디에서다 필요하므로 공통으로 사용되는 레이아웃 파일을 만들어 추가하는 것이 좋다.&lt;/p&gt;
&lt;p&gt;src/main/resources/templates 디렉토리에 layout 디렉토리를 추가로 생성한다. &lt;b&gt;footer.mustache, header.mustache&lt;/b&gt; 를 추가하겠다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99686033-86e55600-2ac6-11eb-8f57-f142699f0f9d.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;header.mustache&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- header.mustache --&amp;gt;
&amp;lt;!DOCTYPE HTML&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;스프링부트 웹서비스&amp;lt;/title&amp;gt;
    &amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&amp;gt;

    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css&quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;footer.mustache&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- footer.mustache --&amp;gt;
&amp;lt;script src=&quot;https://code.jquery.com/jquery-3.3.1.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코드를 보면 css와 js의 위치가 서로 다른 것을 확인할 수 있는데, 이는 &lt;b&gt;페이지 로딩속도를 높이기 위해서 그런 것이다.&lt;/b&gt; 즉, css는 header에 위치하고 js는 footer에 위치한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;HTML의 경우 위에서부터 코드가 실행되므로 head가 다 실행되고 나서야 body가 실행된다. 즉, head가 다 불러지지 않으면 사용자 쪽에서는 백지 화면만 노출되는 것을 볼 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;특히 서비스를 하다보면 js의 용량이 커지게 되는데 크면 클수록 body 부분의 실행이 늦어지기 때문에 js는 body 하단에 두어 화면이 다 그려진 뒤 호출하는 것이 좋다.&lt;/i&gt;&lt;/b&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;반면, css는 당연히 화면을 그리는 역할을 하는 것으로 head에 불러오는 것이 좋다.&lt;/i&gt;&lt;/b&gt; 추가로 bootstrap.js의 경우 제이쿼리가 필수적이므로 bootstrap.min.js보다 먼저 호출하게 하였다. 이런 상황을 &lt;b&gt;&lt;i&gt;bootstrap.js가 제이쿼리에 의존한다&lt;/i&gt;&lt;/b&gt;라고 보면된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 head와 footer를 나누었으니, index.mustache의 코드는 꼭 필요한 코드만 남길 수가 있다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;index.mustache&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;!-- index.mustache --&amp;gt;
{{&amp;gt;layout/header}}
    &amp;lt;h1&amp;gt;스프링 부트로 시작하는 웹 서비스&amp;lt;/h1&amp;gt;
{{&amp;gt;layout/footer}}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 이제 index에 글 등록하는 화면을 만들어보자.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;index.mustache - version2&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{{&amp;gt;layout/header}}
&amp;lt;h1&amp;gt;스프링부트로 시작하는 웹 서비스 Ver.2&amp;lt;/h1&amp;gt;
&amp;lt;div class=&quot;col-md-12&quot;&amp;gt;
    &amp;lt;div class=&quot;row&quot;&amp;gt;
        &amp;lt;div class=&quot;col-md-6&quot;&amp;gt;
            &amp;lt;a href=&quot;/posts/save&quot; role=&quot;button&quot; class=&quot;btn btn-primary&quot;&amp;gt;글 등록&amp;lt;/a&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{{&amp;gt;layout/footer}}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a태그를 통해 글 등록 버튼이 생성되었는데, 이 버튼을 클릭 시 이동할 페이지의 주소는 /posts/save가 되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;IndexController.java&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;그럼 /posts/save에 관련된 Controller를 생성하자.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
public class IndexController {
    ...

    @GetMapping(&quot;/posts/save&quot;)
    public String postsSave() {
        return &quot;posts-save&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;posts-save 페이지를 나타낼 posts-save.mustache를 생성하자. 위치는 index.mustache와 동일하다.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;posts-save.mustache&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;{{&amp;gt;layout/header}}

&amp;lt;h1&amp;gt;게시글 등록&amp;lt;/h1&amp;gt;

&amp;lt;div class=&quot;col-md-12&quot;&amp;gt;
    &amp;lt;div class=&quot;col-md-4&quot;&amp;gt;
        &amp;lt;form&amp;gt;
            &amp;lt;div class=&quot;form-group&quot;&amp;gt;
                &amp;lt;label for=&quot;title&quot;&amp;gt;제목&amp;lt;/label&amp;gt;
                &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;title&quot; placeholder=&quot;제목을 입력하세요&quot;&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;form-group&quot;&amp;gt;
                &amp;lt;label for=&quot;author&quot;&amp;gt; 작성자 &amp;lt;/label&amp;gt;
                &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;author&quot; placeholder=&quot;작성자를 입력하세요&quot;&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;form-group&quot;&amp;gt;
                &amp;lt;label for=&quot;content&quot;&amp;gt; 내용 &amp;lt;/label&amp;gt;
                &amp;lt;textarea class=&quot;form-control&quot; id=&quot;content&quot; placeholder=&quot;내용을 입력하세요&quot;&amp;gt;&amp;lt;/textarea&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/form&amp;gt;
        &amp;lt;a href=&quot;/&quot; role=&quot;button&quot; class=&quot;btn btn-secondary&quot;&amp;gt;취소&amp;lt;/a&amp;gt;
        &amp;lt;button type=&quot;button&quot; class=&quot;btn btn-primary&quot; id=&quot;btn-save&quot;&amp;gt;등록&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

{{&amp;gt;layout/footer}}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그럼 이제 다시 프로젝트를 실행해보자.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99688793-90bc8880-2ac9-11eb-9798-6626f5bf7829.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;하지만 현재는 등록버튼을 눌러도 기능이 존재하지 않아 아무것도 등록할 수가 없다. 그럼 이제 등록을 할 수 있도록 js를 구현해보자.&lt;/p&gt;
&lt;p&gt;src/main/resources에 static/js/app 디렉토리를 생성하자.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;index.js&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99689135-f446b600-2ac9-11eb-9b6d-3b209d9514b7.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const index = {
    init: function () {
        const _this = this;
        $('#btn-save').on('click', function () {
            _this.save();
        });
    },
    save: function () {
        const data = {
            title: $('#title').val(),
            author: $('#author').val(),
            content: $('#content').val()
        };

        $.ajax({
            type: 'POST',
            url: '/api/v1/posts',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(data)
        }).done(function () {
            alert('글이 등록되었습니다.');
            window.location.href = '/';
        }).fail(function (error) {
            alert(JSON.stringify(error));
        });
    }
};
index.init();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;첫 문장을 보면 const index = {...}라는 코드를 선언했는데, 이렇게 선언한 이유에 대해서 살펴보자.&lt;/p&gt;
&lt;p&gt;만약 index.js가 아래와 같이 function을 작성한 상황이라면,&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const init = function() {
  ...
};

const save = function() {
  ...
};

init();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 상황일 때, index.mustache에 a.js가 추가되어 &lt;b&gt;a.js도 a.js만의 init과 save function이 존재한다면 어떨까? 계속 중복된 function들이 존재할 것이다.&lt;/b&gt; 이를 방지하기 위해 &lt;b&gt;브라우저 스코프를 사용하게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;브라우저 스코프는 공용 공간으로 쓰이기 때문에 나중에 로딩된 js의 init, save가 먼저 로딩된 js의 function을 덮어쓰게 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;여러 사람들이 참여하는 프로젝트에서는 중복된 함수 이름이 자주 발생할 수 있는데, function을 만들 때 모든 function의 이름을 확인하면서 function을 만들 수는 없다. &lt;b&gt;&lt;i&gt;그러므로, index.js에서만 유효한 범위(스코프)를 만들어 사용하는 것이다. 즉, const index란 객체를 만들어 해당 객체에서 필요한 모든 function들을 선언하게 되고, 해당 function들은 index 객체 안에서만 유효하기 때문에 다른 js와 겹칠 위험이 사라진다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자 그럼 index.js를 머스테치 파일에서 사용할 수 있도록 footer.mustache에 추가해보자.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;footer.mustache&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- footer.mustache --&amp;gt;
&amp;lt;script src=&quot;https://code.jquery.com/jquery-3.3.1.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;!--index.js 추가--&amp;gt;
&amp;lt;script src=&quot;/js/app/index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;&lt;i&gt;index.js 호출 코드를 보면 절대 경로(/)로 바로 시작하는데 그 이유는, 스프링 부트는 기본적으로 src/main/resources/static에 위치한 자바스크립트, CSS, 이미지 등 정적 파일들은 URL에서 /로 설정된다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Ex. url 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;src/main/resources/static/js/... : http://도메인/js/&lt;/li&gt;
&lt;li&gt;src/main/resources/static/css/... : http://도메인/css/&lt;/li&gt;
&lt;li&gt;src/main/resources/static/image/... : http://도메인/image/&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그럼 이제 게시글 등록을 해보자.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99692681-e8f58980-2acd-11eb-9002-88ffb224dba9.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Alert 창이 정상적으로 나왔으니, 이제 DB에 접속하여 데이터가 등록되었는지 확인해보자.&lt;/p&gt;
&lt;p&gt;Localhost:8082/h2-console에 접속.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99692857-19d5be80-2ace-11eb-914b-bd1828075473.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;등록 기능이 정상적으로 작동한 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Spring/SpringBoot 실습</category>
      <category>mustache</category>
      <category>springboot</category>
      <category>스프링부트</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/204</guid>
      <comments>https://it-mesung.tistory.com/204#entry204comment</comments>
      <pubDate>Sun, 3 Jan 2021 22:25:28 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot 실습] 기본 페이지 만들기</title>
      <link>https://it-mesung.tistory.com/203</link>
      <description>&lt;h2&gt;기본 페이지 만들기&lt;/h2&gt;
&lt;p&gt;&lt;b&gt;index.mustache&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;이제 첫 페이지륻 담당할 index.mustache를 src/main/resources/templates에 생성해보자.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99553353-63a4a300-2a01-11eb-9aa8-661db9080fdb.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE HTML&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;스프링 부트 웹 서비스&amp;lt;/title&amp;gt;
    &amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;/&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;스프링 부트로 시작하는 웹 서비스&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;IndexController.java&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;이제 이 머스테치에 URL을 매핑해보자. URL 매핑은 당연히 Controller에서 해야하니 IndexController를 생성하자.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99553789-e0378180-2a01-11eb-9e48-e2d210345061.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
public class IndexController {
    @GetMapping(&quot;/&quot;)
    public String index() {
        return &quot;index&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;머스테치 스타터로 인해 컨트롤러에서 문자열 반환 시 &lt;b&gt;앞의 경로와 뒤의 파일 확장자는 자동으로 지정이 된다.&lt;/b&gt; 앞의 경로는 src/main/resources/templates이고, 뒤의 파일 확장자는 .mustache가 붙는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;IndexControllerTest.java&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;그럼 이제 코드가 완성되었으니 테스트 코드로 검증해보자.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IndexControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void roadMain() {
        //when
        String body = this.restTemplate.getForObject(&quot;/&quot;, String.class);

        //then
        assertThat(body).contains(&quot;스프링 부트로 시작하는 웹 서비스&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이번 테스트는 URL 호출 시 페이지의 내용이 제대로 호출되는 지에 대한 테스트이다.&lt;/p&gt;
&lt;p&gt;TestRestTemplate을 통해 &quot;/&quot;로 호출이 되면 index.mustache에 포함된 코드들이 있는지 확인하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99555257-75874580-2a03-11eb-866f-ebfe9b8c4064.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 서버를 실행 후 localhost:8082로(포트번호 8082로 설정)로 접속해보면 아래와 같이 index.mustache로 만든 페이지가 성공적으로 브라우저에 나타난다.&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;zoom: 50%;&quot; src=&quot;https://user-images.githubusercontent.com/40616436/99555403-9d76a900-2a03-11eb-915b-871a494e2a90.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;</description>
      <category>Spring/SpringBoot 실습</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/203</guid>
      <comments>https://it-mesung.tistory.com/203#entry203comment</comments>
      <pubDate>Sun, 3 Jan 2021 22:22:01 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot 실습] 서버 템플릿 엔진과 머스테치 소개</title>
      <link>https://it-mesung.tistory.com/202</link>
      <description>&lt;h2&gt;서버 템플릿 엔진과 머스테치 소개&lt;/h2&gt;
&lt;p&gt;이번에는 머스테치(Mustache)를 통해 화면 영역을 개발하는 방법을 배워보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;템플릿 엔진이란 무엇인가?&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;웹 개발에 있어 템플릿 엔진이란, 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어&lt;/b&gt;를 이야기한다. 예전 스프링이나 서블릿을 사용했다면, JSP, Freemaker 등이 떠오를테고 요즘은 리액트, 뷰 등이 떠오를 것이다.&lt;/p&gt;
&lt;p&gt;이것들 모두 결과적으로는 &lt;b&gt;지정된 템플릿과 데이터&lt;/b&gt; 를 이용하여 HTML을 생성하는 &lt;b&gt;템플릿 엔진&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다만 약간의 다른 점이 있는데,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버 템플릿 엔진 : JSP, Freemaker&lt;/li&gt;
&lt;li&gt;클라이언트 템플릿 엔진 : 리액트, 뷰&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;만약 아래와 같은 코드가 주어진다면 결과는 어떻게 되는 지 생각해보자.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;

  $(document).ready(function() {
    if(a==&quot;1&quot;) {
      &amp;lt;%
          System.out.println(&quot;test&quot;);
      %&amp;gt;
    }
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;해당 코드는 if문에 상관없이 무조건 test가 콘솔에 출력된다.&lt;/b&gt; 그 이유는 프론트 엔드의 자바스크립트가 작동하는 영역과 JSP가 작동하는 영역이 서로 다르기 때문인데, &lt;b&gt;JSP를 비롯한 서버 템플릿 엔진은 서버에서 구동되는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;서버 템플릿 엔진&lt;/b&gt;을 이용한 화면 생성은 &lt;b&gt;서버에서 Java 코드로 문자열을 만든 뒤 이 문자열을 HTML로 변환하여 브라우저로 전달&lt;/b&gt; 한다. &lt;b&gt;&lt;i&gt;앞 코드를 다시 보면, HTML을 만드는 과정에서 System.out.println(&quot;test&quot;);를 실행할 뿐이고, 이때 자바스크립트 코드는 단순한 문자열일 뿐이다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;서버 템플릿 엔진&lt;/p&gt;
&lt;p&gt;Server(Java 코드 실행) -&amp;gt; Java 코드 실행값 + HTML(JavaScript 문자)를 브라우저에서 실행&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;반면, 자바스크립트는 &lt;b&gt;브라우저 위에서 작동한다&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;앞 코드에서 자바스크립트 코드가 실행되는 장소는 서버가 아닌 &lt;b&gt;브라우저&lt;/b&gt; 이다. 즉, 브라우저에서 작동될 때는 서버 템플릿 엔진의 손을 벗어나 제어가 불가능한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;클라이언트 템플릿 엔진&lt;/b&gt;인 &lt;b&gt;리액트나 뷰를 이용한 SPA는 브라우저에서 화면을 생성하므로, 서버에서 JSON 혹은 xml 형식으로 데이터만 전달하고 클라이언트에서 조립하는 방식을 사용하는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;클라이언트 템플릿 엔진&lt;/p&gt;
&lt;p&gt;Server(Java 코드 실행) -&amp;gt; JSON || XML 형식으로 반환 -&amp;gt; JSON + HTML(JavaScript(VUE, REACT))를 브라우저에서 실행&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;예로, 최근엔 리액트가 뷰와 같은 자바스크립트 프레임워크에서 서버 사이드 렌더링을 지원하는 것을 볼 수 있다. 이 방식은 자바스크립트 프레임워크의 화면 생성 방식을 서버에서 실행하는 것을 말한다. 다만, 스프링 부트를 사용하면 자바스크립트를 서버사이드에서 렌더링하도록 구현하는 것은 많은 비용이 들므로 현재는 추천하지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;머스테치란&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;머스테치(&lt;a href=&quot;http://mustache.github.io)%EB%8A%94&quot;&gt;http://mustache.github.io)는&lt;/a&gt; 수많은 언어를 지원하는 가장 심플한 템플릿 엔진이다. 이 엔진은 대부분의 언어를 지원하고 있어 자바에서 사용될 때는 서버 템플릿 엔진으로 사용되고, 자바스크립트에서 사용될 때는 클라이언트 템플릿 엔진으로 사용될 수 있다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;템플릿 엔진의 단점들을 살펴보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSP, Velocity : 스프링 부트에서는 권장하지 않는다.&lt;/li&gt;
&lt;li&gt;Freemarker : 템플릿 엔진으로는 과하게 많은 기능을 지원한다.&lt;/li&gt;
&lt;li&gt;Thymeleaf : 스프링에서 적극적으로 밀고 있으나 문법이 어렵다. 하지만 vue.js를 사용해본 경험이 있어 태그 속성 방식이 익숙하면 Thymeleaf도 사용하면 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;머스테치의 장점을 살펴보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문법이 다른 템플릿 엔진보다 심플하다.&lt;/li&gt;
&lt;li&gt;로직 코드를 사용할 수 없어 View의 역할과 서버의 역할이 명확히 구분된다.&lt;/li&gt;
&lt;li&gt;Mustache.js와 Mustache.java 2가지가 다 있어, 하나의 문법으로 클라이언트/서버 템플릿을 모두 사용 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;템플릿 엔진은 화면 역할에만 충실한 것이 가장 좋다.&lt;/b&gt; 많은 기능을 제공하고 있으면 API와 템플릿 엔진, 자바스크립트가 서로 로직을 나눠 갖게 되어 유지보수할 때 어려운 점을 겪을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;머스테치 플러그인 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;Plugins -&amp;gt; Handlebars/Mustache 설치 -&amp;gt; 인텔리제이 재시작&lt;/p&gt;
&lt;p&gt;프로젝트에서 머스테치를 사용할 수 있도록 의존성을 build.gradle에 등록&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;compile('org.springframework.boot:spring-boot-starter-mustache')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일반적으로 머스테치의 파일 위치는 기본적으로 src/main/resource/templates이고, 이 위치에 머스테치 파일을 두면 스프링 부트에서 자동으로 로딩한다.&lt;/p&gt;</description>
      <category>Spring/SpringBoot 실습</category>
      <category>springboot</category>
      <category>스프링부트</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/202</guid>
      <comments>https://it-mesung.tistory.com/202#entry202comment</comments>
      <pubDate>Sun, 3 Jan 2021 22:19:30 +0900</pubDate>
    </item>
    <item>
      <title>[The Java] 프록시 패턴</title>
      <link>https://it-mesung.tistory.com/201</link>
      <description>&lt;h4&gt;&lt;b&gt;프록시 패턴이란,&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40616436/77825653-07c59b00-714e-11ea-802e-08f0b5cd329e.png&quot; alt=&quot;image&quot; width=&quot;521&quot; height=&quot;580&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;프록시는 대리한다&lt;/b&gt; 라는 개념으로 리얼 서브젝트를 참조하고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서브젝트는 인터페이스로서 프록시와 리얼 서브젝트가 공유하고 있고, 클라이언트는 서브젝트 인터페이스 타입으로 프록시를 사용한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;클라이언트는 프록시를 거쳐 리얼 서브젝트를 사용하기 때문에 &lt;i&gt;&lt;b&gt;프록시는 리얼 서브젝트에 대한 접근을 관리하거나 부가기능을 제공하고 리턴값을 변경할 수도 있다.&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;리얼 서브젝트는 자신이 해야할 일만 하면서(SRP) 프록시를 이용해 부가적인 기능(접근제한, AOP, 로깅, 트랜잭션)을 제공할 수 있다.&lt;/b&gt;&lt;/i&gt;
&lt;ul&gt;
&lt;li&gt;부가적인 기능마저 리얼 서브젝트에서 제공해주면 매우 복잡해지고 객체지향적 개념에서도 벗어나게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;b&gt;프록시 패턴을 소스로 확인해보자&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/40616436/77842646-c4ac0c00-71cf-11ea-93d6-571874d6321c.png&quot; alt=&quot;image&quot; width=&quot;558&quot; height=&quot;327&quot; /&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;//BookService
public interface BookService {
  void rent(Book book);
}

//BookServiceProxy
public class BookServiceProxy implements BookService {
  BookService bookService;

  public BookServiceProxy(BookService bookService) {
    this.bookService = bookService;
  }

  @Override
  public void rent(Book book) {
    System.out.println(&quot;aaaa&quot;);
    bookService.rent(book);
    System.out.println(&quot;bbbb&quot;);
  }
}

//DefaultBookService
public class DefaultBookService implements BookService{
  public void rent(Book book) {
    System.out.println(&quot;rent : &quot; + book.getTitle());
  }
}

//Test
public class BookServiceTest {
  //BookServiceProxy가 DefaultBookService를 참조하기 위해 파라미터를 받음
  BookService bookService = new BookServiceProxy(new DefaultBookService());

  @Test
  public void di() {
    Book book = new Book();
    book.setTitle(&quot;spring&quot;);
    bookService.rent(book);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;실행결과&lt;/p&gt;
&lt;img src=&quot;https://user-images.githubusercontent.com/40616436/77842691-4b60e900-71d0-11ea-8008-5e256f06fa0d.png&quot; alt=&quot;image&quot; width=&quot;103&quot; height=&quot;68&quot; /&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;결과에서도 확인하듯이, &lt;b&gt;리얼 서브젝트(DefaultBookService)의 수정없이 프록시(BookServiceProxy)의 수정으로 결과값이 변경되는 것을 볼 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;b&gt;프록시의 단점&lt;/b&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;부가적인 기능이나 서브젝트에 위임하는 작업이 중복 될 수가 있다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;//BookServiceProxy
public class BookServiceProxy implements BookService {
  BookService bookService;

  public BookServiceProxy(BookService bookService) {
    this.bookService = bookService;
  }

  @Override
  public void rent(Book book) {
    System.out.println(&quot;aaaa&quot;);
    bookService.rent(book);
    System.out.println(&quot;bbbb&quot;);
  }

  //1.
  @Override
  public void rent2(Book book) {
    System.out.println(&quot;bbbb&quot;);
    bookService.rent(book);
    System.out.println(&quot;cccc&quot;);
  }

     //2.
  @Override
  public void renturnBook(Book book) {
    System.out.println(&quot;aaaa&quot;);
    bookService.renturnBook(book);
    System.out.println(&quot;bbbb&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;i&gt;부가적인 기능은 다르나 서브젝트에 위임하는 작업이 중복된다.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;부가적인 기능은 동일한데 메소드 호출하는 부분이 달라 부가적인 기능들이 중복으로 발생할 수 있다.&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;b&gt;이를 방지하기 위해 매번 프록시에 해당하는 클래스를 만들지 않고, 동적으로 런타임에 생성하는 방법인 다이나믹 프록시를 활용한다.&lt;/b&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java/The Java</category>
      <author>메성</author>
      <guid isPermaLink="true">https://it-mesung.tistory.com/201</guid>
      <comments>https://it-mesung.tistory.com/201#entry201comment</comments>
      <pubDate>Sun, 3 Jan 2021 22:15:04 +0900</pubDate>
    </item>
  </channel>
</rss>