<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>world_changer.log</title>
        <link>https://velog.io/</link>
        <description>this too shall pass</description>
        <lastBuildDate>Mon, 19 Jun 2023 02:58:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>world_changer.log</title>
            <url>https://velog.velcdn.com/images/world_changer/profile/9a53d57e-78af-4e5b-abe2-bec0a797fe9f/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. world_changer.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/world_changer" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[230619_4주차]]></title>
            <link>https://velog.io/@world_changer/2306194%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@world_changer/2306194%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Mon, 19 Jun 2023 02:58:46 GMT</pubDate>
            <description><![CDATA[<h1 id="월요일-오전">월요일 (오전)</h1>
<p>Tether 에서 usdt 가격은 항상 1달러 (stable coin)</p>
<p>유니스왑, 바이낸스를 통한 시장 프로세스</p>
<p><code>web3@1.9.0</code> 버전 node 진행
(send가 가능한 버전)</p>
<h2 id="hardhat-배포-실습">hardhat 배포 실습</h2>
<ol>
<li><p>etherscan 회원가입 및 api key 발급</p>
</li>
<li><p>hardhat setting</p>
<pre><code>&gt; npm install --save-dev hardhat
&gt; npm install --save -dev @nomicfoundation/hardhat-toolbox
&gt; npx hardhat</code></pre></li>
<li><p>Lock.sol 변경</p>
<pre><code>// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
</code></pre></li>
</ol>
<p>// Uncomment this line to use console.log
// import &quot;hardhat/console.sol&quot;;</p>
<p>contract Lock {
    uint public a;</p>
<pre><code>function getA() public view returns(uint) {
    return a;
}

function setA(uint _a) public {
    a = _a;
}</code></pre><p>}</p>
<pre><code>
4. compile
```&gt; npx hardhat compile```

5. deploy.js 변경</code></pre><p>const hre = require(&quot;hardhat&quot;);</p>
<p>async function main() {
    //contract 변수로 설정
    const Contract_A = await hre.ethers.getContractFactory(&quot;Lock&quot;);
    //contract를 deploy하고
    const contract_a = await Contract_A.deploy();</p>
<pre><code>await contract_a.deployed();
console.log(&quot;Address : &quot;, contract_a.address);</code></pre><p>}</p>
<p>main().catch((error) =&gt; {
    console.error(error);
    process.exitCode = 1;
});</p>
<pre><code>
6. hardhat.config.js 변경</code></pre><p>require(&quot;@nomicfoundation/hardhat-toolbox&quot;);</p>
<p>const PVK = &quot;개인키&quot;</p>
<p>module.exports = {
  solidity: &quot;0.8.17&quot;,
  etherscan : {
    apiKey : &quot;이더스캔 api 키&quot;,
  },
  networks : {
    goerli : {
      url : <code>https://goerli.infura.io/v3/infura api 키</code>,
      accounts : [PVK],
    }
  }
};</p>
<pre><code>
7. 배포</code></pre><p>// goerli
npx hardhat run --network goerli .\scripts\deploy.js</p>
<p>// etherscan verify &amp; publishing</p>
<pre><code>
hardhat을 통해서 더 쉽게 verify &amp; publishing이 가능하다.</code></pre><p>npx hardhat verify --network goerli {컨트랙트 주소}</p>
<pre><code>![](https://velog.velcdn.com/images/world_changer/post/cca384e1-3ab1-4355-b0a6-2d6e90efd8dc/image.png)

이런식으로 바로 업데이트

# 월요일 (오후)
```Flatten``` 기능
import 되어있는 것들도 전부 한 파일에 담는 것.
(remix 에서는 우클릭 후 flatten 누르면 파일 생성됨)

## hardhat 배포 실습 (constructor 있는)
```npm install @openzeppelin/contracts```

1. erc20_3.sol 생성</code></pre><p>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;</p>
<p>import &#39;@openzeppelin/contracts/token/ERC20/ERC20.sol&#39;;</p>
<p>contract ABCToken is ERC20(&quot;LikeLion&quot;, &quot;LION&quot;) {
    constructor(uint totalSupply) {
        _mint(msg.sender, totalSupply);
    }</p>
<pre><code>function MintToken(uint a) public {
    _mint(address(this), a);
}

function decimals() public pure override returns(uint8) {
    return 0;
}

receive() external payable{}</code></pre><p>}</p>
<pre><code>
2. deploy_erc20_3.js 생성</code></pre><p>// scripts 폴더의 deploy.js 파일 수정</p>
<p>const hre = require(&quot;hardhat&quot;);</p>
<p>async function main() {
  const LOCK = await hre.ethers.getContractFactory(&quot;ABCToken&quot;);
  const lock = await LOCK.deploy(1234);
  console.log(&quot;LOCK deployed to : &quot;, lock);
  console.log(&quot;LOCK deployed to : &quot;, lock.target);
}</p>
<p>main().catch((error) =&gt; {
  console.error(error);
  process.exitCode = 1;
});</p>
<pre><code>
3. 배포</code></pre><blockquote>
<p>npx hardhat run --network goerli ./scripts/deploy_erc20_3.js</p>
</blockquote>
<pre><code>
4. verify &amp; publishing</code></pre><blockquote>
<p>npx hardhat verify --network goerli {컨트랙트 주소} {배포시 constructor input}</p>
</blockquote>
<pre><code>위 1234랑 같은 값을 넣어주어야, verify가 작동한다.

## react 실습
1. web3 goerli websocket 설정</code></pre><p>const web3 = new Web3(
    &quot;wss://goerli.infura.io/ws/v3/{apikey}&quot;
);</p>
<p>const privateKey = &#39;&#39;;
const account = web3.eth.accounts.privateKeyToAccount(privateKey).address;</p>
<pre><code>해당 사이트에서 subscribe 사용법 확인 가능!
https://docs.web3js.org/guides/web3_upgrade_guide/x/subscribe_migration_guide

2. subscribe event 추가
전체 코드</code></pre><p>import React from &#39;react&#39;;
import {useEffect, useState} from &#39;react&#39;;
import Web3 from &#39;web3&#39;;</p>
<p>function App() {
  const [blockNumber, setblockNumber] = useState();
  const [balance, setBalance] = useState();</p>
<p>  const web3 = new Web3(&quot;wss://goerli.infura.io/ws/v3/apiKey&quot;);</p>
<p>  const privateKey = &#39;개인키&#39;;
  const account = web3.eth.accounts.privateKeyToAccount(privateKey).address;</p>
<p>  useEffect(()=&gt; {
    async function getBlock() {
      const blockNumber = await web3.eth.getBlockNumber();
      setblockNumber(Number(blockNumber));
    }</p>
<pre><code>getBlock();</code></pre><p>  })</p>
<p>  useEffect(()=&gt; {
    async function subscribeBlock() {
      const subscription = await web3.eth.subscribe(&#39;newHeads&#39;);
      subscription.on(&#39;data&#39;, async blockhead =&gt; {
        console.log(&quot;Number of New Block : &quot;, blockhead.number);
        setblockNumber(Number(blockhead.number));
      })
    }
    subscribeBlock();</p>
<pre><code>async function getBalance() {
  var balance = await web3.eth.getBalance(account);
  setBalance(Number(balance));
}

getBalance();</code></pre><p>  })</p>
<p>  return (
    <div>
      <li>real time current block number is : {blockNumber} </li>
      <li>current Wallet : {account}</li>
      <li>current Balance : {balance}</li>
    </div>
  );
}</p>
<p>export default App;</p>
<pre><code>
## dApp 구조
contract의 주소 접근 관련!!
https://ethereum.stackexchange.com/questions/268/ethereum-block-architecture

## 다양한 event listener 실습</code></pre><p>window.ethereum.request({
        method: &quot;eth_chainId&quot;,
      });</p>
<pre><code></code></pre><p>window.ethereum.on(&quot;chainChanged&quot;, () =&gt; {});</p>
<pre><code></code></pre><p>window.ethereum.on(&quot;accountsChanged&quot;, () =&gt; {});</p>
<pre><code>

# 화요일 (오전)

이쪽 진로 라인 정리 (feat. 민서 강사님)
![](https://velog.velcdn.com/images/world_changer/post/c45e2980-2dfc-47bd-8c40-b7456048b83c/image.png)

## 이더 송금</code></pre><form onSubmit={sendTx}>
   <input type="text" name="address" placeholder="write address"></input>
   <input type="text" name="amount" placeholder="write amount"></input>
   <button type="submit">Send TX</button>
</form>
```
```
  async function sendTx(e) {
    e.preventDefault();
    const data = new FormData(e.target);
    console.log(typeof data.get("amount"));
    /*var a = Number(data.get("amount"));
    a = web3.utils.numberToHex(a);*/
    var a = web3.utils.numberToHex(Number(data.get("amount")));
    console.log(a, typeof a);

<pre><code>await window.ethereum.request({
  method: &quot;eth_sendTransaction&quot;,
  params: [{ from: account, to: data.get(&quot;address&quot;), value: a }],
});</code></pre><p>  }</p>
<pre><code>


# 화요일 (오후)
## 컨트랙트 실습 (erc20)</code></pre><p>import abi from &quot;./abi.json&quot;;
...
var c_addr = &quot;0xf7389e84220FF1165842e38C8e92772846e61A9d&quot;;
var contract = new web3.eth.Contract(abi, c_addr);
console.log(&quot;methods are : &quot;, contract.methods);</p>
<pre><code>토큰 balance를 관리할 useState 추가</code></pre><p>const [tBalance, setTbalance] = useState();</p>
<pre><code>토큰 balance 받아오기</code></pre><p>  async function getTbalance() {
    if (account) {
      setTbalance(await contract.methods.balanceOf(account));
    } else {
      console.log(&quot;connect the wallet&quot;);
    }
  }</p>
<pre><code>
### 토큰 transfer</code></pre><p>  async function sendErc(e) {
    e.preventDefault();
    const data = new FormData(e.target);
    var a = web3.utils.numberToHex(Number(data.get(&quot;amount_2&quot;)));</p>
<pre><code>await window.ethereum.request({
  method: &quot;eth_sendTransaction&quot;,
  params: [
    {
      from: account,
      // to: data.get(&quot;address_2&quot;), &lt;- 함수 호출은 CA 한테 하는 거
      // to: data.get(c_addr), &lt;- 실패
      to: c_addr,
      data: contract.methods.transfer(data.get(&quot;address_2&quot;), a).encodeABI(),
    },
  ],
});</code></pre><p>  }</p>
<pre><code>
### 토큰 minting
</code></pre><form onSubmit={minting}>
    <input type="text" name="amount_3" placeholder="write amount"></input>
    <button type="submit">Mint</button>
</form>
```
```
  async function minting(e) {
    e.preventDefault();
    const data = new FormData(e.target);
    var a = web3.utils.numberToHex(Number(data.get("amount_3")));

<pre><code>await window.ethereum.request({
  method: &quot;eth_sendTransaction&quot;,
  params: [
    {
      from: account,
      to: c_addr,
      data: contract.methods.MintToken(a).encodeABI(),
    },
  ],
});</code></pre><p>  }</p>
<pre><code>
### minttoken payable 추가
contract addreess, abi 변경 필요
![](https://velog.velcdn.com/images/world_changer/post/2b5df538-b96e-4c2a-8e76-976d6aabb1bc/image.png)
</code></pre><p>  async function payMinting(e) {
    e.preventDefault();
    const data = new FormData(e.target);
    var a = web3.utils.numberToHex(Number(data.get(&quot;amount_3&quot;)));
    var b = web3.utils.numberToHex(Number(data.get(&quot;amount_3&quot;))*10000);</p>
<pre><code>await window.ethereum.request({
  method : &quot;eth_sendTransaction&quot;,
  params : [{
    from : account,
    to : c_addr_2,
    value : b,
    data : contract2.methods.MintToken(a).encodeABI()
  }]
})</code></pre><p>  }</p>
<pre><code>### evm 동작 원리 구경
https://www.evm.codes/playground?fork=shanghai
이 사이트에서 stack, memory, storage 등 동작 프로세스 확인 가능



# 수/목 (typescript)

## create react app
</code></pre><blockquote>
<p>npx create-react-app [directory] --template typescript</p>
</blockquote>
<pre><code>
각종 자료형


### FC (Function Component)

### type, interface</code></pre><p>type TCreature = {
  name: string;
  level: number;
};</p>
<p>interface ICreature {
  name: string;
  level: number;
}</p>
<p>const player: TCreature = {
  name: &quot;Knight&quot;,
  level: 10,
};</p>
<p>const monster: ICreature = {
  name: &quot;Skeleton&quot;,
  level: 8,
};</p>
<pre><code>
### type, interface 확장</code></pre><p>interface IHuman extends ICreature {
  age: number;
}</p>
<p>const c: IHuman[] = [
  {
    name: &quot;Knight&quot;,
    level: 10,
    age: 10,
  },
];</p>
<pre><code>
### props
props 변수들을 interface 로 지정해서, &lt;&gt; 안에 적어주면 된다.</code></pre><p>interface BoxProps {
  color: string;
}</p>
<p>const Box: FC<BoxProps> = ({ color }) =&gt; {
  return (
    &lt;div
      style={{
        backgroundColor: color,
        width: 400,
        height: 400,
        margin: 40,
      }}
    &gt;</div>
  );
};</p>
<pre><code></code></pre><p>export interface BoxProps {
  color: Color;
  width: number;
  height?: number;
}</p>
<pre><code>이런식으로 ```?``` 를 붙혀주면 생략가능.

# 금 (오전)

CSR (Client Side Rendering)
SSR (Server Side Rendering)

SSG 라는 것도 있음 (Static Site Generation)

## Next js
```&gt; npx create-next-app [폴더명]```
(전부 yes로 설치)
typescript, eslint, src/, app router, alias(@)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[query dsl - 2]]></title>
            <link>https://velog.io/@world_changer/query-dsl-2</link>
            <guid>https://velog.io/@world_changer/query-dsl-2</guid>
            <pubDate>Fri, 05 May 2023 08:48:20 GMT</pubDate>
            <description><![CDATA[<h1 id="실무-활용">실무 활용</h1>
<h2 id="repository-연동">Repository 연동</h2>
<pre><code>&lt;MemberJpaRepository.java&gt;

    public List&lt;Member&gt; findAll(){
        return em.createQuery(&quot;select m from Member m&quot;, Member.class)
                .getResultList();
    }
    public List&lt;Member&gt; findAll_querydsl(){
        return queryFactory.selectFrom(member).fetch();
    }

    public List&lt;Member&gt; findByUsername(String username){
        return em.createQuery(&quot;select m from Member m where m.username = :username&quot;, Member.class)
                .setParameter(&quot;username&quot;, username)
                .getResultList();
    }
    public List&lt;Member&gt; findByUsername_querydsl(String username){
        return queryFactory.selectFrom(member).where(member.username.eq(username)).fetch();
    }</code></pre><p>&lt;MemberTeamDto.java&gt;</p>
<pre><code>@Data
public class MemberTeamDto {
    private Long memberId;
    private String username;
    private int age;
    private Long teamId;
    private String teamName;

    @QueryProjection
    public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
        this.memberId = memberId;
        this.username = username;
        this.age = age;
        this.teamId = teamId;
        this.teamName = teamName;
    }
}</code></pre><p>&lt;MemberSearchCondition.java&gt;</p>
<pre><code>@Data
public class MemberSearchCondition {
    private String username;
    private String teamName;
    private Integer ageGoe;
    private Integer ageLoe;
}</code></pre><blockquote>
<p><code>ctrl + shift + enter</code> 구문 자동 완성!!</p>
</blockquote>
<h2 id="booleanbuilder-동적-쿼리">BooleanBuilder 동적 쿼리</h2>
<pre><code>    public List&lt;MemberTeamDto&gt; searchByBuilder(MemberSearchCondition condition){

        BooleanBuilder builder = new BooleanBuilder();

        if (StringUtils.hasText(condition.getUsername())) {
            builder.and(member.username.eq(condition.getUsername()));
        }
        if (StringUtils.hasText(condition.getTeamName())) {
            builder.and(team.name.eq(condition.getTeamName()));
        }
        if(condition.getAgeGoe() != null){
            builder.and(member.age.goe(condition.getAgeGoe()));
        }

        if(condition.getAgeLoe() != null){
            builder.and(member.age.loe(condition.getAgeLoe()));
        }

        return queryFactory
                .select(new QMemberTeamDto(
                        member.id.as(&quot;memberId&quot;),
                        member.username,
                        member.age,
                        team.id.as(&quot;teamId&quot;),
                        team.name.as(&quot;teamName&quot;)
                ))
                .from(member)
                .leftJoin(member.team, team)
                .where(builder)
                .fetch();
    }</code></pre><h2 id="where-동적-쿼리">where 동적 쿼리</h2>
<pre><code>    public List&lt;MemberTeamDto&gt; search(MemberSearchCondition condition) {
        return queryFactory
                .select(new QMemberTeamDto(
                        member.id.as(&quot;memberId&quot;),
                        member.username,
                        member.age,
                        team.id.as(&quot;teamId&quot;),
                        team.name.as(&quot;teamName&quot;)
                ))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                        )
                .fetch();
    }

    // BooleanExpression 이 추후  composition 에 유리
    private BooleanExpression usernameEq(String username) {
        return StringUtils.hasText(username) ? member.username.eq(username) : null;
    }

    private BooleanExpression teamNameEq(String teamName) {
        return StringUtils.hasText(teamName) ? team.name.eq(teamName) : null;
    }

    private BooleanExpression ageGoe(Integer num) {
        return num != null ? member.age.goe(num) : null;
    }

    private BooleanExpression ageLoe(Integer num) {
        return num != null ? member.age.loe(num) : null;
    }</code></pre><p>where 절... 너무 깔끔하잖아!! 좋아좋아 최고!
재사용 및 조립도 가능함!</p>
<h2 id="api-컨트롤러">API 컨트롤러</h2>
<h3 id="샘플-데이터-설정">샘플 데이터 설정</h3>
<ul>
<li>테스트를 돌릴때는, 샘플 데이터 추가 로직이 동작 x</li>
<li>톰캣을 띄울때는, 샘플 데이터 추가 로직이 실행 되는 걸로!</li>
</ul>
<p>application.yml 에 <code>profiles -&gt; active: local</code> 추가
<img src="https://velog.velcdn.com/images/world_changer/post/3dbc8567-35a7-4cf4-a0ac-d926b8615882/image.png" alt=""></p>
<p>test 폴더에 application.yml 추가
<img src="https://velog.velcdn.com/images/world_changer/post/88eb9828-b424-4fda-be5d-a1f6078ff003/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/8364e6af-cb88-42d2-ae3e-ffd6519b2c0e/image.png" alt=""></p>
<p>다 똑같은데, profiles active 를 <code>test</code> 로 설정!</p>
<p>&lt;InitMember.java&gt; 파일 생성</p>
<pre><code>@Profile(&quot;local&quot;)
@ComponentScan
@RequiredArgsConstructor
public class InitMember {

    private final InitMemberService initMemberService;

    @PostConstruct
    public void init() {
        initMemberService.init();
    }

    @Component
    static class InitMemberService {
        @PersistenceContext
        private EntityManager em;

        @Transactional
        public void init(){
            Team teamA = new Team(&quot;teamA&quot;);
            Team teamB = new Team(&quot;teamB&quot;);
            em.persist(teamA);
            em.persist(teamB);

            for (int i = 0; i &lt; 100; i++) {
                Team selectedTeam = i % 2 == 0 ? teamA : teamB;
                em.persist(new Member(&quot;member&quot; + i, i, selectedTeam));
            }
        }
    }
}</code></pre><blockquote>
<p><code>@Profile(&quot;local&quot;)</code> 은 profile local 에서만 실행하겠다는 어노테이션
<code>@ComponentScan</code> 은 spring 이 돌아갈떄, component scan 을 하라는 뜻.
<code>@PostConstruct</code> 는 시작할때 실행을 하겠다는 뜻
<code>@Component</code> 는 spring bean 에 component 로 지정</p>
<ul>
<li>굳이 <code>InitMemberService</code> 를 만들어서 init 해주는 이유는 JPA 기능인 <code>@Transactional</code> 은 <code>@PostConstruct</code> 랑 함께 지정할 수가 없기 때문!!</li>
</ul>
</blockquote>
<h3 id="api-controller">api controller</h3>
<p>&lt;MemberController.java&gt;</p>
<pre><code>    @GetMapping(&quot;/v1/members&quot;)
    public List&lt;MemberTeamDto&gt; searchMemberV1(MemberSearchCondition condition){
        return memberJpaRepository.search(condition);
    }</code></pre><p>호출하는 법 
<img src="https://velog.velcdn.com/images/world_changer/post/a3734328-a589-41b7-ac23-5ef7b4c20681/image.png" alt=""></p>
<h1 id="스프링-데이터-jpa-repository-로-변경">스프링 데이터 JPA Repository 로 변경</h1>
<p>기존 &lt;MemberJpaRepository.java&gt;
<img src="https://velog.velcdn.com/images/world_changer/post/2e6a1e8d-8ea3-4f73-bc80-44592c105fa4/image.png" alt=""></p>
<p>Spring 데이터 JPA 를 활용한 &lt;MemberRepository.java&gt;
<img src="https://velog.velcdn.com/images/world_changer/post/6a3b9923-5bd8-499f-ae53-76e247321f9e/image.png" alt=""></p>
<p>Query dsl 은 사용자 정의 리포지토리를 통해 구현한다.</p>
<h2 id="사용자-정의-리포지토리">사용자 정의 리포지토리</h2>
<p>스프링 데이터 JPA 에서 했던 방식.</p>
<p>&lt;MemberRepositoryCumstom.java&gt; 파일 생성</p>
<pre><code>public interface MemberRepositoryCustom {
    List&lt;MemberTeamDto&gt; search(MemberSearchCondition condition);
}</code></pre><p>&lt;MemberRepositoryImpl.java&gt; 파일 생성</p>
<pre><code>public class MemberRepositoryImpl implements MemberRepositoryCustom{

    private final JPAQueryFactory queryFactory;

    public MemberRepositoryImpl(EntityManager em) {
        this.queryFactory = new JPAQueryFactory(em);
    }

    @Override
    public List&lt;MemberTeamDto&gt; search(MemberSearchCondition condition) {
        return queryFactory
                .select(new QMemberTeamDto(
                        member.id.as(&quot;memberId&quot;),
                        member.username,
                        member.age,
                        team.id.as(&quot;teamId&quot;),
                        team.name.as(&quot;teamName&quot;)
                ))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .fetch();
    }
    ...
    // usernameEq, teamNameEq, ageGoe, ageLoe 함수
}</code></pre><p>&lt;MemberRepository.java&gt; 파일 </p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt;, MemberRepositoryCustom {
    List&lt;Member&gt; findByUsername(String username);
}</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/55dd526d-6c23-4c73-85d3-cab10e87bced/image.png" alt=""></p>
<blockquote>
<p><code>MemberRepositoryCustom</code> 이라는 interface를 만들어, <code>MemberRepositoryImpl</code> 로 구현</p>
<ul>
<li>이때 구현체는 <code>JpaRepository</code>를 상속받는 리포지토리 뒤에 <code>impl</code> 붙은 이름과 같아야함
(<code>MemberRepository</code>, <code>MemberRepositoryImpl</code>)</li>
<li><code>MemberRepository</code> 는 <code>JpaRepository</code> 와 <code>&lt;해당 인터페이스&gt;</code> 를 상속받으면 됨.</li>
</ul>
</blockquote>
<blockquote>
<p>조회 쿼리가 너무 복잡한 경우. <code>MemberRepository</code> 에 합치는 것이 아니라,
새로운 <code>MemberQueryRepository</code> 를 만드는 것도 좋음. 
특화된 기능은 따로 만드는 것도 좋음!! 합치는 것이 무조건 정답이 아님.</p>
<ul>
<li>비즈니스, 설계에 따라 유동적으로 도입할 줄 아는 유연성을 가질 것!!</li>
</ul>
</blockquote>
<h2 id="스프링-데이터-페이징-활용">스프링 데이터 페이징 활용</h2>
<ul>
<li><code>Page</code>, <code>Pageable</code> 을 활용해보기!</li>
<li>simple 과 complex 두 버전</li>
</ul>
<pre><code>public interface MemberRepositoryCustom {
    List&lt;MemberTeamDto&gt; search(MemberSearchCondition condition);
    /*추가*/ Page&lt;MemberTeamDto&gt; searchPageSimple(MemberSearchCondition condition, Pageable pageable);
    /*추가*/ Page&lt;MemberTeamDto&gt; searchPageComplex(MemberSearchCondition condition, Pageable pageable);
}</code></pre><h3 id="simple-pageable">simple pageable</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/8f8b7d54-99c0-4f5d-921a-3509b78218f6/image.png" alt=""></p>
<blockquote>
<p>기존 쿼리문에 <code>offset</code> 과 <code>limit</code> 를 추가 해주는 방식으로 pageable 구현이 가능하다!!
<code>PageImpl</code> 구현체를 사용하여 Page 타입으로 반환 가능!</p>
</blockquote>
<p>테스트 코드</p>
<pre><code>    @Test
    public void searchPageSimpleTest() throws Exception{
        // given
        Team teamA = new Team(&quot;teamA&quot;);
        Team teamB = new Team(&quot;teamB&quot;);
        em.persist(teamA);
        em.persist(teamB);

        Member m1 = new Member(&quot;m1&quot;, 10, teamA);
        Member m2 = new Member(&quot;m2&quot;, 20, teamA);

        Member m3 = new Member(&quot;m3&quot;, 15, teamB);
        Member m4 = new Member(&quot;m4&quot;, 25, teamB);

        Member m5 = new Member(&quot;m5&quot;, 30);
        Member m6 = new Member(&quot;m6&quot;, 40);

        em.persist(m1);
        em.persist(m2);
        em.persist(m3);
        em.persist(m4);
        em.persist(m5);
        em.persist(m6);

        em.flush();
        em.clear();

        // when
        MemberSearchCondition condition = new MemberSearchCondition();
        PageRequest pageRequest = PageRequest.of(0, 3);

        Page&lt;MemberTeamDto&gt; result = memberRepository.searchPageSimple(condition, pageRequest);

        // then

        assertThat(result.getSize()).isEqualTo(3);
        assertThat(result.getContent()).extracting(&quot;username&quot;).containsExactly(&quot;m1&quot;, &quot;m2&quot;, &quot;m3&quot;);

    }</code></pre><p>이 방법의 경우, <code>getTotal()</code> 을 통해 count 를 불러오는데, 이때 count query에 특정 쿼리를 지정해주면 좋다.</p>
