카테고리 없음

CRUD 개발시 중요한 개념,방법론, 실수하는 포인트

JUNGKEUNG 2025. 3. 15. 12:52
반응형

CRUD 개발의 핵심 개념

1) RESTful API 원칙

CRUD는 일반적으로 RESTful API와 함께 사용됨.

  • Create → POST /resource
  • Read → GET /resource/{id}
  • Update → PUT/PATCH /resource/{id}
  • Delete → DELETE /resource/{id}

💡 주의할 점

  • POST는 리소스를 새로 생성할 때 사용하고, 같은 요청을 여러 번 보내면 중복 생성될 수 있음.
  • PUT은 전체 리소스를 업데이트할 때, PATCH는 부분 업데이트할 때 사용.
  • DELETE는 리소스를 삭제하지만, 실제 데이터베이스에서 완전 삭제하는 것(물리적 삭제)과 단순히 활성화 여부를 변경하는 것(논리적 삭제)을 구분해야 함.

2) 트랜잭션 관리

CRUD 작업 중 하나라도 실패하면 데이터 정합성을 유지해야 함.

  • 트랜잭션을 적용해 모든 작업이 성공하면 커밋, 하나라도 실패하면 롤백하도록 설계.
  • 예) 은행 계좌 이체 → 출금과 입금이 하나의 트랜잭션으로 묶여야 함.

💡 주의할 점

  • 트랜잭션이 필요하지 않은 간단한 조회 요청(SELECT)에는 사용하지 않는 것이 성능상 유리함.
  • 트랜잭션이 너무 오래 유지되면 성능 저하 및 교착 상태(Deadlock) 발생 가능.

3) 데이터 정규화 vs. 성능 최적화

  • 정규화(Normalization): 데이터 중복을 최소화하여 일관성 유지.
  • 성능 최적화(Denormalization): 조회 속도를 높이기 위해 일부 중복을 허용.

💡 주의할 점

  • 과도한 정규화는 조인(Join) 연산이 많아져 성능이 저하될 수 있음.
  • NoSQL을 사용할 경우 정규화보다는 읽기 성능을 위한 데이터 중복을 허용하는 경우가 많음.

2. CRUD 개발 방법론

1) MVC(Model-View-Controller) 패턴

  • Model: 데이터베이스와 직접 상호작용 (CRUD 구현)
  • View: UI 담당
  • Controller: Model과 View를 연결하여 요청을 처리

💡 Best Practice

  • 비즈니스 로직은 Model(Service) 계층에서 처리하고, Controller는 요청만 전달하도록 함.
  • View(또는 API Response)에서 직접 데이터베이스를 조작하면 유지보수가 어려워짐.

2) DTO & Entity 분리

  • Entity: 데이터베이스 테이블과 매핑되는 객체
  • DTO(Data Transfer Object): 클라이언트와 데이터를 주고받는 객체

💡 Best Practice

  • Entity를 직접 클라이언트에 반환하면 보안 및 확장성이 저하될 수 있음.
  • Entity 내부 필드 변경이 클라이언트 API에 영향을 주지 않도록 DTO를 별도로 정의하는 것이 좋음.

3) 페이징 & 정렬 적용 (Read)

대량의 데이터를 다룰 때는 페이징이 필수.

  • SQL에서 LIMIT 및 OFFSET 사용 (SELECT * FROM users LIMIT 10 OFFSET 20)
  • ORDER BY를 사용하여 정렬 적용

💡 주의할 점

  • OFFSET이 클 경우 성능이 저하될 수 있으므로 Keyset Pagination(기준값 기반 페이징) 적용을 고려.
  • 정렬 기준을 명확히 지정해야 불필요한 성능 저하 방지 가능.

4) RESTful API와 HTTP 상태 코드

CRUD 작업에 맞는 HTTP 상태 코드를 반환해야 함.

  • 200 OK → 조회 성공
  • 201 Created → 생성 성공
  • 204 No Content → 삭제 성공 (응답 본문 없음)
  • 400 Bad Request → 잘못된 요청
  • 404 Not Found → 존재하지 않는 리소스

