'mybatis' 태그의 글 목록 :: 잡다한 프로그래밍
반응형

1. Transaction에서 rollback이 되지않는 경우는?

- checked exception은 트랜잭션에서 롤백되지않는다. (명시적인 예외처리가 필요한 것, try catch해야하는것 들)

- unchecked exception은 롤백대상 (예외처리를 하지 않아도 IDE에서 에러 뱉지 않음, Runtime Exception...)

 

2. checked exception 경우에도 rollback을 하고싶으면?

만약 B에서 에러가 발생했을때 A를 rollback시키고 싶다면? 이럴때 transaction을 이용한다

하지만 B의 exception이 checked exception이라면?

@Transactional
public void test() {
    A();
    B();
}

만약 아래처럼 try catch를 이용하면 에러를 catch에서 처리했기 때문에, rollback이 발생하지 않는다.

@Transactional
public void test() {
    A();
    try {
    	B(); // checked exception을 발생시키는 부분
    } catch {
    
    }
}

 

- @Transcational 어노테이션에 rollbackFor라는 옵션을 이용한다

해당 에러는 throw하고, rollbackFor옵션을 사용하면 checked exception도 확인할 수 있게 된다.

@Transactional(rollbackFor = {Exception.class})
public void test() {
    A();
    B(); // checked exception을 발생시키는 부분에서는 error throw
}

 

- checked 예외를 unchecked 예외로 변경하여 throw한다

public void B() {
    try {
    
    }catch(Exception e) {
    	throw new RuntimeException("예외");
    }
}


@Transactional
public void test() {
    A();
    B(); // unchecked exception으로 바꿈
}

 

3. 아래와 같은 경우에는 롤백이 일어날까 일어나지 않을까?

	// ATest 클래스
    @Transactional
    public void A() {
        try {
            test.B();
        } catch (RuntimeException e) {
            System.out.println("예외 처리");
        }
    }
    
    
    // BTest클래스
    @Transactional
    public void B() {
        //로직
        mapper.save();
        throw new RuntimeException("예외"); // unchecked exception
    }

 

해당 코드는 다음과 같이 동작한다.

1. A클래스의 트랜잭션이 실행된다.

2. B메소드가 실행되면서 1번의 트랜잭션에 참여한다 (기본 propagation 속성이 PROPAGATION_REQUIRED)

3. save를 실행하는 부분의 처리가 끝나고 트랜잭션의 완료처리 (completion)을 진행함

4. checked Exception이 일어나면서 트랜잭션이 완료처리 됨

5. checked Exception때문에 해당 트랜잭션을 롤백 규칙을 적용(기본 규칙적용), 해당 메소드에서 바로 롤백하지않고 rollback mark를 함 (해당 마크는 전역으로 관리함)

6. A의 로직 수행 > 1번에서 생성된 트랜잭션의 완료처리가 진행, 이때 rollback mark를 확인해서 값이 true라면 rollback진행

따라서 이경우에는 롤백되어버린다

 

해당 propagation 옵션을 변경할경우 해당 rollback mark 문제를 해결할 수 있다

 

PROPAGATION_REQUIRES_NEW 사용시 매번 새로운 트랜잭션 생성

- A와 B가 각각 트랜잭션을 생성하고, 매 번 commit하므로 서로의 rollback에 영향을 미치지 않음

하지만 오버헤드 발생할 수 있음 (매번 새로운 커넥션 생성)

반응형
반응형

1. 문법의 차이

#1) #을 이용한 경우

<select id="select" resultType="String" parameterType="Map">
    SELECT name FROM user WHERE id = #{id}
</select>

다음과 같은 SELECT문을 작성하였을경우 아래와 같이 ?에 파라미터가 바인딩 되어 수행된다 이렇게 파싱된 쿼리문은 재활용(캐싱)되므로 효율적이다.