<h3 id="complex-pageable">complex pageable</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/71a182c3-574e-4831-a2ba-1e21b4c5230f/image.png" alt=""></p>
<p>result 를 그냥 <code>fetch</code> 를 활용하여 그대로 받아오고,
<code>fetchCount</code> 를 활용해서, 최적화된 count 함수를 실행함.</p>
<blockquote>
<p>핵심은 count 를 직접 지정해주어서, count와 상관 없는 쿼리가 포함되는 것을 막을 수 있음 (성능 최적화)
count 가 0일때는 search 쿼리를 날리지 않는 다던지 하는
데이터가 얼마 없을때는 그냥... <code>fetchResult</code> 써라...</p>
</blockquote>
<h3 id="count-쿼리-최적화">count 쿼리 최적화</h3>
<ul>
<li>상황에 따라서는 count 쿼리를 생략할 수 있다.</li>
<li>1) 페이지 시작이면서, 컨텐츠 사이즈가 페이지 사이즈 보다 작을때</li>
<li>2) 마지막 페이지 일때</li>
</ul>
<pre><code>        JPAQuery&lt;Member&gt; countQuery = queryFactory
                .selectFrom(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                );

        return PageableExecutionUtils.getPage(content, pageable, () -&gt; countQuery.fetchCount());</code></pre><p>스프링데이터 JPA 에서 <code>PageableExecutionUtils</code> 로 그 기능을 제공해준다.
