2023-06-01

1. DataSource HikariCP로 변경

pom.xml -> commons-dhcp2, commons-pool2, commons-collections 제거, HikariCP 추가

<!-- commons-dhcp2, commons-pool2, commons-collections 제거 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.2</version>
</dependency> -->
<!-- HikariCP 추가 -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>

applicationContext.xml -> DataSource 수정

<!-- (기존) DataSource ApachCommons -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>
<!-- (변경) DataSource HikariCP -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="dataSourceProperties">
        <props>
            <prop key="cachePrepStmts">true</prop>
            <prop key="prepStmtCacheSize">250</prop>
            <prop key="prepStmtCacheSqlLimit">2048</prop>
        </props>
    </property>
</bean>

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
    <constructor-arg ref="hikariConfig"/>
</bean>

2. Mapper 생성

<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>

applicationContext.xml -> sqlSessionFactoryBean -> mybatis-config.xml 생성 및 Mapper 등록

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<typeAliases>
		<typeAlias type="kr.co.springlegacy.vo.Board" alias="Board" />
	</typeAliases>
	<mappers>
		<mapper resource="mapper/BoardMapper.xml" />
	</mappers>
</configuration>
  • configuration: MyBatis 설정 파일의 루트 요소입니다. MyBatis의 전반적인 설정을 포함합니다.
  • typeAliases: Java 객체와 MyBatis 매퍼 XML 파일에서 사용되는 클래스와 별칭(Alias) 간의 매핑을 정의하는 요소입니다. typeAlias 하위 요소를 사용하여 개별 클래스와 해당 별칭을 매핑할 수 있습니다.
  • typeAlias: Java 클래스와 별칭 간의 매핑을 정의하는 요소입니다. type 속성에는 매핑할 Java 클래스의 전체 경로를 지정하고, alias 속성에는 해당 클래스에 대한 별칭을 지정합니다.
  • mappers: MyBatis 매퍼 파일의 위치를 설정하는 요소입니다. mapper 하위 요소를 사용하여 개별 매퍼 파일의 경로를 지정할 수 있습니다.
  • mapper: MyBatis 매퍼 파일의 경로를 지정하는 요소입니다. resource 속성에 매퍼 파일의 위치를 지정합니다. 매퍼 파일은 SQL 문과 결과 매핑 등을 포함하는 MyBatis의 매퍼 인터페이스와 대응되는 XML 파일입니다.

src/main/resources -> mapper/BoardMapper.xml 생성

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.co.springlegacy.mapper.BoardMapper">

    <select id="getTime" resultType="String">
		SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') FROM dual
	</select>
    
	<select id="getList" resultType="Board">
		select * from myboard
	</select>
	
</mapper>

src/main/java -> kr.co.springlegacy.mapper.BoardMapper 인터페이스 생성

package kr.co.springlegacy.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Select;

import kr.co.springlegacy.vo.Board;



public interface BoardMapper {
	
//	@Select("select * from myboard")
	public List<Board> getBoardList();

	public String getTime();

}

※BoardMapper.xml에 쿼리를 안만들고 어노테이션을 사용해 BoardMapper Interface 안에서 바로 사용할수 있습니다.

src/test/java -> kr.co.springlegacy.mapper.BoardMapperTest 클래스 생성

package kr.co.springlegacy.mapper;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import kr.co.springlegacy.mapper.BoardMapper;
import kr.co.springlegacy.vo.Board;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class BoardMapperTest {
	
	@Autowired
	private BoardMapper boardMapper;

	@Test
	public void testInsert() {
		
		System.out.println(boardMapper.getBoardList());
		System.out.println(boardMapper.getTime());
		
	}
}

JUnit Test 실행 결과

image-20230601155228912

3. Service <-> Mapper 연결

src/main/java -> kr.co.springlegacy.service.BoardService 인터페이스 생성

package kr.co.springlegacy.service;

import java.util.List;

import kr.co.springlegacy.vo.Board;

public interface BoardService {

	public List<Board> getBoardList();
}

src/main/java -> kr.co.springlegacy.service.BoardServiceImpl 클래스 생성

package kr.co.springlegacy.service;

import java.util.List;

import org.springframework.stereotype.Service;

import kr.co.springlegacy.mapper.BoardMapper;
import kr.co.springlegacy.vo.Board;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{

	private final BoardMapper boardMapper;
	
	public List<Board> getBoardList(){
		
		return boardMapper.getBoardList();
	}
}

applicationContext.xml -> Component-scan 설정

<context:component-scan base-package="kr.co.springlegacy.service" />

src/test/java -> kr.co.springlegacy.service.BoardServiceTest 클래스 생성

package kr.co.springlegacy.service;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import kr.co.springlegacy.vo.Board;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class BoardServiceTest {
	
	@Autowired
	private BoardService boardService;
	
	@Test
	public void getBoardListTest() {
		
		List<Board> boardList = boardService.getBoardList();
		boardList.forEach(board -> {
			System.out.println(board.toString());
		});
	}
}

JUnit Test 실행 결과

image-20230601162323807

4. 트랜잭션 관리

pom.xml -> aspectjwweaver, aspectjrt 추가

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>

applicationContext.xml -> DataSourceTransactionManager, tx:annotaion-drven 추가

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource" />
<tx:annotation-driven transaction-manager="transactionManager"/>

BoarderServiceImpl 클래스/메소드 @Transactional 추가 및 BoardMapper.xml -> insertBoard 쿼리 수정
Transaction 테스트를 위해 insertBoard메소드에 boardMapper.insertBoard(board) 입력을 반복시키고
insertBoard 쿼리는 Seq를 입력받을수 있도록 수정한다.

package kr.co.springlegacy.service;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import kr.co.springlegacy.mapper.BoardMapper;
import kr.co.springlegacy.vo.Board;
import lombok.RequiredArgsConstructor;

@Transactional
@Service("boardService")
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{

	private final BoardMapper boardMapper;
	
	@Override
	public List<Board> getBoardList(){
		System.out.println("getBoardList() 메소드");
		return boardMapper.getBoardList();
	}

	@Override
	@Transactional
	public void insertBoard(Board board) {
		System.out.println("insertBoard() 메소드");
		boardMapper.insertBoard(board);
		boardMapper.insertBoard(board);
	}
	
}
<!-- 
<insert id="insertBoard" parameterType="Board">
    INSERT INTO myboard (seq, title, writer, content) 
    VALUES((select nvl(max(seq), 0)+1 from myboard), #{title}, #{writer}, #{content})
</insert> 
-->

<insert id="insertBoard" parameterType="Board">
    INSERT INTO myboard (seq, title, writer, content) 
    VALUES(#{seq}, #{title}, #{writer}, #{content})
</insert>

src/test/main -> kr.co.springlegacy.BoardServiceTest 클래스

package kr.co.springlegacy.service;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import kr.co.springlegacy.vo.Board;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
public class BoardServiceTest {
	
	@Autowired
	private BoardService boardService;
	
	@Test
	public void insertBoardTest() {
		Board board = Board.builder()
				.seq(29)
                .title("타이틀")
                .writer("작성자")
                .content("내용")
                .build();

		boardService.insertBoard(board);
		
	}
}

JUnit Test 실행 결과

image-20230601211240353
첫번째 boardMapper.insertBoard(board); 정상적으로 실행이되고
두번째 boardMapper.insertBoard(board); 에서 기본키인 Seq가 unique 제약조건 오류가 발생되면서
트랜잭션이 걸려 있는 Servcie단위로 롤백이 된다.

댓글남기기