이지은님의 블로그
250127 - Java Spring 기초: HTTP 요청과 응답, Database, JDBC, 웹 보안 본문
▷ 오늘 배운 것
오늘은 Spring 기초 5-6주차 개념부분을 듣고 알게된 것을 정리하려고 한다.
<<목차>>
1. HTTP 요청 데이터
1) GET + Query Parameter(=Query String)
2) POST + HTML Form(x-www-form-urlencoded)
3) HTTP Request Body
2. HTTP 응답 데이터
1) 정적 리소스
2) View Template
3) HTTP Message Body
3. Database
1) DBMS(Database Management System)
2) 트랜잭션
3) 데이터 무결성
4) MySQL의 필드에서 사용되는 제약조건
4. JDBC
1) JDBC
2) Statement VS Prepared Statement
3) Persistence Framework
4) SQL Mapper
5. 웹 보안
1) SQL Injection
2) XSS(Cross Site Scription)
1. HTTP 요청 데이터
Client가 Sercer로 데이터를 전달하는 방법은
- Query Parameter
- HttpServletRequest 사용
- @RequestParam(GET)
- HTTP Form Data
- HttpServletRequest 사용
- @ModelAttribute(POST)
- HTTP Request Body
- HttpServletRequest 사용
- I/O
- HttpEntity
- @RequestBody, @ResponseBody
가 있다.
1) GET + Query Parameter(=Query String)
이 방법은 URL의 쿼리 파라미터를 사용하여 데이터 전달하는 방법이다.
(1-1) Query Parameter - HttpServletRequest 사용
@Slf4j
@Controller
public class RequestParamController {
@GetMapping("/request-params")
public void params(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String key1Value = request.getParameter("key1");
String key2Value = request.getParameter("key2");
log.info("key1Value={}, key2Value={}", key1Value, key2Value);
response.getWriter().write("success");
}
}
Query Parameter(=Query String)으로 요청
http://localhost:8080/request-params?key1=value1&key2=value2
HttpServletRequest로 request를 받고, HttpServletResponse로 response를 보낸다.
request.getParameter()로 요청에 있는 파라미터 value값을 가져오며, response.getWriter().write()로 응답을 작성한다.(@Controller를 사용함에도 .getWriter().writer()로 데이터 출력가능 = @RestController를 사용한 것과 같음)
(1-2) Query Parameter - @RequestParam사용
(2-2) HTTP Form Data - @RequestParam사용
@ResponseBody
@GetMapping("/v2/request-param")
public String requestParamV2 (
@RequestParam String name,
@RequestParam int age
) {
// logic
log.info("name={}", name);
log.info("age={}", age);
return "success";
}
- @Controller + @ResponseBody = @RestController
: View를 찾는 것이 아니라 ResponseBody에 응답을 작성한다. - @RequestParam
: 파라미터 이름으로 바인딩
원래는
@RequestParam("name") String userName,
@RequestParam("age") int userAge
방식으로 어노테이션과 변수명을 써주지만, 어노테이션과 변수명이 같으면 생략도 가능하다.
String name, int age
다음과 같이 어노테이션까지 생략가능하지만, 가독성이 낮아 사용되지 않는다. 이럴경우 required = false 다.
@RequestParam(required = true) String name, // 필수
@RequestParam(required = false) int age // 필수가 아님
@RequestParam(required = true, defaultValue = "sparta") String name,
@RequestParam(required = false, defaultValue = "1") int age
Required로 필수값인지 아닌지 지정할 수 있으며(작성하지 않으면 400에러, 디폴트값 true), defaultValue로 기본값을 설정할 수 있다.
단, int타입은 null을 넘을 수 없으며, String의 경우 작성하지 않을시 빈문자열을 반환한다.
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
@RequestParam Map<String, String> map
) {
// logic
log.info("name={}", map.get("name"));
log.info("age={}", map.get("age"));
return "success";
}
다음과 같이 @RequestParam은 Map 또는 MultiValueMap의 형태로도 조회가 가능하다.
파라미터 Map의 Value가 1개인 경우에는 Map, 여러 개인 경우 MultiValueMap을 사용한다. 하지만 대부분의 파라미터 값은 한 개만 존재한다.
2) POST + HTML Form(x-www-form-urlencoded)
HTTP Request Body에 쿼리 파라미터 형태로 전달하는 방법
(2-1) HTTP From Data - HttpServletRequest 사용
HTTP Request
POST /form-data
content-type: application/x-www-form-urlencoded
key1=value1&key2=value2
(2-2) HTTP Form Data - @ModelAttribute사용
요청파라미터를 바인딩하고 보통 POST에 사용된다.
@ResponseBody
@PostMapping("/v2/tutor")
public String modelAttributeV2(
@ModelAttribute Tutor tutor
) {
String name = tutor.getName();
int age = tutor.getAge();
return "tutor name = " + name + " age = " + age;
}
HTTP Request
POST /v2/tutor
content-type: application/x-www-form-urlencoded
name=wonuk&age=100
이때, 만약 @Data의 Setter가 없다면 객체 필드에 set되지 않는다 —> @Getter, @Setter가 전부 있어야 호출가능
또한 파라미터의 타입이 다른 경우라면 BindException 발생하여 400코드를 띄운다 —> Validation(검증)이 필요
@ModelAttribute와 @RequestParam은 모두 생략이 가능하다. 기본타입일 경우 @RequestParam과 Mapping, 나머지 객체의 경우 모두 @ModelAttribute 와 Mapping한다.
3) HTTP Request Body
데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 전달한다.
@RestController + JSON 형식
POST, PUT, PATCH Method에서 주로 사용된다.
GET, DELETE Method는 Body에 데이터를 담는것을 권장하지 않는다.
(3-1) HTTP Request Body - HttpServletRequest 사용 (JSON)
@Slf4j
@Controller
public class RequestBodyController {
// JSON을 객체로 변환해주는 Jackson 라이브러리
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body")
public void requestBody(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
Board board = objectMapper.readValue(messageBody, Board.clss);
log.info("board.getTitle()={}, board.getContent()={}", board.getTitle(), board.getContent());
response.getWriter().write("success");
}
}
ObjectMapper은 JSON형식을 Java Object로 바꿔준다.
request.getInputStream()로 정보를 가져온 요청을 String형식으로 바꾸어서 이를 DTO형식으로 바꿔준다.(objectMapper.readValue(messageBody, Board.clss))
JSON을 Java 객체로 변환하려면 Jackson과 같은 라이브러리를 사용해야 한다. Spring Boot는 기본적으로 Jackson 라이브러리의 ObjectMapper를 제공한다.
(3-1) HTTP Request Body - HttpServletRequest 사용 (TEXT)
@Slf4j
@Controller
public class RequestBodyStringController {
@PostMapping("/v1/request-body-text")
public void requestBodyTextV1(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String bodyText = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
response.getWriter().write("response = " + bodyText);
}
}
Text로 요청을 넣을시 JSON을 ObjectMapping해서 Object로 뽑을 이유가 없으므로 StreamUtils.copyToString()을 이용하여 String으로 변환한다.
실행시 raw -> text 선택 또는 Content-Type : text/plain 설정
(3-2) HTTP Request Body - I/O (TEXT)
@PostMapping("/v2/request-body-text")
public void requestBodyTextV2(
InputStream inputStream,
Writer responseWriter
) throws IOException {
String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
responseWriter.write("response = " + bodyText);
}
InputStream을 이용하여 request.getInputStream()을 생략할 수 있고,
Writer을 이용하여 response.getWriter()을 생략할 수 있다.
(3-3) HTTP Request Body - HttpEntity (TEXT)
HttpEntity를 사용하면 HttpMessageConverter를 사용한다.
@PostMapping("/v3/request-body-text")
public HttpEntity<String> requestBodyTextV3(HttpEntity<String> httpEntity) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String body = httpEntity.getBody();
return new HttpEntity<>("response = " + body); // 매개변수 = Body Message
}
요청 뿐만이 아닌 응답까지 HttpEntity 하나만으로 사용이 가능해진다.
httpEntity.getBody()를 통해 .getInputStream()을 수행.
==> Request 뿐만 아니라 Response도 사용할 수 있도록 만들어준다.
HttpEntity 역할
- Http Request Body Message를 직접 조회한다
- Request 뿐만 아니라 Response도 사용할 수 있도록 만들어준다.
- Response Header 또한 사용할 수 있다.
- Request Parameter를 조회하는 기능들과는 아무 관계가 없다.
- View를 반환하지 않는다.
HttpEntity를 상속받은 객체: RequestEntity<>, ResponseEntity<>
이를 통해 요청과 응답을 분리할 수 있다.
@Controller
public class RequestBodyStringController {
@PostMapping("/v4/request-body-text")
public HttpEntity<String> requestBodyTextV4(RequestEntity<String> httpEntity) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String body = httpEntity.getBody();
// url, method 사용 가능
return new ResponseEntity<>("response = " + body, HttpStatus.CREATED); // Body Data, 상태코드
}
}
ResponseEntity<>를 통해 상태코드를 설정할 수 있다.
(3-4) HTTP Request Body - @RequestBody, @ResponseBody (TEXT)
@Controller // @RestController = @Controller + @ResponseBody
public class RequestBodyStringController {
@ResponseBody
@PostMapping("/v5/request-body-text")
public String requestBodyTextV5(
@RequestBody String body,
@RequestHeader HttpHeaders headers
) {
// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
String bodyMessage = body;
return "request header = " + headers + " response body = " + bodyMessage;
}
}
@RequestBody를 통해 요청 Body Data를 쉽게 조회할 수 있다.
@ResponseBody는 응답 메세지를 전달할 수 있다. (@RestController = @Controller + @ResponseBody)
@RequestHeader는 헤더 정보를 조회할 수 있다.
(3-3) HTTP Request Body - HttpEntity (JSON)
@RestController
public class JsonController {
@PostMapping("/v5/request-body-json")
public String requestBodyJsonV5(
HttpEntity<Tutor> httpEntity
) {
Tutor tutor = httpEntity.getBody();
return "tutor.getName() = " + tutor.getName() + " tutor.getAge() = " + tutor.getAge();
}
}
Generic Type으로 Tutor가 지정되어 있기 때문에 해당 Class로 반환된다.
(3-4) HTTP Request Body - @RequestBody, @ResponseBody (JSON)
// @RestController 사용
@RestController
public class JsonController {
@PostMapping("/v3/request-body-json")
public String requestBodyJsonV3(@RequestBody Tutor tutor) {
Tutor requestBodyTutor = tutor;
return "tutor = " + requestBodyTutor;
}
}
// @Controller 사용
@Controller
public class JsonController {
@ResponseBody // @RestController = @Controller + @ResponseBody
@PostMapping("/v6/request-body-json")
public Tutor requestJson(@RequestBody Tutor tutor) {
return tutor;
}
}
ObjectMapper는 JSON을 Object로 변환시켜주는데, @RequestBody를 사용하면 이를 생략시켜줄수 있다. 그 이유는 HttpEntity<>, @RequestBody를 사용하면 HTTPMessageConverter가 있어 Request Body의 Data를 개발자가 원하는 String이나 Object로 변환해준다.
그리고 @RequestBody는 생략이 불가능하며, 생략을 하게되면 @ModelAttribute로 작동하여 각 파라미터의 기본값으로 요청이 간다.
HTTPMessageConverter
: JSON데이터를 Object로 변환 가능하다.
2. HTTP 응답 데이터
Client가 Sercer로 데이터를 전달하는 방법은
- 정적 리소스
- 바로 출력하는 방법
- View Template
- @Contraller의 응답으로 String 출력
- HTTP Request Body
- HttpServletRequest 사용
- HttpEntity(ResponseEntity<>)
- @ResponseBody
가 있다.
1) 정적 리소스
아래 경로들에 정적 리소스가 존재하면 서버에서 별도의 처리 없이 파일 그대로 반환된다.
- /static
- /public
- /META-INF/resources
- src/main/resources
2) View Template
View Template은 Model을 참고하여 HTML 등이 동적으로 만들어지고 Client에 응답된다.(Thymeleaf 등)
@Controller
public class ViewTemplateController {
@RequestMapping("/response-view")
public String responseView(Model model) {
// key, value
model.addAttribute("data", "sparta");
return "thymeleaf-view";
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1>Thymeleaf</h1>
<h2 th:text="${data}"></h2>
</body>
</html>
Model을 이용하여 addAttribute하여 data부분을 치환 가능
Html(Thymeleaf) 파일의 th:text="${data}”에 data를 “Sparta”로 대체
반환타입을 void로 설정할 수는 있지만 잘 사용하지 않는다.
3) HTTP Message Body
(3-1) HTTP Request Body - HttpServletRequest 사용
@Controller
public class ResponseBodyController {
@GetMapping("/v1/response-body")
public void responseBodyV1(
HttpServletResponse response
) throws IOException {
response.getWriter().write("data");
}
}
@Controller와 response.getWriter()를 이용하여 출력
Response Body에 data 라는 문자열이 입력되어 응답된다.
(3-2) HTTP Request Body - HttpEntity(ResponseEntity<>)
@GetMapping("/v2/response-body")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("data", HttpStatus.OK);
}
@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
Tutor tutor = new Tutor("wonuk", 100);
return new ResponseEntity<>(tutor, HttpStatus.OK);
}
ResponseEntity<>를 통해 상태코드를 설정할 수 있다. —> 동적으로 조건문 등을 사용해 응답코드를 변경할 수도 있다.
(3-3) HTTP Request Body - @ResponseBody (TEXT)
// TEXT 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-text")
public String responseBodyText() {
return "data"; // HTTP Message Body에 "data"
}
(3-3) HTTP Request Body - @ResponseBody (JSON)
// JSON 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-json")
public Tutor responseBodyJson() {
Tutor tutor = new Tutor("wonuk", 100);
return tutor; // HTTP Message Body에 Tutor Object -> JSON
}
3. Database
1) DBMS(Database Management System)
- Database를 관리하고 운영하는 시스템
- 데이터를 정의, 저장, 검색, 수정, 삭제하는 등의 작업을 효율적으로 수행할 수 있게 해준다.
- 다수의 사용자들이 동시에 데이터에 접근하고, 데이터를 안전하게 관리할 수 있도록 다양한 기능을 제공한다.
- SQL이라는 언어로 DBMS에 데이터 관리를 요청하면 DBMS가 요청을 처리한다.
DBMS의 주요기능
: 데이터 정의, 데이터 관리, 데이터 보안, 트랜잭션 관리, 백업 및 복구, 동시성 제어
DBMS의 종류
- 관계형 DBMS(RDBMS): Oracle, MySQL, PostgreSQL, Microsoft SQL Server 등
- 비관계형 DBMS(NoSQL): MongoDB, Cassandra, Redis 등
- 다중 모델 DBMS: Amazon DynamoDB, Microsoft Azure Cosmos DB 등
그중 대표적인 RDBMS인 MySQL의 특징은 다음과 같다.
- 오픈소스 기반의 RDBMS로 무료로 제공된다.
- 빠른 속도와 높은 성능, 다양한 기능을 제공한다.
- 광범위한 운영체제와 플랫폼에서 사용할 수 있다.
- 전 세계적으로 널리 사용되며, 방대한 문서와 커뮤니티가 있어 학습 및 문제 해결이 용이하다.
- 트래픽에 따라 확장할 수 있다.
2) 트랜잭션
데이터베이스에서 하나의 논리적인 작업 단위를 뜻하며 트랜잭션으로 묶여있는 작업들은 모두 성공적으로 완료되거나 하나라도 실패하면 전체가 취소된다.
RDBMS는 트랜잭션이라는 단위를 통해 데이터베이스 작업을 처리하며, 이를 통해 데이터의 일관성과 무결성을 유지한다.
트랜잭션은 원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 지속성(Durability)이라는 ACID 속성을 따른다.
- Atomicity(원자성): 트랜잭션의 모든 작업이 성공적으로 완료되거나, 실패 시 모든 작업이 롤백
- Consistency(일관성): 트랜잭션이 데이터베이스를 일관된 상태로 유지
- Isolation(고립성): 동시에 실행되는 트랜잭션 간의 영향을 최소화
- Durability(지속성): 트랜잭션이 완료된 후 데이터의 변경 사항은 영구적으로 저장
👉 트랙잭션을 수행하는 명령어 - TCL(Transaction Control Language)
여러 DML 작업을 하나의 논리적 단위로 묶어 트랜잭션으로 처리하는 데 사용된다.
- COMMIT: 트랜잭션이 성공한 것을 데이터베이스에 알리고 모든 변경 사항을 영구적으로 저장한다.
- ROLLBACK: 트랜잭션 중 발생한 모든 변경 사항을 취소하고, 데이터베이스를 트랜잭션 시작 시점의 상태로 되돌린다.
3) 데이터 무결성
테이블은 데이터 무결성을 가진다.
데이터 무결성이란,
테이블은 특정 규칙과 제약 조건(기본 키, 외래 키, 유니크 등)을 통해 데이터를 저장함으로써 데이터의 무결성(정확성, 일관성, 유효성)을 유지하는 것이다.
- 정확성: 데이터가 올바르고 오류 없이 저장되는 것
ex) 숫자로 저장되어야 하는 값이 문자로 저장되지 않도록 하는 것. - 일관성: 데이터가 서로 모순되지 않고 조화를 이루는 상태를 유지하는 것
ex) 외래 키 제약 조건을 통해 두 테이블 간의 관계가 일치하는 것을 보장하는 것. - 완전성: 필요한 모든 데이터가 빠짐없이 저장되고 관리되는 것
ex) 필수 입력 항목이 비어 있지 않도록 하는 것.
==> 데이터가 입력, 저장, 전송, 처리되는 동안 변경되거나 손상되지 않도록 보장하는 개념
데이터 무결성의 종류
- 엔터티 무결성: 각 테이블의 기본 키(PK)가 중복되지 않고 NULL 값이 아닌 상태를 유지한다.
- 참조 무결성: 외래 키(FK)를 통해 참조되는 데이터가 유효성을 유지하도록 보장한다.
- 도메인 무결성: 각 열이 정의된 데이터 타입과 제약 조건에 따라 유효한 값을 유지하도록 한다.
4) MySQL의 필드에서 사용되는 제약조건
1️⃣ AUTO_INCREMENT: 고유번호 자동생성(칼럼의 값이 중복되지 않게 1씩 자동으로 증가)
2️⃣ NOT NULL: 해당 필드는 NULL 값을 저장할 수 없게 된다.
3️⃣ UNIQUE: 해당 필드는 서로 다른 값을 가져야만 한다. (동일한 값은 존재할 수 없다.)
4️⃣ PRIMARY KEY(기본 키): 테이블에서 각 행(row)을 고유하게 식별하는 하나 이상의 열(column)에 설정되는 제약조건, NOT NULL과 UNIQUE 제약 조건의 특징을 모두 가진다.
5️⃣ FOREIGN KEY(외래 키): 한 테이블의 열이 다른 테이블의 PRIMARY KEY(또는 UNIQUE 제약 조건이 적용된 열)를 참조하도록 설정한다.
6️⃣ CASCADE: 참조 무결성을 유지하기 위한 동작을 정의하는 규칙, 외래 키(Foreign Key) 제약 조건과 관련된 변경 사항이 발생할 때 참조하는 레코드에 대한 동작을 자동으로 처리하는 기능(FOREIGN KEY 로 연관된 데이터를 삭제,변경할 수 있다.)
7️⃣ DEFAULT: 필드의 기본 값을 설정
4. JDBC
1) JDBC
: Java 언어를 사용하여 DB와 상호 작용하기 위한 자바 표준 인터페이스로 데이터베이스 관리 시스템(DBMS)과 통신하여 데이터를 삽입(C), 검색(R) , 수정(U) 및 삭제(D)할 수 있게 해준다.
주요 특징
- 표준 API: 대부분의 RDBMS (관계형 데이터베이스 관리 시스템)에 대한 드라이버가 제공되어 여러 종류의 DB 대해 일관된 방식으로 상호 작용할 수 있다.
- 데이터베이스 연결
- SQL 쿼리 실행
- Prepared Statement
- 결과 집합 처리(Result Set)
- 트랜잭션 관리
2) Statement VS Prepared Statement
Statement란,
DB와 연결되어 있는 Connection 객체를 통해 SQL문을 Database에 전달하여 실행하고, 결과를 반환받는 객체
- SQL 쿼리를 직접 문자열로 작성하여 데이터베이스에 보내는 방법이다.
- SQL 쿼리는 실행 전에 문자열 형태로 전달되고, 실행 시점에 데이터베이스에 직접 파싱되고 실행.
- 매번 실행할 때마다 SQL 문을 다시 파싱하므로 성능에 영향을 미칠 수 있고, 보안 취약점을 가질 수 있다.
Statement statement = connection.createStatement();
String query = "SELECT * FROM MEMBER WHERE NAME = 'wonuk'";
ResultSet rs = statement.execute(query);
Prepared Statement란,
SQL문을 Complie 단계에서 ? 를 사용하여 preCompile 하여 미리 준비해놓고 Query문을 파라미터 바인딩 후 실행하고 결과를 반환하는 객체
- SQL 쿼리를 미리 컴파일하여 데이터베이스에 전송할 때 값만 바뀌는 형태로 전달한다.
- 쿼리가 한 번 컴파일되면 여러 번 실행할 수 있으며, 성능이 향상되고 보안 측면에서 더 안전한다.
- 동적인 입력값을 placeholder?로 대체하고 파라미터 바인딩을 통해 쿼리를 삽인한다. 즉, 사용자 입력을 직접적으로 쿼리에 삽입하지 않는다.
- 이스케이핑 처리를 지원한다. 말그대로 탈출(Escape), 입력값이 자동으로 쿼리에 안전하게 이스케이핑된다. 이스케이핑은 입력 데이터에서 잠재적인 SQL 쿼리 문자열을 무력화한다.
String query = "SELECT * FROM employees WHERE department = ?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, "HR");
ResultSet resultSet = preparedStatement.executeQuery();
3) Persistence Framework
JDBC는 간단한 SQL을 실행하는 경우에도 중복된 코드가 너무 많았다. DB에 따라 일관성 없는 정보를 가진 채로 Checked Exception(SQL Exception) 처리를 하기 떄문에 예외메세지와 코드 마다 명시적으로 처리해줄 필요가 있었다.
Connection과 같은 공유 자원을 제대로 반환하지 않으면 한정된 시스템 자원(CPU, Memory)에 의해 서버가 다운되는 등의 문제가 발생한다.
무엇보다 JDBC는 SQL Query를 개발자가 직접 작성한다는 한계가 존재했다.
이를 보완하기 위해 Persistence Framework이 등장했다.
Persistence Framework란,
JDBC 처럼 복잡함이나 번거로움 없이 간단한 작업만으로 Database와 연동되는 시스템이다.
크게 SQL Mapper, ORM 두가지로 나눌 수 있다.
👉 JDBC, SQL MAPPER, ORM의 공통점
영속성(Persistence) 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지 않는 데이터의 특성, 영구히 저장되는 특성
4) SQL Mapper
SQL Mapper란,
직접 작성한 SQL 문의 실행 결과와 객체(Object)의 필드를 Mapping하여 데이터를 객체화한다.
대표적인 SQL Mapper로 Spring JDBC Template, MyBatis가 있다.
1️⃣ Spring JDBC Template: Spring Framework에서 제공하는 JDBC 작업을 단순화하고 개선한 유틸리티 클래스
- 간편한 데이터베이스 연결: 손수 적었던 Connection 관련 코드들을 yml 혹은 properties 파일에 설정만으로 해결한다.
- Prepared Statement를 사용한다.
- 예외 처리와 리소스 관리: DB Connection을 자동으로 처리하여 리소스 누수를 방지한다.
- 결과 집합(ResultSet) 처리: 데이터를 자바 객체로 변환할 수 있도록 돕는다.
- 배치 처리 작업을 지원한다.: 매일 동일한 시간에 수행되는 쿼리, 주로 통계에 사용된다.
@Repository
public class MemberRepository {
private final JdbcTemplate jdbcTemplate;
public MemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Member> findById(Long id) {
String query = "SELECT * FROM MEMBER WHERE id = " + id;
return jdbcTemplate.query(query, (rs, rowNum) -> {
Member member = new Member ();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
member.setAge(rs.getInt("age"));
return member;
});
}
}
2️⃣ Mybatis: SQL 쿼리들을 XML 파일에 작성하여 코드와 SQL을 분리하여 관리되도록 만들어준다
- 자동으로 Connection 관리를 해주면서 JDBC 사용할 때의 중복 작업 대부분을 없애준다.
- DB 결과 집합을 자바 객체로 매핑할 수 있다.
- 복잡한 쿼리나 다이나믹하게(동적쿼리) 변경되는 쿼리 작성이 쉽다.
- 관심사 분리 - DAO로부터 SQL문을 분리하여 코드의 간결성 및 유지보수성이 향상된다.
- 쿼리 결과를 캐싱하여 성능을 향상시킬 수 있다.
SQL Mapper의 한계
- SQL을 직접 다룬다.
- 특정 DB에 종속적으로 사용하기 쉽다.
- → 다른 DB를 사용하면 쿼리도 변경해야할 가능성이 높다.
- 테이블마다 비슷한 CRUD SQL, DAO(Data Access Object) 개발이 반복된다. → 코드 중복
- 테이블 필드가 변경될 시 이와 관련된 모든 DAO의 SQL문, 객체의 필드 등을 수정해야 한다.
- 객체와의 관계는 사라지고 DB에 대한 처리에 집중하게 된다. → SQL 의존적인 개발
5. 웹 보안
1) SQL Injection
: 악의적인 사용자가 애플리케이션에서 입력 데이터를 이용하여 SQL 쿼리를 조작하고 데이터베이스에 무단 접근하거나 데이터를 변조하는 공격이다.
SQL Injection 종류
- Error Based SQL Injection: Database에 고의적으로 오류를 발생시켜 에러 응답을 통해 DB 구조를 파악하는 방법.
- Union Based SQL Injection: DB의 UNION 연산자를 사용하여 쿼리 결과값의 조합을 통해 정보를 조회한다.
- Blind Based SQL Injection
- Stored Procedure SQL Injection
- Time Based SQL Injection
해결방법
- 클라이언트에게 에러 메세지 노출을 차단한다.
- 입력값을 검증(Validation)한다.
- Prepared Statements를 사용한다.
2) XSS(Cross Site Scription)
: 악성 스크립트를 웹사이트에 주입하는 Code Injection 기법 중 하나. 공격자가 웹 어플리케이션에 보낸 악성 코드가 다른 사용자에게 전달될 때 발생한다.
XSS의 종류
- Stored XSS: 공격자가 취약점이 있는 Web Application에 악성 스크립트를 영구적으로 저장하여 다른 사용자에게 전달하는 방식. 해당 script를 게시글에 삽입하면 HTML로 구성되어 있기 때문에 해당 스크립트가 조회하는 사용자에게 실행된다.
- Reflected XSS: 외부 링크 페이지로 이동시킨다.
- DOM based XSS
해결방법
- 입/출력 값을 검증(Validation)하고 필터링하여 해결한다.
- 외부 보안관련 라이브러리를 사용한다.
- 보안 솔루션을 사용한다.