세번째 input 으로는 page count 가 필요할때 실행할 함수를 넣어주면 된다!</p>
<blockquote>
<p>if 문 넣어서 직접 해도 되긴함!!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/aa9431ad-14f0-4d38-89b3-da758519357f/image.png" alt="">
이런식으로 controller 에서 불러와 사용하면 됨!
<img src="https://velog.velcdn.com/images/world_changer/post/3dadd121-2152-4627-a671-dc0dc7db4efc/image.png" alt=""></p>
<h1 id="스프링-데이터-jpa-가-지원하는-querydsl">스프링 데이터 JPA 가 지원하는 Querydsl</h1>
<p>여러개 가 있기는 하지만, 복잡한 실무에서 사용하기는 좀 애매함... 참고만!!</p>
<ul>
<li><p>QuerydslPredicateExecutor 
1 - query dsl 코드를 인자로 넘길 수 있음
2 - left join 지원 안해줌. 
3 - 서비스, 컨트롤러 계층에 query 문이 들어감</p>
</li>
<li><p>Querydsl Web 지원
1 - <code>@QuerydslPredicate</code> 를 활용해서 파라미터를 편하게 받을 수 있음
2 - 조건문이 한정적임 / custom 하려면 너무 복잡해짐
3 - 마찬가지로 상위 계층에 query 관련 function 이 포함됨.</p>
</li>
<li><p>QuerydslRepositorySupport
1 - 추상클래스임 (특정 리포지토리에 상속시키면 됨)
2 - queryFactory 없이, 구현된 특정 메소드들을 활용하여 쉽게 메소드 짤 수 있음</p>
</li>
</ul>
<h2 id="querydsl-지원-클래스-직접-만들기-advance">Querydsl 지원 클래스 직접 만들기 (Advance)</h2>
<blockquote>
<p>김영한 강사님의. <code>QuerydslRepositorySupport</code> 한계 극복 클래스 직접 제작!!</p>
</blockquote>
<pre><code>@Repository
public abstract class Querydsl4RepositorySupport {
    private final Class domainClass;
    private Querydsl querydsl;
    private EntityManager entityManager;
    private JPAQueryFactory queryFactory;
    public Querydsl4RepositorySupport(Class&lt;?&gt; domainClass) {
        Assert.notNull(domainClass, &quot;Domain class must not be null!&quot;);
        this.domainClass = domainClass;
    }
    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        Assert.notNull(entityManager, &quot;EntityManager must not be null!&quot;);
        JpaEntityInformation entityInformation =
                JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
        SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
        EntityPath path = resolver.createPath(entityInformation.getJavaType());
        this.entityManager = entityManager;
        this.querydsl = new Querydsl(entityManager, new
                PathBuilder&lt;&gt;(path.getType(), path.getMetadata()));
        this.queryFactory = new JPAQueryFactory(entityManager);
    }
    @PostConstruct
    public void validate() {
        Assert.notNull(entityManager, &quot;EntityManager must not be null!&quot;);
        Assert.notNull(querydsl, &quot;Querydsl must not be null!&quot;);
        Assert.notNull(queryFactory, &quot;QueryFactory must not be null!&quot;);
    }
    protected JPAQueryFactory getQueryFactory() {
        return queryFactory;
    }
    protected Querydsl getQuerydsl() {
        return querydsl;
    }
    protected EntityManager getEntityManager() {
        return entityManager;
    }
    protected &lt;T&gt; JPAQuery&lt;T&gt; select(Expression&lt;T&gt; expr) {
        return getQueryFactory().select(expr);
    }
    protected &lt;T&gt; JPAQuery&lt;T&gt; selectFrom(EntityPath&lt;T&gt; from) {
        return getQueryFactory().selectFrom(from);
    }
    protected &lt;T&gt; Page&lt;T&gt; applyPagination(Pageable pageable,
                                          Function&lt;JPAQueryFactory, JPAQuery&gt; contentQuery) {
        JPAQuery jpaQuery = contentQuery.apply(getQueryFactory());
        List&lt;T&gt; content = getQuerydsl().applyPagination(pageable,
                jpaQuery).fetch();
        return PageableExecutionUtils.getPage(content, pageable,
                jpaQuery::fetchCount);
    }
    protected &lt;T&gt; Page&lt;T&gt; applyPagination(Pageable pageable,
                                          Function&lt;JPAQueryFactory, JPAQuery&gt; contentQuery, Function&lt;JPAQueryFactory,
            JPAQuery&gt; countQuery) {
        JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory());
        List&lt;T&gt; content = getQuerydsl().applyPagination(pageable,
                jpaContentQuery).fetch();
        JPAQuery countResult = countQuery.apply(getQueryFactory());
        return PageableExecutionUtils.getPage(content, pageable,
                countResult::fetchCount);
    }
}</code></pre><p>이런 식으로 기존에 있던게 불편하다면, 중간에 함수들을 약간 변경해서 내 맛대로 사용해도 되는 거였음!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[query dsl - 1]]></title>
            <link>https://velog.io/@world_changer/query-dsl-1</link>
            <guid>https://velog.io/@world_changer/query-dsl-1</guid>
            <pubDate>Thu, 04 May 2023 03:40:09 GMT</pubDate>
            <description><![CDATA[<h1 id="query-dsl-기본-문법">Query DSL 기본 문법</h1>
<p><img src="https://velog.velcdn.com/images/world_changer/post/1a265fb9-f17a-40b4-9ddb-fd89dd95d4a4/image.png" alt=""></p>
<p>query 위주의 실습이기에, 첫 데이터를 항상 넣고 시작.
(같은 class 내의 테스트 들만)
++ compileQuerydsl 실행해두기 (Q type 을 만들어줌)</p>
<h2 id="jpql-과-차이">JPQL 과 차이</h2>
<pre><code>    @Test
    public void startJPQL() {
        // find m1
        String queryString = &quot;select m from Member m where m.username =:username&quot;;

        Member findByJPQL = em.createQuery(queryString, Member.class)
                .setParameter(&quot;username&quot;, &quot;m1&quot;)
                .getSingleResult();

        assertThat(findByJPQL.getUsername()).isEqualTo(&quot;m1&quot;);
    }
    @Test
    public void startQuerydsl() {
        // find m1
        JPAQueryFactory queryFactory = new JPAQueryFactory(em);
//        QMember m = new QMember(&quot;member&quot;); 이거는 에러가 뜨더라, Member 랑 같아서 그런가봄
        QMember m = new QMember(&quot;m&quot;);

        Member findMember = queryFactory.select(m).from(m).where(m.username.eq(&quot;m1&quot;)).fetchOne();
        assertThat(findMember.getUsername()).isEqualTo(&quot;m1&quot;);
    }</code></pre><p>컴파일 시점에 오류를 발견할 수 있음!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/9cc5fa24-176b-4308-badf-a1cd9cb81f77/image.png" alt=""></p>
<p>이런식으로 지정해두고, 가져올 수 있다!! (할때마다 지정해주지 않아도 됨)</p>
<h2 id="기본-q-type-활용">기본 Q-Type 활용</h2>
<pre><code>1. 직접 별칭 지정
QMember m = new QMember(&quot;m&quot;);

2. default 사용
QMember m = QMember.class; // &lt;= new QMember(&quot;member1&quot;)

3. static 화 해서 바로 사용
QMember.member 말고 member 로 바로 사용.</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/41e33c55-0bee-4af3-a8a1-e777ad4582d4/image.png" alt=""></p>
<p>JPQL 로 어떻게 생성되서 만들어지는지 확인하고 싶으면,</p>
<pre><code>application.yml

jpa -&gt; properties -&gt; hibernate -&gt; use_sql_comments:true 추가</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/a324f883-37b2-486a-8f45-4af7f894add4/image.png" alt=""></p>
<h2 id="검색-조건-query">검색 조건 Query</h2>
<p>select, from 을 합쳐서 selectFrom 으로 쓸 수 있음 (같은 경우에)</p>
<pre><code>Member findMember = queryFactory
    .selectFrom(member)
    .where(
        member.username.eq(&quot;m1&quot;).and(member.age.eq(10))
    ).fetchOne();
assertThat(findMember.getUsername()).isEqualTo(&quot;m1&quot;);</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/3ba4d43f-5c04-4452-aa56-14728f63d20a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/a23587d0-ed98-474c-adbc-f82e3ed3c049/image.png" alt=""></p>
<p>and 의 경우 이렇게 <code>,</code> 로 끊어 갈 수도 있다!</p>
<blockquote>
<p>값으로 null 이 들어갈때는 해당 조건문을 무시한다. =&gt; 동적 쿼리 작성에 기가 막힌 구조!!!</p>
</blockquote>
<h2 id="결과-조회">결과 조회</h2>
<p>주로 <code>fetch</code> 를 사용함. &lt;- <code>getResultList</code> 와 비슷</p>
<pre><code>        List&lt;Member&gt; fetch = queryFactory.selectFrom(member).fetch();

        Member fetchOne = queryFactory.selectFrom(member).fetchOne();

        // queryFactory.selectFrom(member).limit(1).fetchOne(); 이거랑 같음
        Member fetchFirst = queryFactory.selectFrom(member).fetchFirst();

        // paging 형식으로! 쿼리 2번 날림 (count)
        QueryResults&lt;Member&gt; results = queryFactory.selectFrom(member).fetchResults();
        results.getTotal(); // 전체 개수 -&gt; count 쿼리를 따로 날림 =&gt; member 의 id 값으로만 count
        List&lt;Member&gt; content = results.getResults();
        // getLimit, getOffset 같은 여러 함수 지원

        // paging 말고 count 만 가지고 오고 싶을때
        long total = queryFactory.selectFrom(member).fetchCount();</code></pre><p><code>fetch</code>, <code>fetchOne</code>, <code>fetchFirst</code>, <code>fetchResults</code>, <code>fetchCount</code> 메소드로 결과 조회 가능!</p>
<h2 id="정렬">정렬</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/4344a1e0-0730-41e3-a9c6-e1fcd74a2f0c/image.png" alt=""></p>
<p>이런식으로 바로 <code>QMember</code> 에서 엔티티 필드값에 desc, asc 적용 가능하고, 기타 설정들도 적용 가능하다!</p>
<h2 id="페이징">페이징</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/4b14d481-fd30-4cc3-9526-615e837781f2/image.png" alt=""></p>
<p>offset 과 limit 로 가능하고, <code>fetchResults</code> 로 받아오면, 여러 페이징 관련 메소드를 사용할 수 있음!!</p>
<blockquote>
<p>여기에 where 문 이나, 다른 테이블 참조 등 여러 복합쿼리가 나가게 되면, count 절에도 똑같이 적용이 되어 성능이 안나올 때가 있음! count 문을 위한 쿼리를 따로 짜주는게 좋음!!</p>
</blockquote>
<h2 id="집합">집합</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/9b79b291-fe36-41c1-8574-41a589b72283/image.png" alt="">
<code>Tuple</code> 은 querydsl 에서 제공해주는 자료형 (데이터 타입이 여러개 들어올때)</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/932b45e1-d891-4cbe-a1b1-dec600b13508/image.png" alt=""></p>
<p>이런식으로 Tuple 값 접근 가능! =&gt; 실무에서는 DTO 로 처리함!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/c10cf2d9-41a4-4ac9-8f61-909158690a01/image.png" alt=""></p>
<p>이런식의 groupBy 기능도 가능</p>
<h2 id="join---기본">join - 기본</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/7143f9d6-c199-4ed8-a2a9-325b4cf043f0/image.png" alt=""></p>
<pre><code>        List&lt;Member&gt; result = queryFactory
                .selectFrom(member)
                .join(member.team, team)
                .where(team.name.eq(&quot;teamA&quot;))
                .fetch();

        assertThat(result)
                .extracting(&quot;username&quot;)
                .containsExactly(&quot;m1&quot;, &quot;m2&quot;);</code></pre><ul>
<li><code>team</code> 은 <code>QTeam.team</code> 을 static 화 해놓은 것!
<img src="https://velog.velcdn.com/images/world_changer/post/47bd13c6-69ff-4e9f-a486-15c44066b21e/image.png" alt=""></li>
</ul>
<blockquote>
<p>덧. <strong><em>세타조인</em></strong> 이라고, 막 조인을 하는 것도 가능!
<img src="https://velog.velcdn.com/images/world_changer/post/5da74398-5111-4c43-ab50-5bfd3e0079a3/image.png" alt="">
DB 가 최적화를 하겠지만, member 와 team Table 을 싹 불러와 NxM 테이블 만들고 where 절 처리! (cross join)</p>
<ul>
<li>단점 : 외부 조인이 불가능 함 =&gt; 조인 on 을 사용하여 해결 가능!</li>
</ul>
</blockquote>
<h2 id="join---on-절">join - on 절</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/334f1e4d-c51e-4bf3-9653-dc8d2875526d/image.png" alt=""></p>
<p>이를 qeurydsl 로 가져가면,</p>
<pre><code>List&lt;Tuple&gt; result = queryFactory
                .select(member, team)
                .from(member)
                .leftJoin(member.team, team).on(team.name.eq(&quot;teamA&quot;)).fetch();</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/92a5e3aa-4d2a-4fb3-8160-9b8ab8e55044/image.png" alt=""></p>
<p>left join 을 할건데, team name 이 <code>teamA</code> 인 애들만 join 할거야.</p>
<p>나머지는 team 이 있어도 조회 안되는 걸루!!</p>
<p>추가) join 이라면.</p>
<pre><code>List&lt;Tuple&gt; result = queryFactory
                .select(member, team)
                .from(member)
                .join(member.team, team).on(team.name.eq(&quot;teamA&quot;)).fetch();</code></pre><p>join 을 할건데, team name 이 <code>teamA</code> 인 애들만 조인할거야.
<img src="https://velog.velcdn.com/images/world_changer/post/73bafa5f-e1f2-467d-b076-d2453c7b76b6/image.png" alt=""></p>
<blockquote>
<p>on 절은 join에 조건을 걸어주는 것!!
-&gt; inner 조인의 경우 on 이나 where 이나 같은 역할.
-&gt; outer 조인의 경우 on 으로만 가능함!</p>
</blockquote>
<h2 id="join---fetch-join">join - fetch join</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/19758d10-d6fd-4e9d-aa68-af3bc239f024/image.png" alt=""></p>
<h2 id="서브-쿼리">서브 쿼리</h2>
<p><code>JPAExpressions</code> 를 활용하여 서브쿼리 구현!
따로 <code>QMember</code> 별칭과 함께 지정해두고 사용해야함!!</p>
<pre><code>// 나이가 가장 많은 회원들 조회
        QMember subMember = new QMember(&quot;subMember&quot;);

        List&lt;Member&gt; result = queryFactory
                .selectFrom(member)
                .where(member.age.eq(
                        JPAExpressions
                                .select(subMember.age.max())
                                .from(subMember)
                ))
                .fetch();

        for (Member member : result) {
            System.out.println(&quot;member = &quot; + member);
        }</code></pre><pre><code>// 평균나이보다 많은 회원들 조회
        QMember subMember = new QMember(&quot;subMember&quot;);

        List&lt;Member&gt; result = queryFactory
                .selectFrom(member)
                .where(member.age.gt(
                        JPAExpressions
                                .select(subMember.age.avg())
                                .from(subMember)
                ))
                .fetch();

        for (Member member : result) {
            System.out.println(&quot;member = &quot; + member);
        }</code></pre><h2 id="case-문">case 문</h2>
<pre><code>        List&lt;String&gt; result = queryFactory
                .select(member.age
                        .when(10).then(&quot;열살&quot;)
                        .when(20).then(&quot;스무살&quot;)
                        .otherwise(&quot;기타&quot;)
                )
                .from(member)
                .fetch();

        for (String s : result) {
            System.out.println(&quot;s = &quot; + s);
        }</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/bd638289-f6eb-4efa-93ef-fdec7a1f245b/image.png" alt=""></p>
<pre><code>        List&lt;String&gt; result = queryFactory
                .select(new CaseBuilder()
                        .when(member.age.between(0, 20)).then(&quot;0 ~ 20 살&quot;)
                        .when(member.age.between(21, 30)).then(&quot;21 ~ 31 살&quot;)
                        .otherwise(&quot;기타&quot;)
                )
                .from(member)
                .fetch();

        for (String s : result) {
            System.out.println(&quot;s = &quot; + s);
        }</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/83ca930e-5139-41ca-9f12-b8d44ec6cb33/image.png" alt=""></p>
<blockquote>
<p>가능하면 DB 에서 이런 작업들은 하지 않는 걸로!
이런 작업들은 DB 밖에서 하는 것이 왠만하면 좋음!! (클라이언트 나 presentation layer 에서)</p>
</blockquote>
<h2 id="상수-문자-더하기">상수 문자 더하기</h2>
<pre><code>        List&lt;Tuple&gt; result1 = queryFactory
                .select(member.username, Expressions.constant(&quot;A&quot;))
                .from(member)
                .fetch();
        for (Tuple tuple : result1) {
            System.out.println(&quot;tuple = &quot; + tuple);
        }

        List&lt;String&gt; result2 = queryFactory
                .select(member.username.concat(member.age.stringValue()))
                .from(member)
                .fetch();
        for (String v : result2) {
            System.out.println(&quot;v = &quot; + v);
        }</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/f1cb1250-e9c2-4d3d-90a5-a426a4474dcb/image.png" alt=""></p>
<p><code>.stringValue()</code> 이게 은근 쓸 데가 많음 (enum 의 경우 자주 사용)</p>
<h1 id="query-dsl-중급-문법">query dsl 중급 문법</h1>
<h2 id="프로젝션">프로젝션</h2>
<p>Tuple 형태 말고 DTO 형태로!!</p>
<p>&lt;MemberDto.java&gt;</p>
<pre><code>@Data
@NoArgsConstructor
public class MemberDto {
    private String username;
    private int age;

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}</code></pre><p>기존 JPQL 에서 dto 로 불러오는 법</p>
<pre><code>List&lt;MemberDto&gt; resultList = em.createQuery(
    &quot;select new study.querydsl.dto.MemberDto(m.username, m.age) &quot;
    + &quot;from Member m&quot;, MemberDto.class).getResultList();</code></pre><blockquote>
<p>QueryDSL 에서 더 깔끔하게 지원해줌</p>
</blockquote>
<h3 id="1-프로퍼티-접근">1. 프로퍼티 접근</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/838a5a9f-f7d9-4e0e-9e80-33156d9969ba/image.png" alt=""></p>
<h3 id="2-필드-직접-접근">2. 필드 직접 접근</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/b9d3d8c5-cec7-4950-b682-a718c75c8007/image.png" alt=""></p>
<h3 id="3-생성자-사용">3. 생성자 사용</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/a9864228-14f8-4452-ac47-3744541bf4f2/image.png" alt=""></p>
<h3 id="4-프로젝션과-결과-반환">4. 프로젝션과 결과 반환</h3>
<p><code>@QueryProjection</code> 어노테이션을 달아주면, Query DSL compile 할때 DTO 클래스가 Q형태로 새로 만들어진다!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/5432507c-64c4-4b4f-abd9-b767a643f8a8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/3e509422-1322-443e-9242-2895791551e9/image.png" alt=""></p>
<p>정말 편하긴 한데!!
DTO는 보통 Respository 에서 Query DSL 함수로만 쓰이는 친구가 아님
Service, Controller 단에서도 쓰이게 되는데, Query DSL 관련 의존을 하고 있다는 건 뭔가 순수하지 않음...</p>
<h2 id="동적쿼리">동적쿼리</h2>
<h3 id="booleanbuilder">BooleanBuilder</h3>
<pre><code>    private List&lt;Member&gt; searchMember1(String usernameCond, Integer ageCond){

        BooleanBuilder builder = new BooleanBuilder();

        if(usernameCond != null){
            builder.and(member.username.eq(usernameCond));
        }

        if(ageCond != null){
            builder.and(member.age.eq(ageCond));
        }

        return queryFactory
                .selectFrom(member)
                .where(builder)
                .fetch();
    }</code></pre><h3 id="where-다중-파라미터">where 다중 파라미터</h3>
<pre><code>    private List&lt;Member&gt; searchMember2(String usernameCond, Integer ageCond){
        return queryFactory.selectFrom(member).where(
                usernameEq(usernameCond),
                ageEq(ageCond)
        ).fetch();
    }
    private Predicate usernameEq(String usernameCond){
        return usernameCond != null ? member.username.eq(usernameCond) : null;
    }

    private Predicate ageEq(Integer ageCond){
        return ageCond != null ? member.age.eq(ageCond) : null;
    }</code></pre><h2 id="수정-삭제-배치-쿼리">수정, 삭제 배치 쿼리</h2>
<ul>
<li><p>필드 업데이트</p>
<pre><code>      long count = queryFactory
              .update(member)
              .set(member.username, &quot;비회원&quot;)
              .where(member.age.lt(23))
              .execute();

      List&lt;Member&gt; result = queryFactory.selectFrom(member).fetch();

      for (Member member1 : result) {
          System.out.println(&quot;member1 = &quot; + member1);
      }</code></pre><p>update가 실행되고 나서 (벌크 연산), 영속성 컨텍스트가 유지되면, 조회를 하더라도 바뀐 값으로 업데이트 하지 않는 경우가 있다.
update 같은 벌크연산을 하고 난 뒤에는, em.flush 및 clear 하기를!!</p>
</li>
<li><p>다른 벌크 기능</p>
<pre><code>      long executeCount = queryFactory
              .update(member)
              //.set(member.age, member.age.add(1))
              .set(member.age, member.age.multiply(2))
              .execute();

      System.out.println(&quot;executeCount = &quot; + executeCount);</code></pre><pre><code>      long executeCount = queryFactory
              .delete(member)
              .where(member.age.gt(18))
              .execute();
      System.out.println(&quot;executeCount = &quot; + executeCount);</code></pre><h2 id="sql-function-호출">SQL function 호출</h2>
<pre><code>    List&lt;String&gt; result = queryFactory
              .select(Expressions.stringTemplate(
                      &quot;function(&#39;replace&#39;, {0}, {1}, {2})&quot;,
                      member.username, &quot;m&quot;, &quot;member&quot;))
              .from(member).fetch();</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/ab470c41-b08f-45a3-99a3-608ec3bfd062/image.png" alt=""></p>
</li>
</ul>
<p>이런식으로 다양한 여러 function 을 사용할 수 있음!</p>
<blockquote>
<p><code>Expressions.stringTemplate(&quot;function(&#39;lower&#39;, {0})&quot;, member.username)</code> 
<code>member.username.lower()</code> 와 같은 의미임. 대부분 다 querydsl 에 구현이 되어 있긴 함</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[til_230501]]></title>
            <link>https://velog.io/@world_changer/til230501</link>
            <guid>https://velog.io/@world_changer/til230501</guid>
            <pubDate>Wed, 03 May 2023 01:49:38 GMT</pubDate>
            <description><![CDATA[<h1 id="합의-알고리즘">합의 알고리즘</h1>
<ol>
<li>마지막 블록 헤더 해시값 비교 (고유한 체인의 특성)</li>
<li>길이 비교</li>
<li>길이가 긴 쪽에서 체인을 넘겨줌!</li>
<li>유효성 검사 (채굴 결과 검증)</li>
<li>유효하면 블록체인 동기화</li>
</ol>
<p>1 이 달라서 2를 하는데 길이가 다르다?
잠시 대기상태임!
계속 상호 물어봄!!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/764f10e0-b688-4bc1-acec-0b1df8d9106c/image.png" alt=""></p>
<p>합의 알고리즘의 끝은 네트워크 동기화!!
모든 full node 들의 목적은 동기화가 되는 것이다! (주변 노드들과)</p>
<h1 id="스크립트">스크립트</h1>
<p>돈이 들어올때는 묶인 상태로 옴.
이걸 풀어줘야함.</p>
<p>sender 가 나의 공개키로 묶어서, 나에게 보내고
나는 private 로 풀어서 내 지갑에 넣는다?</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/f2aec160-b027-4e0b-887a-631dbc52248b/image.png" alt=""></p>
<h1 id="utxo">UTXO</h1>
<p>mempool 에서 수많은 UTXO 들이 모이고, 어떤 UTXO 를 담은 블록들을 채굴자들이 채굴한다.</p>
<ul>
<li><a href="https://developer.bitcoin.org/devguide/transactions.html">https://developer.bitcoin.org/devguide/transactions.html</a></li>
<li><a href="https://txstreet.com/v/eth-btc">https://txstreet.com/v/eth-btc</a></li>
<li><a href="https://mempool.space/ko/mempool-block/0">https://mempool.space/ko/mempool-block/0</a></li>
<li><a href="https://bits.monospace.live/">https://bits.monospace.live/</a></li>
</ul>
<h1 id="full---node">full - node</h1>
<p>노드가 다 같은 역할을 하지는 않음.</p>
<h3 id="핸드셰이킹">핸드셰이킹</h3>
<ul>
<li>version 을 보내고, versack 을 받음 (응답 받았다는 것을 확인)</li>
<li>version 을 받고, versack 을 보냄 (version + acknowledgement)</li>
</ul>
<h2 id="종자-노드">종자 노드</h2>
<p>오래되고, 신뢰가 쌓여있는 이웃노드들 정보가 있음.
이 노드에 요청하면, 그 노드들 받을 수 있음.</p>
<h2 id="처음에-블록-받아오는-법">처음에 블록 받아오는 법</h2>
<p><a href="https://developer.bitcoin.org/reference/p2p_networking.html">https://developer.bitcoin.org/reference/p2p_networking.html</a></p>
<ol>
<li>get block 방법 (구식)</li>
<li>get headers 방법 (신식)</li>
</ol>
<blockquote>
<p>블록체인 풀노드로 첫 시작을 할거면, 풀노드가 세팅해놓은 api 를 활용해서 정보들을 받아와야함!</p>
</blockquote>
<h2 id="spv">SPV</h2>
<ul>
<li>Simplified Payment Verification</li>
<li>블록헤더만을 다운로드 받는 노드</li>
<li>풀노드의 도움을 받아야함.</li>
</ul>
<h3 id="머클트리">머클트리</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/64598a54-2063-4b16-80e1-0ee3246f320a/image.png" alt=""></p>
<p>이를 하는 이유는 검증에 있어서, 효율적인 방법을 제공해주기 위함!</p>
<p>spv 가 특정 tx 가 옳은지 검증을 하기 위해, 풀노드에 관련 자료들을 요청함.
특정 tx 를 검증할 수 있게, 풀노드에서 힌트를 주는 느낌?</p>
<h1 id="요즘-블체-네트워크">요즘 블체 네트워크</h1>
<p>순수한 p2p 는 지금 아님</p>
<p>compact block optimization -&gt; 안정적으로 운영되는 특정 노드들을 선발하여 AWS 에 호스팅!!
FIBRE 개발 (Fast Internet Bitcoin Relay Engine)</p>
<p>덧.</p>
<ol>
<li>채굴자(풀노드X)는 새로운 블록을 생성</li>
<li>생성된 블록은 풀 노드에 전송</li>
<li>풀 노드는 새로운 블록을 검증하고, 이전 블록과 연결하여 블록체인에 추가</li>
<li>새로운 블록이 추가된 블록체인은 모든 풀 노드에게 동기화</li>
<li>SPV 노드는 블록 헤더와 머클루트값을 사용하여 블록체인의 일부분을 다운로드</li>
<li>SPV 노드는 검증을 요청하는 TX의 해시값을 풀 노드에 보내어, 해당 거래의 정보 받음</li>
<li>SPV노드는 풀보드가 보내준 포함된 블록의 머클루트값과 path를 확인하여, 해당 거래의 유효성을 검증</li>
</ol>
<p>1.채굴자(풀노드)는 새로운 블록을 생성 후 검증하고, 이전 블록과 연결하여 블록체인에 추가</p>
<p>2.새로운 블록이 추가된 블록체인은 모든 풀 노드에게 동기화</p>
<p>3.SPV 노드는 블록 헤더와 머클루트값을 사용하여 블록체인의 일부분을 다운로드</p>
<p>4.SPV 노드는 검증을 요청하는 TX의 해시값을 풀 노드에 보내어, 해당 거래의 정보 받음</p>
<p>5.SPV노드는 풀노드가 보내준 포함된 블록의 머클루트값과 path를 확인하여, 해당 거래의 유효성을 검증</p>
<h1 id="솔리디티">솔리디티</h1>
<pre><code>// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.18;

contract StoreandReturn {
    uint a; //숫자형 변수 a
    uint b; //숫자형 변수 b
    uint c=2; // 숫자형 변수 c, 값은 2

    // 함수이고, 이름은 valueA, input값은 없음, public 하고 view 함, output 값은 1개 있음, uint 형임
    function getA() public view returns(uint) {
        return a;
    }

    function setA(uint _a) public {
        a = _a;
    }

    function getB() public view returns(uint) {
        return b;
    }

    // 함수, 이름은 valueAB, input 없음, public하고 view 함, output 값은 2개, 둘 다 uint형
    function getAB() public view returns(uint, uint) {
        return (a, b);
    }

    function getABC() public view returns(uint, uint, uint) {
        return (a,b,c);
    }
}</code></pre><p><code>view</code> 의 경우 상태변수를 건드리지 않는 경우에 선언</p>
<p>상태변수 : contract 에서 관리되는 변수
지역변수 : 메소드의 input 값으로 들어와, 메소드 안에서만 관리되는 변수</p>
<h2 id="함수-종류">함수 종류</h2>
<p>none : 상태변수가 수정이 될 때 (가스 필요)
<code>view</code> : 상태변수 수정은 x, 불러오기만 할때 (가스 x)
<code>pure</code> : 상태변수 를 사용하지 않을때</p>
<pre><code>/*
    view와 pure 함수는 state variable(상태변수)의 값을 변화시키지는 않음 -&gt; gas비 안씀
    */
    // 숫자 _aa와 _bb를 받아서 이 2개의 숫자를 더한 결과값을 반환하는 함수 Add를 구현하세요
    function Add(uint _aa, uint _bb) public pure/*상태변수는 하나도 필요없을 때 pure*/ returns(uint) {
        return _aa+_bb;
    }

    // 숫자 a와 b를 갖고와서 이 2개의 숫자를 더한 결과값을 반환하는 함수 Add2를 구현하세요
    function Add2() public view/*상태변수를 갖고오기 때문에 view로로*/ returns(uint) {
        return a+b;
    }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[query dsl - 0]]></title>
            <link>https://velog.io/@world_changer/query-dsl-0</link>
            <guid>https://velog.io/@world_changer/query-dsl-0</guid>
            <pubDate>Sat, 29 Apr 2023 10:12:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/world_changer/post/f04dad1b-6e54-443b-b8b9-ffe5aea3aa6d/image.png" alt="">
컴터에 설치한 java가 11 버전이라 spring 2.x 를 사용. </p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/e5b155d1-3a89-43f3-8734-93a300d635e0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/c0dd1a76-9903-41d0-b2b8-21bc843e80f4/image.png" alt=""></p>
<h1 id="query-dsl-설정">Query DSL 설정</h1>
<p><code>build.gradle</code> 파일에 query dsl 관련 내용 추가</p>
<pre><code>plugin {
    ...
    //querydsl 추가
    id &quot;com.ewerk.gradle.plugins.querydsl&quot; version &quot;1.0.10&quot;
}

dependencies {
    ...
    //querydsl 추가
    implementation &#39;com.querydsl:querydsl-jpa&#39;
}

//querydsl 추가 시작
def querydslDir = &quot;$buildDir/generated/querydsl&quot;
querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}
//querydsl 추가 끝</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/f0975267-4bd4-463f-8518-4545ecfff6a3/image.png" alt=""></p>
<p>내 경우 에러가 뜸. 스프링 버전이랑 쿼리 dsl 버전이 안 맞아서 그런듯.
<img src="https://velog.velcdn.com/images/world_changer/post/c23b95f5-a2a6-482d-bccb-6ee1a5452444/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/444290fe-c2ca-4fd1-8cad-bf4974c85148/image.png" alt=""></p>
<p>이후 compileQueryDsl 다시 해주면, 이처럼 QHello 라는 파일이 생김!!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/f95b4ce5-49dc-411c-a332-070d1658808d/image.png" alt=""></p>
<p>이런것 gitignore 되어야 하는 빌드 파일임!! (query dsl 버전따라 달라질 수 있으니!)</p>
<h1 id="h2-데이터-베이스">h2 데이터 베이스</h1>
<p>첫 한번(생성시)은 파일 모드로 접근해야함.
<img src="https://velog.velcdn.com/images/world_changer/post/e47cd425-923d-4808-bda3-97d3bac7191f/image.png" alt="">
이후에 tcp 모드로 접근 ㄱㄱ
<img src="https://velog.velcdn.com/images/world_changer/post/27a4f732-48c4-40e4-9eea-97959422760e/image.png" alt=""></p>
<h1 id="앱-설정">앱 설정</h1>
<p><img src="https://velog.velcdn.com/images/world_changer/post/db28fa1a-4cdf-4681-9a7d-eb497e92c7ea/image.png" alt=""></p>
<p>yml 파일 따로 생성</p>
<pre><code>spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/querydsl
    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</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/96e859ed-133b-46df-a520-f2c0762a5208/image.png" alt=""></p>
<p>쿼리로 나가는 값을 알고 싶을때는 외부 라이브러리를 설치해주면 됨.
(실무에서는 성능을 꼭 확인하길 바람)</p>
<pre><code>    // query log 남기기
    implementation &#39;com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.8&#39;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 데이터 JPA - 3]]></title>
            <link>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-3</link>
            <guid>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-3</guid>
            <pubDate>Thu, 27 Apr 2023 02:47:26 GMT</pubDate>
            <description><![CDATA[<h1 id="spring-data-jpa-구현체-분석">Spring Data JPA 구현체 분석</h1>
<p><img src="https://velog.velcdn.com/images/world_changer/post/47456db1-8319-4cd2-8551-421bc43f95ed/image.png" alt=""></p>
<p>우리가 평소에 만드는 Repository 처럼,
<code>@Repository</code>, <code>@Transactional(readOnly=true)</code> 어노테이션을 가진다!
<img src="https://velog.velcdn.com/images/world_changer/post/2ef5d073-ff48-4661-bd6d-c3b711e91e54/image.png" alt=""></p>
<p>save 같은 친구들에겐 따로 <code>@Transactional</code> 을 달아주면서 처리!</p>
<h2 id="save-함수">save 함수</h2>
<p>새로운 엔티티면 저장 (persist)
새로운 엔티티가 아니면 병합 (merge) &lt;- 그냥 바꿔치기 해버리기!!
(잘 활용해야함!)</p>
<h3 id="새로운-엔티티-인지-어떻게-판단">새로운 엔티티 인지 어떻게 판단?</h3>
<p><code>@GeneratedValue</code> 를 사용한다면, id 값이 존재하지 않는 것으로 확인을 할 수 있으나</p>
<p>생성자로 id 를 받아오는 경우에는, id 유무로 isNew 를 판단해야하는데, DB 에 올라가있지도 않은 엔티티에 merge 가 일어남 -&gt; 사이드 이펙트 느낌 물씬</p>
<p>이런 방식으로해서 isNew를 덮어씌워주면 됨.</p>
<pre><code>@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable&lt;String&gt; {
    @Id
    private String id;

    @CreatedDate
    private LocalDateTime createdDate;

    public Item(String id){
        this.id = id;
    }
    @Override
    public String getId(){
        return id;
    }

    @Override
    public boolean isNew(){
        return createdDate == null;
    }
}</code></pre><h1 id="기타-쿼리-기능들">기타 쿼리 기능들</h1>
<h2 id="specifications">Specifications</h2>
<p>Jpa Criteria 를 활용해서 하는데, 김영한 개발자님 비추...</p>
<h2 id="query-by-example">Query By Example</h2>
<p>ExampleMatcher 와 Prove 로 Example 을 생성하여, 쿼리 조건 만들기!
(QueryDSL 로 해결 가능)</p>
<h2 id="projections">Projections</h2>
<p>Spring Data Jpa 를 사용해서 조회를 하는데, Entity 말고 특정 값들만 가지고 오고 싶을때!</p>
<ul>
<li>엔티티 대신에 DTO를 바로 조회할때<pre><code>public interface UsernameOnly {
  String getUsername();
}</code></pre>이렇게 만들어 두고,</li>
</ul>
<pre><code>&lt;MemberRepository.java&gt; - interface
    ...
    List&lt;Member&gt; findByUsername(@Param(&quot;username&quot;) String username);
    List&lt;UsernameOnly&gt; findProjectionsByUsername(@Param(&quot;username&quot;) String username);</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/0108f941-97a3-4903-b7ec-b7f146d732d6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/173370dc-5d69-4e92-9b8b-ef8e2e9c3af3/image.png" alt=""></p>
<p>분명 interface 인데, 알아서 proxy 객체로 생성을 해줌!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/ba9dcd37-2798-43ca-af93-10cfdd9ae79a/image.png" alt=""></p>
<p>이런식으로 지정해주는 것도 가능함!
<img src="https://velog.velcdn.com/images/world_changer/post/1d15e76c-46fd-4838-b588-1cdd48f343b5/image.png" alt=""></p>
<ul>
<li><p>class 기반의 Projections</p>
<pre><code>public class UsernameOnlyDto {
  private final String username;

  public UsernameOnlyDto(String username) {
      this.username = username;
  }

  public String getUsername() {
      return username;
  }
}</code></pre><p>생성자에서 받아오는 인자의 이름이 중요!! 이름 기반으로 엔티티 필드 찾아감</p>
<pre><code>&lt;MemberRepository.java&gt; interface 에 이와 같이 구현
List&lt;UsernameOnlyDto&gt; findClassProjectionsByUsername(@Param(&quot;username&quot;) String username);</code></pre><p>이래도 똑같이 작동함!! class 가 명시되어 있기에, proxy 객체가 아님
<img src="https://velog.velcdn.com/images/world_changer/post/232028ff-0c1e-48d3-9812-d951d06d4507/image.png" alt=""></p>
</li>
</ul>
<p>애초에 <code>findByUsername</code> 함수를 만들때, class type 을 지정하도록 할 수 있음</p>
<pre><code>&lt;T&gt; List&lt;T&gt; findCustomByUsername(@Param(&quot;username&quot;) String username, Class&lt;T&gt; type);</code></pre><p>사용 예시</p>
<pre><code>List&lt;UsernameOnlyDto&gt; result = memberRepository.findCustomByUsername(&quot;m1&quot;, UsernameOnlyDto.class);</code></pre><blockquote>
<p>덧) join 된 엔티티의 필드들도 같이 가져올 수 있음!!
실무에서는 단순할 때만 사용하고, 복잡해지면 QueryDSL을 사용하자!!</p>
</blockquote>
<h2 id="native-query">Native Query</h2>
<p>JPQL 문법이 아닌, 실제 SQL 문법으로 적어서 쓸 수 있도록 지원해줌!!
위에서 이야기한 join 된 복합 DTO를 가져오는 것이 좀더 편리하게 가능함!</p>
<pre><code>public interface MemberProjection {
    Long getId();
    String getUsername();
    String getTeamName();
}</code></pre><pre><code>    @Query(value = &quot;select m.member_id as id, m.username, t.name as teamName &quot; +
        &quot;from member m left join team t&quot;,
            countQuery = &quot;select count(*) from member&quot;,
            nativeQuery = true)
    Page&lt;MemberProjection&gt; findByNativeProjection(Pageable pageable);</code></pre><blockquote>
<p>역시나 가급적 안씀! JPQL, QueryDSL 로 왠만하면 다 해결하려고 함!!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 데이터 JPA - 2]]></title>
            <link>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-2</link>
            <guid>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-2</guid>
            <pubDate>Wed, 26 Apr 2023 03:43:35 GMT</pubDate>
            <description><![CDATA[<h1 id="custom-repository">Custom Repository</h1>
<ul>
<li>아얘 다른 형태로 쿼리를 작성하고 싶을때!! (주로 QueryDSL)</li>
<li>내가 따로 interface 구현체를 만들어서 모든 기능을 override 시켜야하나?</li>
</ul>
<p>원하는 기능을 가지는 interface 를 만들어, 구현체까지 만든다.</p>
<p>&lt;MemberRepositoryCustom.java&gt;</p>
<pre><code>public interface MemberRepositoryCustom {
    List&lt;Member&gt; findMemberCustom();
}</code></pre><p>&lt;MemberRepositoryImpl.java&gt;</p>
<pre><code>@RequiredArgsConstructor
public class MemberRepositoryImpl implements  MemberRepositoryCustom{

    private final EntityManager em;
    @Override
    public List&lt;Member&gt; findMemberCustom() {
        return em.createQuery(&quot;select m from Member m&quot;+
                &quot; left join fetch m.team t&quot;, Member.class)
                .getResultList();
    }
}</code></pre><p>&lt;MemberRepository.java&gt;</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt;, MemberRepositoryCustom {
    ...
}</code></pre><p>이런 식으로 JpaRespository 랑 같이 상속시켜주면 된다!!</p>
<blockquote>
<p>주의사항!!
<img src="https://velog.velcdn.com/images/world_changer/post/c7856b7a-7a13-453b-b3e1-aaa6957d2e4b/image.png" alt="">
구현체의 경우 이름을 꼭 맞춰줘야함! 그래야지 Spring Data JPA 가 추적 가능!
(repository이름)<strong><em>Impl</em></strong> 으로 구현체 이름을 지정해주어야함!
xml 파일에 뒤에 붙을 이름 지정해줄 수 있다!! (왠만하면 관례 따르자)</p>
<ul>
<li>그렇다고 해서! Custom 에 기능들 다 때려박으면 안된다! (항상 필요한게 아님)</li>
<li>유지보수 측면에서 Repository Class 를 나누어 관리하는 것이 좋을 때가 많음
ex) MemberQueryRepository, MemberUpdateRepository 등등
ex) Command 와 Query 구분, 핵심 비즈니스 로직과 아닌 것의 구분</li>
</ul>
</blockquote>
<h1 id="auditing">Auditing</h1>
<ul>
<li>엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶으면?</li>
<li>등록일/수정일, 등록자/수정자</li>
</ul>
<p>&lt;JpaBaseEntity.java&gt;</p>
<pre><code>public class JpaBaseEntity {

    @Column(updatable = false)
    private LocalDateTime createdDate;
    private LocalDateTime updatedDate;

    @PrePersist // persist 하기 전에 event 발생
    public void prePersist(){
        LocalDateTime now = LocalDateTime.now();
        createdDate = now;
        updatedDate = now;
    }

    @PreUpdate
    public void preUpdate(){
        updatedDate = LocalDateTime.now();
    }
}</code></pre><pre><code>public class Member extends JpaBaseEntity{
    ...
}</code></pre><p>상속을 했지만, 이게 JPA 에서 DB Table 과 연결한다는 뜻은 아님!!
그래서 <code>@MappedSuperclass</code> 을 추가해줘야함</p>
<pre><code>@MappedSuperclass
@Getter // 아래 테스트 코드를 위함
public class JpaBaseEntity {...}</code></pre><pre><code>    @Test
    public void JpaEventBaseEntity() throws Exception{
        // given
        Member member = new Member(&quot;member1&quot;, 10);
        memberRepository.save(member); // @PrePersist 발생

        Thread.sleep(1000);
        member.setUsername(&quot;member2&quot;);

        em.flush();
        em.clear();

        // when
        Member findMember = memberRepository.findById(member.getId()).get();

        // then
        System.out.println(&quot;findMember.getCreatedDate() = &quot; + findMember.getCreatedDate());
        System.out.println(&quot;findMember.getUpdatedDate() = &quot; + findMember.getUpdatedDate());
    }</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/46f188be-45bb-417c-adf8-300c723df613/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/1f02fa1e-35fb-4c66-a791-ca73f9b3720e/image.png" alt=""></p>
<p>공통 관심사인 <code>JpaBaseEntity</code> 이거를 모든 엔티티에 상속하기한 하면, 
Table 에서 관리 알아서 가능함!! 엄청나게 강력함!!</p>
<blockquote>
<p>그런데 이거를 Spring 데이터 JPA가 관리해줌!! ( 더 깔끔하게 )</p>
</blockquote>
<h2 id="spring-data-jpa-auditing">Spring Data JPA Auditing</h2>
<p><code>@EnableJpaAuditing</code> 을 꼭 넣어줘야함!! (Spring 시작 클래스)
<img src="https://velog.velcdn.com/images/world_changer/post/dd5705ca-5bf3-447d-bb10-cec83e24f9e4/image.png" alt=""></p>
<p>&lt;BaseEntity.java&gt;</p>
<pre><code>@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}</code></pre><p><code>@EntityListeners(AuditingEntityListener.class)</code> 가 추가적으로 필요함!</p>
<p>위의 &lt;JpaBaseEntity.java&gt; 와 비교하면! 좀 간결함 (별 차이 없긴 하네...)</p>
<pre><code>public class Member extends BaseEntity{...}</code></pre><p>이렇게 하고 실행하면,
<img src="https://velog.velcdn.com/images/world_changer/post/3e72cd99-706b-448b-8bfd-df8ebf11e958/image.png" alt=""></p>
<p>똑같이 Table 이 만들어지는 것을 확인 가능!! (이름이 좀 다르긴 함 last modified)</p>
<h3 id="생성자수정자">생성자/수정자</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/2b902999-bfab-40b7-b4ae-3148619c4e1a/image.png" alt=""></p>
<p>이런식으로 &lt;BaseEntity.java&gt; 에 추가하면, 우리가 원하는 대로 생성/수정자를 지정할 수 있게 된다.</p>
<blockquote>
<p>한가지 세팅이 필요함!!
<img src="https://velog.velcdn.com/images/world_changer/post/8ee10ebd-a7fa-427e-99e8-d9c920ee4d38/image.png" alt="">
이렇게 <code>@Bean</code> 에 <code>AuditorAware&lt;&gt;</code> 을 등록해줘야함.</p>
<ul>
<li>해당 함수는 <code>Create</code>, <code>Modify</code> 가 일어날때 계속 호출 됨</li>
<li>위 예시는 랜덤 값이지만, 실제로는 세션에서 회원정보를 불러오는 식으로!!</li>
</ul>
</blockquote>
<h3 id="덧">덧</h3>
<blockquote>
<p>각 BaseEntity 마다 따로 <code>@EntityListeners</code> 를 달아주지 않고, 전체 설정도 가능
<code>pom.xml</code> 파일에 지정.. (생략)</p>
</blockquote>
<blockquote>
<p>김영한 개발자님의 경우 어떻게 실무에서 쓰는가
&lt;BaseTimeEntity.java&gt; 를 만들고, &lt;BaseEntity.java&gt; 는 이를 상속하도록!!
특정 엔티티는 Time 만 필요하고, 생성/수정자 추적은 필요가 x</p>
</blockquote>
<h2 id="도메인-클래스-컨버터">도메인 클래스 컨버터</h2>
<pre><code>    @GetMapping(&quot;/members/{id}&quot;)
    public String findMember(@PathVariable(&quot;id&quot;) Long id){
        Member member = memberRepository.findById(id).get();
        return member.getUsername();
    }

    @GetMapping(&quot;/members2/{id}&quot;)
    public String findMember2(@PathVariable(&quot;id&quot;) Member member){
        return member.getUsername();
    }</code></pre><p>받아오는 값이 엔티티의 PK 값이면, 바로 Member로 받아올 수 있게 해줌!
<code>findMember2</code> 처럼!!</p>
<p>(조회용 엔티티 이므로, 영속성컨텍스트에서 관리 되지는 않음!)</p>
<h2 id="페이징과-정렬">페이징과 정렬</h2>
<p>findAll 함수는 pageable을 받을 수 있도록 Spring Data JPA 에서 제공!</p>
<pre><code>    @GetMapping(&quot;/members&quot;)
    public Page&lt;Member&gt; list(Pageable pageable){
        return memberRepository.findAll(pageable);
    }</code></pre><p>input 값으로 pageable을 받는건 어떤 의미?</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/ea5a7bad-b7ea-4496-815f-f34b23bf0aa1/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/1d1862de-c860-47cf-a829-022bd4de0542/image.png" alt=""></p>
<p>해당 데이터들과 함께, 21 ~ 40 번째 데이터를 content 로 반환한다.
(default 값이 20개임. 다양한 방식으로 바꿀 수 있음!)
(글로벌 설정)
<img src="https://velog.velcdn.com/images/world_changer/post/6dbd37e7-38e2-4809-b49a-6f370faf9086/image.png" alt="">
(로컬설정)
<img src="https://velog.velcdn.com/images/world_changer/post/ea20cef7-793a-4661-80e8-4ee25eac972d/image.png" alt=""></p>
<p>size 를 지정해줄 수도 있음!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/2076207f-e1f7-429d-8512-a86ccf26b234/image.png" alt=""></p>
<p>아래처럼 여러 파라미터를 전달할 수 있음.
pageable 에 자동으로 매칭되는 것들!!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/c12ea30c-a472-4a82-b791-c88a3b9770aa/image.png" alt=""></p>
<h3 id="entity-를-page-에-넣어서-하지-x">Entity 를 Page&lt;&gt; 에 넣어서 하지 x</h3>
<p>DTO 로 넘기자.
<img src="https://velog.velcdn.com/images/world_changer/post/b08c21c4-1042-4b98-bbe0-9838aaeacea1/image.png" alt=""></p>
<p>DTO Constructor를 Member 와 연결시켜 두면,</p>
<pre><code>public MemberDto{
    ...
     public MemberDto(Member member){
        this.id = member.getId();
        this.username = member.getUsername();
        this.teamName = member.getTeam().getName();
    }
}</code></pre><p>이런식으로 축약이 가능하다!</p>
<pre><code>    @GetMapping(&quot;/members&quot;)
    public Page&lt;MemberDto&gt; list(@PageableDefault(size = 5) Pageable pageable) {
        Page&lt;Member&gt; page = memberRepository.findAll(pageable);
//        Page&lt;MemberDto&gt; dtoPage = page.map(member -&gt; new MemberDto(member.getId(), member.getUsername(), &quot;&quot;));
        Page&lt;MemberDto&gt; dtoPage = page.map(MemberDto::new);
        return dtoPage;
    }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 데이터 JPA - 1]]></title>
            <link>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-1</link>
            <guid>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-1</guid>
            <pubDate>Wed, 19 Apr 2023 13:54:56 GMT</pubDate>
            <description><![CDATA[<p>JPA -&gt; 스프링 데이터 JPA 방식으로 진행!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/fc924202-0f31-465e-acc3-6b34d39547e5/image.png" alt=""></p>
<p>세팅을 원래는 해줬어야 하나, Spring 부트가 알아서 해줌!</p>
<h1 id="repository">Repository</h1>
<p>&lt;MemberJpaRepository.java&gt;</p>
<pre><code>public class MemberJpaRepository {
    private final EntityManager em;

    public Member save(Member member){
        em.persist(member);
        return member;
    }

    public Member find(Long id){
        return em.find(Member.class, id);
    }

    public void delete(Member member){
        em.remove(member);
    }

    public List&lt;Member&gt; findAll(){
        return em.createQuery(&quot;select m from Member m&quot;, Member.class).getResultList();
    }

    public Optional&lt;Member&gt; findById(Long id){
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    public long count(){
        return em.createQuery(&quot;select count(m) from Member m&quot;, Long.class).getSingleResult();
    }
}</code></pre><p>&lt;MemberJpaRepositoryTest.java&gt;</p>
<pre><code>@SpringBootTest
@Transactional
@Rollback(false)
class MemberJpaRepositoryTest {

    @Autowired MemberJpaRepository memberJpaRepository;

    @Test
    public void testMember() throws Exception {
        // given
        Member member = new Member(&quot;memberA&quot;, 10);
        Member saveMember = memberJpaRepository.save(member);

        // when
        Member findMember = memberJpaRepository.find(saveMember.getId());

        // then
        assertThat(findMember.getId()).isEqualTo(member.getId());
        assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        assertThat(findMember).isEqualTo(member);
    }

    @Test
    public void basicCRUD(){
        Member memberA = new Member(&quot;memberA&quot;, 10);
        Member memberB = new Member(&quot;memberB&quot;, 12);
        memberJpaRepository.save(memberA);
        memberJpaRepository.save(memberB);

        Member findMember1 = memberJpaRepository.findById(memberA.getId()).get();
        Member findMember2 = memberJpaRepository.findById(memberB.getId()).get();

        assertThat(findMember1).isEqualTo(memberA);
        assertThat(findMember2).isEqualTo(memberB);

        List&lt;Member&gt; all = memberJpaRepository.findAll();
        assertThat(all.size()).isEqualTo(2);

        long count = memberJpaRepository.count();
        assertThat(count).isEqualTo(2);

        memberJpaRepository.delete(memberA);
        memberJpaRepository.delete(memberB);

        long afterCount = memberJpaRepository.count();
        assertThat(afterCount).isEqualTo(0);
    }
}</code></pre><h2 id="interface-로-repository">interface 로 Repository</h2>
<p>&lt;MemberRepository.java&gt;</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
}</code></pre><p>Test 쪽 코드에서, <code>memberRepository</code> 를
MemberJpaRepository =&gt; MemberRepository 로 바꿔주면 똑같이 작동함!</p>
<blockquote>
<p>interface 만 있고, 구현한 적이 없는데 대체 어떻게?</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/d6ab91dc-b5b9-4c2e-a654-81356ee2c0b6/image.png" alt="">
Spring Data JPA가 로딩 시점에 알아서 프록시 객체로 구현을 해줌!
<img src="https://velog.velcdn.com/images/world_changer/post/86cc1b39-84b2-48fc-b4be-ddace1591524/image.png" alt=""></p>
<h2 id="teamrepository">TeamRepository</h2>
<p>&lt;TeamJpaRepository.java&gt;</p>
<pre><code>@Repository
@RequiredArgsConstructor
public class TeamJpaRepository {
    private final EntityManager em;

    public Team save(Team team){
        em.persist(team);
        return team;
    }

    public Team find(Long id){
        return em.find(Team.class, id);
    }

    public void delete(Team team){
        em.remove(team);
    }

    public List&lt;Team&gt; findAll(){
        return em.createQuery(&quot;select t from Team t&quot;, Team.class).getResultList();
    }

    public Optional&lt;Team&gt; findById(Long id){
        Team team = em.find(Team.class, id);
        return Optional.ofNullable(team);
    }

    public long count(){
        return em.createQuery(&quot;select count(t) from Team t&quot;, Long.class).getSingleResult();
    }
}</code></pre><p>&lt;TeamRepository.java&gt;</p>
<pre><code>public interface TeamRepository extends JpaRepository&lt;Team, Long&gt; {
}</code></pre><blockquote>
<p>Team 의 경우도 바꿔줄 수 있다!</p>
</blockquote>
<p><code>JpaRepository&lt;___, ___&gt;</code>
Type 이 중요하다! 그것에 따라 내부 동작 메소드들이 결정되니깐!</p>
<blockquote>
<p>기본적으로 사용할 것 같은 메소드들은 다 구현되어 있으니, CRUD 코드 노가다를 엄청 줄일 수 있음!! 최고!!</p>
</blockquote>
<p>save, delete, findById, getOne, findAll, paging, sort 등의 메소드를 제공해준다!!
getOne의 경우 getReference 프록시 객체로 받음!!</p>
<p>개발자로서 상상할 수 있는 공통기능은 전부다 제공해줌!!!</p>
<blockquote>
<p>공통기능 외에 다른 기능이 필요하다면!!</p>
</blockquote>
<p>ex. field 값에 따른 find
<code>List&lt;Member&gt; findByUsername(String username)</code></p>
<ul>
<li>이런걸 구현하려고 하면, 구현체에서 interface 모든 메소드를 override 해야하는데... 크흠...</li>
</ul>
<p>이걸 해결해주기 위해!! query method 를 제공해줌!</p>
<h2 id="query-method">Query Method</h2>
<ul>
<li>method 이름으로 Query 작성 가능!</li>
</ul>
<p>순수하게 했다고 치면</p>
<pre><code>   public List&lt;Member&gt; findByUsernameAndAgeGreaterThen(String username, int age){
        return em.createQuery(&quot;select m from Member m&quot; +
                        &quot; where m.username = :username&quot; +
                        &quot; and m.age &gt; :age&quot;, Member.class
                ).setParameter(&quot;username&quot;, username)
                .setParameter(&quot;age&quot;, age)
                .getResultList();
    }</code></pre><p>뭐 그닥 어렵지 않기는 한데, 이런거 좀 알아서 해주면 안되나?</p>
<p>정말 웃기게도, Spring Data JPA 가 알아서 메소드를 연결해준다.</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    public List&lt;Member&gt; findByUsernameAndAgeGreaterThan(String username, int age);
}</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/5788a1bf-4bd7-46e0-8d58-7ee06e65b339/image.png" alt="">
(IntelliJ 유료 버전에서는 이렇게 추천도 해줌!)
<img src="https://velog.velcdn.com/images/world_changer/post/a0c2f770-3d77-4eee-a736-21bf08456e68/image.png" alt=""></p>
<blockquote>
<p>메소드 이름으로 쿼리를 생성!!!
<a href="https://docs.spring.io/spring-data/jpa/docs/2.7.11/reference/html/#jpa.query-methods.query-creation">https://docs.spring.io/spring-data/jpa/docs/2.7.11/reference/html/#jpa.query-methods.query-creation</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/9a513fc7-90b9-4f97-8e9e-6ede04232ef8/image.png" alt=""></p>
<p>엔티티 필드명이 변경된다거나, 많은 양의 쿼리 조건이 들어간다면!!
아무래도 메소드 명도 길어지고, 뭔가 유지보수가 애매해진다!!
그래서 다른 방안들이 여러 개 존재한다!!</p>
<blockquote>
<p>김영한 개발자님은 한두개 정도 필드의 쿼리는 메소드 쿼리로 생성한다고 하심!
나머지는 다른 방법으로!!</p>
</blockquote>
<blockquote>
<p>덧. 오류의 순위</p>
</blockquote>
<ul>
<li>가장 좋은 거 : 컴파일 단에서</li>
<li>그 다음 : Tdd 돌려서 알 수 있는</li>
<li>그 다음 : 실행을 하면서 세팅단에서 알 수 있는</li>
<li>최악 : 고객이 버튼을 클릭해야 나오는</li>
</ul>
<h2 id="jpa-named-query">JPA Named Query</h2>
<pre><code>...
@NamedQuery(
        name=&quot;Member.findByUsername&quot;,
        query=&quot;select m from Member m where m.username = :username&quot;
)
public class Member {
...}</code></pre><pre><code>    public List&lt;Member&gt; findByUsername(String username) {
        return em.createNamedQuery(&quot;Member.findByUsername&quot;, Member.class).setParameter(&quot;username&quot;, &quot;aaa&quot;).getResultList();
    }</code></pre><p><code>createNamedQuery 를 활용해서 사용 !!</code></p>
<blockquote>
<p>이 방식을 Spring Data JPA 는 어떻게 도와준다는 걸까</p>
</blockquote>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    List&lt;Member&gt; findByUsernameAndAgeGreaterThan(String username, int age);

    @Query(name = &quot;Member.findByUsername&quot;)
    List&lt;Member&gt; findByUsername(@Param(&quot;username&quot;) String username);
}</code></pre><ul>
<li><code>@Query</code> 를 활용하여 매칭해주고, <code>@Param</code> 으로 매개변수 지정!!</li>
<li>메소드 이름이 같을 필요 x</li>
</ul>
<p><img src="https://velog.velcdn.com/images/world_changer/post/8398f006-0cbd-46e4-bc91-ab39702ae362/image.png" alt=""></p>
<p>웃긴건, <code>@Query</code> 어노테이션을 지워도 동작을 하게 해줌! (Spring boot)
이런 관례가 있다고함! 
메소드 쿼리를 생성할때,
JpaRepository 에서 Type으로 받은 <code>Member</code> 로 <code>Member.____</code> 이름의 NamedQuery  가 있는지 먼저 탐색!
없으면 메소드 쿼리로 생성!</p>
<ul>
<li>장점이 있음!!</li>
<li>보통 jpql 상에서 오류가 뜨면, 해당 쿼리를 실행할 때 오류가 발생함!!</li>
<li>그런데 NamedQuery 안에 있는 jpql 문은 어플리케이션 로딩 시점에 에러를 체크함!!~<blockquote>
<p>좋아 보이지만... 사실 실무에서 많이 사용되지는 않음!!
뭔가 엔티티에 쿼리가 들어가 있는게 좀 어색...? 위의 장점을 가진 다른 기능!!!</p>
</blockquote>
</li>
</ul>
<h2 id="repository에-query-직접-지정">Repository에 Query 직접 지정</h2>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    ...

    @Query(&quot;select m from Member m where m.username = :username and m.age = :age&quot;)
    List&lt;Member&gt; findUser(@Param(&quot;username&quot;) String username, @Param(&quot;age&quot;) int age);

    @Query(&quot;select m.username from Member m&quot;)
    List&lt;String&gt; findUsernameList();
}</code></pre><p>기본 CRUD 말고 필요한 쿼리들은 <code>@Query</code> 를 바로 적용하여 메소드 생성!
<img src="https://velog.velcdn.com/images/world_changer/post/036639e2-bf46-4024-ac7a-1b90fbd51db1/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/db465eca-454c-4058-bc69-1de5637e03ed/image.png" alt=""></p>
<p>쿼리문도 똑같이 들어가는 것을 알 수 있다!!
위의 <code>NamedQuery</code> 처럼 어플리케이션 로딩 시점에 에러를 체크함!!
미리 에러를 발견하고 넘길 수 있다!</p>
<h3 id="dto-직접-조회">DTO 직접 조회</h3>
<pre><code>    @Query(&quot;select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t&quot;)
    List&lt;MemberDto&gt; findMemberDto();</code></pre><p>test code</p>