💡 Best Practice

  • PUT 또는 DELETE 요청에서 리소스가 존재하지 않으면 404 Not Found 반환.
  • POST 요청 후에는 생성된 리소스의 URI를 Location 헤더에 포함.

3. CRUD 개발 시 자주 하는 실수

1) API 보안 취약점

문제: 인증/인가 없이 누구나 데이터를 수정/삭제할 수 있음.
해결:

  • JWT, OAuth2 등 인증 방식 적용
  • 사용자 권한(Role) 체크하여 접근 제어

2) N+1 문제 (Read)

문제: 여러 개의 데이터를 조회할 때, 각각 추가 쿼리가 실행되는 문제.
해결:

  • JOIN 사용: INNER JOIN, LEFT JOIN을 활용해 한 번의 쿼리로 필요한 데이터를 가져옴.
  • Lazy Loading 대신 Fetch Join 사용: EAGER FETCH를 적용해 한 번의 쿼리로 필요한 데이터 불러오기.

3) 데이터 삭제 방식 오류 (Delete)

문제: 삭제된 데이터를 복구할 수 없음.
해결:

  • 논리적 삭제(Soft Delete) 적용 → is_deleted 같은 플래그 추가
  • 삭제 요청이 오면 DELETE 대신 UPDATE is_deleted = true 수행

4) 비효율적인 대량 업데이트 (Update)

문제: 데이터 수만 개를 UPDATE 할 때 성능 저하 발생.
해결:

  • Batch Update 사용 → 여러 개의 업데이트를 한 번에 처리
  • 인덱스 활용 → 업데이트가 자주 발생하는 컬럼에는 인덱스 조정

5) 잘못된 인덱스 설계

문제: 인덱스를 과도하게 사용하여 오히려 성능 저하 발생.
해결:

  • 자주 검색되는 컬럼에만 인덱스를 적용.
  • WHERE, ORDER BY 절에 자주 사용되는 컬럼에 우선적으로 인덱스 추가.
  • 다중 컬럼 인덱스 사용 시 순서를 신중히 결정.

6) 트랜잭션 범위 설정 오류

문제: 너무 넓은 범위에서 트랜잭션을 유지하면 성능 저하.
해결:

  • 꼭 필요한 부분에서만 트랜잭션을 유지하고, 빠르게 종료하도록 설계.
  • 트랜잭션 안에서 네트워크 호출(예: API 요청) 금지.

1. 보안 강화

CRUD 개발에서 보안은 필수적으로 고려해야 하는 요소야.

1) SQL Injection 방어

문제: 사용자의 입력을 그대로 SQL 쿼리에 포함하면 해킹에 노출됨.
해결:

  • Prepared Statement(프리페어드 스테이트먼트) 사용 → ? 바인딩 방식으로 SQL 실행
  • ORM(Object Relational Mapping) 사용 → 직접 SQL을 다루지 않고 객체로 데이터 조작
 
// SQL Injection 방어 예제 (JDBC)
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();

2) XSS (Cross-Site Scripting) 방어

문제: 사용자가 입력한 데이터를 그대로 출력하면, 악성 스크립트 실행 가능.
해결:

  • 입력 값 검증(Sanitization) → HTML 태그 필터링
  • 출력 시 이스케이프 처리(Escape Handling) → <script> 같은 태그 무효화
import org.apache.commons.text.StringEscapeUtils;

public class XSSPrevention {
    public static String sanitizeInput(String input) {
        return StringEscapeUtils.escapeHtml4(input);  // HTML 태그 이스케이프 처리
    }

    public static void main(String[] args) {
        String unsafeInput = "<script>alert('XSS Attack');</script>";
        String safeInput = sanitizeInput(unsafeInput);
        System.out.println(safeInput);  // 출력: &lt;script&gt;alert('XSS Attack');&lt;/script&gt;
    }
}

3) 인증 & 권한 관리

문제: 인증 없이 API 호출 가능하면 보안 취약점 발생.
해결:

  • JWT (JSON Web Token) 사용 → 사용자의 로그인 상태를 유지
  • OAuth 2.0 적용 → 외부 서비스 인증 (예: 구글, 카카오 로그인)
  • Role-Based Access Control(RBAC) 적용 → 관리자, 일반 사용자 등의 권한 설정

