본문 바로가기
수업 일지/Spring

63일차 - [Spring] spring 트랜잭션/빌더 패턴/JUnit 테스트 케이스

by 쿠쿠씨 2022. 4. 7.
반응형

트랜잭션

하나의 업무를 처리하는 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] 를 선택하여 실행합니다.

 

실행 결과

테스트 성공 시

 

테스트 실패 시

반응형

댓글