<pre><code>    @Test
    public void findMemberDto() throws Exception {
        // given
        Member m1 = new Member(&quot;aaa&quot;, 10);
        Member m2 = new Member(&quot;bbb&quot;, 20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        Team team = new Team(&quot;teamA&quot;);
        teamRepository.save(team);

        m1.changeTeam(team);

        em.flush();
        em.clear();

        // when
        List&lt;MemberDto&gt; memberDtoList = memberRepository.findMemberDto();

        // then
        assertThat(memberDtoList.get(0).getTeamName()).isEqualTo(&quot;teamA&quot;);
        assertThat(memberDtoList.get(0).getUsername()).isEqualTo(&quot;aaa&quot;);
        assertThat(memberDtoList.size()).isEqualTo(1);
    }</code></pre><blockquote>
<p>동적 Query 의 경우는? QueryDSL 로 해야한다!!</p>
</blockquote>
<h2 id="파라미터-바인딩">파라미터 바인딩</h2>
<ul>
<li>위치기반 (사용하지 말것)</li>
<li>이름기반</li>
</ul>
<h3 id="컬렉션-파라미터-바인딩">컬렉션 파라미터 바인딩</h3>
<pre><code>    @Query(&quot;select m from Member m where m.username in :names&quot;)
    List&lt;Member&gt; findByNames(@Param(&quot;names&quot;) List&lt;String&gt; names);</code></pre><h2 id="반환타입">반환타입</h2>
<blockquote>
<p><code>컬렉션</code>, <code>단건</code>, <code>단건 Optional</code></p>
</blockquote>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    ...
    List&lt;Member&gt; findListByUsername(String username);
    Member findMemberByUsername(String username);
    Optional&lt;Member&gt; findOptionalByUsername(String username);
}</code></pre><p><code>find...ByUsername</code>  형태의 모든 naming이 가능함!
위 형태는 반환값만 다르게 지정한 것임!</p>
<p>JPA 의 경우 SingleResult 메소드를 사용하면, 값이 없거나 2개 이상일때는 Exception 을 띄움
Spring Data JPA 의 경우 findMemberByUsername 의 경우</p>
<ul>
<li>없으면 null</li>
<li>2개 이상일때는 Exception</li>
</ul>
<pre><code>    @Test
    public void returnType() throws Exception {
        // given
        Member m1 = new Member(&quot;aaa&quot;, 10);
        Member m2 = new Member(&quot;aaa&quot;, 20);
        memberRepository.save(m1);
        memberRepository.save(m2);

        em.flush();
        em.clear();

        // when

        List&lt;Member&gt; memberList = memberRepository.findListByUsername(&quot;aaa&quot;);
        Member oneMember = memberRepository.findMemberByUsername(&quot;bbb&quot;);
        Optional&lt;Member&gt; optionalMember = memberRepository.findOptionalByUsername(&quot;bbb&quot;);

        // then

        assertThat(memberList.size()).isEqualTo(2);
        assertThat(oneMember).isEqualTo(null);
        assertThat(optionalMember.isPresent()).isEqualTo(false);
    }</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/10b828e0-7158-4af7-9128-606d4ebe74cf/image.png" alt=""></p>
<p>다양한 반환 타입을 지원한다!</p>
<h2 id="순수-jpa-페이징-정렬">순수 Jpa 페이징, 정렬</h2>
<pre><code>    public List&lt;Member&gt; findByPage(int age, int offset, int limit) {
        return em.createQuery(&quot;select m from Member m where m.age = :age order by m.username desc&quot;, Member.class)
                .setParameter(&quot;age&quot;, age)
                .setFirstResult(offset)
                .setMaxResults(limit)
                .getResultList();
    }

    public long totalCount(int age) {
        return em.createQuery(&quot;select count(m) from Member m where m.age = :age&quot;, Long.class)
                .setParameter(&quot;age&quot;, age)
                .getSingleResult();
    }</code></pre><p>위와 같이 </p>
<ul>
<li>offset, limit 를 받아서 특정 요소들만 가져오고</li>
<li>전체 페이지를 알기위해 totalCount 를 불러온다 (이때 sorting은 필요 없음)</li>
</ul>
<h2 id="스프링-데이터-페이징-정렬">스프링 데이터 페이징, 정렬</h2>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
   ...
   Page&lt;Member&gt; findByAge(int age, Pageable pageable);
}</code></pre><p>반환타입을 <code>Page</code> 로 지정해주고!
input 값으로 <code>Pageable</code> 을 받는다!!</p>
<blockquote>
<p>주의 사항 : 기존에 offset, limit 를 넣었던 과는 다르게
pageNum, pageSize 를 넣는 것임!!</p>
</blockquote>
<p>find, sorting, paging 쿼리를 날리고, 추가적으로 count 쿼리도 알아서 날림!
Page에 맞게 쿼리를 날려줌!!
test code</p>
<pre><code>    @Test
    public void pagingTest() throws Exception {
        // given
        Member m1 = new Member(&quot;aaa&quot;, 10);
        Member m2 = new Member(&quot;bbb&quot;, 20);
        Member m3 = new Member(&quot;ccc&quot;, 10);
        Member m4 = new Member(&quot;ddd&quot;, 10);
        Member m5 = new Member(&quot;eee&quot;, 10);
        Member m6 = new Member(&quot;fff&quot;, 10);
        Member m7 = new Member(&quot;ggg&quot;, 10);
        Member m8 = new Member(&quot;hhh&quot;, 10);

        memberRepository.save(m1);
        ...
        memberRepository.save(m8);

        int age = 10;
        int pageOffset = 0;
        int onePageContent = 3;
        PageRequest pageRequest = PageRequest.of(
                pageOffset,
                onePageContent,
                Sort.by(Sort.Direction.DESC, &quot;username&quot;));

        // when
        Page&lt;Member&gt; memberPage = memberRepository.findByAge(age, pageRequest);
        for (Member member : memberPage.getContent()) {
            System.out.println(&quot;member = &quot; + member);
        }

        // then
        assertThat(memberPage.getContent().get(0).getUsername()).isEqualTo(&quot;hhh&quot;);
        assertThat(memberPage.getContent().get(1).getUsername()).isEqualTo(&quot;ggg&quot;);
        assertThat(memberPage.getContent().get(2).getUsername()).isEqualTo(&quot;fff&quot;);
        assertThat(memberPage.getTotalElements()).isEqualTo(7);
        assertThat(memberPage.getNumber()).isEqualTo(pageOffset);
        assertThat(memberPage.getTotalPages()).isEqualTo(3);
        assertThat(memberPage.isFirst()).isTrue();
        assertThat(memberPage.hasNext()).isTrue();
    }</code></pre><p>Page 가 가지고 있는 기능들 중에, totalPage 나 totalElements 이런 것들은 좀 필요 없는데 좀 더 가벼운 같은 기능은 없나? (count 쿼리를 매번 호출하는게 약간 불만일때)</p>
<blockquote>
<p><code>Page</code> 가 상속받고 있는 <code>Slice</code> 를 활용하면 됨!</p>
</blockquote>
<h3 id="page---slice">Page -&gt; Slice</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/99455a67-1172-43ed-bb50-a4affcab440d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/d646e4c4-a8d3-4126-939e-027861ddc710/image.png" alt=""></p>
<ul>
<li>Page 경우
<img src="https://velog.velcdn.com/images/world_changer/post/1bf40d87-23ff-4956-bf16-bf2d9047e58c/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/4eee72fa-6ca7-4b3f-9858-c43da28cc289/image.png" alt=""></li>
</ul>
<ul>
<li>Slice 경우
<img src="https://velog.velcdn.com/images/world_changer/post/afe3e71a-159a-4566-acf9-3d4687dd898e/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/d9aa46c3-4eae-4ec3-a86e-14d3ea8c31f9/image.png" alt=""></li>
</ul>
<blockquote>
<p>Slice 의 경우 limit 보다 하나 더 들고와서, <code>hasNext</code> 에 대한 대응을 한다!!</p>
<ul>
<li>Page 의 경우 3개를 불러오고, Slice 는 4개를 불러온다</li>
</ul>
</blockquote>
<h3 id="slice---list">Slice -&gt; List</h3>
<ul>
<li>근데 Slice 까지도 필요 없어. 그냥 데이터 몇개 끊어서 가져와!! 하는 경우에는 이것도 가능
<img src="https://velog.velcdn.com/images/world_changer/post/e37bdc28-7934-4518-b449-f6869ee33e67/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/world_changer/post/1e2c9bff-5f92-4a7e-a869-f42f2f9c85d9/image.png" alt=""></p>
<p>기존의 여러 메소드들을 전혀 사용할 수 없음!!</p>
<h3 id="덧">덧</h3>
<p>total count 의 경우 복잡한 연관관계에 맞춰 성능이 저하될 수 있음.(join 등)
사실 테이블의 한 필드값만 접근해서 개수만 세어도 되는 건데!!
해당 쿼리를 따로 간단하게 제한하는 방법이 존재!!</p>
<p><code>Team</code> 값을 함께 가져오는 join query 를 만들었다고 하자.</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/c741ebd9-0f05-4804-b93d-a826d313b1d8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/8272cae0-b50c-4407-9687-7b64590a13a8/image.png" alt=""></p>
<p>잘 보면, count를 하는데도 left join 이 들어간다!
counter query 가 table join 을 할 필요는 없음!</p>
<p>그래서, 이런식으로 추가해서 해줄 수 있다!
<img src="https://velog.velcdn.com/images/world_changer/post/6f511f21-e8f3-435c-8357-adc708de6555/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/e7c5b9d6-7938-4ff2-8943-31c08131d557/image.png" alt=""></p>
<p>countQuery 항목에 새로 넣어서, count에 복잡한 쿼리가 나가지 않도록 할 수 있음!!</p>
<h3 id="덧1">덧1</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/3e877156-811b-40dc-ad12-881dfd81ec6d/image.png" alt="">
외부 API 로 반환할때는 이런식으로 mapping 해서 DTO 로 보낼 수 있도록!</p>
<h2 id="벌크성-수정-쿼리">벌크성 수정 쿼리</h2>
<h3 id="순수-jpa">순수 jpa</h3>
<pre><code>    public int bulkAgePlus(int age) {
        return em.createQuery(&quot;update Member m&quot; +
                        &quot; set m.age = m.age + 1&quot; +
                        &quot; where m.age &gt;= :age&quot;)
                .setParameter(&quot;age&quot;, age).executeUpdate();
    }</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/c71bfeee-ca62-4ea7-a0e1-619331975ac3/image.png" alt="">
이런 쿼리를 날림!</p>
<h3 id="스프링-데이터-jpa">스프링 데이터 JPA</h3>
<pre><code>    @Modifying // 이게 있어야 executeUpdate() 를 실행함! 필수임!
    @Query(&quot;update Member m set m.age = m.age + 1 where m.age &gt;= :age&quot;)
    int bulkAgePlus(@Param(&quot;age&quot;) int age);</code></pre><ul>
<li><code>@Modifying</code> 필수!!</li>
</ul>
<p>테스트 코드</p>
<pre><code>    @Test
    public void bulkUpdate() throws Exception {
        // given
        Member m1 = new Member(&quot;aaa&quot;, 10);
        Member m2 = new Member(&quot;bbb&quot;, 20);
        Member m3 = new Member(&quot;ccc&quot;, 10);
        Member m4 = new Member(&quot;ddd&quot;, 10);
        Member m5 = new Member(&quot;eee&quot;, 20);
        Member m6 = new Member(&quot;fff&quot;, 10);
        Member m7 = new Member(&quot;ggg&quot;, 10);
        Member m8 = new Member(&quot;hhh&quot;, 20);

        memberRepository.save(m1);
        ...
        memberRepository.save(m8);

        // when
        int resultCount = memberRepository.bulkAgePlus(15);
        System.out.println(&quot;resultCount : &quot; + resultCount);

        // then
        assertThat(resultCount).isEqualTo(3);
    }</code></pre><ul>
<li><p>주의해야할 사항이 있음!</p>
</li>
<li><p>update 벌크 연산의 경우, 영속성 컨텍스트를 거치지 않고 진행됨!</p>
<pre><code>      ...
      Member m = new Member(&quot;hhh&quot;, 20);
      ...
      memberRepository.save(m);

      // when
      int resultCount = memberRepository.bulkAgePlus(15);
      System.out.println(&quot;resultCount : &quot; + resultCount);

      Member memberH1 = memberRepository.findMemberByUsername(&quot;hhh&quot;);
      System.out.println(&quot;memberH1 : &quot; + memberH1); // Member(id=8, username=hhh, age=20)

      em.clear();

      Member memberH2 = memberRepository.findMemberByUsername(&quot;hhh&quot;);
      System.out.println(&quot;memberH2 : &quot; + memberH2); // Member(id=8, username=hhh, age=21)</code></pre><blockquote>
<p>영속성 컨텍스트를 거치치 않고 DB 업데이트 쿼리를 날리기 때문에, 
업데이트 된 값을 사용하고 싶으면, clear 를 해주어야함</p>
<ul>
<li>가능하면 벌크연산 후에 EntityManager 를 flush, clear 해버려라!!
업데이트 함수에서 먼저 flush 를 실행함!!</li>
<li>이걸 생략하고 싶으면, <code>@Modifying(clearAutomatically = true)</code> 추가</li>
</ul>
</blockquote>
<pre><code>  @Modifying(clearAutomatically = true)
  @Query(&quot;update Member m set m.age = m.age + 1 where m.age &gt;= :age&quot;)
  int bulkAgePlus(@Param(&quot;age&quot;) int age);</code></pre><p><code>// em.clear();</code></p>
</li>
</ul>
<h2 id="entitygraph">@EntityGraph</h2>
<p>평범하게 findAll 쿼리메소드를 활용하면, lazy 같은 것들은 어떻게 해결 할까?
여기서 fetch 조인은 어떻게 해야할까!!</p>
<ul>
<li>무조건 <code>@xToOne</code> 관계에 있는 요소들을 N+1 문제가 있다고, fetch 조인 하는 것은 아님</li>
<li>상황에 따라 fetch 조인 하지 않는 경우도 있음!!</li>
</ul>
<pre><code>    @Query(&quot;select m from Member m left join fetch m.team&quot;)
    List&lt;Member&gt; findMemberFetchJoin();</code></pre><p>Query 어노테이션을 활용해서, 이와 같이 하면 된다!</p>
<p>그런데 이것마저 귀찮다!!!!!
<code>@EntityGraph</code> 로 해결!!!</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    ...
    @Override
    @EntityGraph(attributePaths = {&quot;team&quot;})
    List&lt;Member&gt; findAll();
}</code></pre><p>findAll 자체에 fetch join 을 <code>@Override</code> 하면서, <code>@EntityGraph</code> 설정!!</p>
<pre><code>    @Query(&quot;select m from Member m left join m.team&quot;)
    @EntityGraph(attributePaths = {&quot;team&quot;})
    List&lt;Member&gt; findMemberEntityGraph();</code></pre><p>이렇게 쓰는 것도 가능하다!!</p>
<p>쿼리 메소드의 경우도 <code>@EntityGraph</code> 를 추가하면서, fetch join 기능이 가능하도록 해줌!!</p>
<h2 id="jpa-hint--lock">JPA Hint &amp; Lock</h2>
<ul>
<li>find 하고 나서, 영속성 컨텍스트 관리 안해도 됨!!</li>
<li>수정할 일도 없고, 삭제할 일도 없어!! 그 데이터 그대로 사용만 할거야!</li>
<li>변경감지 이런거 필요 없어!!
(기존)
<img src="https://velog.velcdn.com/images/world_changer/post/8ed64bc1-5689-4b18-8bed-89e2a31aa184/image.png" alt=""></li>
</ul>
<p>Transaction 이 끝나는 (flush 가 일어남) 시점에 update 쿼리가 실행됨.</p>
<p>하지만, <code>@QueryHints</code> 와 <code>@QueryHint</code> 를 통해 read only 라는 설정을 주게 되면</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    ...
    @QueryHints(value = @QueryHint(name = &quot;org.hibernate.readOnly&quot;, value = &quot;true&quot;))
    Member findReadOnlyByUsername(String username);
}</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/1fbbcd40-d435-4fc7-8078-995830b6f573/image.png" alt=""></p>
<p>분명 setUsername을 하여, findMember 에 변경이 일어났는데 이를 체크하지 않고 업데이트 쿼리를 실행하지 않음!!
(각 엔티티마다 스냅샷을 지정해주고, 변경감지 등의 리소스를 사용하는 행위들을 하지 않도록 하여 성능을 향상 시킨다!!)</p>
<blockquote>
<p>우리 서버 트래픽이 너무너무 많아서 최적화 시키는게 아닌 이상, 
이 Read-only 기능은 다른 최적화들에 비해 미미하다!!
페치 조인, 페이징 등등이 훨씬 더 중요!</p>
</blockquote>
<blockquote>
<p>Lock 이라는 기능도 있는데, 동시 접근하여 수정을 하게 되면 안되므로, lock을 걸어 권한 있는 쪽만 수정하도록 하는 것!!
실무에서 꽤나 중요해 보임!!
<a href="https://velog.io/@backtony/JPA-Lock">https://velog.io/@backtony/JPA-Lock</a>
꼭 따로 공부해야함!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 데이터 JPA - 세팅]]></title>
            <link>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</link>
            <guid>https://velog.io/@world_changer/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA</guid>
            <pubDate>Mon, 17 Apr 2023 12:16:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>한계를 넘어 마법 같은 기술</p>