2. 성능 최적화

1) 쿼리 최적화

  • EXPLAIN 분석 → SQL 실행 계획 확인하여 불필요한 풀 스캔(Full Scan) 방지
  • 인덱스(Index) 적용 → 자주 조회하는 컬럼에 인덱스 추가
  • JOIN 최적화 → 필요할 때만 JOIN을 사용하고, 너무 많은 테이블을 JOIN하지 않도록 설계
EXPLAIN 사용 예시
 EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';

2) 캐싱 적용

문제: 같은 요청을 반복할 때 DB 부하 증가.
해결:

  • Redis/Memcached 사용 → 자주 조회되는 데이터 캐싱
  • HTTP Cache-Control 적용 → 클라이언트가 캐싱하도록 설정
Redis 캐싱 예제
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JWTUtil {
    private static final String SECRET_KEY = "mySecretKey";

    public static String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1일 유효
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    public static void main(String[] args) {
        String token = generateToken("testUser");
        System.out.println("Generated Token: " + token);
    }
}

3) 비동기(Async) & 배치 처리

문제: 데이터가 많을 때 CRUD 작업이 오래 걸림.
해결:

  • 비동기 처리 (Async/Await) → 요청을 비동기적으로 실행
  • Batch Processing (배치 작업) → 대량 데이터를 처리할 때 한 번에 묶어서 실행python
비동기 예제 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query("SELECT u FROM User u WHERE u.email = ?1")
    User findByEmail(String email);
}

3. 확장성 고려

1) Sharding & Replication

해결:

  • MySQL Replication을 사용하여 읽기 부하를 분산.
CHANGE MASTER TO MASTER_HOST='master-db', MASTER_USER='replica', MASTER_PASSWORD='password';
START SLAVE;
  • Spring에서 RoutingDataSource를 사용하여 읽기/쓰기 분리 가능.

2) CQRS 패턴 적용 (읽기/쓰기 분리)

해결:

  • CommandService와 QueryService를 분리하여 성능 최적화.
@Service
public class UserCommandService {
    @Autowired
    private UserRepository userRepository;

    public User createUser(User user) {
        return userRepository.save(user);
    }
}

@Service
public class UserQueryService {
    @Autowired
    private UserRepository userRepository;

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}

 


4. 로깅 & 모니터링

1) Spring Boot + Logback 설정

해결:

  • logback.xml을 설정하여 로그 레벨 관리 가능.
<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logs/app.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="FILE"/>
    </root>
</configuration>

5. 테스트 자동화

1) JUnit 기반 유닛 테스트

해결:

  • @Test 어노테이션을 사용하여 단위 테스트 수행.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class UserServiceTest {

    @Test
    public void testAddition() {
        int result = 2 + 3;
        assertEquals(5, result);
    }
}

2) Spring Boot 통합 테스트

해결:

  • @SpringBootTest 어노테이션을 사용하여 전체 애플리케이션 테스트 수행.
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class ApplicationTests {

    @Test
    void contextLoads() {
        // Spring Boot 애플리케이션이 정상적으로 로드되는지 확인
    }
}

3) JMeter / Gatling을 사용한 부하 테스트

해결:

  • Apache JMeter 또는 Gatling을 사용하여 대량 요청을 시뮬레이션.
jmeter -n -t load_test.jmx -l result.jtl
 

4. 결론

CRUD 개발을 할 때는 단순히 API를 만드는 것이 아니라 데이터 정합성, 성능, 보안까지 고려해야 함.

  • RESTful 원칙 준수
  • 트랜잭션 관리
  • 페이징, 정렬 고려
  • 보안 강화 (SQL Injection 방어, XSS 방지, JWT 인증)
  • 쿼리 최적화 (EXPLAIN, 인덱스, 페이징)
  • 캐싱 적용 (Redis, @Cacheable)
  • 확장성 고려 (CQRS, Sharding, Replication)
  • 로깅 & 모니터링 (Logback, ELK Stack)
  • 테스트 자동화 (JUnit, Spring Boot Test)
  • 부하 테스트 (JMeter, Gatling)