<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dohan 개발노트</title>
    <link>https://tislwlstnf.tistory.com/</link>
    <description>https://devtoprod95.github.io/dohan-portfolio/</description>
    <language>ko</language>
    <pubDate>Tue, 21 Jul 2026 10:19:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>역발산기개세</managingEditor>
    <image>
      <title>dohan 개발노트</title>
      <url>https://tistory1.daumcdn.net/tistory/3520598/attach/d7c9b14caa86479ab92c3e258b2a382b</url>
      <link>https://tislwlstnf.tistory.com</link>
    </image>
    <item>
      <title>[PCCP 기출문제] 1번 / 붕대 감기</title>
      <link>https://tislwlstnf.tistory.com/90</link>
      <description>&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;br /&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/250137&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/250137&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt; 풀이&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1733130843704&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(bandage, health, attacks) {
    const [castTime, healPerSec, bonusHeal] = bandage;
    const maxHealth = health;
    let currentHealth = health;
    let consecutiveHealing = 0;
    let lastAttackTime = 0;
    
    // attacks를 Object로 변환하여 빠른 검색이 가능하게 함
    const attackMap = {};
    attacks.forEach(([time, damage]) =&amp;gt; {
        attackMap[time] = damage;
        lastAttackTime = Math.max(lastAttackTime, time);
    });
    
     // 0초부터 마지막 공격 시간까지 진행
    for (let time = 0; time &amp;lt;= lastAttackTime; time++) {
        // 공격이 있는 경우
        if (attackMap[time] !== undefined) {
            currentHealth -= attackMap[time];
            consecutiveHealing = 0;
            
            // 체력이 0 이하가 되면 캐릭터 사망
            if (currentHealth &amp;lt;= 0) {
                return currentHealth = -1;
            }
        } 
        // 공격이 없는 경우 회복
        else {
            // 기본 회복
            currentHealth += healPerSec;
            consecutiveHealing++;
            
            // 연속 성공 보너스 체력 회복
            if (consecutiveHealing === castTime) {
                currentHealth += bonusHeal;
                consecutiveHealing = 0;
            }
            
            // 최대 체력을 초과할 수 없음
            if (currentHealth &amp;gt; maxHealth) {
                currentHealth = maxHealth;
            }
        }
    }
    
    return currentHealth;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>에러&amp;amp;&amp;amp;공부노트</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/90</guid>
      <comments>https://tislwlstnf.tistory.com/90#entry90comment</comments>
      <pubDate>Mon, 2 Dec 2024 18:14:15 +0900</pubDate>
    </item>
    <item>
      <title>[PCCP 기출문제] 1번 / 동영상 재생기</title>
      <link>https://tislwlstnf.tistory.com/89</link>
      <description>&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;br /&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/340213&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/340213&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt; 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1733130258711&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// &quot;mm:ss&quot; 형식의 시간 문자열을 초 단위로 변환
function timeToSeconds(timeStr) {
    const [minutes, seconds] = timeStr.split(&quot;:&quot;).map(Number);
    return minutes * 60 + seconds;
}

// 초 단위를 &quot;mm:ss&quot; 형식의 시간 문자열로 변환
function secondsToTime(totalSeconds) {
    const minutes = Math.floor(totalSeconds / 60) % 60;
    const seconds = totalSeconds % 60;
    return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}

function solution(video_len, pos, op_start, op_end, commands) {
    // 시간들을 초 단위로 변환
    const videoLengthSeconds = timeToSeconds(video_len);
    const posSeconds = timeToSeconds(pos);
    const startSeconds = timeToSeconds(op_start);
    const endSeconds = timeToSeconds(op_end);
    
    let currentSeconds = posSeconds;
    
    // 오프닝 구간인 경우 오프닝이 끝나는 위치로 이동
    if (currentSeconds &amp;gt;= startSeconds &amp;amp;&amp;amp; currentSeconds &amp;lt;= endSeconds) {
        currentSeconds = endSeconds;
    }
    
    commands.forEach(command =&amp;gt; {
       switch(command){
           case &quot;prev&quot;:
               if (currentSeconds &amp;lt; 10) {
                    currentSeconds = 0;
                } else {
                    currentSeconds = currentSeconds - 10;
                }
                break;
           case &quot;next&quot;:
                if(videoLengthSeconds - currentSeconds &amp;lt; 10) {
                    currentSeconds = videoLengthSeconds;
                } else {
                    currentSeconds = currentSeconds + 10;
                }
                break;
           default:
             break;
       } 
        
        // 오프닝 구간인 경우 오프닝이 끝나는 위치로 이동
        if (currentSeconds &amp;gt;= startSeconds &amp;amp;&amp;amp; currentSeconds &amp;lt;= endSeconds) {
            currentSeconds = endSeconds;
        }
    });
        
    return secondsToTime(currentSeconds);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&quot;problem_input&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>에러&amp;amp;&amp;amp;공부노트</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/89</guid>
      <comments>https://tislwlstnf.tistory.com/89#entry89comment</comments>
      <pubDate>Mon, 2 Dec 2024 18:04:42 +0900</pubDate>
    </item>
    <item>
      <title>락 전략 with Laravel</title>
      <link>https://tislwlstnf.tistory.com/88</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.  낙관적 락&lt;span&gt;&amp;nbsp;&lt;/span&gt;(Optimistic Lock)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;낙관적 락은 데이터 충돌이 거의 발생하지 않을 것이라는 가정하에 동작합니다. 실제로 데이터를 수정할 때만 충돌을 확인합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 상황&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;읽기 작업이 쓰기 작업보다 훨씬 많은 경우&lt;/li&gt;
&lt;li&gt;동시 수정 가능성이 낮은 경우&lt;/li&gt;
&lt;li&gt;높은 동시성이 요구되는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라라벨에서의 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라라벨에는 version 컬럼을 사용하여 낙관적 락을 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729478891485&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use \Illuminate\Database\Eloquent\Concerns\HasAttributes;

    protected $fillable = ['name', 'price', 'version'];
}

// 사용 예시
$product = Product::find(1);
$product-&amp;gt;price = 100;
$product-&amp;gt;save();

if ($product-&amp;gt;wasChanged()) {
    // 성공적으로 업데이트됨
} else {
    // 다른 프로세스가 이미 수정함
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;높은 동시성: 락을 획득하기 위해 대기하지 않음&lt;/li&gt;
&lt;li&gt;데드락 위험이 없음&lt;/li&gt;
&lt;li&gt;데이터베이스 리소스 사용이 적음&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;충돌 발생 시 애플리케이션에서 재시도 로직을 구현해야 함&lt;/li&gt;
&lt;li&gt;높은 충돌 가능성이 있는 환경에서는 성능이 저하될 수 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 비관적 락 (Pessimistic Lock)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비관적 락은 데이터 충돌이 자주 발생할 것이라고 가정하고, 데이터를 읽는 시점에 락을 겁니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 상황&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시 수정 가능성이 높은 경우&lt;/li&gt;
&lt;li&gt;데이터 일관성이 매우 중요한 경우&lt;/li&gt;
&lt;li&gt;트랜잭션이 짧고 빨리 처리되는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라라벨에서의 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라라벨에서는 lockForUpdate() 메소드를 사용하여 비관적 락을 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729478969736&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    $product = Product::lockForUpdate()-&amp;gt;find(1);
    $product-&amp;gt;price = 100;
    $product-&amp;gt;save();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 일관성 보장&lt;/li&gt;
&lt;li&gt;충돌 처리 로직이 단순함&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;동시성 저하: 다른 트랜잭션이 락이 해제될 때까지 대기&lt;/li&gt;
&lt;li&gt;데드락 발생 가능성&lt;/li&gt;
&lt;li&gt;데이터베이스 리소스 사용량 증가&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 명시적 락 (Explicit Locking)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시적 락은 개발자가 직접 락을 제어하는 방식입니다. 라라벨에서는 Cache나 Redis를 사용하여 구현할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 상황&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세밀한 락 제어가 필요한 경우&lt;/li&gt;
&lt;li&gt;분산 환경에서의 동기화가 필요한 경우&lt;/li&gt;
&lt;li&gt;데이터베이스 외부의 리소스에 대한 락이 필요한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라라벨에서의 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라라벨의 Cache 파사드를 사용하여 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729479010162&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use Illuminate\Support\Facades\Cache;

if (Cache::lock('product-1-lock')-&amp;gt;get()) {
    // 락 획득 성공, 작업 수행
    $product = Product::find(1);
    $product-&amp;gt;price = 100;
    $product-&amp;gt;save();

    Cache::lock('product-1-lock')-&amp;gt;release();
} else {
    // 락 획득 실패
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;세밀한 제어 가능&lt;/li&gt;
&lt;li&gt;데이터베이스에 의존하지 않는 락 구현 가능&lt;/li&gt;
&lt;li&gt;분산 환경에서 효과적&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;구현 복잡도 증가&lt;/li&gt;
&lt;li&gt;락 해제 실패 시 문제 발생 가능&lt;/li&gt;
&lt;li&gt;성능 오버헤드 발생 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데드락 (Deadlock) 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락은 두 개 이상의 트랜잭션이 서로가 점유한 자원을 요구하며 무한정 대기하는 상황을 말합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 타임아웃 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1729479060197&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DB::transaction(function () {
    // 트랜잭션 내용
}, 5); // 5초 타임아웃&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 락 범위 최소화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 최소한의 데이터에만 락을 적용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 재시도 로직 구현&lt;/h3&gt;
&lt;pre id=&quot;code_1729479120967&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use Illuminate\Database\QueryException;

$maxAttempts = 5;
$attempts = 0;

while ($attempts &amp;lt; $maxAttempts) {
    try {
        DB::transaction(function () {
            // 트랜잭션 내용
        });
        break; // 성공 시 루프 탈출
    } catch (QueryException $e) {
        if ($e-&amp;gt;getCode() == 40001) { // 데드락 에러 코드
            $attempts++;
            sleep(1); // 잠시 대기 후 재시도
        } else {
            throw $e; // 다른 에러는 그대로 던짐
        }
    }
}

if ($attempts == $maxAttempts) {
    // 최대 시도 횟수 초과
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>PHP</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/88</guid>
      <comments>https://tislwlstnf.tistory.com/88#entry88comment</comments>
      <pubDate>Mon, 21 Oct 2024 11:53:00 +0900</pubDate>
    </item>
    <item>
      <title>[Laravel] beginTransaction, lockForUpdate 활용 (일관성, 동시성) with. ORM</title>
      <link>https://tislwlstnf.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문제 상황]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 endPoint에 거의 동시간(1초 이내 시간) 통신요청이 들어와 DB에 중복적인 데이터가 처리됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[문제 당시 코드]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 upsert 코드였지만 1초 이내에 아래 로직을 실행 시 동일한 pk_id를 가진 행이 실행 수 만큼 생성이 되었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723452021687&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
	DB::beginTransaction();
	try {
    	OrderTradeData::updateOrCreate(['pk_id' =&amp;gt; $pkId], [$upsertVo]);
       	DB::commit();
    } catch (Exception $ee) {
        DB::rollBack();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transaction으로 해당 로직을 묶었지만 transaction은 일관성을 유지할 뿐 선택 된 행에 대해 동시성 문제를 해결하지 못하는 상황이 연출되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[해결 코드]&lt;/p&gt;
&lt;pre id=&quot;code_1723452327109&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php
	DB::beginTransaction();
	try {
    	OrderTradeData::lockForUpdate()-&amp;gt;updateOrCreate(['pk_id' =&amp;gt; $pkId], [$upsertVo]);
       	DB::commit();
    } catch (Exception $ee) {
        DB::rollBack();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lockForUpdate는 transaction안에서만 작동을 하며 lockForUpdate로 선택된 사용자 레코드는 해당 트랜젝션이 완료될 때까지 다른 트랜잭션는 대기 상태로 만들어 동시성 문제를 방지하게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아보이지만 과도한 사용은 데드락이나 성능에 이슈를 발생시켜 참고하여 사용해야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>PHP</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/87</guid>
      <comments>https://tislwlstnf.tistory.com/87#entry87comment</comments>
      <pubDate>Mon, 12 Aug 2024 17:49:32 +0900</pubDate>
    </item>
    <item>
      <title>DTO DAO 정리 with 라라벨</title>
      <link>https://tislwlstnf.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DTO와 DAO는 데이터베이스와 상호 작용하는데 사용되는 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DTO(Data Transfer Object)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; DTO는 데이터베이스와 통신하는 데 사용되는 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 속성과 그 속서에 접근하기 위한 getter / setter 메소드를 이용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 저장 및 타입변환 기능&lt;/p&gt;
&lt;pre id=&quot;code_1682047205247&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserDTO {
   private $id;
   private $name;
   private $email;
   private $age;

   public function __construct($id, $name, $email, $age) {
      $this-&amp;gt;id = $id;
      $this-&amp;gt;name = $name;
      $this-&amp;gt;email = $email;
      $this-&amp;gt;age = $age;
   }

   public function getId() {
      return $this-&amp;gt;id;
   }

   public function getName() {
      return $this-&amp;gt;name;
   }

   public function getEmail() {
      return $this-&amp;gt;email;
   }

   public function getAge() {
      return $this-&amp;gt;age;
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DAO(Data Access Object)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DAO는 데이터베이스와 상호 작용하며 CRUD의 기능을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DTO에서 가져온 데이터를 가지고 데이터베이스에 CRUD&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실직적인 데이터의 조작 영역을 담당&lt;/p&gt;
&lt;pre id=&quot;code_1682047219644&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserDAO {
   private $conn;

   public function __construct($conn) {
      $this-&amp;gt;conn = $conn;
   }

   public function getUserById($id) {
      $stmt = $this-&amp;gt;conn-&amp;gt;prepare(&quot;SELECT * FROM users WHERE id = ?&quot;);
      $stmt-&amp;gt;execute([$id]);
      $row = $stmt-&amp;gt;fetch(PDO::FETCH_ASSOC);

      if ($row) {
         return new UserDTO($row['id'], $row['name'], $row['email'], $row['age']);
      }

      return null;
   }

   public function createUser(UserDTO $user) {
      $stmt = $this-&amp;gt;conn-&amp;gt;prepare(&quot;INSERT INTO users (name, email, age) VALUES (?, ?, ?)&quot;);
      $stmt-&amp;gt;execute([$user-&amp;gt;getName(), $user-&amp;gt;getEmail(), $user-&amp;gt;getAge()]);
      $user-&amp;gt;setId($this-&amp;gt;conn-&amp;gt;lastInsertId());
   }

   public function updateUser(UserDTO $user) {
      $stmt = $this-&amp;gt;conn-&amp;gt;prepare(&quot;UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?&quot;);
      $stmt-&amp;gt;execute([$user-&amp;gt;getName(), $user-&amp;gt;getEmail(), $user-&amp;gt;getAge(), $user-&amp;gt;getId()]);
   }

   public function deleteUser(UserDTO $user) {
      $stmt = $this-&amp;gt;conn-&amp;gt;prepare(&quot;DELETE FROM users WHERE id = ?&quot;);
      $stmt-&amp;gt;execute([$user-&amp;gt;getId()]);
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라라벨에서는 어떻게 적용되어있는지?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 라라벨은 ORM를 이용하게 되는데 ORM은 Model Class가 데이터를 저장하고 검색하는데 사용되어 DTO의 역할을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 DAO는 라라벨에서 Repository class에서 따로 기능을 담당할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 코드에서는 User Model이 DAO이면서 User Repository가 DTO의 역할을 수행하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;User Model은 DAO처럼 객체를 저장하는 클래스를 생성하지 않으며 ORM자체 적으로 데이터를 객체로 변환하고, 객체에서 데이터를 검색하고 저장할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. User Model&lt;/p&gt;
&lt;pre id=&quot;code_1682047536765&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model {
   protected $table = 'users';
   protected $fillable = ['name', 'email', 'age'];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. User Repository&lt;/p&gt;
&lt;pre id=&quot;code_1682047564729&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;namespace App\Repositories;

use App\Models\User;

class UserRepository {
   public function getUserById($id) {
      $user = User::find($id);

      if ($user) {
         return $user;
      }

      return null;
   }

   public function createUser($userData) {
      return User::create($userData);
   }

   public function updateUser($id, $userData) {
      $user = User::find($id);

      if ($user) {
         $user-&amp;gt;fill($userData);
         $user-&amp;gt;save();
      }

      return $user;
   }

   public function deleteUser($id) {
      $user = User::find($id);

      if ($user) {
         $user-&amp;gt;delete();
      }
   }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>PHP</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/86</guid>
      <comments>https://tislwlstnf.tistory.com/86#entry86comment</comments>
      <pubDate>Fri, 21 Apr 2023 13:12:25 +0900</pubDate>
    </item>
    <item>
      <title>아키텍쳐 패턴과 디자인 패턴</title>
      <link>https://tislwlstnf.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[아키텍쳐 패턴]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Application의 전반적인 구조를 설계하는 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대표적으로 MVC, MVP, MVVM, SOA(서비스 지향 아키텍쳐), EDA(비동기 방식으로 메세지 전달: Kafka, RabbitMQ)..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[디자인 패턴]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Application의 전반적인 구조를 개선하기 위해 사용하는 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 객체 지향 프로그래밍에서 클래스 및 객체 간의 상효 작용 방법, 책임 분담, 관계 등을 고려하여 문제를 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대표적으로 Singleton, Factory Method, Observer..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[요약]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아키텍쳐 패턴은 개발을 시작하기 전 구조를 생각할 때 도입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 디자인 패턴은 개발 후 효율적인 코딩을 할 때 도입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>에러&amp;amp;&amp;amp;공부노트</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/85</guid>
      <comments>https://tislwlstnf.tistory.com/85#entry85comment</comments>
      <pubDate>Fri, 21 Apr 2023 11:59:31 +0900</pubDate>
    </item>
    <item>
      <title>upsert query에서 발생한 문제점</title>
      <link>https://tislwlstnf.tistory.com/84</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[환경]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mysql5.7(InnoDB), Python&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[상황]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upsert 쿼리에서 id(auto_increment)컬럼이 순차적이 아닌 건너 뛰면서 저장되고 있는 현상이였습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[원인]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InnoDB에서 auto_increment 열은 각각의 트랜잭션에서 순차적으로 증가되며, 각 트랜잭션에서 할당된 auto_increment 값은 InnoDB의 메모리 버퍼에 저장되어, 나중에 디스크에 쓰여질 때까지 버퍼링 된다는 개념이 있습니다. 제 나름대로 정리를 하면 쿼리를 실행하기전 이미 auto_increment값이 각 각의 트랜잭션마다 할당 되어져 있다고 보입니다. 그렇기 때문에 만약 id가 1로 insert로 할려다가 이미 해당 row가 있다고 가정하면 update를 실행 후 그 다음 row를 실행하면 id가 그대로 1로 사용되어져야 하지만 이미 메모리에 저장 된 각각의 트랜잭션으로 인해 id가 2로 할당 되는 것이라고 정리가 되어집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[결론]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 트랜잭션이 발생되지 않는 코드를 작성하면 되는 것으로 보입니다. php에서는 1개의 쿼리에 1개의 row만 영향을 줄 수 있어 별도의 트랜잭션으로 해당 오류가 나타나진 않을 것으로 보이지만 python에서 1개의 쿼리에 List 형식으로 쿼리를 실행해 여러 트랜잭션이 동시에 작동해 문제가 생긴 것으로 보였습니다. 즉 어떤 언어에서든 loop로 쿼리를 실행하면 되겠다고 정리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/84</guid>
      <comments>https://tislwlstnf.tistory.com/84#entry84comment</comments>
      <pubDate>Wed, 19 Apr 2023 15:22:26 +0900</pubDate>
    </item>
    <item>
      <title>where , having 특징</title>
      <link>https://tislwlstnf.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;where&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 집계 함수(group)가 적용되기 이전에 해당하는 레코드들을 선택합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;having&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 그룹화된 결과에 대한 필터링을 수행합니다. group by와 무조건 같이 써야합니다. 즉 그룹화 된 결과에 대해서만 조건문이 적용 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[예시]&lt;/p&gt;
&lt;pre id=&quot;code_1681882675511&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 올바른 sql
SELECT department, AVG(salary) AS avg_salary 
FROM employees 
GROUP BY department 
HAVING AVG(salary) &amp;gt;= 55000 
ORDER BY avg_salary DESC;

// 잘못된 sql
SELECT department, AVG(salary) AS avg_salary 
FROM employees
where AVG(salary) &amp;gt;= 55000
GROUP BY department 
ORDER BY avg_salary DESC;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/83</guid>
      <comments>https://tislwlstnf.tistory.com/83#entry83comment</comments>
      <pubDate>Wed, 19 Apr 2023 14:38:06 +0900</pubDate>
    </item>
    <item>
      <title>Mysql now(), curdate() 차이</title>
      <link>https://tislwlstnf.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;now() 함수는 현재 시간과 날짜를 &quot;년-월-일 시:분:초&quot; 형식으로 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curdate() 함수는 현재 날짜 정보만 &quot;년-월-일&quot; 형식으로 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/82</guid>
      <comments>https://tislwlstnf.tistory.com/82#entry82comment</comments>
      <pubDate>Wed, 19 Apr 2023 14:22:55 +0900</pubDate>
    </item>
    <item>
      <title>Chrome, Firefox 차이 in selenium</title>
      <link>https://tislwlstnf.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[환경]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mac m1칩에서 docker container로 ubuntu18.04 환경을 구축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[목적]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;container로 구축한 ubuntu18.04로 selenium을 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 window10 환경에서는 ubuntu container에서 chrome, chromedriver를 설치 후 정상으로 selenium을 실행한 것을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[에러]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상하게 같은 소스에 같은 container였지만 mac m1에서만 Selenium: WebDriverException:Chrome failed to start: crashed as google-chrome is no longer running so ChromeDriver is assuming that Chrome has crashed과 같은 에러가 발생함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[이유]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 에러의 질문이 조금 있어 해당 글들을 요약해서 인용하자면 m1칩은 100% 가상환경에 적합하지 않으며 amd64 운영체제와 가상환경에서 크롬이랑 충돌나는 부분이 있다고 해석되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[해결]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 일 동안 os버전 업그레이드, 크롬 버전 120 ~ 98까지 재설치, selenium 옵션 변경 등 여러 방법을 이용해봤지만 도저히 안되서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 글 중에 firefoxf를 이용하라는 글을 확인해 적용해보니 정상 실행되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Chrome, Firefox 차이]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Firefox는 Gecko 엔진을 사용하고, Chrome은 Blink 엔진을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span&gt;Chrome&lt;/span&gt;은&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;빠르고&lt;span&gt; &lt;/span&gt;안정적이며&lt;span&gt; &lt;/span&gt;최신&lt;span&gt; &lt;/span&gt;기술을&lt;span&gt; &lt;/span&gt;더&lt;span&gt; &lt;/span&gt;빠르게&lt;span&gt; &lt;/span&gt;적용합니다. 또 메모리 사용 또한 더 많이 사용가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Chrome은 Chrome 설치 후 해당 버전에 맞는 드라이버를 따로 설치해줘야 하지만 Firefox는 자동으로 드라이버까지 설치되어 설치 과정은 편합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[결론]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m1 가상환경에서는 되도록 selenium을 자제하고 window로 테스트하던지, 가상환경이 아닌 실제 os에서 테스트를 하자!&lt;/p&gt;</description>
      <category>Python</category>
      <category>chrome</category>
      <category>Docker</category>
      <category>Firefox</category>
      <category>Linux</category>
      <category>M1</category>
      <category>Mac</category>
      <category>ubuntu</category>
      <category>window</category>
      <author>역발산기개세</author>
      <guid isPermaLink="true">https://tislwlstnf.tistory.com/81</guid>
      <comments>https://tislwlstnf.tistory.com/81#entry81comment</comments>
      <pubDate>Wed, 19 Apr 2023 11:50:45 +0900</pubDate>
    </item>
  </channel>
</rss>