</blockquote>
<h1 id="세팅">세팅</h1>
<p>스프링 부트 스타터 : <a href="https://start.spring.io">https://start.spring.io</a></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/08f8dc68-c40c-4c2c-b34c-7dff715bfd0d/image.png" alt=""></p>
<blockquote>
<p>왜인지 잘 안됨. 그냥 이거 복붙해서 gradle 돌리기!!</p>
</blockquote>
<p>&lt;build.gradle&gt;</p>
<pre><code>plugins {
    id &#39;java&#39;
    id &#39;org.springframework.boot&#39; version &#39;2.7.10&#39;
    id &#39;io.spring.dependency-management&#39; version &#39;1.0.15.RELEASE&#39;
}

group = &#39;study&#39;
version = &#39;0.0.1-SNAPSHOT&#39;
sourceCompatibility = &#39;11&#39;

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    compileOnly &#39;org.projectlombok:lombok&#39;
    runtimeOnly &#39;com.h2database:h2&#39;
    annotationProcessor &#39;org.projectlombok:lombok&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
}

tasks.named(&#39;test&#39;) {
    useJUnitPlatform()
}</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/42c5de2a-bddb-4fae-8e15-c23448b59a26/image.png" alt=""></p>
<blockquote>
<p>Settings &gt; Build, Execution, Deployment &gt; Build Tools &gt; Gradle &gt; ... InteilliJ IDEA 로 변경</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/2f54b2f7-00f2-4a4f-b9ee-cad08cdf86d7/image.png" alt=""></p>
<blockquote>
<p>Settings &gt; Build, Execution, Deployment &gt; Compiler &gt; Annotaion Processors &gt; Enable annotation processing 체크!! (lombok 사용)</p>
</blockquote>
<blockquote>
<p>H2 세팅
첫 JDBC URL : <code>jdbc:h2:~/datajpa</code>
이후에는 : <code>jdbc:h2:tcp://localhost/~/datajpa</code></p>
</blockquote>
<blockquote>
<p>Application.yml 세팅
<img src="https://velog.velcdn.com/images/world_changer/post/6a31527d-cb50-40d3-a2c4-1212d22c0fdd/image.png" alt="">
처음엔 application.properties 가 있음 -&gt; application.yml 만들고 지워주기!</p>
</blockquote>
<pre><code>spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/datajpa
    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</code></pre><h1 id="jpa-연결-설정-db-동작-확인">JPA 연결 설정, DB 동작 확인</h1>
<p>&lt;Member.java&gt;</p>
<pre><code>@Entity
@Getter @Setter
public class Member {
    @Id
    @GeneratedValue
    private Long id;

    private String username;

    protected Member(){}

    public Member(String username) {
        this.username = username;
    }
}</code></pre><p><code>protected Member(){}</code> JPA 에서 엔티티 생성할때, 프록시로 구현할 때도 있어서, protected 레벨까지는 빈 Constructor를 열어주어야함!!
덧. <code>@NoArgsConstructor(access = AccessLevel.PROTECTED)</code> 로 대체 가능</p>
<p>&lt;MemberJpaRepository.java&gt;</p>
<pre><code>@Repository
@RequiredArgsConstructor
public class MemberJpaRepository {
    private final EntityManager em;

    public Member save(Member member){
        em.persist(member);
        return member;
    }

    public Member find(Long id){
        return em.find(Member.class, id);
    }
}</code></pre><p>test code</p>
<pre><code>class MemberJpaRepositoryTest {

    @Autowired MemberJpaRepository memberJpaRepository;
    @Test
    public void testMember() throws Exception {
        // given
        Member member = new Member(&quot;memberA&quot;);
        Member saveMember = memberJpaRepository.save(member);

        // when
        Member findMember = memberJpaRepository.find(saveMember.getId());
        // then
        assertThat(findMember.getId()).isEqualTo(member.getId());
        assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        assertThat(findMember).isEqualTo(member);
    }
}</code></pre><p>&lt;MemberRespository.java&gt;</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    // 이걸로 끝, interface 라는 점!!
}</code></pre><p>test code</p>
<pre><code>class MemberRepositoryTest {
    @Autowired MemberRepository memberRepository;

    @Test
    public void testMember() throws Exception {
        // given
        Member member = new Member(&quot;memberA&quot;);
        Member saveMember = memberRepository.save(member);

        // when
        Member findMember = memberRepository.findById(saveMember.getId()).get();
        // then
        assertThat(findMember.getId()).isEqualTo(member.getId());
        assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        assertThat(findMember).isEqualTo(member);
    }
}</code></pre><p>memberRepository 내부에 메소드가 엄청 많다!! (save, findById 등등)
아무것도 해준게 없는데!!!</p>
<blockquote>
<p><code>ctrl + p</code> 자료형, 파라미터 알려주는!!
get() 을 쓸때 예외처리 해주는게 맞음. 지금은 그냥 편의상 하는 것!!</p>
</blockquote>
<p>query 파라미터를 로그로 남기는 외부 라이브러리가 있음 (운영 시스템에서는 꼭 성능 테스트 ㄱ)
<code>implementation &#39;com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.7&#39;</code>
<img src="https://velog.velcdn.com/images/world_changer/post/dc4e259e-e6fe-4219-ac35-860c8f58b39b/image.png" alt=""></p>
<blockquote>
<p>스프링부트 3.0 이상 버전부터는 추가 세팅이 필요함!!
<img src="https://velog.velcdn.com/images/world_changer/post/012549b0-bc2b-4dbd-8d2a-0ffd865cbf7b/image.png" alt=""></p>
</blockquote>
<h1 id="도메인-모델">도메인 모델</h1>
<p>&lt;Member.java&gt;</p>
<pre><code>@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {&quot;id&quot;, &quot;username&quot;, &quot;age&quot;})
public class Member {
    @Id
    @GeneratedValue
    @Column(name=&quot;member_id&quot;)
    private Long id;

    private String username;

    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;team_id&quot;)
    private Team team;

    public Member(String username) {
        this.username = username;
    }
}</code></pre><p>덧. JPA 에서 모든 <code>@xToOne</code> 관계는 <code>FetchType.LAZY</code> 로 설정해줘라!</p>
<p>&lt;Team.java&gt;</p>
<pre><code>@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of={&quot;id&quot;, &quot;name&quot;})
public class Team {
    @Id @GeneratedValue
    @Column(name = &quot;team_id&quot;)
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;team&quot;)
    private List&lt;Member&gt; member = new ArrayList&lt;Member&gt;();
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[jpa-spring boot - api OSIV]]></title>
            <link>https://velog.io/@world_changer/jpa-spring-boot-api-OSIV</link>
            <guid>https://velog.io/@world_changer/jpa-spring-boot-api-OSIV</guid>
            <pubDate>Mon, 17 Apr 2023 05:48:22 GMT</pubDate>
            <description><![CDATA[<h1 id="osiv-와-성능-최적화">OSIV 와 성능 최적화</h1>
<ul>
<li>Open Session In View : 하이버네이트</li>
<li>Open EntityManger in View : JPA
(관례상 OSIV 라 함)</li>
</ul>
<h2 id="open-in-view---true">open in view - true</h2>
<p><code>spring.jpa.open-in-view</code> true 가 기본값</p>
<p> <img src="https://velog.velcdn.com/images/world_changer/post/92885dff-70e5-4cce-842c-747629f91735/image.png" alt=""></p>
<p>true 일때는, transaction 이 끝나더라도 영속성 컨텍스트를 유지하고 있음. (데이터 베이스 커넥션을 유지)
화면이 렌더링 될때까지 혹은, Api 값이 반환 될때까지!</p>
<p>그래야, transaction 사용하는 서비스 계층 외부에서 사용되는 것들에서 활용이 가능하기 때문에!!</p>
<p>Lazy 로딩 을 할 수 있는 등, 데이터베이스 커넥션을 유지해준다!!</p>
<p>그런데 이 전략은... 오랜시간동안 데이터베이스 커넥션 리소스를 사용하기 때문에, 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있음!! -&gt; 이는 장애로 이어질 수 있다.
만약 api Controller 에서 외부 API 를 반환하게 되면, 그 시간 만큼 커넥션 리소스를 붙잡고 있게 되고, 이는 리소스의 부족을 야기!!</p>
<h2 id="open-in-view---false">open in view - false</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/cf48a741-6778-4bf4-ac8c-135aecd0ad3d/image.png" alt=""></p>
<p>데이터 베이스 커넥션 리소스를 낭비하지 않음!
하지만 lazy 로딩 같은 기능들은 트랜잭션 내 에서 해결이 되어야함!
트랜잭션 외부에서 쉽게 사용하던 것들을 트랜잭션 내부로 옮겨야함</p>
<p>기존에 lazy 로딩으로 잘 되던것들이, proxy 객체로 넘어오게 됨! (에러)</p>
<h1 id="open-in-view---false-기법">open in view - false 기법</h1>
<h2 id="orderqueryservice-만들기">OrderQueryService 만들기</h2>
<p>이 서비스 계층에서 모든 걸 처리한 뒤에,
Controller 로 내보내기!!
<img src="https://velog.velcdn.com/images/world_changer/post/2d0a2495-08de-4e3d-b8b1-d4cc77680a58/image.png" alt=""></p>
<p>이런식으로 따로 만들어서, 최종적으로 데이터가 다 담겨 있는 데이터를 넘겨주기!!</p>
<blockquote>
<p>커맨드와 쿼리를 구분하는 것이 좋다</p>
<ul>
<li>커맨드 : C, U, D</li>
<li>쿼리 : Read (조회)</li>
</ul>
</blockquote>
<blockquote>
<p>보통의 성능 문제는 쿼리에서 일어남!!
이 둘을 분리하는 것은 유지보수 관점에서 충분히 의미 있음.</p>
</blockquote>
<ul>
<li>OrderService: 핵심 비즈니스 로직</li>
<li>OrderQueryService: 화면이나 API에 맞춘 서비스 (주로 읽기 전용 트랜잭션 사용)</li>
</ul>
<blockquote>
<p>고객 서비스의 실시간 API 는 OSIV 를 끄고
ADMIN 사이트 처럼 커넥션을 많이 사용하지 않는 곳에서는 OSIV 를 켠다.</p>
</blockquote>
<h1 id="스프링-데이터-jpa">스프링 데이터 JPA</h1>
<p>기본적으로 중복되는 메소드들을 줄이기 위해, 시작한 프로젝트!!</p>
<p>기존 &lt;MemberRepository.java&gt;</p>
<pre><code>@Repository
@RequiredArgsConstructor
public class MemberRepository {

    private final EntityManager em;

    public void save(Member member){
        em.persist(member);
    }
    public Member findOne(Long id){
        return em.find(Member.class, id);
    }

    public List&lt;Member&gt; findAll(){
        return em.createQuery(&quot;select m from Member m&quot;, Member.class).getResultList();
    }

    public List&lt;Member&gt; findByName(String name){
        return em.createQuery(&quot;select m from Member m where m.name= :name&quot;, Member.class)
                .setParameter(&quot;name&quot;, name)
                .getResultList();
    }

}</code></pre><p>이후 &lt;MemberRepository.java&gt;</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; {
    List&lt;Member&gt; findByName(String name);
}</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/4651214c-2e80-4e3c-98fa-5a2626e0cdd6/image.png" alt=""></p>
<p>Repository 들이 기본적으로 제공할 것 같은 것들 메소드로 싹 저장 되어 있음!!
심지어 <code>findByName</code> 도 메소드 이름을 보고, 알아서 완성된다!
=&gt; <code>select m from Member m where m.name = :name</code> 으로 jpql 쿼리</p>
<p>다른 계층에서 사용하는 <code>findOne()</code> 은 <code>findById()</code> 로 바꾸어 주면 됨!</p>
<pre><code>// 이전
return memberRepository.findOne(memberId);
// 이후
return memberRepository.findById(memberId).get();</code></pre><p>interface 로 해놔도, spring에서 알아서 주입을 해줌!!</p>
<h1 id="querydsl">QueryDSL</h1>
<p>jpql 쿼리를 java 문법으로 바꿈!</p>
<h2 id="세팅">세팅</h2>
<h3 id="buildgradle">build.gradle</h3>
<p>QueryDSL 을 사용하려면 build.gradle 에 dependencies 추가해줘야함</p>
<pre><code>//querydsl 추가
buildscript {
    ext {
        queryDslVersion = &quot;5.0.0&quot;
    }
}

plugins {
    id &#39;java&#39;
    id &#39;org.springframework.boot&#39; version &#39;2.7.10&#39;
    id &#39;io.spring.dependency-management&#39; version &#39;1.0.15.RELEASE&#39;

    //querydsl 추가
    id &#39;com.ewerk.gradle.plugins.querydsl&#39; version &#39;1.0.10&#39;
}


group = &#39;jpabook&#39;
version = &#39;0.0.1-SNAPSHOT&#39;
sourceCompatibility = &#39;11&#39;

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-validation&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-thymeleaf&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    implementation &#39;org.springframework.boot:spring-boot-devtools&#39;
    implementation &#39;com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.6&#39;
    implementation &#39;com.fasterxml.jackson.datatype:jackson-datatype-hibernate5&#39;
    testImplementation &#39;junit:junit:4.13.1&#39;
    compileOnly &#39;org.projectlombok:lombok&#39;
    runtimeOnly &#39;com.h2database:h2&#39;
    annotationProcessor &#39;org.projectlombok:lombok&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;

    //querydsl 추가
    implementation &quot;com.querydsl:querydsl-jpa:${queryDslVersion}&quot;
    implementation &quot;com.querydsl:querydsl-apt:${queryDslVersion}&quot;
}

tasks.named(&#39;test&#39;) {
    useJUnitPlatform()
}
//querydsl 추가
def querydslDir = &#39;src/main/generated&#39;
//def querydslDir = &quot;$buildDir/generated/querydsl&quot;

querydsl {
    library = &quot;com.querydsl:querydsl-apt&quot;
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main {
        java {
            srcDirs = [&#39;src/main/java&#39;, querydslDir]
        }
    }
}
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}
configurations {
    querydsl.extendsFrom compileClasspath
}</code></pre><h3 id="compilequerydsl">compileQuerydsl</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/b0c09ed0-e40d-4c39-a490-f85bedf32da5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/b6dea920-599c-46ae-83ea-16ed128653ab/image.png" alt=""></p>
<h2 id="사용">사용</h2>
<pre><code>    public List&lt;Order&gt; findAllByQueryDSL(OrderSearch orderSearch){
        QOrder order = QOrder.order;
        QMember member = QMember.member;

        JPAQueryFactory query = new JPAQueryFactory(em);

        return query.select(order)
                .from(order)
                .join(order.member, member)
                .limit(1000)
                .fetch();
    }</code></pre><p>메소드 체인으로 쉽게 구현 가능!
문자열 에러 걱정할 필요도 없음!!</p>
<h3 id="조건을-걸고-싶을땐">조건을 걸고 싶을땐</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/6fb65ca3-b363-4eba-993c-6572d5c31c40/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/093441a9-2de9-438e-a56b-53ff47cebdad/image.png" alt=""></p>
<p>이런 식들로 메소드 안에서 편하게 condition 적용 가능!
(이런건 재사용도 됨)</p>
<blockquote>
<p>QueryDSL generate 된 파일들은 .gitignore 에 포함시키기!!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[jpa-spring boot - api 실습 (3)]]></title>
            <link>https://velog.io/@world_changer/jpa-spring-boot-api-%EC%8B%A4%EC%8A%B5-3</link>
            <guid>https://velog.io/@world_changer/jpa-spring-boot-api-%EC%8B%A4%EC%8A%B5-3</guid>
            <pubDate>Mon, 17 Apr 2023 05:48:14 GMT</pubDate>
            <description><![CDATA[<h1 id="컬렉션-조회-최적화-onetomany">컬렉션 조회 최적화 (OneToMany)</h1>
<h2 id="orderitems-조회-v1">OrderItems 조회 V1</h2>
<pre><code>    @GetMapping(&quot;/api/v1/orders&quot;)
    public List&lt;Order&gt; ordersV1(){
        List&lt;Order&gt; all = orderRepository.findAll(new OrderSearch());
        for (Order order : all) {
            // 강제 초기화
            order.getMember().getName();
            order.getDelivery().getAddress();
            List&lt;OrderItem&gt; orderItems = order.getOrderItems();
            orderItems.stream().forEach(o -&gt; o.getItem().getName());
        }

        return all;
    }</code></pre><pre><code>&lt;이건 기본적으로 추가해줘야하는!&gt;
    @Bean
    Hibernate5Module hibernate5Module() {
        return new Hibernate5Module();
    }</code></pre><blockquote>
<p>강제로 프록시 초기화를 시켜주어, orderItems 각 객체마다, orderItem 프록시를 초기화 시켜주어 name에 값을 불러온다.</p>
</blockquote>
<ul>
<li><p>데이터는 잘 불러와지나, 각각 프록시 강제 초기화마다 sql 문을 날리는 것을 확인할 수 있음.</p>
</li>
<li><p>엔티티를 직접 노출하기 때문에, api 스펙이 Entity의 영향을 받게 됨</p>
</li>
</ul>
<h2 id="orderitems-조회-v2---dto화">OrderItems 조회 V2 - DTO화</h2>
<blockquote>
<p>일단 DTO 를 만들자</p>
</blockquote>
<pre><code>    @GetMapping(&quot;/api/v2/orders&quot;)
    public List&lt;OrderDto&gt; ordersV2(){
        List&lt;Order&gt; all = orderRepository.findAll(new OrderSearch());
        List&lt;OrderDto&gt; collect = all.stream().map(o -&gt; new OrderDto(o)).collect(Collectors.toList());
        return collect;
    }

    @Data
    static class OrderDto {
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List&lt;OrderItem&gt; orderItems;


        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
            order.getOrderItems().stream().forEach(o -&gt; o.getItem().getName());
            orderItems = order.getOrderItems();
        }
    }</code></pre><p>이제 Entity의 변경에 Api 스펙이 변경되는 일은 없다!
(컴파일 에러로 잡을 수 있게 됨)
<code>@Data</code> 는 api 끝단에서 역할!</p>
<blockquote>
<p>DTO 안에 Entity 가 있는 것은 좋지 않음!
직접적으로 1차 Api 스펙에는 영향을 주지 않지만, 내부 Entity 속성이 그대로 노출됨
(OrderItem, Item)</p>
<ul>
<li>엔티티에 대한 의존을 완전하게 끊어야함!!
귀찮더라도,<code>List&lt; OrderItem &gt;</code> 을 전부 DTO 로 변환해 주어야함</li>
</ul>
</blockquote>
<pre><code>    @Data
    static class OrderDto {
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List&lt;OrderItemDto&gt; orderItems; // DTO로 수정

        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();

            // DTO 로 수정
            orderItems = order.getOrderItems().stream().map(o -&gt; new OrderItemDto(o)).collect(Collectors.toList());
        }
    }

    // 새로운 DTO
    @Data
    static class OrderItemDto {
        private String itemName;
        private int orderPrice;
        private int count;
        public OrderItemDto(OrderItem orderItem) {
            itemName = orderItem.getItem().getName();
            orderPrice = orderItem.getOrderPrice();
            count = orderItem.getCount();
        }
    }
</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/90239705-0d71-450b-97f6-03a99003a0c7/image.png" alt=""></p>
<blockquote>
<p>원하는 정보들만 뽑아내서 DTO로 만들 수 있음!!</p>
</blockquote>
<h2 id="orderitems-조회-v3---fetch-join">OrderItems 조회 V3 - fetch join</h2>
<pre><code>    @GetMapping(&quot;/api/v3/orders&quot;)
    public List&lt;OrderDto&gt; ordersV3(){
        List&lt;Order&gt; all = orderRepository.findAllWithItem();
        List&lt;OrderDto&gt; collect = all.stream().map(o -&gt; new OrderDto(o)).collect(Collectors.toList());
        return collect;
    }
</code></pre><pre><code>&lt;OrderRepository.java&gt;
    public List&lt;Order&gt; findAllWithItem() {
        return em.createQuery(
                &quot;select o from Order o&quot; +
                &quot; join fetch o.member m&quot; +
                &quot; join fetch o.delivery d&quot; +
                &quot; join fetch o.orderItems oi&quot; +
                &quot; join fetch oi.item i&quot;, Order.class).getResultList();
    }</code></pre><p>이처럼 <code>fetch join</code> 하면 될 것 같음!!</p>
<p>  <img src="https://velog.velcdn.com/images/world_changer/post/f4819c65-0005-490f-8858-ef7461e5f577/image.png" alt=""></p>
<p>(이 사진도 다가 아님)
정말 어마무시한 쿼리문 하나가 나가는 것을 볼 수 있다!!
n 번의 쿼리를 날리면서 성능을 잡아먹다가, 한번으로 최적화된 모습!</p>
<blockquote>
<p><code>Fetch Join</code> 의 고질적인 문제인, 테이블 데이터 중복!!
데이터를 받아보면 티가 난다! 같은 id 값이 두번 들어옴!!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/6997918b-cae2-49c9-8202-1af4b7d300f9/image.png" alt=""></p>
<blockquote>
<p>Database 입장에서는 table join 이 되어버린 것임! (데이터 뻥튀기)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/421e4c81-694f-42ff-b0f7-91eb717f6937/image.png" alt=""></p>
<blockquote>
<p>아이러니하게도 같은 참조값을 가지기는 함! jpa 가 자체적으로 같은 객체로 만들지만, 알아서 하나로 보내주지는 않음!!
기억나는가? 어떤걸 써야하는지? 바로 <code>Distinct</code></p>
</blockquote>
<pre><code>    public List&lt;Order&gt; findAllWithItem() {
        return em.createQuery(
                &quot;select distinct o from Order o&quot; +
                &quot; join fetch o.member m&quot; +
                &quot; join fetch o.delivery d&quot; +
                &quot; join fetch o.orderItems oi&quot; +
                &quot; join fetch oi.item i&quot;, Order.class).getResultList();
    }</code></pre><p>이제 중복없이 잘 나온다!!</p>
<blockquote>
<p>SQL 문에서의 <code>distinct</code> 와 무엇이 다를까?
DB Query 에서의 행동이 다르다기 보다는! (이거는 <code>distinct</code> 가 있으나 없으나 같음)
JPA 에서 자체적으로 버려주는 기능임!!! (들어오는 데이터는 뻥튀기 된 채로 들어옴)</p>
</blockquote>
<h3 id="엄청난-제약-조건-fetch-조인">엄청난 제약 조건 (fetch 조인)</h3>
<blockquote>
<p>치명적인 단점 하나가 있음!!!!!!!!!!!!!!!!!!!!!
진정한 의미의 <code>페이징</code> 이 불가능해진다
전부다 받아온 다음에, 그 데이터를 기반으로 페이징처럼 출력을 함!!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/7da72039-fa2b-472a-806a-6ba0a3a252fc/image.png" alt=""></p>
<ol>
<li>메모리 관점에서 의미 퇴색 (n개의 데이터를 다 불러오고, 그 중 몇개를 반환하는 꼴)</li>
<li>진정한 의미의 페이징 애매 (distinct 일때는 비슷하게 가능하긴 하지만...)
order 기준으로 페이징 하는 것이 아닌, 다 쪽의 item 기준으로 페이징이 일어남</li>
</ol>
<p><img src="https://velog.velcdn.com/images/world_changer/post/fe33a281-920f-46f6-ad72-6516684697ef/image.png" alt="">
(hibernate에서도 warn 창을 띄워줌)</p>
<blockquote>
<p>일대다의 경우엔 오히려 <code>fetch join</code> 을 사용하지 않는 것이 좋음!</p>
</blockquote>
<h3 id="🔶🔶-일대다컬렉션-fetch-join-금지-🔶🔶">🔶🔶 일대다(컬렉션) fetch join 금지!!!!!! 🔶🔶</h3>
<ul>
<li>10만개의 데이터가 메모리에 한방에 불러와진다면...!!</li>
<li>오우... 대형사고...</li>
<li>작은 데이터라는 것이 확실하면, 상관 없긴 함</li>
<li>컬렉션 페치 조인은 2개 이상 사용 금지!!</li>
</ul>
<h2 id="orderitems-조회-v31---페이징--한계-돌파">OrderItems 조회 V3.1 - 페이징 &amp; 한계 돌파</h2>
<blockquote>
<p>페이징도 하고, 컬렉션 엔티티를 함꼐 조회하려면 어떻게 해야할까!!
(아주 비장하게) 지금부터 코드도 단순하고, 성능 최적화도 보장하는 매우 강력한 방법을 소개하겠다!</p>
</blockquote>
<p>기존에 <code>@xToOne</code> 은 전부 <code>fetch join</code>
나머지는 하나씩 프록시 초기화 시키면서 들고오는 거 대신, batch 로 들고오는 방법으로!!</p>
<pre><code>&lt;application.yaml&gt;
  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true
        default_batch_fetch_size: 100</code></pre><p>그러면 sql query 문이 세번만 일어남!!
100개의 프록시 초기화를 동시에 진행한다고 생각하면 됨! (lazy 걸려있는 모든 프록시들)</p>
<ol>
<li>Order : fetch join (1) &lt;= batch 전 1</li>
<li>OrderItem : batch (1)  &lt;= batch 전 n</li>
<li>Item : batch (1)       &lt;= batch 전 n</li>
</ol>
<p>batch 사이즈 설정만으로도!
1 / n / n ======&gt;&gt;&gt;&gt;  1 / 1 / 1 처럼 구현할 수 있음 (batch 사이즈에 따라 약간 차이)</p>
<blockquote>
<ul>
<li><ol>
<li>각각 프록시 초기화를 사용한다면, query sql 효율성이 떨어지고,</li>
</ol>
</li>
<li><ol start="2">
<li>모두 fetch join 을 사용한다면, 메모리 이슈 / 페이징 이슈가 생김</li>
</ol>
</li>
</ul>
<p>1) batch를 활용해서 쿼리 듬성듬성 날리기
2) <code>@xToOne</code> 관계에만 <code>fetch join</code> 사용!
2-1) join을 안하기 때문에, <code>@OneToMany</code> 일때 fetch join 은 데이터 뻥튀기가 일어나기에, 데이터 전송량 자체를 줄여주는 효과도 있음!! (이거 만개라고 생각하면, 데이터 양이 어마무시함)</p>
</blockquote>
<p>꼭 글로벌하게 설정할 필요는 없음 
<code>@BatchSize(size = 100)</code> 이런식으로 지정 가능</p>
<h3 id="결론">결론</h3>
<blockquote>
<ul>
<li><code>@xToOne</code> 관계는 <code>fetch join</code> 해도 페이징에 영향을 주지 않는다.
따라서 <code>@xToOne</code> 관계는 <code>fetch join</code> 으로 쿼리수를 줄이고</li>
<li>나머지는 batch size 를 설정하여 최적화!!</li>
</ul>
</blockquote>
<ul>
<li>사이즈를 얼마로 두는 것이 좋은가?
maximum 은 보통 1000 으로 보는 편!! (100 ~ 1000 사이 권장)
애매하면 100, 500 으로 두고 쓰셈!</li>
</ul>
<h2 id="orderitems-조회-v4---dto-로-조회-1">OrderItems 조회 V4 - DTO 로 조회 (1)</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/404e4f69-99c7-4a39-9b65-b6d88213ca9d/image.png" alt=""></p>
<p><code>OrderRepository</code> 에 구현하는 것이 아닌,
새롭게 <code>OrderQueryRepository</code> 를 만들어서 따로 관리!!</p>
<p>&lt;OrderItemQueryDto.java&gt;</p>
<pre><code>@Data
public class OrderItemQueryDto {
    @JsonIgnore
    private Long orderId;
    private String itemName;
    private int orderPrice;
    private int count;

