1. 동적 쿼리
#{ } vs ${ } (추가 예정)#{ } : ‘ ‘ 문자열 형식 → 이걸 더 권장
${ } : 문자열 형태로 들어가지 않고 다이렉트로 변수 그대로 → 보안 위험
1) DTO가 없을 땐
public String search(@RequestParam(”key”) String key, … )
2) DTO가 있을 땐
public String search(@ModelAttribute SearchCondition searchCondition, Model moidel){
//model.addAttribute("list", model)
model.addAttribute("list", boardService.search(searchCondition));
return "/board/list";
}
public interface BoardService{
// ... 생략
public List<Board> search(SearchCondition searchCondition);
}
public class BoardServiceImpl implemnets BoardService{
// 생략
@Override
public List<Board> search(SearchCondition searchCondition){
return boardDao.search(searchCondition);
}
}
public interface BoardDao{
//생략
// 검색 기능
public List<Board> search(SearchCondition searchCondition);
}
<!-- BoardMapper.xml 에 검색 기능 추가 -->
<select id="search" resultMap="boardMap" parameterType="SearchCondition">
SELECT id, content, writer, title, view_cnt, date_format(reg_date, '%y-%m-%d') AS reg_date
FROM board
<!-- 동적 쿼리 (검색 조건) -->
<if test="key != 'none'">
WHERE ${key} LIKE concat('%', #{word}, '%')
</if>
<!-- 동적 쿼리 (정렬 조건) -->
<if test="orderBy != 'none'">
ORDER BY ${orderBy} ${orderByDir}
</if>
</select>
<!-- type 과 map 은 엄연히 다르다! -->
<select id="search" resultType="board"></select>
BoardController.java
// 생략
@PostMapping("/write")
public String write(@ModelAttribute Board board){
boardService.writeBoard(board);
//목록으로 가겠다!
//return "redirect:list";
//상세 게시글 조회
return "redirect:detail?id=" + board.getId();
}
// 생략
BoardMapper.xml
<!-- 게시글 등록 -->
<insert id="insertBoard" parameterType="Board" keyProperty="id" userGenerateKeys="true">
INSERT INTO board (title, writer, content)
VALUES (#{title}, #{writer}, #{content});
</insert>
[MyBatis/마이바티스] - resultMap, resultType
2. Spring TX
데이터 베이스의 상태를 변화시키기 위해 수행하는 논리적인 작업의 단위
원자성 / 일관성 / 격리성 / 지속성
spring 프레임워크의 일부. 선언적인 방식을 통해 DB 트랜잭션을 관리하도록 설계됨

<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<!-- Spring의 트랜잭션 관리 모듈 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
데이터 소스와 트랜잭션 관리자 Bean을 선언
<!-- Q2. Transaction 처리를 위한 transactionManager를 등록한다. (아이디는 언제나 고정적으로 transactionManager로 짓는다.) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
bean으로 등록된 상태에 있는 선언적 트랜잭션을 사용할 대상을 스캔할 수 있도록 tx:annotation-driven 설정을 추가
선언적 트랜잭션을 사용하는 대상은 이미 Bean으로 등록된 상태여야 가능
<!-- Q3. 트랜잭션 기능을 사용하기 위해 트랜잭션 매니저 등록 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
트랜잭션을 적용할 메서드를 선언. 클래스 또는 메서드 수준에서 사용 가능
@Transactional
public int insert(Movie movie){
return movieDao.insert(movie);
}
spring.io > spring Framework > Learn > current version Reference Doc > search > ‘tx’ 검색


root-context.xml 헤더 xsi:schemaLocation= 이하에 추가해야 할 2가지 설정을 찾을 수 있다.

<if test = "condition">
-- condition이 참일 때만 SQL쿼리에만 포함되는 부분
</if>
<!-- case문을 사용하여 order by를 동적으로 처리하는 SQL문 -->
<!-- Q2. order가 name이면 name으로 정렬, position이면 position으로 정렬, 그 외에는 id로 정렬 -->
<select id="selectByCase" resultType="Employee" parameterType="java.lang.String">
SELECT * FROM employee
ORDER BY
<choose>
<when test="order == 'name'">
name
</when>
<when test="order == 'position'">
position
</when>
<otherwise>
id
</otherwise>
</choose>
desc
</select>
<!-- Q1. name이나 position 중 하나 또는 둘 다 전달받아 해당 조건을 만족하는 사원을 조회하는 SQL문 -->
<select id="selectByCondition" resultType="Employee" parameterType="Employee">
SELECT * FROM employee
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
AND name = #{name}
</if>
<if test="position != null">
AND position = #{position}
</if>
</trim>
</select>
<foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
#{item}
</foreach>
list 컬렉션의 각 item을 반복하며 각 항목을 ()로 감싸고, 항목 사이에 쉼표를 삽입해 SQL 쿼리를 생성하는 코드
HTML <select> 요소 내에서 selected 속성은 특정 <option>이 기본적으로 선택되어 있어야 함을 나타냅니다. 이 속성을 동적으로 처리함으로써, 폼이 로드될 때 사용자의 이전 선택이나 특정 기본값을 선택 상태로 표시할 수 있습니다.
사용자가 선택한 정렬 기준과 방향을 서버로 전달하기 위해 select 태그를 사용합니다.
list.jsp
<!-- 검색 조건을 입력할 수 있는 form -->
<form action="${root}/board/search" method="get">
<label for="title">제목: </label>
<input type="text" id="title" name="title" value="${condition.title}">
<label for="content">내용: </label>
<input type="text" id="content" name="content" value="${condition.content}">
<!-- 정렬 순서와 정렬 방향 -->
<!--
- order의 값이 created인 경우 '생성일', viewCnt인 경우 '조회수'를 표시
- direction의 값이 asc인 경우 '오름차순', desc인 경우 '내림차순'을 표시
-->
<select name="order" id="order">
<c:forEach var="order" items="created,viewCnt">
<option value="${orderOption}" ${orderOption == condition.order ? 'selected' : ''}>${orderOption == 'created' ? '생성일': '조회수'}</option>
</c:forEach>
</select>
<select name="direction">
<option value="asc" ${condition.direction == 'asc' ? 'selected' : ''}>오름차순</option>
<option value="desc" ${condition.direction == 'desc' ? 'selected' : ''}>내림차순</option>
</select>
<input type="submit" value="검색">
</form>
[요구 조건]
검색 조건에 따라 게시글을 검색하는 쿼리문 검색 조건:
제목, 내용, 정렬, 정렬방향 제목, 내용은 like 연산자를 사용하여
검색 정렬은 order by를 사용하여 정렬 정렬방향은 asc, desc로 설정
(**검색 조건이 없을 경우 전체 게시글을 검색**)
수정 전
<trim> 태그와 prefixOverrides="AND" 사용: <trim> 태그는 조건식의 시작 부분에 있는 AND를 제거하는 데 사용됩니다. 하지만, 이 경우에는 <where> 태그를 사용하는 첫 번째 쿼리와 달리 조건이 전혀 없을 때 WHERE 절 자체를 제거하지 않습니다. 따라서, 모든 조건이 없을 경우에도 WHERE 절이 남아있게 되어 SQL 문법 오류가 발생할 수 있습니다.${} 사용: ORDER BY 절에서 ${order}와 ${direction}을 직접 변수 치환으로 사용합니다. 이 방식은 변수의 값이 직접 쿼리 문자열로 삽입되기 때문에 SQL 인젝션 공격에 취약할 수 있습니다. 또한, order 또는 direction 변수가 비어있거나 잘못된 값일 경우 문법적으로 유효하지 않은 SQL 쿼리가 생성되어 오류가 발생할 수 있습니다.<select id="searchBoard" parameterType="SearchCondition" resultType="Board">
SELECT *
FROM board
WHERE
<trim prefixOverrides="AND">
<if test= "title!=null">
AND title LIKE concat('%', #{title}, '%')
</if>
<if test="content!=null">
AND content LIKE concat('%', #{content}, '%')
</if>
</trim>
<if test="order != null">
ORDER BY ${order} ${direction}
</if>
</select>
수정 후
<where> 태그 사용: 이 태그는 자동으로 조건이 있을 경우에만 WHERE 절을 추가합니다. 또한, 내부 조건 앞의 AND나 OR 같은 접두사를 자동으로 처리합니다. 즉, 조건이 하나도 없을 경우 WHERE 절 자체가 생략되며, 첫 번째 조건 앞에 있는 불필요한 AND를 제거해줍니다.<choose> 사용: ORDER BY 절에서 order 변수의 값에 따라 다른 컬럼으로 정렬을 수행합니다. 이는 유연하게 여러 정렬 기준을 적용할 수 있게 해 줍니다. 또한, ${} 대신 #{}를 사용하지 않음으로써 SQL 인젝션 공격에 대한 잠재적 위험을 감소시킵니다.asc와 desc를 선택할 수 있게 하며, 사용자가 입력하지 않았을 경우 기본적으로 desc로 설정합니다.<select id="searchBoard" parameterType="SearchCondition" resultType="Board">
SELECT *
FROM board
<where>
<if test="title != null and title != ''">
AND title LIKE concat('%', #{title}, '%')
</if>
<if test="content != null and content != ''">
AND content LIKE concat('%', #{content}, '%')
</if>
</where>
<if test="order != null and order != ''">
ORDER BY
<choose>
<when test="order == 'created'">created</when>
<when test="order == 'viewCnt'">viewCnt</when>
<otherwise>id</otherwise>
</choose>
<choose>
<when test="direction == 'asc'">ASC</when>
<otherwise>DESC</otherwise>
</choose>
</if>
</select>
두 번째 쿼리에서 오류가 발생하는 주된 이유는 ${order}와 ${direction} 변수 사용과 관련된 문제 때문입니다. 만약 이 변수들 중 하나라도 적절한 값을 갖지 않는 경우(예: 빈 문자열, 예상치 못한 값), ORDER BY 절이 문법적으로 유효하지 않게 됩니다. 예를 들어, ORDER BY 뒤에 적절한 컬럼 이름이 오지 않거나, direction이 asc나 desc가 아닌 값으로 설정될 경우 SQL 문법 오류가 발생합니다.
첫 번째 쿼리 방식을 사용하면 이러한 문제를 방지할 수 있습니다. order와 direction에 대해 명시적으로 처리를 하고, 유효하지 않은 값이 들어왔을 때 기본적으로 안전한 동작을 할 수 있도록 해 줍니다. 또한, <where> 태그의 사용은 조건이 없을 때 WHERE 절 자체를 생략하여 불필요한 오류를 방지합니다.
header.jsp
<!-- 생략 -->
<div id="nav-header">
<%-- session에서 loginUser를 가져와서 존재 여부에 따라 로그인 폼 또는 사용자 정보를 출력한다. --%>
<div class="nav-search">
**<form action="search" method="get">
<select name="key">
<option value="none">선택</option>
<option value="id">ID</option>
<option value="name">이름</option>
<option value="email">이메일</option>
</select>
<input type="text" name="word" placeholder="검색어">
<input type="submit" value="검색">
</form>**
</div>
<div class="nav-login">
<!--이하 생략 -->
userMapper.xml
<!-- 생략 -->
<select id="searchByCondition" parameterType="SearchCondition"
resultType="User">
SELECT * FROM users
<!-- 동적쿼리 작성 -->
<where>
<choose>
<when test="key=='id'">
AND id LIKE CONCAT('%', #{word}, '%')
</when>
<when test="key=='name'">
AND name LIKE CONCAT('%', #{word}, '%')
</when>
<when test="key=='email'">
AND email LIKE CONCAT('%', #{word}, '%')
</when>
</choose>
</where>
<if test="orderBy != 'none'">
ORDER BY
<choose>
<when test="orderBy=='id'">
id
</when>
<when test="orderBy=='name'">
name
</when>
<when test="orderBy=='email'">
email
</when>
<otherwise>
id
</otherwise>
</choose>
<choose>
<when test="orderByDir == 'asc'">
ASC
</when>
<otherwise>
DESC
</otherwise>
</choose>
</if>
</select>**
</mapper>
int err = 1/0; 는 고의로 ArithmeticException (0으로 나누기)을 발생시키는 예시 코드
1) @Transactional annoation 존재
**@Override
@Transactional // 무결성을 위한 애노테이션
public void insert(User user) {
userDao.insert(user);
int err = 1/0;
}**
insert 수행 시 @Transactional annoation이 존재하므로 regist.jsp에서 유저 가입 정보 입력 시 DB에 반영되지 않는다.
2) @Transactional annoation 생략
**@Override
public void insert(User user) {
userDao.insert(user);
int err = 1/0;
}**
insert 수행 시 @Transactional annoation이 존재하지 않으므로 regist.jsp에서 유저 가입 정보 입력 시 DB에 반영된다. == 무결성이 보장되지 않는다.