트랜잭션
하나의 업무를 처리하는 SQL 명령들을 트랜잭션이라고 합니다.
트랜잭션은 모두 정상 실행되어야합니다.
정상 실행 완료 후 COMMIT 명령을 실행합니다.
명령 처리 중 오류가 발생하면 이미 실행된 SQL 명령은 취소가 되어야 합니다.
ROLLBACK 명령을 실행하여 이전 COMMIT 또는 ROLLBACK 지점으로 돌아갑니다.
spring 트랜잭션 예제 : 도서 대여 프로그램
회원, 도서, 대여 테이블을 준비합니다.
대여 테이블 대여일자를 insert하고, 반납예정일자를 대여일자+14 하여 update하는
두 DML을 트랜잭션으로 실행하려 합니다.
DTO
BookRentDto
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder //생성자 대신 프로퍼티 값을 설정 : 빌더 패턴
public class BookRentDto {
private int rent_no; //sequence 자동생성
private int mem_idx;
private String bcode;
private Date rent_date; //대여날짜 : sysdate
private Date exp_date; //반납기한날짜=대여날짜+14일, insert 후에 update로 변경
private Date return_date; //실제 반납일
private int delay_days; //기본값 0
}
//내부 클래스 : 클래스 안의 클래스 BookRentDtoBuilder
Mapper 인터페이스
public interface BookRentMapper {
void insert(BookRentDto dto);
void updateExpDate(int rent_no);
List<BookRentDto> selectAll();
}
Service 클래스
BookRentService
@Component
public class BookRentService {
private BookRentMapper mapper;
public BookRentService(BookRentMapper mapper) {
this.mapper = mapper;
}
//insert와 update를 트랜잭션으로 처리하기 위한 어노테이션
@Transactional
public void insert(BookRentDto dto) {
mapper.insert(dto);
mapper.updateExpDate(dto.getRent_no());
}
}
insert문과 update문을 실행하는 insert메소드를 선언합니다.
mybatis 설정 파일
<configuration>
<typeAliases> <!-- 패키지명.클래스명 대신에 사용할 별칭을 짧게 지정합니다.-->
<typeAlias type="day4.dto.MemberDto" alias="Member"/>
<typeAlias type="day4.dto.BookDto" alias="Book"/>
<typeAlias type="day4.dto.BookRentDto" alias="BookRent"/>
</typeAliases>
</configuration>
typeAlias 속성으로 클래스의 별칭을 지정합니다.
Mapper XML 파일
bookrent.xml
<mapper namespace="day4.mapper.BookRentMapper">
<insert id="insert" parameterType="BookRent"
useGeneratedKeys="true" keyColumn="rent_no" keyProperty="rent_no">
insert into tbl_bookrent (rent_no,mem_idx,bcode,rent_date)
values(bookrent_seq.nextval,#{mem_idx},#{bcode},sysdate)
</insert>
<update id="updateExpDate" parameterType="int">
update tbl_bookrent set exp_date=rent_date+14 //반납예정일자 = 빌린 날짜 + 14
where rent_no = #{rent_no}
</update>
</mapper>
***** 시퀀스로 생성된 값을 insert 후에 받아옵니다.
useGeneratedKeys="true"
keyColumn="sql 컬럼명"
keyProperty="변수(프로퍼티)명"
insert 후에 만들어진 시퀀스 rent_no 컬럼의 값을 가져와 dto 객체의 프로퍼티 rent_no에 저장합니다.
→ insert 전 dto 객체의 rent_no 값은 0, insert 후 dto 객체의 rent_no 값은 시퀀스값입니다.
받아온 값을 update문의 조건식에 사용합니다.
→ where rent_no = #{rent_no}
insert 전 후 프로퍼티 rent_no 확인
applicationContext.xml (빈 설정 파일)
*****
스프링 트랜잭션 처리를 하기 위한 bean을 설정합니다.
calss 속성에 DataSourceTransactionManager 클래스
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
id="transactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
트랜잭션 적용을 어노테이션으로 하기 위한 설정을 합니다.
Namespaces 탭에서 tx를 체크합니다.
<tx : annotation-driven> 태그를 추가합니다.
<tx:annotation-driven transaction-manager="transactionManager"/>
이렇게 설정하면 @Transactional 어노테이션으로 트랜잭션을 설정할 수 있습니다.
BookRentService - insert메소드
트랜잭션 처리를 위한 서비스 메소드에 어노테이션 @Transactional을 설정합니다.
@Transactional
public int insert(BookRentDto dto) {
int result = mapper.insert(dto);
mapper.updateExpDate(dto.getRent_no());
return result;
}
@Transactional을 설정하면
→ insert문은 실행되고, update문에서 오류 발생 시 rollback 됩니다.
@Transactional을 설정하지 않으면
→ insert문은 실행되고, update문에서 오류 발생 시 rollback 되지 않습니다.
→ 새로운 데이터가 insert됩니다.
빌더 패턴(Builder Pattern)
빌더 패턴을 이용하여 생성자 대신 프로퍼티 값을 설정할 수 있습니다.
커스텀 생성자를 사용하여 객체를 생성할 때, 인자의 순서를 맞춰야 합니다.
필요 없는 인자가 있을 때, 더미 값을 넣거나 생성자를 새로 만들어야 합니다.
생성자의 인자가 많을 때 빌더 패턴을 사용하면 위의 불편함을 줄일 수 있습니다.
lombok 라이브러리의 @Builder 어노테이션으로 빌더 패턴을 사용할 수 있습니다.
DTO - BookRent
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BookRentDto {
private int rent_no;
private int mem_idx;
private String bcode;
private Date rent_date;
private Date exp_date;
private Date return_date;
private int delay_days;
}
@Builder 어노테이션을 사용하면 내부 클래스로 BookRentDtoBuilder 클래스가 생성됩니다.
Builder로 객체 생성하기
// 1) 생성자
BookRentDto dto = new BookRentDto(0,10005,"T1234",null,null,null,0);
// 2) 빌더
BookRentDto dto = BookRentDto.builder()
.bcode("T1234")
.mem_idx(10005).build();
클래스명.builder( ) : 내부 Builder 클래스를 호출합니다.
.프로퍼티(값) : 프로퍼티에 값을 전달합니다.
.build( ) : 생성자를 호출하여 객체를 생성합니다.
JUnit
JUnit은 Java에서 사용하는 단위 테스트 프레임워크입니다.
테스트할 내용을 메소드(기능별) 단위로 작성해서 실행할 수 있습니다.
테스트 코드는 src/test/java 소스 폴더에 작성하고, 최종 패키징(배포 파일)에서 제외됩니다.
@Test 어노테이션으로 테스트할 메소드를 지정합니다.
테스트 케이스 생성하기
src/test/java 소스 폴더에 패키지를 생성합니다.
패키지를 우클릭하고 [New] - [JUnit Test Case]를 선택합니다.
[New JUnit Jupiter test]를 선택하고 클래스 이름을 작성 후 [Finish] 버튼을 누릅니다.
테스트 실행
@Test 어노테이션이 설정된 메소드를 테스트합니다.
@ExtendWith(SpringExtension.class) //spring-test를 사용해서 bean에 접근하기 위함입니다.
@ContextConfiguration(locations= {"classpath:META-INF/spring/applicationContext.xml"})
class BookRentTest {
@Autowired
BookRentService rentService;
@Autowired
BookService bookService;
@Autowired
MemberService memberService;
@Autowired
ApplicationContext context;
@Test
void rentServiceTest() {
assertNotNull(rentService); //rentService 객체가 not null이면 성공
BookRentDto dto = BookRentDto.builder()
.bcode("T1234")
.mem_idx(10005).build();
int result = rentService.insert(dto);
assertEquals(result, 1); //result 값이 1이면 성공
}
}
테스트 결과는 성공 또는 실패입니다.
assertNull( ) 메소드 : 인자가 null이면 성공입니다.
assertNotNull( ) 메소드 : 인자가 null이 아니면 성공입니다.
assertEquals( ) 메소드 : 두 개의 인자가 값이 같으면 성공입니다.
[Run] -[Run As] -[JUnit Test] 를 선택하여 실행합니다.
실행 결과
테스트 성공 시
테스트 실패 시
'수업 일지 > Spring' 카테고리의 다른 글
65일차 - [Spring] Model/@ModelAttribute/@SessionAttribute (0) | 2022.04.11 |
---|---|
64일차 - [Spring] 서블릿/MVC 패턴 (0) | 2022.04.08 |
62일차 - [Spring] mybatis-spring/dbcp (0) | 2022.04.06 |
61일차 - [Spring] 스프링 Bean 등록(어노테이션) (0) | 2022.04.05 |
60일차 - [Spring] 스프링 설치/스프링 Bean 등록(XML) (0) | 2022.04.04 |
댓글