    public OrderItemQueryDto(Long orderId, String itemName, int orderPrice, int count) {
        this.orderId = orderId;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }
}</code></pre><p>&lt;OrderQueryDto.java&gt;</p>
<pre><code>@Data
public class OrderQueryDto {
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;
    private List&lt;OrderItemQueryDto&gt; orderItems;

    /**
     * collection 을 바로 주입 받을 수가 없음
     * 해당 DTO Constructor 는 jpql 에서 사용됨.
      */
    public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address/*, List&lt;OrderItemQueryDto&gt; orderItems*/) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
        this.orderItems = orderItems;
    }
}</code></pre><p>&lt;OrderQueryRepository.java&gt;</p>
<pre><code>@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
    private final EntityManager em;

    public List&lt;OrderQueryDto&gt; findOrderQueryDtos(){
        List&lt;OrderQueryDto&gt; result = findOrders();
        result.forEach(o -&gt; {
           List&lt;OrderItemQueryDto&gt; orderItems = findOrderItems(o.getOrderId());
           o.setOrderItems(orderItems);
        });
        return result;
    }

    private List&lt;OrderItemQueryDto&gt; findOrderItems(Long orderId) {
        return em.createQuery(&quot;select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)&quot; +
                        &quot; from OrderItem oi&quot; +
                        &quot; join oi.item i&quot; +
                        &quot; where oi.order.id = :orderId&quot;, OrderItemQueryDto.class)
                .setParameter(&quot;orderId&quot;, orderId)
                .getResultList();
    }

    private List&lt;OrderQueryDto&gt; findOrders() {
        return em.createQuery(&quot;select new jpabook.jpashop.repository.order.query.OrderQueryDto(o.id, m.name, o.orderDate, o.status, d.address)&quot; +
                        &quot; from Order o&quot; +
                        &quot; join o.member m&quot; +
                        &quot; join o.delivery d&quot;, OrderQueryDto.class)
                .getResultList();
    }
}</code></pre><blockquote>
<p>Query 문 : 루트 1번, 컬렉션 N번 (for 문 돌면서)
ToOne 관계들을 먼저 조회하고, ToMany(1:N) 관계는 각각 별도로 처리한다.
row 수가 증가하지 않는 ToOne 관계는 조인으로 최적화하기 쉬우므로 한번에 조회하고, ToMany 관계는 최적화하기 어려우므로 <code>findOrderItems()</code> 같은 별도의 메소드로 조회</p>
</blockquote>
<p>이런 방식은 N+1 문제를 야기한다.</p>
<h2 id="orderitems-조회-v5---dto-로-조회-2">OrderItems 조회 V5 - DTO 로 조회 (2)</h2>
<pre><code>    public List&lt;OrderQueryDto&gt; findAllByDto_optimization(){
        List&lt;OrderQueryDto&gt; result = findOrders();

        List&lt;Long&gt; orderIds = result.stream().map(o -&gt; o.getOrderId()).collect(Collectors.toList());

        List&lt;OrderItemQueryDto&gt; orderItemQueryDtos = em.createQuery(&quot;select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)&quot; +
                        &quot; from OrderItem oi&quot; +
                        &quot; join oi.item i&quot; +
                        &quot; where oi.order.id in :orderIds&quot;, OrderItemQueryDto.class)
                .setParameter(&quot;orderIds&quot;, orderIds)
                .getResultList();

        Map&lt;Long, List&lt;OrderItemQueryDto&gt;&gt; orderItemMap = orderItemQueryDtos.stream().collect(Collectors.groupingBy(orderItemQueryDto -&gt; orderItemQueryDto.getOrderId()));

        result.forEach(o -&gt; o.setOrderItems((orderItemMap.get(o.getOrderId()))));

        return result;
    }</code></pre><p><code>in</code> 을 활용해서 한방에 다 가져오기!
<code>OrderItemQueryDto</code> 에 <code>orderId</code> 를 넣은 이유가 여기에 있었음!!
<img src="https://velog.velcdn.com/images/world_changer/post/c57e9808-ce8c-4bbb-b825-910d02697abd/image.png" alt=""></p>
<p>v4 와 다른 점은 query 문을 한번만 날린다는 것!!</p>
<h2 id="orderitems-조회-v6---dto-로-조회-3">OrderItems 조회 V6 - DTO 로 조회 (3)</h2>
<pre><code>&lt;OrderFlatDto.java&gt;
@Data
public class OrderFlatDto {

    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;

    private String itemName;
    private int orderPrice;
    private int count;

