#{}: PreparedStatement의 ? 를 대체 → 값에 대한 대체로 SQL문장을 구성하지는 않음${}: 문장 자체에 대한 재구성 가능 → Statement 사용public List<Address> queryStructureChange(Map<String, Object> param);
<select id="queryStructureChange" resultMap="addressMap">
select * from address where ${key}=#{word}
</select>
@Test
public void queryStructureChangeTest() {
List<Address> address = aDao.queryStructureChange(Map.of("key", "title", "word", "7|="));
Assertions.assertEquals(1, address.size());
address = aDao.queryStructureChange(Map.of("key", "mno", "word", "100"));
Assertions.assertEquals(0, address.size());
address = aDao.queryStructureChange(Map.of("key", "1=1 or mno", "word", "100"));
Assertions.assertEquals(2, address.size());
}
⚠️ 테스트 결과 1=1 or (SQL Injection) 공격에 취약!
상황에 따라 분기 처리를 통해 SQL을 동적으로 만드는 것
가장 많이 사용되는 태그로 주로 조건절에 따라 where 문장을 구성할 때 사용
<select id="dynamicQueryIf" resultMap= "addressMap">
select * from address
<if test="title != null">
where title = #{title}
</if>
<if test="mno != null">
where mno=#{mno}
</if>
</select>
where가 두개? 어떻게 해결할까?
<select id="dynamicQueryIf" resultMap="addressMap">
select * from address where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="mno != null">
and mno=#{mno}
</if>
</select>
오! 해결되긴 했는데 뭔가 찜찜.. (1=1도 연산인데..)
<select id="dynamicQueryChoose" resultMap="addressMap">
select * from address where 1=1
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="mno != null">
and mno = #{mno}
</when>
<otherwise></otherwise>
</choose>
</select>
<select id="dynamicQueryWhere" resultMap="addressMap">
select * from address
<where>
<if test="title != null">
and title = #{title}
</if>
<if test="mno != null">
and mno=#{mno}
</if>
</where>
</select>
좀 더 다양한 상황에서 사용 가능
<select id="dynamicQueryTrim" resultMap="addressMap">
select * from address
<trim prefix="where" prefixOverrides="and|or">
<if test="title != null">
and title = #{title}
</if>
<if test="mno != null">
and mno=#{mno}
</if>
</trim>
</select>
주로 in 절을 추가할 경우 사용
<select id="selectUseIn" resultMap="addressMap">
select * from address
<where>
<foreach collection="titles" item="title" open="title in (" separator=", " close=")">
#{title}
</foreach>
</where>
</select>
update에서 set 절을 생성하며 맨 마지막 column 표기에서 쉼표 제거
<update id="dynamicUpdate">
update address
<set>
<if test="mno != null">mno=#{mno},</if>
<if test="title != null">title=#{title},</if>
<if test="address != null">address=#{address},</if>
<if test="detailAddress != null">detail_address=#{detailAddress},</if>
<if test="x != null">x=#{x},</if>
<if test="y != null">y=#{y},</if>
</set>
where ano=#{ano}
</update>
<, >, <=, >= 조건 절에서 부등호를 사용시 XML이라서 태그로 인식할 수도 있다!
해결 방법 2가지
1. < > ≤ ≥
2. <
PlatformTransactionManager

@Transactional로 트랜잭션 처리 선언Service에서 SQLException이 전파되지 않음 → DataAccessException으로 대체!
private String registMember(@ModelAttribute Member member, Model model, HttpSession session) {
try {
// 처리 로직
} catch (DataAccessException e) {
e.printStackTrace();
model.addAttribute("error", e.getMessage());
return "member/member-regist-form";
}
}
@Target({ElementType.TYPE, ElementType.METHOD}) // 클래스나 메서드에 적용 가능
public @interface Transactional {
// 지정된 예외 발생 시 롤백 수행
Class<? extends Throwable>[] rollbackFor() default {};
// 지정된 예외 발생해도 롤백하지 않음
Class<? extends Throwable>[] noRollbackFor() default {};
// 트랜잭션 전파 방식 정의 (기본값: 이미 있으면 참여, 없으면 생성)
Propagation propagation() default Propagation.REQUIRED;
// 트랜잭션 격리 수준 정의 (기본값: 데이터베이스 기본 설정 사용)
Isolation isolation() default Isolation.DEFAULT;
// 트랜잭션 제한 시간(초) 설정 (기본값: -1, 제한 없음)
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
// 읽기 전용 트랜잭션 여부 (true면 데이터 변경 불가)
boolean readOnly() default false;
}
RuntimeException 계열의 예외 발생 시 Rollback
기본 동작 2가지
1. noRollbackFor: RuntimeException 중 commit 대상 예외
2. rollbackFor: Checked Exception(business exception) 중 rollback 대상이 되는 예외 설정
@Transaction에서 다른 @Transaction을 호출하는 경우 기존 트랜잭션의 전파 속성
PROPAGATION.REQUIRED
PROPAGATION.REQUIRED_NEW
PROPAGATION.NESTED

일반적으로 ISOLATION_DEFAULT 사용: 개별 Programmer의 영역이 아닌 DBA의 영역
➡️ 그래서 명시적 트랜잭션 처리가 필요하다!
@Autowired
private PlatformTransactionManager txManager; // TransactionManager 획득
@Override
public int joinProgrammingTX(Member member) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // transaction definition 생성
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def); // txManager를 통해 TransactionStatus 획득
int result = -1;
try {
// insert 작업
// update 작업
txManager.commit(status); // 성공 - commit
} catch (RuntimeException e) {
txManager.rollback(status); // 실패 - rollback
}
return result;
}