SELECT name FROM user WHERE id = ?

 또한 변수에 작은 따옴표(')가 붙어 쿼리가 수행되므로 '#{id}'라고 쿼리문을 작성할 필요가 없다. 대신 다음과 같이 사용할 수 없다. 아래와 같이 사용할 경우 user_'tableName'이 되어버리므로 에러가 발생한다.

<select id="select" resultType="String" parameterType="Map">
    SELECT name FROM user_#{tableName} WHERE id = #{id}
</select>

 

#2) $를 이용한 경우

반면 $를 이용하게 되면 파라미터값이 바뀔 때마다 새로운 쿼리문의 파싱을 진행해야해서 성능상 단점이 존재한다.

또한 쿼리문에 #{}과 다르게 작은 따옴표(')가 붙지 않아서 테이블 이름이나 컬럼이름을 동적으로 결정할때 사용할 수 있다.

<select id="select" resultType="String" parameterType="Map">
    SELECT name FROM user_${tableName} WHERE id = #{id}
</select>

2. SQL Injection 차이

보안적으로 #과 $에는 차이가 존재한다. #은 $보다 보안에 안전하다 다음과 같은 예시를 보자

<select id="select" parameterType="Map" resultType="...">
    SELECT * FROM user WHERE id = '${id}' AND password = '${password}'
</select>

만약 이때 사용자가 id 값에 root' --를 입력했다고 가정하면 다음과 같은 결과를 초래한다.

SELECT * FROM user WHERE id = 'root' -- 'AND password = ''

사용자는 비밀번호를 입력하지 않았지만 뒷부분이 주석처리되어 로그인에 성공하게되어버린다 따라서 $는 #보다 SQL Injection에 취약하다

반응형
반응형

1. 사전 준비

- pom.xml 수정

다음과 같은 내용을 pom.xml에 추가합니다 1. mybatis (버전은 스프링 버전에 맞게 선택 가능) 2. mybatis-spring

3.dbcp2, 4. mysql-connector 5. spring-jdbc

		<!-- MySQL -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.3</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>2.0.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.7.0</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

 

- jdbc.properties생성

WEB-INF에 properties폴더 생성 > 다음과 같은 jdbc.properties 파일 생성

jdbc.username = user_id
jdbc.password = password
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://URL/DB_NAME?serverTimezone=Asia/Seoul&useSSL=false

 

- mysql 연동

다음과 같이 root-context.xml에 추가한다 

	<!-- MySQL dataSource -->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>
    
	<!-- jdbc.properties를 찾게 해주는 placeholder -->    
 	<context:property-placeholder location="/WEB-INF/properties/jdbc.properties" />

2. mybatis 연동 및 사용

- TestModel 생성

DB로부터 받아온 데이터를 담을 Model 클래스를 생성한다

@Getter
@Setter
public class Test_Model {

	private String date;

	private int count;

}

 

- mybatis-config.xml 생성

main/resources에 다음과 같은 mybatis-config.xml을 생성한다. 등록된 모델을 test라는 별칭으로 사용할 수 있다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration 
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<typeAliases>
		<typeAlias type="test.main.model.Test_Model" alias="test" />
	</typeAliases>

</configuration>

 

- rmcMapper.xml 생성

main/resource에 mappers라는 패키지를 만들고 패키지 내에 다음과 같은 testMapper.xml을 생성한다.

resultType를 config.xml에 지정한 test타입으로 반환하고 사용할 쿼리의 id를 getData로 지정한다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="test.main.mapper.testMapper">
	<select id="getData" resultType="test">
		select * FROM test_db
	</select>
</mapper>

 

- root-config.xml 수정

다음과 같이 root-config.xml에 mybatis를 설정하기 위한 위한 코드를 추가한다. mybatis-config.xml과 Mapper.xml을 bean으로 등록하고 sqlsession을 bean으로 등록한다.

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:/mybatis-config.xml" />
		<property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml"></property>
	</bean>
	
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
		<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
	</bean>
	

3. Controller, Service, DAO 만들기

- Controller 생성

다음과 같은 Controller를 작성한다. 간단하게 getData를 해오는 코드이다.

@Controller
@RequestMapping(value = "/brokenSVC")
public class testController {
	@Autowired
	private BrokenService testService;
	
	@GetMapping(value = "test")
	public String home(Model model) {
		System.out.println(testService.getdata().get(0).getDate());
		return "testpage";
	}
}

 

- Service 생성

다음과 같은 Service를 작성한다.

@Service
public class BrokenService {

	@Autowired
	private testDAO testdao;

	public List<Test_Model> getData() {
		return testdao.getData();
	}
}

 

- DAO 생성

다음과 같은 DAO를 작성한다.

@Repository
public class BrokenDAO {
	
	@Autowired private SqlSession sqlSession;
	private static final String Namespace = "test.main.mapper.rmcMapper"; //rmcMapper가 있는 위치
	
	public List<Test_Model> getData() {
		return sqlSession.selectList(Namespace+".getData");
	}
}

4. 결론

mybatis를 사용할 경우 기존 방법은 DAO에 쿼리문이 섞여있다는 단점이 있었는데 쿼리를 완벽하게 분리하여 사용할 수 있다는 장점이 있다.

반응형

+ Recent posts