    public OrderFlatDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address, String itemName, int orderPrice, int count) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }
}</code></pre><p>plat 형태로 DTO를 설정하여, 직접 jpql 을 설정하면, query 문은 한방에 나간다! (페이징은 x)</p>
<pre><code>    public List&lt;OrderQueryDto&gt; findAllByDto_flat(){
        List&lt;OrderFlatDto&gt; orderFlatDtoList = em.createQuery(&quot;select new&quot; +
                &quot; jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, i.name, oi.orderPrice, oi.count)&quot; +
                &quot; from Order o&quot; +
                &quot; join o.member m&quot; +
                &quot; join o.delivery d&quot; +
                &quot; join o.orderItems oi&quot; +
                &quot; join oi.item i&quot;, OrderFlatDto.class).getResultList();
        return orderFlatDtoList.stream()
                .collect(groupingBy(o -&gt; new OrderQueryDto(o.getOrderId(),
                                o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
                        mapping(o -&gt; new OrderItemQueryDto(o.getOrderId(),
                                o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
                )).entrySet().stream()
                .map(e -&gt; new OrderQueryDto(e.getKey().getOrderId(),
                        e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(),
                        e.getKey().getAddress(), e.getValue()))
                .collect(toList());
    }</code></pre><p>추가적으로 DB 쪽 아닌, 코드 단에서 <code>OrderQueryDto</code> 형태로 매핑을 해주어 변환해주면 된다!</p>
<p>쿼리 한방에 원하는 기능을 할 수 있게 됨!!
<img src="https://velog.velcdn.com/images/world_changer/post/fd33d218-d9a0-47d7-9ad6-a9008f3c6789/image.png" alt=""></p>
<p><code>OrderQueryDto</code> 에 해당 어노테이션을 달아줘야, 
매핑이 정상 작동!!</p>
<blockquote>
<p>V5 (컬렉션 조회 최적화) vs V6 (플랫데이터 최적화)</p>
<ul>
<li>V5는 쿼리 2번, V6는 쿼리 1번</li>
<li>V6는 쿼리가 1번이지만 조인으로 인해 중복 데이터가 추가되므로, 상황에 따라 더 느릴 수도 있음!</li>
<li>애플리케이션 추가 작업이 큼! + 페이징 x</li>
</ul>
</blockquote>
<h1 id="요약-정리">요약 정리</h1>
<h2 id="엔티티-조회">엔티티 조회</h2>
<h3 id="v1---엔티티-그대로-반환">v1 - 엔티티 그대로 반환</h3>
<p>큰일 난다!
엔티티 스펙이 변함에 따라 API 스펙이 변함</p>
<h3 id="v2---dto-로-변환">v2 - DTO 로 변환</h3>
<p>이때 문제는, 성능이 안나올 때가 있음!</p>
<h3 id="v3---페치-조인으로-쿼리수-최적화">v3 - 페치 조인으로 쿼리수 최적화</h3>
<p>fetch join 으로 하면, 페이징이 힘들때가 있음</p>
<h3 id="v31---컬렉션-페이징과-한계돌파">v3.1 - 컬렉션 페이징과 한계돌파</h3>
<ul>
<li><code>@xToOne</code> 관계는 페치 조인으로 쿼리 수 최적화</li>
<li>컬렉션 은 지연 로딩을 유지하되, <code>@BatchSize</code> 로 최적화</li>
</ul>
<h2 id="dto-직접-조회">DTO 직접 조회</h2>
<h3 id="v4---jpa에서-dto-조회">v4 - JPA에서 DTO 조회</h3>
<p>쿼리문을 많이 날려야함.</p>
<h3 id="v5---컬렉션-조회-최적화">v5 - 컬렉션 조회 최적화</h3>
<p><code>in</code> jpql 문을 활용하여 쿼리 한방으로 &#39;다&#39; 를 불러옴</p>
<h3 id="v6---플랫-데이터-최적화">v6 - 플랫 데이터 최적화</h3>
<p>어플리케이션 단에서 매핑하여 원하는 형태로 사용</p>
<h2 id="권장-순서">권장 순서</h2>
<h3 id="1-엔티티-조회">1. 엔티티 조회</h3>
<h3 id="-1-1-페치-조인-쿼리-수-최적화">* 1-1. 페치 조인 (쿼리 수 최적화)</h3>
<h3 id="-1-a-컬렉션-최적화-페이징-여부에-따라">* 1-a. 컬렉션 최적화 (페이징 여부에 따라)</h3>
<h3 id="2-dto-조회">2. DTO 조회</h3>
<h3 id="-2-a-최적화">* 2-a. 최적화</h3>
<h3 id="3-nativesql-or-jdbctemplate">3. NativeSQL or JdbcTemplate</h3>
<h3 id="why">Why?</h3>
<ol>
<li>엔티티의 경우 여러 기능들을 손쉽게 적용 가능</li>
<li>DTO 의 경우 최적화에 코드 변경이 많이 필요하다!</li>
</ol>
<blockquote>
<p>개발자는 성능 최적화와 코드 복잡도 사이에서 줄타기!
보통 성능 최적화는 단순한 코드를 복잡한 코드로 몰고감</p>
</blockquote>
<blockquote>
<p>DTO 조회 방식의 선택지
각 v4, v5, v6 는 엔티티로 했다면 어노테이션으로 해결되는 문제들</p>
</blockquote>
<ul>
<li>단건 조회의 경우 V4</li>
<li>여러 주문을 한꺼번에 조회하는 경우 V5 (쿼리 수가 확 줄어듬)</li>
<li>V6 의 경우 실무에서는 페이징이 많기에, 활용되기 어려움!</li>
</ul>
<blockquote>
<p>DTO 직접 조회를 하다보면, 거의 V5를 하게 됨!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[til_230415]]></title>
            <link>https://velog.io/@world_changer/til230415</link>
            <guid>https://velog.io/@world_changer/til230415</guid>
            <pubDate>Sat, 15 Apr 2023 11:16:48 GMT</pubDate>
            <description><![CDATA[<h1 id="react">react</h1>
<h2 id="spa에서-화면-변경은-어떻게">SPA에서 화면 변경은 어떻게?</h2>
<ul>
<li>HTML 5 의 History API 를 사용해서 한다.
세션 관리!
<img src="https://velog.velcdn.com/images/world_changer/post/14d56ec3-e1ed-4e3c-8b95-63f1b431ea9b/image.png" alt=""></li>
</ul>
<h2 id="react-router-dom">react-router-dom</h2>
<p><code>npm install react-router-dom --save</code>
--save 해주면, package.json 에 추가가 됨.</p>
<h2 id="rfce">rfce</h2>
<p>vscode 창에 <code>rfce</code> 하고 enter 하면 자동으로 export 가 되는 function을 만들어주게 하는 법!
<img src="https://velog.velcdn.com/images/world_changer/post/e41356ca-cb07-4aa5-9e58-94421fb5910a/image.png" alt="">
요거 설치해주면 됨.
<img src="https://velog.velcdn.com/images/world_changer/post/953b2783-2003-470c-81ab-14f216ed300b/image.png" alt=""></p>
<h1 id="css-framework">CSS Framework</h1>
<ul>
<li>기능을 만드는데 더 집중하기 위해!!
<img src="https://velog.velcdn.com/images/world_changer/post/cc39624c-d4da-42bf-8eac-023231e11514/image.png" alt=""></li>
</ul>
<h2 id="react-bootstrap">React Bootstrap</h2>
<p><a href="https://react-bootstrap.github.io/">https://react-bootstrap.github.io/</a>
<img src="https://velog.velcdn.com/images/world_changer/post/4ee8ac9b-e17a-4550-8c39-f457e9cdee5b/image.png" alt=""></p>
<p>다양한 기본 컴포넌트들을 import 해서 쉽게 사용 가능!
(Button, Modal 등등)</p>
<blockquote>
<p>설치
<code>npm install react-bootstrap bootstrap --save</code></p>
</blockquote>
<p>&lt;index.js&gt;
<code>import &quot;bootstrap/dist/css/bootstrap.min.css&quot;;</code>
추가</p>
<h1 id="clayful">Clayful</h1>
<p>backend 연동 솔루션 서비스! (다양한 커머스를 지원해주는!)</p>
<p><code>npm install clayful --save</code>
<code>npm install axios --save</code></p>
<pre><code>import clayful from &quot;clayful/client-js&quot;;
import axios from &quot;axios&quot;;

clayful.config({
  client: &quot;TODO Client Token&quot;,
});

clayful.install(&quot;request&quot;, require(&quot;clayful/plugins/request-axios&quot;)(axios));</code></pre><h1 id="authcontext">AuthContext</h1>
<p><img src="https://velog.velcdn.com/images/world_changer/post/c16d1cde-267b-4a69-b823-375aa4fce46e/image.png" alt=""></p>
<p>Context가 많아질 거기 때문에, 따로 Context들을 분리!
<img src="https://velog.velcdn.com/images/world_changer/post/3428d2c8-c39f-4899-bc22-7aba953906b5/image.png" alt=""></p>
<pre><code>const AuthContextProvider = ({ children }) =&gt; {
  const [isAuth, setIsAuth] = useState(false);
  const navigate = useNavigate();

  const AuthContext = createContext();

  const signOut = () =&gt; {
    setIsAuth(false);
    localStorage.removeItem(&quot;accessToken&quot;);
    navigate(&quot;/login&quot;);
  };
  const isAuthenticated = () =&gt; {
    return;
    var Customer = clayful.Customer;
    let options = {
      customer: localStorage.getItem(&quot;accessToken&quot;),
    };
    Customer.isAuthenticated(options, function (err, result) {
      if (err) {
        console.log(err.code);
        setIsAuth(false);
        return;
      }
      var data = result.data;
      if (data.authenticated) {
        setIsAuth(true);
      } else {
        setIsAuth(false);
      }
    });
  };

  const AuthContextData = {
    isAuth,
    isAuthenticated,
    signOut,
  };
  return (
    &lt;AuthContext.Provider value={AuthContextData}&gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  );
};

export const AuthContext = createContext();</code></pre><p>이런식으로 children 을 들고와서, 내부적으로 감싸주고</p>
<ul>
<li>value 값은 AuthContextData 로 묶어주기</li>
<li>적절히 필요한 기능들 구현</li>
</ul>
<blockquote>
<p>사용은 이런식으로 하면 됨.
<img src="https://velog.velcdn.com/images/world_changer/post/4e2e6c9b-969d-45ac-92ff-b2c1c96b2603/image.png" alt=""></p>
</blockquote>
<h2 id="기타">기타</h2>
<pre><code>&lt;React.Fragment&gt; &lt;/React.Fragment&gt;
&lt;&gt;&lt;/&gt;
위 아래 같은 거임</code></pre><h3 id="dangerouselysetinnerhtml">dangerouselySetInnerHTML</h3>
<p><code>dangerouslySetInnerHTML</code> 은 브라우저 DOM에서 innerHTML을 사용하기 위한 React의 대채 방법.</p>
<h3 id="react-bootstrap-1">react-bootstrap</h3>
<blockquote>
<p>Container, Row, Col 잘쓰면 반응형 대박!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[til_230413_prisma]]></title>
            <link>https://velog.io/@world_changer/til230413prisma</link>
            <guid>https://velog.io/@world_changer/til230413prisma</guid>
            <pubDate>Thu, 13 Apr 2023 08:10:10 GMT</pubDate>
            <description><![CDATA[<h1 id="prisma-설치">prisma 설치</h1>
<p><code>npx install prisma</code></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/fdfcc1f2-aae4-4be0-a827-0d93182b23bd/image.png" alt=""></p>
<p>prisma 폴더와 .env 파일을 만들어줌!</p>
<p>&lt;schema.prisma&gt; 파일</p>
<pre><code>generator client {
  provider = &quot;prisma-client-js&quot;
}

datasource db {
  provider = &quot;mysql&quot;
  url      = env(&quot;DATABASE_URL&quot;)
  relationMode = &quot;prisma&quot;
}

model User {
  id Int @id @default(autoincrement())
  account String @unique
  name String?
  todos Todo[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Todo {
  id Int @id @default(autoincrement())
  todo String
  isDone Boolean
  user User @relation(fields: [userId], references: [id])
  userId Int
  createAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([userId])
}</code></pre><blockquote>
<p>schema 에서 model 안에 주석을 넣으면 안됨!!!</p>
</blockquote>
<h1 id="planet-scale-설치">planet scale 설치</h1>
<p>DB 그 자체 라고 생각하면 됨.</p>
<blockquote>
<p>윈도우 주의! 관리자 권한으로 하지 말것 !
PowerShell 에서 실행할 것!!</p>
</blockquote>
<h3 id="관리자-뭐시기-에러-뜰때">관리자 뭐시기 에러 뜰때</h3>
<p><code>irm get.scoop.sh -outfile &#39;install.ps1&#39;</code>
<code>iex &quot;&amp; {$(irm get.scoop.sh)} -RunAsAdmin&quot;</code></p>
<h2 id="scoop-설치">Scoop 설치</h2>
<p><a href="https://scoop.sh/">https://scoop.sh/</a> 에서 아래 명령어 2개 실행. (윈도우) ★터미널 파워쉘로 열기★
<code>Set-ExecutionPolicy RemoteSigned -Scope CurrentUser</code>
<code>irm [get.scoop.sh](http://get.scoop.sh/) | iex</code></p>
<h2 id="planet-scale-과정">planet scale 과정</h2>
<p><a href="https://github.com/planetscale/cli#installation">https://github.com/planetscale/cli#installation</a>
여기서 윈도우 / Mac에 따라 설치명령어 실행
윈도우 ↓
<code>scoop bucket add pscale https://github.com/planetscale/scoop-bucket.git</code></p>
<p><code>scoop install pscale mysql</code></p>
<p>error 뜨는 경우</p>
<ol>
<li>관리자 권한으로 실행</li>
<li>용량이 부족한 경우</li>
</ol>
<h3 id="planet-scale-로그인">planet scale 로그인</h3>
<p><code>pscale auth login</code></p>
<h3 id="실행">실행</h3>
<p><code>pscale connect todolist</code>
todolist : db 명 (미리 만들어놓은)
<img src="https://velog.velcdn.com/images/world_changer/post/06ba89b3-ae9e-452d-b87d-e34f6962f3d6/image.png" alt=""></p>
<h3 id="db-등록">db 등록</h3>
<p>위에서 실행해놓고, 틀어놓은 상태에서</p>
<p><code>npx prisma db push</code>
하면 DB 상태가 푸쉬됨.
<img src="https://velog.velcdn.com/images/world_changer/post/ea42b23b-caac-47ee-9d82-b4b04a29f909/image.png" alt=""></p>
<blockquote>
<p>이것을 기반으로 SQL 이 작성되기 때문에, 수정이 되었다면, 다시 푸시를 해주어야함!!</p>
</blockquote>
<h1 id="prisma-client">prisma client</h1>
<p><code>npm i @prisma/client</code></p>
<h1 id="prisma-studio">prisma studio</h1>
<p><code>npx prisma studio</code>
<img src="https://velog.velcdn.com/images/world_changer/post/9a9c8ead-8422-4e65-b5a0-df11e079fdc1/image.png" alt="">
이런식으로 db 를 쉽게 접근/관리 할 수 있도록 도와주는 패키지!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[jpa-spring boot - api 실습 (2)]]></title>
            <link>https://velog.io/@world_changer/jpa-spring-boot-api-%EC%8B%A4%EC%8A%B5-2</link>
            <guid>https://velog.io/@world_changer/jpa-spring-boot-api-%EC%8B%A4%EC%8A%B5-2</guid>
            <pubDate>Wed, 12 Apr 2023 16:18:17 GMT</pubDate>
            <description><![CDATA[<h1 id="조회용-샘플-데이터-입력">조회용 샘플 데이터 입력</h1>
<pre><code>@Component
@RequiredArgsConstructor
public class InitDb {

    private final InitService initService;

    @PostConstruct
    public void init(){
        initService.dbInit1();
        initService.dbInit2();
    }

    @Component
    @Transactional
    @RequiredArgsConstructor
    static class InitService {
        private final EntityManager em;
        public void dbInit1(){
            Member member = getMemberWidthNameAndAddressProps(&quot;user A&quot;, &quot;서울&quot;, &quot;1&quot;, &quot;1245&quot;);
            em.persist(member);

            Book book1 = getBookWidthNamePriceStock(&quot;JPA1 bOOK&quot;, 10000, 100);
            em.persist(book1);

            Book book2 = getBookWidthNamePriceStock(&quot;JPA2 bOOK&quot;, 20000, 200);
            em.persist(book2);

            OrderItem orderItem1 = OrderItem.createOrderItem(book1, 10000, 1);
            OrderItem orderItem2 = OrderItem.createOrderItem(book2, 20000, 2);

            Delivery delivery = new Delivery();
            delivery.setAddress(member.getAddress());
            Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);
            em.persist(order);
        }
        public void dbInit2(){
            ...
        }


        private static Member getMemberWidthNameAndAddressProps(String name, String cityName, String street, String zipcode) {
            ...
            return member;
        }

        private static Book getBookWidthNamePriceStock(String name, int price, int stock) {
            ...
            return book;
        }

    }
}</code></pre><p>Spring 이 실행되면서, <code>@PostConstruct</code> 의 함수를 먼저 실행함!
이때 목업/샘플 데이터를 주입해주면 됨!!</p>
<h1 id="지연-로딩과-조회-성능-최적화">지연 로딩과 조회 성능 최적화</h1>
<blockquote>
<p>지금부터 설명하는 내용은 정말 중요!!
대충 넘어가면 엄청난 시간을 날리고 인생을 허비하게 될 것임!! feat. 김영한 개발자님</p>
</blockquote>
<h2 id="simple-order-조회-v1">simple order 조회 v1</h2>
<pre><code>    @GetMapping(&quot;/api/v1/simple-orders&quot;)
    public List&lt;Order&gt; ordersV1(){
        List&lt;Order&gt; all = orderRepository.findAll(new OrderSearch());
        return all;
    }</code></pre><p>무난하게 <code>List&lt;Order&gt;</code> 를 json 파일로 바꿔줄 것 같은 식이다! 
하지만!! json 으로 바꿔주는 과정에서, </p>
<pre><code>&lt;Order.java&gt;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;member_id&quot;)
    private Member member;</code></pre><p>member를 타고</p>
<pre><code>&lt;Member.java&gt;
    @OneToMany(mappedBy = &quot;member&quot;)
    private List&lt;Order&gt; orders = new ArrayList&lt;Order&gt;();</code></pre><blockquote>
<p>여기서 orders를 호출하여 이를 다시 json 화 시키는데,
이때 무한 루프가 발생한다!!
양방향 연관관계에서의 문제가 생김!</p>
</blockquote>
<h3 id="해결">해결</h3>
<blockquote>
<p><code>@JsonIgnore</code> 을 걸어주면 된다!
양방향 중에 한쪽은 무조건 걸어줘야함!! (일대다, 일대일 이던, 양방향이라면)
<code>Member</code> <code>OrderItem</code> <code>Delivery</code> 다 바꿔주기</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/cc60ae79-69d4-47bf-b815-795381d95247/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/83cc1dbd-0e98-4be0-a21d-41c3d44995fa/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/a992af66-9a7a-474e-ac40-0d724565c89b/image.png" alt=""></p>
<h3 id="또-다른-이슈">또 다른 이슈</h3>
<p><img src="https://velog.velcdn.com/images/world_changer/post/47e31c29-2146-4eb8-b454-0b65564df3a4/image.png" alt=""></p>
<blockquote>
<p><code>class org.hibernate.proxy.poho.bytebuddy.ByteBuddyInterceptor</code> 하는 친구가 뜬다.
바로, <code>lazy loading</code> 에 걸려있는 객체들이 json화 되는 과정에서 문제가 된 것!</p>
</blockquote>
<ul>
<li>지연 로딩인 경우, 그냥 뿌리지 말라고 지정해줄 수 있음!</li>
<li>해당 라이브러리를 다운로드 받아야함. (스프링부트가 알아서 버전 맞춰줌)</li>
</ul>
<pre><code>&lt;build.gradle&gt;
implementation &#39;com.fasterxml.jackson.datatype:jackson-datatype-hibernate5&#39; // 추가</code></pre><pre><code>&lt;mainController&gt; 추가
@Bean
Hibernate5Module hibernate5Module() {
    return new Hibernate5Module();
}</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/1c9e0074-e312-4435-b755-7538b6712e5f/image.png" alt=""></p>
<p>위 처럼, <code>lazy loading</code> 데이터들은 null로 넘어온다!
lazy 로딩을 억지로 불러와서 data를 강제할 수 있는 속성도 추가할 수 있음!
<code>hibernate5Module.configure(Hibernate5Module.Featrue.FORCE_LAZY_LOADING, true);</code></p>
<blockquote>
<p>엔티티를 직접 반환하는 api 는 여러모로 제약이 많다.
json화, lazy loading 등 여러가지를 신경써줘야함.</p>
<ol>
<li>왠만하면 DTO 로 변환해서 반환하자!!</li>
<li>lazy loading 때문에, eager 로 바꾸는 것이 아니라, 필요한 경우 페치 조인으로 ㄱㄱ</li>
</ol>
</blockquote>
<blockquote>
<p>덧) 반환 값은 리스트로 보내는 일이 없도록하자!
추가적으로 필드 값을 수정할때, 확장이 용이 하지 않음</p>
</blockquote>
<pre><code>&lt;newField 추가 하기 쉽네~&gt;
{
   newField: ~~~,
   data: [ ... ]
}</code></pre><pre><code>&lt;newField 어디에 추가하지..?&gt;
[
  ...
]</code></pre><h2 id="simple-order-조회-v2">simple order 조회 v2</h2>
<blockquote>
<p>v1 에서의 엔티티 직접 반환을 해결하여, DTO를 세팅하면 아래와 같다.</p>
</blockquote>
<pre><code>    @GetMapping(&quot;/api/v2/simple-orders&quot;)
    public List&lt;SimpleOrderDto&gt; ordersV2() {
        List&lt;Order&gt; orders = orderRepository.findAll(new OrderSearch());

        List&lt;SimpleOrderDto&gt; orderDtoList = orders.stream()
                .map(o -&gt; new SimpleOrderDto(o))
                .collect(Collectors.toList());

        return orderDtoList;
    }

    @Data
    static class SimpleOrderDto {
        public SimpleOrderDto(Order order) {
            this.orderId = order.getId();
            this.name = order.getMember().getName(); // LAZY 초기화
            this.orderDate = order.getOrderDate();
            this.orderStatus = order.getStatus();
            this.address = order.getDelivery().getAddress(); // LAZY 초기화
        }

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
    }</code></pre><h3 id="한계">한계</h3>
<blockquote>
<p><code>lazy loading</code> 으로 인한, 엔티티 개별로 query 호출!! (영속성 컨텍스트로 호출이긴 함)
<code>n + 1</code> 문제
그렇다고 Lazy -&gt; Eager로 바꾸면? NoNo!
Fetch Join 튜닝!! ㄱㄱ</p>
</blockquote>
<h2 id="simple-order-조회-v3">simple order 조회 v3</h2>
<p>&lt; OrderRespository.java &gt;</p>
<pre><code>    public List&lt;Order&gt; findAllWithMemberDelivery() {
        return em.createQuery(&quot;select o from Order o&quot; +
                &quot; join fetch o.member m&quot; +
                &quot; join fetch o.delivery d&quot;, Order.class).getResultList();


    }</code></pre><blockquote>
<p>Fetch Join 을 활용하여, 애초에 값을 다 가지고오기!
JPA 가 고맙게도 쿼리를 한방에 작성해서 날려줌</p>
</blockquote>
<h3 id="100-이해할-것-거의-이게-성능의-90퍼센트-임">100% 이해할 것!!!! 거의 이게 성능의 90퍼센트 임!!!</h3>
<pre><code>&lt;OrderSimpleApiController.java&gt;
    @GetMapping(&quot;/api/v3/simple-orders&quot;)
    public List&lt;SimpleOrderDto&gt; ordersV3() {

        List&lt;Order&gt; orders = orderRepository.findAllWithMemberDelivery();

        List&lt;SimpleOrderDto&gt; result = orders.stream()
                .map(o -&gt; new SimpleOrderDto(o))
                .collect(Collectors.toList());

        return result;
    }</code></pre><p>v2 와 결과는 같지만! 실행하는 Query 개수가 다름!!</p>
<h2 id="simple-order-조회-v4">simple order 조회 v4</h2>
<blockquote>
<p>JPA 에서 바로 DTO 로 조회!!</p>
</blockquote>
<pre><code>&lt;OrderSimpleApiController.java&gt;
    @GetMapping(&quot;/api/v4/simple-orders&quot;)
    public List&lt;OrderSimpleQueryDto&gt; ordersV4() {
        return orderRepository.findOrderDtos();
    }</code></pre><pre><code>&lt;OrderRepository.java&gt;
    public List&lt;OrderSimpleQueryDto&gt; findOrderDtos(){
        return em.createQuery(&quot;select new jpabook.jpashop.repository.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address) from Order o&quot; +
                &quot; join o.member m&quot; +
                &quot; join o.delivery d&quot;, OrderSimpleQueryDto.class).getResultList();
    }</code></pre><pre><code>&lt;OrderSimpleQueryDto.java&gt;
@Data
public class OrderSimpleQueryDto {
    public OrderSimpleQueryDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
    }

    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;
}</code></pre><p>차이점이라 하면!</p>
<blockquote>
<p>Entity에 종속되어 DTO를 만들지 않고, 
Entity와 별개로 원하는 것만 얻어오는 DTO 를 만들 수 있다는 것!!
(member 나 delivery 상세 정보 같은 것들 생략 가능)</p>
<ul>
<li>하지만 재사용성이 떨어짐! 확장성은 거의 없기 때문에</li>
</ul>
</blockquote>
<blockquote>
<p>V3 과 V4 는 <code>Trade-Off</code> 가 있음.
가급적 V3 를 추천!! 
너무나도 많은 Traffic을 다루는 거라면, 해당 프로세스에 맞게 V4 방식으로 고치면 됨.</p>
</blockquote>
<blockquote>
<p>김영한 개발자님 실무 팁</p>
</blockquote>
<p><code>repository</code> 에 query api 스펙이 포함된 DTO 파일을 관리하는게, 뭔가 애매 (+ repository 의 return 값이 순수하지 않음)
query 용 <code>repository</code> 를 따로 만드는 것으로 하는 편
<img src="https://velog.velcdn.com/images/world_changer/post/162eb331-c798-4de1-b356-55d0dcb86bf1/image.png" alt=""></p>
<pre><code>&lt;OrderSimpleQueryRepository.java&gt;
@Repository
@RequiredArgsConstructor
public class OrderSimpleQueryRepository {
    private final EntityManager em;

    public List&lt;OrderSimpleQueryDto&gt; findOrderDtos(){
        return em.createQuery(&quot;select new jpabook.jpashop.repository.order.simplequery.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address) from Order o&quot; +
                &quot; join o.member m&quot; +
                &quot; join o.delivery d&quot;, OrderSimpleQueryDto.class).getResultList();
    }
}</code></pre><pre><code>&lt;OrderSimpleApiController.java&gt;
    ...
    private final OrderSimpleQueryRepository orderSimpleQueryRepository;
    ...    
    @GetMapping(&quot;/api/v4/simple-orders&quot;)
    public List&lt;OrderSimpleQueryDto&gt; ordersV4() {
        return orderSimpleQueryRepository.findOrderDtos();
    }
    ...</code></pre><p>이런 식으로 새로운 repository 를 추가해서, Controller 나 Service 계층에서 사용하는 방식을 선호하심!! (V4의 경우)</p>
<h2 id="정리">정리</h2>
<blockquote>
<p>조회 방식</p>
<ol>
<li>엔티티로 받아 DTO로 변환 후 전달 (V3)</li>
</ol>
<p>-- 리포지토리 재사용성 좋음
-- 개발이 단순해짐
-- fetch join 활용 가능</p>
<ol start="2">
<li>DTO로 받아 그대로 전달 (V4)</li>
</ol>
<p>-- 하게 된다면!
-- 새로운 독립된 repository 를 만들어서, 주입시키는 것을 추천!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[til_230411 ~ 12]]></title>
            <link>https://velog.io/@world_changer/til230411</link>
            <guid>https://velog.io/@world_changer/til230411</guid>
            <pubDate>Tue, 11 Apr 2023 12:29:42 GMT</pubDate>
            <description><![CDATA[<h1 id="express-routing">Express Routing</h1>
<p>&lt; user.js &gt;</p>
<pre><code>const express = require(&quot;express&quot;);

const router = express.Router();

router.get(&quot;/:id&quot;, (req, res) =&gt; {
  res.send(&quot;유저 조회&quot;);
});

module.exports = router;</code></pre><p>&lt; app.js &gt;</p>
<pre><code>const express = require(&quot;express&quot;);
const userRouter = require(&quot;./routes/user&quot;);

const app = express();

const port = 3010;

app.use(&quot;/user&quot;, userRouter);

app.get(&quot;/&quot;, (req, res) =&gt; {
  res.send(&quot;Hello, Express!&quot;);
});

app.listen(port, () =&gt; {
  console.log(`Server listening on port: ${port} 🚀🚀🚀`);
});</code></pre><blockquote>
<p>Middle ware</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/b8b924dc-0e92-4305-8efd-b631b9191e07/image.png" alt=""></p>
<blockquote>
<p>요즘은 서버 짜놓으면, 알아서 문서화 까지 해주는 패키지가 있다고 함!!</p>
</blockquote>
<h2 id="실시간-개발-모드">실시간 개발 모드</h2>
<blockquote>
<p>nodemon &lt;= <code>npm i nodemon</code></p>
</blockquote>
<p>&lt; package.json &gt;
<img src="https://velog.velcdn.com/images/world_changer/post/50652639-b047-4868-8ab2-abd739c9ad9c/image.png" alt=""></p>
<p><code>npm run dev</code> 로 실행하면, 파일 바뀔 때마다 서버 재시작!</p>
<blockquote>
<p>json post 요청을 받지 못함!
-&gt; <code>app.use(express.json());</code> 을 추가해주어, json 을 읽을 수 있도록 세팅!!</p>
</blockquote>
<h2 id="오류-보내기">오류 보내기</h2>
<pre><code>  if (parseInt(id) &gt;= todoData.length) {
    res.status(400).json({ error: &quot;존재하지 않는 ID입니다.&quot; });
  }</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/c77d193d-883a-471f-a6f6-4de86dc3291a/image.png" alt=""></p>
<h2 id="포트">포트</h2>
<blockquote>
<p>포트 번호는 공용 번호를 제외하고는(http, https 등) 커스텀하게 사용해도 됨!!</p>
</blockquote>
<h2 id="기타">기타</h2>
<p>&lt;package.json&gt; 에 </p>
<pre><code>&quot;scripts&quot;: {
    &quot;start&quot;: &quot;node app.js&quot;,
    &quot;dev&quot;: &quot;nodemon app.js&quot;,
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;
  },</code></pre><h3 id="sop-same-origin-policy">SOP (Same-Origin Policy)</h3>
<ul>
<li>같은 출처를 가진 곳에서만 리소스를 요청할 수 있는 정책</li>
</ul>
<h3 id="cors-cross-origin-resoure-sharing">CORS (Cross-Origin Resoure Sharing)</h3>
<ul>
<li>교차 출처(다른 출처) 간의 리소스를 공유할 수 있는 정책</li>
<li>서버 &lt;-&gt; 브라우저 간에 check를 함</li>
<li>서버 &lt;-&gt; 서버 간에는 따로 check 하지 않음
(포스트맨에서는 잘 됨)</li>
</ul>
<blockquote>
<p>브라우저에서 해결을 해버리면, 문을 활짝 열어버리게 됨.
따로 웹사이트를 올릴때, 정책 추가해주기!</p>
</blockquote>
<p>CORS 라이브러리 설치 후 , Express 미들웨어로 추가!</p>
<h1 id="뜬금-덧">뜬금 덧</h1>
<p><img src="https://velog.velcdn.com/images/world_changer/post/b939d024-6427-41ed-aad7-119796d1eace/image.png" alt=""></p>
<p>포커 아웃츠 남았을때, 확률계산하는 알고리즘 진행중...
<img src="https://velog.velcdn.com/images/world_changer/post/06d3dcfe-c374-47ae-a975-7734db3bd347/image.png" alt="">
<img src="https://velog.velcdn.com/images/world_changer/post/8db544ec-d93e-4c16-a95a-516488839e26/image.png" alt=""></p>
<p>식을 대충 요약하자면, 나올 수 있는 모든 커뮤니티 카드를 <code>potentialCommunityCards</code> 에 담아서, 그 모든 경우마다 누가 이기는지를 리스트에 저장한다.</p>
<blockquote>
<p>핸드가 2명일때 프리플랍 : (52 - 4) C 5 : 1712304
핸드가 3명일때 프리플랍 : (52 - 6) C 5 : 1370754
핸드가 9명일때 프리플랍 : (52 - 18) C 5 : 278256</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/62373ee8-4f9d-4517-8828-582a51e1d5af/image.png" alt=""></p>
<p><code>calculatePokerRank</code> 라는 함수는 7장의 카드를 받아, 족보/카드를 출력해준다!</p>
<blockquote>
<ul>
<li>calculatePokerRank 호출 회수
핸드가 2명일때 프리플랍 : (52 - 4) C 5 : 1712304 * 2 : 3424608
핸드가 3명일때 프리플랍 : (52 - 6) C 5 : 1370754 * 3 : 4112262
핸드가 4명일때 프리플랍 : (52 - 8) C 5 : 1086008 * 4 : 4344032
핸드가 9명일때 프리플랍 : (52 - 18) C 5 : 278256 * 9 : 2504304</li>
</ul>
</blockquote>
<p>대략적으로 약 300 - 400만회의 calculatePokerRank 함수 호출이 일어난다!
평균 25초가 걸리는데... 이건 말이 안됨!!</p>
<p><a href="https://www.cardplayer.com/poker-tools/odds-calculator/texas-holdem">https://www.cardplayer.com/poker-tools/odds-calculator/texas-holdem</a>
<a href="https://www.pokernews.com/poker-tools/poker-odds-calculator.htm">https://www.pokernews.com/poker-tools/poker-odds-calculator.htm</a>
<img src="https://velog.velcdn.com/images/world_changer/post/db32c375-50cc-4428-a6f5-8a8f9e69a5d9/image.png" alt=""></p>
<p>이런 사이트에서는 1초도 안걸려서 해주는데!!</p>
<h2 id="노력-1-reduce-같은-함수가-오래걸리지-않을까">노력 1. reduce 같은 함수가 오래걸리지 않을까?</h2>
<ul>
<li>포카드, 풀하우스, 트리플 확인할때,
numList로 변환한뒤에, 각 개수를 확인하기 위해 reduce 함수를 쓰는데, 그럴 가치 조차 없는 카드들은 배제하기 (카드 개수가 많으면 뜰수가 없는!)
<img src="https://velog.velcdn.com/images/world_changer/post/3cf5d63c-57a7-4bc1-bf68-77fb96cb11bf/image.png" alt=""></li>
</ul>
<blockquote>
<p>택도 없네... 여전히 25초대</p>
</blockquote>
<h2 id="노력-2-straight-에-쓸데없는게-많다">노력 2. straight 에 쓸데없는게 많다</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/14e5f324-df7f-4d40-8dd7-0c1170545dcd/image.png" alt="">
5,4,3,2,a 스트레이트를 확인하기 위해, 만든 함수 <code>getBottomStraight</code> 가 쓸데없이 많이 도는 것 같아,
<code>a,2,3,4,5</code> 를 가지고 있을때만 실행하도록 함!
<img src="https://velog.velcdn.com/images/world_changer/post/3358ff0a-1d9d-4406-a759-9e62c8956a4e/image.png" alt="">
미미하지만 효과는 있는 것 같다!!</p>
<h2 id="노력-3-reduce-함수의-중복">노력 3. reduce 함수의 중복</h2>
<p>포카드, 풀하우스, 트리플, 투페어, 원페어
5개의 함수에서 reduce가 쓰이고 있다. 
하나로 만들어서, 넘기면?
노력1은 수포로 돌아가지만, 효과는 있어보이는 느낌적인...?
<img src="https://velog.velcdn.com/images/world_changer/post/334c4132-309c-4c43-b592-a1cf6e3ef844/image.png" alt=""></p>
<p>흠 이게 효과가 있다고 해야할까? 진짜아 쬐끔 줌....</p>
<h2 id="노력4-list-에-몇백만개-쌓이는게-느려지나">노력4. list 에 몇백만개 쌓이는게 느려지나?</h2>
<ul>
<li>쌓지 말고, 계속 더해보자!
<img src="https://velog.velcdn.com/images/world_changer/post/0ecab418-b2c2-49f5-9b52-a02686556549/image.png" alt=""></li>
</ul>
<blockquote>
<p>이전</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/a19d22f7-81df-44bc-87ab-fd07067f1066/image.png" alt=""></p>
<blockquote>
<p>후</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/9ddb1a34-540e-43a2-ba5b-402ec5ffd773/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/d0f80dbb-8e49-4d18-a2c7-d83eacc52548/image.png" alt="">
흠... 미미하다... 쬐금...</p>
<h2 id="노력5-7장의-카드에-대한-filter가-너무-많음">노력5. 7장의 카드에 대한 filter가 너무 많음</h2>
<p>순서 : </p>
<ol>
<li>스트레이트</li>
<li>플러시</li>
<li>포카드</li>
<li>풀하우스</li>
<li>트리플</li>
<li>투페어</li>
<li>원페어</li>
</ol>
<p>각 모든 경우에, 배열값을 리턴함에 있어, 계속 filter 함수가 실행됨.
cards를 다 넣지 말고, 숫자만 넣고, filter 를 쓰지 않는 걸로!</p>
<blockquote>
<p>스트레이트만 바꿔줬는데!!
<img src="https://velog.velcdn.com/images/world_changer/post/485d93a3-8076-4e55-b849-fe030ffdf9b9/image.png" alt=""></p>
</blockquote>
<p>에라라아ㅏㅇ아 모르곘다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[til_230410]]></title>
            <link>https://velog.io/@world_changer/til230410</link>
            <guid>https://velog.io/@world_changer/til230410</guid>
            <pubDate>Mon, 10 Apr 2023 08:36:01 GMT</pubDate>
            <description><![CDATA[<h1 id="rest-api">Rest Api</h1>
<blockquote>
<p><strong>* REpresentational State Transfer *</strong>
이렇게 쓰는 편 ( 자원의 이름과 전달 방식만으로 해당역할 추론 가능 )</p>
</blockquote>
<ul>
<li>조회 : Get</li>
<li>생성 : Post</li>
<li>업데이트 : Put</li>
<li>삭제 : Delete</li>
</ul>
<h2 id="rest-api-특징">REST API 특징</h2>
<ul>
<li>Server - Client 구조로 되어 있습니다.</li>
<li>자윈을 가지고 있는 쪽이 서버, 제공받는 쪽이 클라이언트가 됩니다.</li>
<li>Stateless(무상태)입니다.</li>
<li>HTTP 프로토콜을 사용하므로 HTTP 프로토콜처럼 무상태성을 가지고 있습니다.</li>
<li>클라이언트의 context를 서버에 저장하지 않습니다. (서버는 신경쓸 것 없이 본연에 업무에만 집중하면 됩니다.)</li>
<li>서버는 각각에 요청에 대한 응답만 하면 됩니다.</li>
</ul>
<h2 id="rest-api의-장-단점">Rest API의 장, 단점</h2>
<p><img src="https://velog.velcdn.com/images/world_changer/post/8467a07c-88be-4221-a181-503044eb4bf4/image.png" alt=""></p>
<h2 id="실습">실습</h2>
<blockquote>
<ul>
<li>Axios - http 통신을 위한 라이브러리 <code>npm install axios</code></li>
<li>Insomnia (PostMan) - rest api 테스트 툴</li>
<li>(번외) React Icons <code>npm install react-icons</code></li>
</ul>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/35edaa50-31db-48cf-978d-e61f3aecfae2/image.png" alt="">
이런 식으로 react-icons 불러올 수 있음!
<img src="https://velog.velcdn.com/images/world_changer/post/24e6c5a3-3756-4244-95d6-b1b73f956345/image.png" alt=""></p>
<h3 id="async-await">async, await</h3>
<blockquote>
<p>Geolocation 사용!
useEffect 에서 사용되는 함수 자체를 <code>async</code> 로 할 수 없다.</p>
</blockquote>
<pre><code>useEffect(async () =&gt; {
    await .... // 이런 방식은 동작하지 않음
}, []);</code></pre><pre><code>const getGeolocation = async () =&gt; {};

useEffect(async () =&gt; {
    getGeolocation(); // 이런식으로 사용이 되어야함.
}, []);</code></pre><ul>
<li><code>await</code> 같은 비동기 함수를 활용할때는, <code>try {} catch(e) {}</code> 을 감싸는 것이 좋음</li>
<li>api 서버에서 오류를 주거나, 무한 로딩에 빠지는 경우 등을 <code>catch</code> 해서 처리해줘야함!</li>
</ul>
<pre><code>const getGeolocation = async () =&gt; {
    try{
        navigator.geolocation.getCurrentPosition((position) =&gt; {
            setLat(position.coords.latitude);
            setLon(position.coords.longitude);
        },
        () =&gt; {
            // 에러 콜백
            alert(&quot;위치 정보에 동의 해주셔야 합니다.&quot;);
        });
    } catch (e) {
           console.error(e); // error message
    }    
};</code></pre><pre><code>const getWeatherInfo = (_lat, _lon) =&gt; {
    ...
    return &lt;날씨정보&gt;;
};</code></pre><blockquote>
<p>함수는 최대한 캡슐화 시켜주는 것이 좋음!!</p>
</blockquote>
<h3 id="promise">Promise</h3>
<pre><code>function checkPromise() {
  let promise = new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; resolve(&quot;완료!&quot;), 1000);
  });

  console.log(&quot;1초 대기&quot;);
  let result = promise;

  console.log(result);
}

checkPromise();</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/06c135de-f2ea-44bd-a44a-9a1867936aae/image.png" alt=""></p>
<pre><code>async function checkPromise() {
  let promise = new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; resolve(&quot;완료!&quot;), 1000);
  });

  console.log(&quot;[await] 1초 대기&quot;);
  let result = await promise;

  console.log(result);
}
checkPromise();
</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/e0a600da-c1ca-4981-85be-fad52ba06995/image.png" alt=""></p>
<p><code>Promise</code> 함수 사용법!</p>
<ul>
<li><code>resolve</code> 콜백함수에, value를 넣어서 반환값 지정</li>
<li><code>reject</code> 콜백함수에, value 넣어서 예외때 반환값</li>
</ul>
<h3 id="api-key-값-보안">api key 값 보안</h3>
<blockquote>
<p>API KEY 같은 경우는, 외부로 유출되면 안되는 값이므로, 밖에서 보이지 않게 관리하는 것이 좋음</p>
</blockquote>
<pre><code>const getWeatherInfo = async () =&gt; {
    try {
      await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&amp;lon=${lon}&amp;appid=${process.env.REACT_APP_WEATHER_API}&amp;units=metric`
      );
    } catch (error) {
      console.error(error);
    }
  };</code></pre><p><code>process.env.REACT_APP_WEATHER_API</code> 를 통해 외부에서 입력 가능!</p>
<p><img src="https://velog.velcdn.com/images/world_changer/post/75656c99-ac7d-4e48-92a4-5c08ff4b9bcf/image.png" alt=""></p>
<p>&lt; .env &gt;</p>
<pre><code>REACT_APP_WEATHER_API=&lt;YOUR_API_KEY&gt;</code></pre><blockquote>
<p>주의! <code>REACT_APP</code> 을 앞에 꼭 붙혀주어야함!</p>
<ul>
<li>.gitignore 파일에 <code>.env</code> 를 추가해주어야함!! (숨기는 의미가 없어짐)
<img src="https://velog.velcdn.com/images/world_changer/post/02f9f0d4-9f34-4b81-beaa-6d4dbaae345d/image.png" alt=""></li>
</ul>
</blockquote>
<h3 id="api-response-error-catch">API response error catch</h3>
<pre><code>    const response = await axios.get(&lt;Get 요청 Url&gt;);

    if (response.status !== 200) {
        alert(&quot;날씨 정보를 가져오지 못했습니다.&quot;);
        return;
    }</code></pre><blockquote>
<p>보통 data가 없을때 2xx 서버 오류를 많이 만들어낸다!
weather의 경우! 200!</p>
</blockquote>
<h2 id="api-테스트-툴">API 테스트 툴</h2>
<blockquote>
<p>PostMan, Insomnia 같은 것들로 ㄱㄱ</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/da7d2c45-e642-4e26-ab32-90a6cb177a33/image.png" alt=""></p>
<pre><code>const response = await axios.post(&quot;https://holy-fire-2749.fly.dev/chat&quot;,
      { question: &quot;안녕?&quot;, },
      { headers: { Authorization: &quot;Bearer BLOCKCHAINSHCOOL3&quot; }
  });</code></pre><p>코드로는 이런 형태를 띈다!</p>
<h1 id="chat-gpt-part">Chat GPT part</h1>
<p>&lt; Card.jsx &gt;</p>
<pre><code>const Chat = () =&gt; {
  const [question, setQuestion] = useState(&quot;&quot;);
  const [content, setContent] = useState(&quot;&quot;);
  const [isLoading, setIsLoading] = useState(false);

  const onSubmitChat = async (e) =&gt; {
    try {
      e.preventDefault();

      if (isLoading) {
        alert(&quot;검색중입니다...&quot;);

        return;
      }
      if (!question) {
        alert(&quot;질문을 입력해주세요.&quot;);

        return;
      }

      setIsLoading(true);
      setContent(&quot;&quot;);

      const response = await axios.post(
        &quot;https://holy-fire-2749.fly.dev/chat&quot;,
        {
          //   question: `${question}`,
          //   question: question,
          question,
        },
        {
          headers: {
            Authorization: &quot;Bearer BLOCKCHAINSCHOOL3&quot;,
          },
        }
      );

      if (response.status !== 200) {
        alert(&quot;오류가 발생했습니다.&quot;);
        setIsLoading(false);

        return;
      }

      console.log(response);
      setContent(response.data.choices[0].message.content);

      setIsLoading(false);
    } catch (error) {
      console.error(error);

      setIsLoading(false);
    }
  };

  return (
    &lt;div className=&quot;bg-black min-h-screen flex flex-col justify-center items-center text-white&quot;&gt;
      &lt;form onSubmit={onSubmitChat}&gt;
        &lt;input
          className=&quot;text-black&quot;
          type=&quot;text&quot;
          value={question}
          onChange={(e) =&gt; setQuestion(e.target.value)}
        /&gt;
        &lt;input type=&quot;submit&quot; value=&quot;검 색&quot; /&gt;
      &lt;/form&gt;
      {content &amp;&amp; &lt;div className=&quot;mt-4 px-16&quot;&gt;{content}&lt;/div&gt;}
    &lt;/div&gt;
  );
};

export default Chat;</code></pre><blockquote>
<p><code>preventDefault()</code> 는 <code>Enter</code> 를 눌렀을때 자동으로 submit이 실행되는 것을 막기 위해!!
<code>Enter</code> 를 누르면, 자동으로 submit 이 되는 것이 default</p>
</blockquote>
<h2 id="백엔드-node-js-세팅">백엔드 Node js 세팅</h2>
<p><code>npm install express --save</code></p>
<p>&lt; package.json &gt;</p>
<pre><code>{
  &quot;name&quot;: &quot;node_test&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;changer&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;
  },
  &quot;author&quot;: &quot;changer&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^4.18.2&quot;
  }
}</code></pre><blockquote>
<p>Module System 이란?</p>
<ul>
<li>모듈 시스템 등장 이전의 자바스크립트 개발은
<code>&lt; script src=&#39;…&#39; &gt;</code> 로 불러와야 했습니다.</li>
<li>모듈 시스템의 등장으로 보기에 직관적이고 효율성 있게 됨</li>
<li>가장 많이 쓰이는 <code>ES6</code> 와 <code>CommonJS</code></li>
<li>우리는 <code>CommonJS</code> 로 사용</li>
</ul>
</blockquote>
<p>&lt; a.js &gt;</p>
<pre><code>function goModule() {
  console.log(&quot;Hello, Module!&quot;);
}

// export default goModule;
module.exports = goModule;</code></pre><p>&lt; b.js &gt;</p>
<pre><code>const goModule = require(&quot;./a&quot;);

goModule();</code></pre><p><code>node b.js</code> 입력
<img src="https://velog.velcdn.com/images/world_changer/post/0a64b0a3-acc7-43d8-8cf0-c97d65241666/image.png" alt=""></p>
<h2 id="서버-세팅">서버 세팅</h2>
<p><code>CommonJS</code> 방식으로, express 를 불러오기
&lt; app.js &gt;</p>
<pre><code>const express = require(&quot;express&quot;);

const app = express(); // get express

const port = 3010; // server port num

app.get(&quot;/&quot;, (req, res) =&gt; {
  res.send(&quot;Hellow, Express!!&quot;);
}); // handler

app.listen(port, () =&gt; {
  console.log(`Server listening on port ${port}`);
});</code></pre><blockquote>
<p>get 요청 <code>(req, res)</code> 파라미터를 받아, 요청을 처리함!</p>
</blockquote>
<p><code>node app.js</code> 로 실행
<img src="https://velog.velcdn.com/images/world_changer/post/f84e9c00-fbce-481a-97f9-fc3c241e83d6/image.png" alt=""></p>
<ul>
<li>post의 경우! <pre><code>app.post(&quot;/&quot;, (req, res) =&gt; {
res.send(&quot;This is Post Request&quot;);
});</code></pre></li>
</ul>
<p><img src="https://velog.velcdn.com/images/world_changer/post/55bc9dba-59e2-49c3-9dd0-8a93f041b522/image.png" alt=""></p>
<ul>
<li>그 외
<img src="https://velog.velcdn.com/images/world_changer/post/16c758ab-d5a2-42ba-8487-362ce2d0d4b3/image.png" alt=""></li>
</ul>
<blockquote>
<p>한번더 리마인드 </p>
<ul>
<li>조회 : Get</li>
<li>생성 : Post</li>
<li>업데이트 : Put</li>
<li>삭제 : Delete</li>
</ul>
</blockquote>
<ul>
<li>url path<pre><code>app.get(&quot;/abc&quot;, (req, res) =&gt; {
res.send(&quot;Hello, ABC ABC!!&quot;);
});</code></pre><img src="https://velog.velcdn.com/images/world_changer/post/23ead65f-a3ea-43fe-8542-fa003b533ad3/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[react_230331]]></title>
            <link>https://velog.io/@world_changer/react230331</link>
            <guid>https://velog.io/@world_changer/react230331</guid>
            <pubDate>Mon, 10 Apr 2023 07:54:02 GMT</pubDate>
            <description><![CDATA[<h1 id="가상-dom">가상 DOM</h1>
<ul>
<li>Document Object Model (DOM)</li>
<li>html을 javascript 모델로 만든 것</li>
<li>리액트에서는 직접적으로 DOM을 사용하지 않음</li>
<li>리액트에서는 상태관리가 중요함! (useContext, Redux, SWR ...)</li>
</ul>
<h2 id="usestate">useState()</h2>
<ul>
<li>useState를 사용하지 않는다면!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[jpa-spring boot 실습 - 4]]></title>
            <link>https://velog.io/@world_changer/jpa-spring-boot-%EC%8B%A4%EC%8A%B5-4</link>
            <guid>https://velog.io/@world_changer/jpa-spring-boot-%EC%8B%A4%EC%8A%B5-4</guid>
            <pubDate>Sun, 09 Apr 2023 12:10:10 GMT</pubDate>
            <description><![CDATA[<h1 id="order">Order</h1>
<ul>
<li><p>order 같은 복잡한 class 같은 경우는 별도의 생성 메소드가 있으면 좋음</p>
<pre><code>@Entity
@Table(name = &quot;orders&quot;)
@Getter @Setter
public class Order {
  @Id @GeneratedValue
  @Column(name = &quot;order_id&quot;)
  private Long id;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = &quot;member_id&quot;)
  private Member member;

  @OneToMany(mappedBy = &quot;order&quot;, cascade = CascadeType.ALL)
  private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;OrderItem&gt;();

  // 주로 접근하는 것에 FK (Foreign 키를 두는 편) &lt;- 연관관계 주인
  @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  @JoinColumn(name = &quot;delivery_id&quot;)
  private Delivery delivery;

  private LocalDateTime orderDate;

  @Enumerated(EnumType.STRING)
  private OrderStatus status;

  // == 연관관계 편의 메서드 ==  //
  public void setMember(Member member){
      this.member = member;
      member.getOrders().add(this);
  }

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

  public void setDelivery(Delivery delivery){
      this.delivery = delivery;
      delivery.setOrder(this);
  }
}</code></pre></li>
</ul>
<pre><code>   //== 생성 메서드 ==//
   public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems){ 
       Order order = new Order();
       order.setMember(member);
       order.setDelivery(delivery);
       for (OrderItem orderItem : orderItems) {
           order.addOrderItem(orderItem);
       }
       order.setStatus(OrderStatus.ORDER);
       order.setOrderDate(LocalDateTime.now());

       return order;
   }</code></pre><p>여러개 받아올 수 있을때, <code>...</code> 을 붙혀주면 가능함!!
<img src="https://velog.velcdn.com/images/world_changer/post/16510681-a7bc-4b97-9179-822d2b9c3262/image.png" alt=""></p>
<blockquote>
<ul>
<li>생성 메소드로만 인스턴스 생성이 가능하도록 하고 싶으면!!</li>
</ul>
<ol>
<li><code>protected</code></li>
</ol>
</blockquote>
<pre><code>protected Order(){}</code></pre><blockquote>
<p>이렇게 생성자를 protected 로 지정해두면, 생성 메소드안에서만 가능하게 됨!</p>
<ol start="2">
<li><code>@NoArgsConstructor(access = AccessLevel.PROTECTED)</code>
lombok 의 어노테이션을 활용하면, 외부에서 인스턴스 생성 불가능!</li>
</ol>
</blockquote>
<pre><code>    //== 비즈니스 로직 ==//

    /**
     * 주문 취소
     */
    public void cancel() {
        if(delivery.getStatus() == DeliveryStatus.COMP){
            throw new IllegalStateException(&quot;이미 배송완료된 상품은 취소가 불가능합니다.&quot;);
        }
        this.setStatus(OrderStatus.CANCEL);
        for (OrderItem orderItem : orderItems) {
            orderItem.cancel();
        }
    }</code></pre><pre><code>    //== 조회 로직 ==//

    /**
     * 전체 주문 가격 조회
     */
    public int getTotalPrice(){
        return orderItems.stream().mapToInt(OrderItem::getTotalPrice).sum();
//        똑같이 동작
//        int totalPrice = 0;
//        for (OrderItem orderItem : orderItems) {
//            totalPrice += orderItem.getTotalPrice();
//        }
//        return totalPrice;
    }</code></pre><h2 id="orderservice">OrderService</h2>
<pre><code>@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class OrderService {
    private final OrderRepository orderRepository;
    private final MemberRepository memberRepository;
    private final ItemRepository itemRepository;

    // 주문
    /**
     * 주문
     */
    @Transactional
    public Long order(Long memberId, Long itemId, int count){
        // 엔티티 조회
        Member member = memberRepository.findOne(memberId);
        Item item = itemRepository.findOne(itemId);

        // 배송정보 생성
        Delivery delivery = new Delivery();
        delivery.setAddress(member.getAddress());

        // 주문상품 생성
        OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);

        // 주문 생성
        Order order = Order.createOrder(member, delivery, orderItem);

        // 주문 저장
        orderRepository.save(order);

        return order.getId();
    }

    // 취소

    // 검색
}</code></pre><blockquote>
<p>OrderService 의 경우 <code>MemberRepository, ItemRepository</code> 가 추가로 필요함. final field 로 지정하여, @RequiredArgsConstructor 에 의해 생성될때 주입 받음</p>
</blockquote>
<p>위 기능에서 <code>orderRepository.save(order)</code> 만으로 되는 이유!!
<img src="https://velog.velcdn.com/images/world_changer/post/57cc2680-b196-4d61-af21-c0041e6184af/image.png" alt=""></p>
<p>Order.java -&gt; <code>CascadeType.ALL</code> 으로 지정해두었기 때문에 Order 만 영속성 컨텍스트에 persist 하더라도,
OrderItem, Delivery는 자동으로 따라 올라감!!</p>
<p>Order 가 두 객체 상대로 확실하게 private owner 포지션이기 때문에 가능!!</p>
<h2 id="도메인-모델-패턴">도메인 모델 패턴</h2>
<blockquote>
<p>비즈니스 로직 대부분이 엔티티에 있음!
서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다.</p>
<ul>
<li>이처럼 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 <code>도메인 모델 패턴</code> 이라고 함</li>
<li>반대로 엔티티에는 비즈니스 로직이 거의 없고, 서비스 계층에서 대부분의 비즈니스 로직을 처리하는 것을
<code>트랜잭션 스크립트 패턴</code> 이라고 함</li>
</ul>
</blockquote>
<p>==&gt; 한 프로젝트 안에서도, 문맥에 따라 두개가 공존하기도 한다!!</p>
<h1 id="test-code">Test Code</h1>
<blockquote>
<p>참고! 정말로 좋은 Test Code는 운영 코드와 완전히 분리되어 비즈니스 로직들만 테스트 할 수 있는 것!!
지금의 경우, spring 이나 jpa 가 함께 종속되어 있으므로, 완벽하게 좋은 Test Code 라고 할 수는 없다.</p>
</blockquote>
<pre><code>    ...
    @Test
    public void 상품주문() throws Exception {
        // given
        Member member = getMember();
        Book book = getBook();

        // when
        int orderCount = 2;
        Long orderId = orderService.order(member.getId(), book.getId(), orderCount);

        // then
        Order getOrder = orderRepository.findOne(orderId);

        assertEquals(&quot;상품 주문시 상태는 ORDER&quot;, OrderStatus.ORDER, getOrder.getStatus());
        assertEquals(&quot;주문한 상품 종류 수가 정확해야 한다.&quot;, 1, getOrder.getOrderItems().size());
        assertEquals(&quot;주문한 가격은 가격 * 수량 이다.&quot;, 10000 * orderCount, getOrder.getTotalPrice());
        assertEquals(&quot;주문 수량만큼 재고가 줄어야 한다.&quot;, 8, book.getStockQuantity());
    }
    ...
    private Book getBook() {
        Book book = new Book();
        book.setName(&quot;시골 JPA&quot;);
        book.setPrice(10000);
        book.setStockQuantity(10);
        em.persist(book);
        return book;
    }

    private Member getMember() {
        Member member = new Member();
        member.setName(&quot;member1&quot;);
        member.setAddress(new Address(&quot;서울&quot;, &quot;강가&quot;, &quot;123-123&quot;));
        em.persist(member);
        return member;
    }
    ...</code></pre><blockquote>
<p><code>ctrl + alt + p</code> 하면, 함수 내 값을 parameter 로 받도록 자동 세팅!!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/world_changer/post/8cb7edb1-7788-4fc5-a3dc-bcdfec6062b8/image.png" alt=""></p>
<h2 id="재고-초과-에러">재고 초과 에러</h2>
<pre><code>    @Test(expected = NotEnoughStockException.class)
    public void 상품주문_재고수량초과() throws Exception {
        // given
        Member member = getMember(&quot;member1&quot;);
        Book book = getBook(&quot;시골 JPA&quot;, 10000, 10);

        int orderCount = 11;

        // when
        orderService.order(member.getId(), book.getId(), orderCount);

        // then
        fail(&quot;여기가 실행이 되면 안된다!&quot;);
    }</code></pre><p><code>expected</code> 에 Exception 정의해주면, 메소드 종료</p>
<blockquote>
<p>실제 재고 수량 초과에 대한 알고리즘은 <code>Item.java</code> 파일에 있음</p>
</blockquote>
<pre><code>    public void removeStock(int quantity){
        int restStock = this.stockQuantity - quantity;
        if(restStock &lt; 0){
            throw new NotEnoughStockException(&quot;need more stock&quot;);
        }
        this.stockQuantity = restStock;
    }</code></pre><blockquote>
<p>이 메소드에 대한, 단위 테스트를 만드는 것이 좋음!!</p>
</blockquote>
<blockquote>
<ul>
<li>Entity 에 비즈니스 로직이 들어가 있으므로, Entity 단위로 테스트를 만드는 것도 괜찮다!!</li>
</ul>
</blockquote>
<h2 id="주문-검색-기능">주문 검색 기능</h2>
<ul>
<li>동적 쿼리가 필요한 상황
<code>OrderSearch.java</code> 를 만들어서, search 관련 파라미터들을 전달하는 용도로 사용.<pre><code>@Getter @Setter
public class OrderSearch {
  private String memberName; // 회원 이름
  private OrderStatus orderStatus; // 주문 상태 [ ORDER, CANCEL ]
}</code></pre>&lt; OrderRepository.java &gt;<pre><code>  public List&lt;Order&gt; findAll(OrderSearch orderSearch) {
      List&lt;Order&gt; resultList = em.createQuery(&quot;select o from Order o join o.member m&quot;
              + &quot; where o.status = :status &quot;
              + &quot; and m.name like :name&quot;, Order.class)
              .setParameter(&quot;status&quot;, orderSearch.getOrderStatus())
              .setParameter(&quot;name&quot;, orderSearch.getMemberName())
              .setMaxResults(1000)
              .getResultList();
      return resultList;
  }</code></pre>이처럼 orderSearch 를 받아서, jpql 생성가능!!<blockquote>
<p>여기서, memberName 이 null 이거나 orderStatus 가 null 인 경우도 처리해 주어야지!!!</p>
<ol>
<li>(권장 x) if 분 분기시켜서, jpql 구문 수정. query 수정</li>
<li>(권장 x) JPA Criteria 활용 =&gt; jpql / 쿼리 자동 생성 =&gt; 직관적이지 못함. 유지보수 힘듬</li>
<li>Query DSL [ 추후 제대로 ㄱㄱ ]</li>
</ol>
</blockquote>
</li>
</ul>
<p>동적 쿼리, 복잡한 jpql 문 등등 자바 문법으로 가시화 하기 위해, <code>Query DSL</code> 을 쓰는 것을 강추</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[jpa-spring boot - api 실습 (1)]]></title>
            <link>https://velog.io/@world_changer/jpa-spring-boot-api-%EC%8B%A4%EC%8A%B5-1</link>
            <guid>https://velog.io/@world_changer/jpa-spring-boot-api-%EC%8B%A4%EC%8A%B5-1</guid>
            <pubDate>Sun, 09 Apr 2023 12:00:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/world_changer/post/7be0126f-4b13-4bc1-b081-42be8990e183/image.png" alt=""></p>
<ul>
<li>이런식으로 api 패키지를 만들어서 따로 관리하는 편 (김영한 개발자님)</li>
<li><code>@Controller @ResponseBody</code> 로 쓰면 됨</li>
<li><code>@RestController</code> 로 쓰면 똑같이 기능!</li>
</ul>
<h1 id="memberapicontroller">MemberApiController</h1>
<h2 id="savememberv1">saveMemberV1</h2>
<pre><code>    @PostMapping(&quot;/api/v1/members&quot;)
    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member){
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }
    @Data
    static class CreateMemberResponse{
        private Long id;

        public CreateMemberResponse(Long id) {
            this.id = id;
        }
    }</code></pre><ul>
<li><p><code>@RequestBody</code> 는 json 으로 input 이 들어오면, 해당 내용을 자동으로 Member에 맞게 매핑해주는 역할!!</p>
</li>
<li><p><code>http://localhost:8080/api/v1/members</code> 로  post 요청을 하게 되면, 값이 반환 되는거 확인 가능!!</p>
</li>
<li><p><code>@Valid</code> 는 엔티티 속의 <code>@NotEmpty</code> 가 있는 필드가 있는지 확인을 해줌.</p>
</li>
</ul>
<blockquote>
<p>Entity 가 Api Controller 의 파라미터로 쓰이는 것은 좋지 않다!</p>
<ul>
<li>Entity 내부 코드 자체에 API function 이 섞이고 ( @NotEmpty 같은 )</li>
<li>Entity 수정에 api도 같이 영향을 받는다.</li>
</ul>
</blockquote>
<h2 id="savememberv2">saveMemberV2</h2>
<pre><code>    @PostMapping(&quot;/api/v2/members&quot;)
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request){
        Member member = new Member();
        member.setName(request.name);
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }
    @Data
    static  class CreateMemberRequest {
        @NotEmpty
        private String name;
    }</code></pre><ul>
<li>Entity 를 직접 쓰지않고, 따로 data 규약(DTO)을 만들어서 데이터를 왔다갔다!!</li>
<li>API 스펙이 Entity 영향을 받지 않음!</li>
</ul>
<h2 id="updatememberv2">updateMemberV2</h2>
<ul>
<li>수정의 경우, rest api 규약, 관례(?) 에 따라 <code>Put</code> 방식을 활용</li>
<li>url 에 id 를 받아오는 방식으로 ㄱㄱ!</li>
</ul>
<pre><code>    @PutMapping(&quot;/api/v2/members/{id}&quot;)
    public UpdateMemberResponse updateMemberV2(
            @PathVariable(&quot;id&quot;) Long id,
            @RequestBody @Valid UpdateMemberRequest request){
        memberService.update(id, request.getName());
        Member findMember = memberService.findOne(id);
        return new UpdateMemberResponse(findMember.getId(), findMember.getName());
    }</code></pre><ul>
<li>Member Entity를 내부에서 직접 사용하지 않고, memberService 로 파라미터 넘기기 (캡슐화)</li>
</ul>
<h2 id="member-조회-v1">member 조회 v1</h2>
<pre><code>    @GetMapping(&quot;/api/v1/members&quot;)
    public List&lt;Member&gt; membersV1(){
        return memberService.findMembers();
    }
</code></pre><p><img src="https://velog.velcdn.com/images/world_changer/post/42871f9b-7069-4c0f-a450-60fc48ce81fc/image.png" alt=""></p>
<ul>
<li>Entity를 직접 반환하면, 내부 정보들을 다 노출하게됨!!</li>
<li>원치 않은 정보에는 <code>@JsonIgnore</code> 을 넣어주면 된다. (+ 양방향 json 화 무한루프 방지)</li>
<li>회원과 관련된 다양한 API 가 있을텐데, 해당 기능마다 어노테이션을 달아주는 것이 어려움.</li>
<li>위와 마찬가지로, Entity 변경에 API 스펙이 변경된다!</li>
</ul>
<h2 id="member-조회-v2">member 조회 v2</h2>
<pre><code>    @GetMapping(&quot;/api/v2/members&quot;)
    public Result memberV2(){
        List&lt;Member&gt; findMembers = memberService.findMembers();

        List&lt;MemberDTO&gt; collect = findMembers.stream()
            .map(m -&gt; new MemberDTO(m.getName()))
            .collect(Collectors.toList());
        return new Result(collect);
    }

    @Data
    @AllArgsConstructor
    static class Result&lt;T&gt; {
        private T data;
    }
    @Data
    @AllArgsConstructor
    static class MemberDTO {
        private String name;
    }</code></pre><blockquote>
<p><code>@AllArgsConstructor</code> 는 class 내부 필드를 생성때 바로 받을 수 있게 해줌.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[jpa-spring boot 실습 - 5]]></title>
            <link>https://velog.io/@world_changer/jpa-spring-boot-%EC%8B%A4%EC%8A%B5-5</link>
            <guid>https://velog.io/@world_changer/jpa-spring-boot-%EC%8B%A4%EC%8A%B5-5</guid>
            <pubDate>Sun, 09 Apr 2023 06:49:13 GMT</pubDate>
            <description><![CDATA[<ul>
<li>Logger 를 사용할때는 <code>import org.slf4j.Logger;</code> 를 사용할 것</li>
<li><code>@Slf4j</code> lombok 에서 해당 어노테이션 지원! 바로 코드에서 log 로 사용 가능!</li>
<li>Spring boot 에서 지원해주는 것 : re-run 을 하지 않고, <code>ctrl + shift + F9</code> 으로 즉시 컴파일 가능!</li>
</ul>
<h1 id="웹-계층-세팅">웹 계층 세팅</h1>
<ul>
<li>boot strap 주입
<img src="https://velog.velcdn.com/images/world_changer/post/dc845279-5c06-471a-9300-693760e3cb30/image.png" alt=""></li>
</ul>
<ul>
<li>각종 html 파일 세팅
<img src="https://velog.velcdn.com/images/world_changer/post/3ea81e3d-c7fb-4545-92a8-d26356b1a848/image.png" alt=""></li>
</ul>
<blockquote>
<p><code>ctrl + E</code> 최근에 썼던 명령어, 리소스 뜸!
<code>ctrl + alt + shift + T</code> 리팩토링 관련 추천!
<code>ctrl + shift + U</code> 해당 문자 upper case 로 변환</p>
</blockquote>
<h1 id="변경-감지와-병합-merge">변경 감지와 병합 (merge)</h1>
<h2 id="변경-감지-dirty-checking">변경 감지 (dirty checking)</h2>
<p>영속성 컨텍스트에 올라와 있는 엔티티 에 대해서는, 어떤 변경들이 일어나더라도,
EntityManager 가 flush 되는 시점에 감지를 하여 sql 문이 날아간다.</p>
<blockquote>
<p>but, 준영속 엔티티는 어떻게 해야하나?</p>
<ul>
<li>영속성 컨텍스트가 더는 관리하지 않는 엔티티</li>
<li>하지만, jpa DB를 한번 거쳐서, 식별자는 가지고 있는 경우!</li>
<li>임의로 만든 엔티티 여도, 기존 식별자를 가지고 있으면, <code>준영속 엔티티</code> 로 볼 수 있다.<blockquote>
<p>직접적으로 JPA 가 관리를 하지 않기에, 수정 이후 영속성 컨텍스트에 올려주어야함.
다른 방법은 없나? HOW?</p>
<ul>
<li><ol>
<li>변경 감지 기능</li>
</ol>
</li>
<li><ol start="2">
<li>병합 (merge) 사용</li>
</ol>
</li>
</ul>
</blockquote>
</li>
</ul>
</blockquote>
<h3 id="준영속-경우-업데이트-기존">준영속 경우 업데이트 기존</h3>
<pre><code>    @PostMapping(&quot;items/{itemId}/edit&quot;)
    public String updateItem( @PathVariable String itemId, @ModelAttribute(&quot;form&quot;) BookForm form){
        Book book = new Book();
        book.setId(form.getId());
        book.setName(form.getName());
        book.setPrice(form.getPrice());
        book.setStockQuantity(form.getStockQuantity());
        book.setAuthor(form.getAuthor());
        book.setIsbn(form.getIsbn());

        itemService.saveItem(book); // 영속성 컨텍스트 등록!
        return &quot;redirect:/items&quot;;
    }</code></pre><h3 id="1-변경-감지-활용">1. 변경 감지 활용</h3>
<ul>
<li>Service 계층에서 호출하면서, 영속성 컨텍스트에 올리고,</li>
<li>마음껏 수정 (flush 때 인식하여, db sql 문 날림)<pre><code>  @PostMapping(&quot;items/{itemId}/edit&quot;)
  public String updateItem( @PathVariable String itemId, @ModelAttribute(&quot;form&quot;) BookForm form){
      itemService.updateItem(itemId, form); // Service 계층에 메소드 추가
      return &quot;redirect:/items&quot;;
  }</code></pre>&lt; ItemService.java &gt;<pre><code>  @Transactional
  public void updateItem(Long itemId, BookForm form){
      Book book = (Book) itemRepository.findOne(itemId);
      book.setName(form.getName());
      book.setPrice(form.getPrice());
      book.setStockQuantity(form.getStockQuantity());
      book.setAuthor(form.getAuthor());
      book.setIsbn(form.getIsbn());
  }</code></pre><blockquote>
<p>이 부분도, 하나하나 setter 로 바꾸는 것 보다는,  book 단의 Set Method 를 따로 만들어서, 하는 것을 추천!
우리 협업 좀 하자!! 몇달 뒤 너를 위해서라도! 동료 파트너를 위해서라도!</p>
</blockquote>
</li>
</ul>
<pre><code>    @Transactional
    public void updateItem(Long itemId, BookForm form){
        Book book = (Book) itemRepository.findOne(itemId);
        book.changeWholeField(form.getName(),
                              form.getPrice(),
                              form.getStockQuantity(),
                              form.getAuthor(),
                              form.getIsbn());
    }</code></pre><h3 id="2-병합-merge-활용">2. 병합 (merge) 활용</h3>
<p>&lt; ItemRepository.java &gt;</p>
<pre><code>    public void save(Item item){
        if(item.getId() == null){
            em.persist(item);
        } else {
            em.merge(item);
        }
    }</code></pre><p>위의 변경감지랑 똑같은 flow 로 작동함!!
다만!!</p>
<blockquote>
<p>불러온 값에서, merge 한 엔티티에 있는 값들로 전부 바꿔치기함!!
즉 영속성 컨텍스트 속에서는 내가 원하는 수정 상태로 가지고 있고, flush 하면 업데이트 sql 이 실행됨.</p>
</blockquote>
<h3 id="주의-사항">주의 사항!!</h3>
<ul>
<li>item 을 merge 했다고 해서, item 자체가 영속성 컨텍스트 위에 있지는 x</li>
<li><ul>
<li>영속성 엔티티를 활용하고 싶으면, <code>em.merge()</code> 의 반환값을 사용해야함!!</li>
</ul>
</li>
<li>변경감지를 사용하면, 원하는 속성만 변경 가능하지만, 병합은 모든 속성이 변경됨 (null 값들도)</li>
<li><ul>
<li>수정을 원치 않던 값에 null 이 들어가서 기존 DB의 값을 덮어버리는 대참사 가능성...</li>
</ul>
</li>
<li>가급적 <code>(1) 변경감지</code> 를 사용하는 것이 맞음. 보통 update는 몇가지만 일어나니!!</li>
</ul>
<h1 id="서비스-계층에서의-엔티티-관리">서비스 계층에서의 엔티티 관리</h1>
<h2 id="controller-계층에서의-엔티티">Controller 계층에서의 엔티티</h2>
<ul>
<li>바로 위 변경감지에서 Book Entity를 넘기지 않고, <code>id</code> 와 <code>BookForm</code> 을 service 계층에 파라미터로 전달</li>
<li><code>@Transactional</code> 을 가지고 있는 서비스 계층에서, 엔티티의 시작과 끝을 관리하는 것이 좋음</li>
<li>파라미터 값으로는 엔티티와 최대한 연관이 없도록 분리 시켜주는 것이 유지 보수 등 코드에 유리!</li>
</ul>
<p>&lt; OrderController.java &gt;</p>
<pre><code>    @PostMapping(&quot;/order&quot;)
    public String order(@RequestParam(&quot;memberId&quot;) Long memberId,
                        @RequestParam(&quot;itemId&quot;) Long itemId,
                        @RequestParam(&quot;count&quot;) int count){
        orderService.order(memberId, itemId, count);
        return &quot;redirect:/orders&quot;;
    }</code></pre><p>이런 식으로 service 계층에 엔티티를 직접 넘기지 않는 방법이 좋음!!</p>
]]></description>
        </item>
    </channel>
</rss>