초기 프론트 화면에서 정적인 정보를 제공하는 경우, 해당 정보를 프론트엔드에 직접 포함시키는 것이 효율적
넷플릭스처럼 사용자 맞춤 정보를 제공해야 하는 경우, DB에서 데이터를 가져와야 하므로 상대적으로 성능이 저하될 수 있음
메모리 캐싱을 활용한 불필요한 DB 조회 방지
기존 방식
새로고침 시마다 DB에서 데이터를 조회
package com.shop.cafe.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.shop.cafe.dto.Product;
import com.shop.cafe.service.ProductService;
@RestController
public class ProductController {
@Autowired
ProductService productService;
@GetMapping("getAllProducts")
public List<Product> getAllProducts() {
try {
return productService.getAllProducts();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
- getAllProducts()가 새로고침 할 때마다 호출됨
- 매번 productService.getAllProducts()를 실행하여 DB에서 데이터를 가져옴
- 사용자가 페이지를 새로고침할 때마다 성능 부담이 증가함
변경된 방식
첫 호출 후 데이터를 저장하여 재사용
package com.shop.cafe.controller;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.shop.cafe.dto.Product;
import com.shop.cafe.service.ProductService;
@RestController
public class ProductController {
@Autowired
ProductService productService;
Map<String, Object> storage = new HashMap();
@GetMapping("getAllProducts")
public List<Product> getAllProducts() {
try {
Object o = storage.get("firstPageProducts");
if(o==null) {
List<Product> list = productService.getAllProducts();
storage.put("firstPageProducts", list);
return list;
}
return (List<Product>)o;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
- storage라는 **메모리 캐시(Map)**를 활용하여 데이터를 저장
- 첫 번째 요청에서는 productService.getAllProducts()를 호출하고, 결과를 storage에 저장
- 이후 요청에서는 storage에서 데이터를 가져와 DB 호출 없이 반환
- 새로고침해도 DB를 다시 조회하지 않음 → 성능 향상
- storage는 애플리케이션이 꺼지면 초기화
- 새로운 데이터가 추가되거나 변경될 경우, 이를 갱신하는 로직 필요
SQL 인젝션
사용자가 입력한 데이터를 통해 SQL 쿼리의 구조를 조작하여 의도하지 않은 데이터 조회, 변경, 삭제 등을 수행할 수 있는 보안 취약점
ResultSet rs = stmt.executeQuery("SELECT * FROM member WHERE email = '"+ m.getEmail() +"' AND pwd = '" + m.getPwd() + "'");
위 코드처럼 문자열 연결 방식으로 SQL 쿼리를 만들면, 입력값에 따라 SQL 구조가 변경될 수 있음
{
"email" : "' or ''='",
"pwd" : "' or ''='"
}
예제 입력
SELECT * FROM member WHERE email='' OR ''='' AND pwd='' OR ''='';
실행되는 SQL : WHERE 조건이 항상 TRUE가 되어 모든 멤버 정보가 반환됨 → 보안 취약
SQL 인젝션 방어
PreparedStatement 사용
- SQL 구조를 고정하고 데이터만 전달하여 SQL 인젝션 방어
- function(email, pwd)처럼 실행됨
- function안의 내용은 컴파일되어있기 때문에 구조가 변경되지 않음. 데이터만 전달함
- 성능, 사용성, 보안 증가
MyBatis
- Apache iBatis에서 발전하여 만들어진 SQL 매핑 프레임워크
- SQL을 XML 또는 애노테이션으로 작성하여 직접 관리 가능
- JDBC보다 편리한 SQL 작성 및 실행 지원
- Connection Pool 제공으로 성능 최적화
@Mapper
public interface ProductDao {
public List<Product> getAllProducts() throws Exception;
}
- @Mapper를 사용하여 MyBatis가 자동으로 생성
- getAllProducts()는 SQL 매핑과 연결됨
<mapper namespace="com.shop.cafe.dao.ProductDao">
<select id="getAllProducts" resultType="Product">
select * from product limit 6
</select>
</mapper>
- namespace="com.shop.cafe.dao.ProductDao"
→ DAO 인터페이스와 연결 - id="getAllProducts"
→ ProductDao.getAllProducts() 메서드와 매핑 - resultType="Product"
→ 조회 결과를 Product 객체 리스트로 변환
Connection Pool
- 데이터베이스(DB) 연결을 미리 생성하고 재사용하는 기법
- 새로운 DB 연결(Connection)을 매번 생성하는 대신, 미리 생성된 연결을 풀(Pool)에 보관하고 필요할 때 가져다 사용
- 클래스를 직접 생성하면 요청마다 새 연결이 필요 → 비효율적
- 일정 개수의 DB 연결을 미리 생성하여 재사용
- 요청이 들어오면 풀에서 연결을 할당
- 요청이 끝나면 풀에 반환하여 대기
- 성능 향상 및 자원 낭비 방지
과제 (미니 프로젝트 도메인 생각하기)
도메인 : 패션 플랫폼(ex. 무신사, w컨셉)
주요 엔티티 : Member, Brand, Product, Cart, Order, Payment
'[LG 유플러스] 유레카 > Today I Learned' 카테고리의 다른 글
[TIL][03.11] 세션 & 토큰 로그인, 토큰 수명 관리 (0) | 2025.03.12 |
---|---|
[TIL][03.10] XSS, 외래키, tabnabbing, 토큰, 로그인 (0) | 2025.03.11 |
[TIL][03.06] @CrossOrigin, WebMvcConfigurer, 쿠키, 세션 스토리지, 로컬 스토리지, fetch, axios, jwt (0) | 2025.03.07 |
[TIL][03.05] secu.properties, MVC, 자원 자동 해제, 의존성 주입, Http Session (0) | 2025.03.06 |
[TIL][03.04] SQL, DISTINCT, WHERE, JOIN, MVC, Annotation, fetch, async/await (0) | 2025.03.05 |
초기 프론트 화면에서 정적인 정보를 제공하는 경우, 해당 정보를 프론트엔드에 직접 포함시키는 것이 효율적
넷플릭스처럼 사용자 맞춤 정보를 제공해야 하는 경우, DB에서 데이터를 가져와야 하므로 상대적으로 성능이 저하될 수 있음
메모리 캐싱을 활용한 불필요한 DB 조회 방지
기존 방식
새로고침 시마다 DB에서 데이터를 조회
package com.shop.cafe.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.shop.cafe.dto.Product;
import com.shop.cafe.service.ProductService;
@RestController
public class ProductController {
@Autowired
ProductService productService;
@GetMapping("getAllProducts")
public List<Product> getAllProducts() {
try {
return productService.getAllProducts();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
- getAllProducts()가 새로고침 할 때마다 호출됨
- 매번 productService.getAllProducts()를 실행하여 DB에서 데이터를 가져옴
- 사용자가 페이지를 새로고침할 때마다 성능 부담이 증가함
변경된 방식
첫 호출 후 데이터를 저장하여 재사용
package com.shop.cafe.controller;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.shop.cafe.dto.Product;
import com.shop.cafe.service.ProductService;
@RestController
public class ProductController {
@Autowired
ProductService productService;
Map<String, Object> storage = new HashMap();
@GetMapping("getAllProducts")
public List<Product> getAllProducts() {
try {
Object o = storage.get("firstPageProducts");
if(o==null) {
List<Product> list = productService.getAllProducts();
storage.put("firstPageProducts", list);
return list;
}
return (List<Product>)o;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
- storage라는 **메모리 캐시(Map)**를 활용하여 데이터를 저장
- 첫 번째 요청에서는 productService.getAllProducts()를 호출하고, 결과를 storage에 저장
- 이후 요청에서는 storage에서 데이터를 가져와 DB 호출 없이 반환
- 새로고침해도 DB를 다시 조회하지 않음 → 성능 향상
- storage는 애플리케이션이 꺼지면 초기화
- 새로운 데이터가 추가되거나 변경될 경우, 이를 갱신하는 로직 필요
SQL 인젝션
사용자가 입력한 데이터를 통해 SQL 쿼리의 구조를 조작하여 의도하지 않은 데이터 조회, 변경, 삭제 등을 수행할 수 있는 보안 취약점
ResultSet rs = stmt.executeQuery("SELECT * FROM member WHERE email = '"+ m.getEmail() +"' AND pwd = '" + m.getPwd() + "'");
위 코드처럼 문자열 연결 방식으로 SQL 쿼리를 만들면, 입력값에 따라 SQL 구조가 변경될 수 있음
{
"email" : "' or ''='",
"pwd" : "' or ''='"
}
예제 입력
SELECT * FROM member WHERE email='' OR ''='' AND pwd='' OR ''='';
실행되는 SQL : WHERE 조건이 항상 TRUE가 되어 모든 멤버 정보가 반환됨 → 보안 취약
SQL 인젝션 방어
PreparedStatement 사용
- SQL 구조를 고정하고 데이터만 전달하여 SQL 인젝션 방어
- function(email, pwd)처럼 실행됨
- function안의 내용은 컴파일되어있기 때문에 구조가 변경되지 않음. 데이터만 전달함
- 성능, 사용성, 보안 증가
MyBatis
- Apache iBatis에서 발전하여 만들어진 SQL 매핑 프레임워크
- SQL을 XML 또는 애노테이션으로 작성하여 직접 관리 가능
- JDBC보다 편리한 SQL 작성 및 실행 지원
- Connection Pool 제공으로 성능 최적화
@Mapper
public interface ProductDao {
public List<Product> getAllProducts() throws Exception;
}
- @Mapper를 사용하여 MyBatis가 자동으로 생성
- getAllProducts()는 SQL 매핑과 연결됨
<mapper namespace="com.shop.cafe.dao.ProductDao">
<select id="getAllProducts" resultType="Product">
select * from product limit 6
</select>
</mapper>
- namespace="com.shop.cafe.dao.ProductDao"
→ DAO 인터페이스와 연결 - id="getAllProducts"
→ ProductDao.getAllProducts() 메서드와 매핑 - resultType="Product"
→ 조회 결과를 Product 객체 리스트로 변환
Connection Pool
- 데이터베이스(DB) 연결을 미리 생성하고 재사용하는 기법
- 새로운 DB 연결(Connection)을 매번 생성하는 대신, 미리 생성된 연결을 풀(Pool)에 보관하고 필요할 때 가져다 사용
- 클래스를 직접 생성하면 요청마다 새 연결이 필요 → 비효율적
- 일정 개수의 DB 연결을 미리 생성하여 재사용
- 요청이 들어오면 풀에서 연결을 할당
- 요청이 끝나면 풀에 반환하여 대기
- 성능 향상 및 자원 낭비 방지
과제 (미니 프로젝트 도메인 생각하기)
도메인 : 패션 플랫폼(ex. 무신사, w컨셉)
주요 엔티티 : Member, Brand, Product, Cart, Order, Payment
'[LG 유플러스] 유레카 > Today I Learned' 카테고리의 다른 글
[TIL][03.11] 세션 & 토큰 로그인, 토큰 수명 관리 (0) | 2025.03.12 |
---|---|
[TIL][03.10] XSS, 외래키, tabnabbing, 토큰, 로그인 (0) | 2025.03.11 |
[TIL][03.06] @CrossOrigin, WebMvcConfigurer, 쿠키, 세션 스토리지, 로컬 스토리지, fetch, axios, jwt (0) | 2025.03.07 |
[TIL][03.05] secu.properties, MVC, 자원 자동 해제, 의존성 주입, Http Session (0) | 2025.03.06 |
[TIL][03.04] SQL, DISTINCT, WHERE, JOIN, MVC, Annotation, fetch, async/await (0) | 2025.03.05 |