@Transactional 주의점

@Transactional은 생성된 객체의 처음 불린 method의 트랜잭션 상태를 유지한다.

동일 오브젝트 내에서 method 호출이 있을 때, 처음 호출 method에 @Transactional이 있으면 이후 호출된 메소드들도 모두 transaction 처리 되지만, 처음 호출된 method가 아닌 곳에서 @Transactional이 있을 때는 transaction 처리가 되지 않는다.

예제)

POM dependency
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

Data Schema
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
CREATE TABLE users
(
    no bigint NOT NULL auto_increment primary key,
    name character varying(64) NOT NULL
);

CREATE TABLE user_data
(
    no bigint NOT NULL auto_increment primary key,
    name character varying(12) NOT NULL,
    value character varying(12) NOT NULL
);

Mapper
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserDataMapper {
    @Insert("INSERT INTO user_data (name, value) VALUES (#{name}, #{value})")
    long addData(@Param("name") String name, @Param("value") String value);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UsersMapper {
    @Insert("INSERT INTO users (name) VALUES (#{name})")
    long addUser(@Param("name") String name);
}

Service
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import test.db.demo.mapper.UserDataMapper;
import test.db.demo.mapper.UsersMapper;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class UserService {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    @Autowired
    private UsersMapper usersMapper;

    @Autowired
    private UserDataMapper userDataMapper;

    @Transactional
    public long addUser(String name) {
        return proxyAddUser(name);
    }

    public long proxyAddUser(String name) {
        String join_date = sdf.format(new Date());
        long no = usersMapper.addUser(name);
        userDataMapper.addData("join_date", join_date);
//        int error = 0/0;
        return no;
    }
}

Main
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
    @Autowired
    private UserService userService;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        log.info("유저추가: " + userService.addUser("abc"));
    }
}

위 코드에서 @Transactional이 UserService의 addUser()에 있으니 transaction 유지 되지만 @Transactional을 proxyAddUser()로 옮기면 처음 호출된 addUser가 @Transactional이 없기 때문에 전체가 transaction 없는 상태로 처리 된다.

int error의 주석을 풀어서 테스트 해보면, addUser()에 @Transactional이 붙어 있으면 roll-back 되지만, proxyAddUser()로 옮기면 roll-back 되지 않음.

출처: https://gs.saro.me/dev?page=6&tn=471

댓글

이 블로그의 인기 게시물

[Protocol] WIEGAND 통신

Orange for Oracle에서 한글 깨짐 해결책

[UI] GNB·LNB·SNB·FNB 용어 설명