@Transactional ์ ๋์์๋ฆฌ, ํธ๋์ญ์ ๋งค๋์
by JiwonDev๐ญ JDBC์์ ์ฌ์ฉํ๋ ํธ๋์ญ์
์๋ฐ์ JDBC์์ ๊ฐ๋ฐ์๊ฐ ์ง์ ํธ๋์ญ์ ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ํ๊ฐ์ง ๋ฐ์ ์์ต๋๋ค.
// ์ปค๋ฅ์
ํ์์ DB์ปค๋ฅ์
์ ๋ฐ์์๋ค๊ณ ๊ฐ์
Connection connection = dataSource.getConnection();
try (connection) {
connection.setAutoCommit(false); // ์๋ ์ปค๋ฐ off
// ...DB ์์
...
connection.commit(); // ์ฑ๊ณต์ ํธ๋์ญ์
์ปค๋ฐ
} catch (SQLException e) {
connection.rollback(); // ์ค๋ฅ ๋ฐ์์ ํธ๋์ญ์
๋กค๋ฐฑ
}
์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ Spring์ ํ์ด๋ฒ๋ค์ดํธ์์ ์ ๊ณตํด์ฃผ๋ @Transactional๋ ์์์ ํธ๋์ญ์ ์ ๊ด๋ฆฌํด์ฃผ๋ ๋ง๋ฒ์ ํค์๋๊ฐ ์๋๋๋ค. ์ถ์ํํด์ ์ฌ์ฉํ ๋ฟ์ด์ง ์ค์ ๋ ์์ ์ฝ๋์ฒ๋ผ JDBC ํธ๋์ญ์ ์ ์ฌ์ฉํ์ฌ ๊ตฌํํฉ๋๋ค.
๐ญSpring์ @Transactional
์คํ๋ง์์ ํธ๋์ญ์ ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ์ํ๋ฉด ์๋์ ์ด๋ ธํ ์ด์ ์ ๋ฉ์๋์ ๋ถ์ด๋ฉด ๋ฉ๋๋ค.
@Transactional(propagation = TransactionDefinition.NESTED,
isolation = TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
์ ํ์ต์ ์์ (propagation)
@SpringBootTest
class UserServiceTest {
/* ... */
@Autowired
UserService userService;
User setUpUser;
@BeforeEach
void setUp() {
setUpUser = userService.save("set-up-nickname", 20);
}
@Test
@DisplayName("Non Transactional")
void changeNicknameTest() {
String nickname = "sample-nickname";
int age = 20;
User savedUser = userService.save(nickname, age);
/*
* INSERT USER
* */
String newNickname = "new-nickname";
User findUser = userService.changeNickname(savedUser.getId(), newNickname);
/*
* UPDATE USER
* */
/*
* savedUser = {User@9086}
* id = 9
* nickname = "sample-nickname"
* age = 20
*
* findUser = {User@9146}
* id = 9
* nickname = "new-nickname"
* age = 20
* */
assertEquals(newNickname, findUser.getNickname());
assertNotEquals(newNickname, savedUser.getNickname());
assertEquals(findUser.getId(), savedUser.getId());
assertNotEquals(findUser, savedUser);
/*
* No Rollback
* */
}
@Test
@DisplayName("Propagation: Required (๊ธฐ๋ณธ ์ธํ
๊ฐ. ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ๋ฉด ์ด์ด์ ๋ฐ์๋ค์ด๊ณ , ์กด์ฌํ์ง ์๋๋ค๋ฉด ์๋ก์ด ํธ๋์ญ์
์ ์์)")
@Transactional
void changeNicknameAsRequiredTransactionalTest() {
String nickname = "sample-nickname";
int age = 20;
User savedUser = userService.save(nickname, age);
/*
* No INSERT USER
* */
String newNickname = "new-nickname";
User findUser = userService.changeNickname(savedUser.getId(), newNickname);
/*
* No UPDATE USER
* */
/*
* savedUser = {User@9055}
* id = 11
* nickname = "new-nickname"
* age = 20
*
* findUser = {User@9055}
* id = 11
* nickname = "new-nickname"
* age = 20
* */
assertEquals(findUser, savedUser);
/*
* Rollback Well
* */
}
@Test
@DisplayName("Propagation: Supports (ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ๋ฉด ์ด์ด์ ๋ฐ์๋ค์ด๊ณ , ์กด์ฌํ์ง ์๋๋ค๋ฉด ๊ตณ์ด ์์ฑํ์ง ์๊ณ Non-ํธ๋์ญ์
์ํ๋ก ์คํ)")
@Transactional(propagation = Propagation.SUPPORTS)
void changeNicknameAsSupportsTransactionalTest() {
String nickname = "sample-nickname";
int age = 20;
User savedUser = userService.save(nickname, age);
/*
* INSERT USER
* */
String newNickname = "new-nickname";
User findUser = userService.changeNickname(savedUser.getId(), newNickname);
/*
* UPDATE USER
* */
/*
* savedUser = {User@9086}
* id = 6
* nickname = "sample-nickname"
* age = 20
*
* findUser = {User@9151}
* id = 6
* nickname = "new-nickname"
* age = 20
* */
assertEquals(newNickname, findUser.getNickname());
assertNotEquals(newNickname, savedUser.getNickname());
assertEquals(findUser.getId(), savedUser.getId());
assertNotEquals(findUser, savedUser);
/*
* No Rollback
* */
}
/*
* ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ๋ฉด ์ด์ด์ ๋ฐ์๋ค์ด๊ณ , ์กด์ฌํ์ง ์๋๋ค๋ฉด ์์ธ๋ฅผ ๋์ง๋ค.
* */
@Test
@DisplayName("Propagation - Mandatory")
@Transactional(propagation = Propagation.MANDATORY)
void changeNicknameAsMandatoryTransactionalTest() {
/*
* IllegalTransactionStateException ๋ฐ์
* */
System.out.println("์คํ ๋ ๊น?");
}
@Test
@DisplayName("Propagation: Requires New (ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ๋ฉด ์ค๋จํ๊ณ , ์๋ก์ด ํธ๋์ญ์
์ ์์)")
@Transactional(propagation = Propagation.REQUIRES_NEW)
void changeNicknameAsRequiresNewTransactionalTest() {
String nickname = "sample-nickname";
int age = 20;
User savedUser = userService.save(nickname, age);
/*
* No INSERT USER
* */
String newNickname = "new-nickname";
User findUser = userService.changeNickname(savedUser.getId(), newNickname);
/*
* No UPDATE USER
* */
/*
* savedUser = {User@9055}
* id = 8
* nickname = "new-nickname"
* age = 20
*
* findUser = {User@9055}
* id = 8
* nickname = "new-nickname"
* age 20
* */
assertEquals(findUser, savedUser);
/*
* Rollback Well
* */
}
@Test
@DisplayName("Propagation: Not Supported (ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ๋ฉด ์ค๋จ์ํค๊ณ , Non-ํธ๋์ญ์
์ํ๋ก ์คํ)")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
void changeNicknameAsNotSupportedTransactionalTest() {
String nickname = "sample-nickname";
int age = 20;
User savedUser = userService.save(nickname, age);
/*
* INSERT USER
* */
String newNickname = "new-nickname";
User findUser = userService.changeNickname(savedUser.getId(), newNickname);
/*
* UPDATE USER
* */
/*
* savedUser = {User@9086}
* id = 15
* nickname = "sample-nickname"
* age = 20
*
* findUser = {User@9145}
* id = 15
* nickname = "new-nickname"
* age = 20
* */
assertEquals(newNickname, findUser.getNickname());
assertNotEquals(newNickname, savedUser.getNickname());
assertEquals(findUser.getId(), savedUser.getId());
assertNotEquals(findUser, savedUser);
/*
* No Rollback
* */
}
@Test
@DisplayName("Propagation: Never (ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ๋ฉด ์์ธ๋ฅผ ๋์ง๊ณ , Non-ํธ๋์ญ์
์ํ๋ก ์คํ)")
@Transactional(propagation = Propagation.NEVER)
void changeNicknameAsNeverTransactionalTest() {
String nickname = "sample-nickname";
int age = 20;
User savedUser = userService.save(nickname, age);
/*
* INSERT USER
* */
String newNickname = "new-nickname";
User findUser = userService.changeNickname(savedUser.getId(), newNickname);
/*
* UPDATE USER
* */
/*
* savedUser = {User@9086}
* id = 16
* nickname = "sample-nickname"
* age = 20
*
* findUser = {User@9145}
* id = 16
* nickname = "new-nickname"
* age = 20
* */
assertEquals(newNickname, findUser.getNickname());
assertNotEquals(newNickname, savedUser.getNickname());
assertEquals(findUser.getId(), savedUser.getId());
assertNotEquals(findUser, savedUser);
/*
* No Rollback
* */
}
@Test
@DisplayName("Propagation: Nested (ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ๋ฉด ์ค์ฒฉ๋ ํธ๋์ญ์
์ ์์ฑํ์ฌ ์คํํ๊ณ , ํ์ฌ ํธ๋์ญ์
์ด ์กด์ฌํ์ง ์์ผ๋ฉด Required์ ๋์ผํ๊ฒ ๋์)")
@Transactional(propagation = Propagation.NESTED)
void changeNicknameAsNestedTransactionalTest() {
String nickname = "sample-nickname";
int age = 20;
User savedUser = userService.save(nickname, age);
/*
* No INSERT USER
* */
String newNickname = "new-nickname";
User findUser = userService.changeNickname(savedUser.getId(), newNickname);
/*
* No UPDATE USER
* */
/*
* savedUser = {User@9055}
* id = 17
* nickname = "new-nickname"
* age = 20
*
* findUser = {User@9055}
* id = 17
* nickname = "new-nickname"
* age 20
* */
assertEquals(findUser, savedUser);
/*
* Rollback Well
* */
}
}
์ด๋ ๊ฒ ๋จ์ํ ์ค์ ๋ณ์ ํ๋๋ก ํธ๋์ญ์ ์ propagation level๊ณผ isolation level์ ์ง์ ํ ์ ์์ต๋๋ค. ๋ง๋ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง, ๊ฒฐ๊ตญ ์ค์ ๋ก๋ ์ ์ผ ์ฒ์๋ณธ JDBC์ฝ๋์ ๋์ผํ๊ฒ ๋์ํฉ๋๋ค.
// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
// propagation=TransactionDefinition.NESTED
Savepoint savePoint = connection.setSavepoint();
...
connection.rollback(); // ์ค๋ฅ ๋ฐ์์ ํธ๋์ญ์
๋กค๋ฐฑ
๐ญ์คํ๋ง์ ๋ง์ , Transaction Management
๐ ์คํ๋ง์ ์ถ์ํ, PlatformTranscationManager
์คํ๋ง์ ํธ๋์ญ์ ์ฒ๋ฆฌ๋ฅผ TranscationManager ๊ฐ์ฒด๋ฅผ ํตํด ์ฒ๋ฆฌํฉ๋๋ค.
๊ตฌํ์ฒด๋ ๊ฐ์๋ผ์ธ ์ ์๊ฒ ์ธํฐํ์ด์ค์ธ PlatformTranscationManager๊ฐ ์ฃผ์ ๋์ด ์ฌ์ฉ๋ฉ๋๋ค.
// ๊น-๋.
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
๋ฌผ๋ก ๊ตฌํ์ฒด๋ง๋ค ๊ฑฐ์ ๋์ผํ ๋ถ๋ถ์ด ์์ ์ ์์ด์, ์ด๋ฅผ ๊ตฌํํ๋ AbstarctPlatformTxManager๊ฐ ์กด์ฌํฉ๋๋ค.
์ด๊ธฐ ์คํ๋ง์ XML์ ๋น์ ์์ฑํ์ฌ ๊ด๋ฆฌํ์์ต๋๋ค.
ํ์ง๋ง ์ด๋ ๋งค์ฐ ๋ถํธํ๊ณ ์ฝ๋์ ์ค์ ํ์ผ์ ๋งค๋ฒ ๋ง์ถฐ์ฃผ์ด์ผํด์, ์ต๊ทผ์๋ ๊ฑฐ์ ์ฐ์ง์๋ ๋ฐฉ๋ฒ์ ๋๋ค.
</beans> <!-- ์ด๋ ๊ฒ ์ค์ ํ์๋ต๋๋ค. ์ต๊ทผ์๋ yaml์ด๋ @์ด๋
ธํ
์ด์
, ์๋ฐ์ฝ๋๋ก ์ง์ ์ค์ ํ์ฃ .-->
<!-- dataSource ์ค์ , spring-jdbc ๋ชจ๋ ์ฌ์ฉ, spring ์์ jdbc ๋ฅผ ํตํด mysql ์ ์ ์ํ ์ ์๊ฒ ํจ -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://35.200.109.72:3306/testdb?useSSL=false"></property>
<property name="username" value="root"></property>
<property name="password" value="๋น~๋ฐ"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
์ต๊ทผ์๋ yaml ๊ฐ์ ์ค์ ํ์ผ๊ณผ ์๋ฐ์ฝ๋๋ก ์ฝ๊ฒ ์ค์ ํ ์ ์์ฃ .
spring:
datasource:
url: jdbc:h2:tcp://์ฃผ์:ํฌํธ/...
username: ์์ด๋
password: ๋น๋ฐ๋ฒํธ
driver-class-name: org.h2.Driver
@Configuration
public class DatabaseConfiguration {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
var factoryBean = new SqlSessionFactoryBean();
var configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setDataSource(dataSource);
factoryBean.setConfiguration(configuration);
var resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/xml/*.xml"));
return factoryBean.getObject();
}
}
์ฐธ๊ณ ๋ก ํ ์คํธ ์ฉ๋๋ก ์ฐ์ด๋ ์ธ๋ฉ๋ชจ๋ฆฌ DB๋, Datasource๋ฅผ ์ค์ ํ์ง ์์๋ ๋ฐ๋ก ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ด๋ SpringBoot์ ์๋ @AutoConfiguration์ ์ํด DataSource์ JdbcTemplate์ด ๋น์ผ๋ก ์์ฑ๋๊ธฐ ๋๋ฌธ์
๋๋ค.
๋ฌผ๋ก ํด๋น ์ธ๋ฉ๋ชจ๋ฆฌ DB Driver ์์กด์ฑ์ ์์ด์ผ ์๋์ค์ ๋ฉ๋๋ค. ์คํ๋ง์ ๋ง์ ์ด ์๋์์.
์์ฑ๋ DataSource๋ ์์กด์ฑ์ผ๋ก ์ฃผ์
๋ฐ์์ ํ์ธํด๋ณผ ์ ์์ต๋๋ค.
@Slf4j
@Component
/* ์ฐธ๊ณ ๋ก ์คํ๋ง ์ฑ ์คํํ, ์ด๋ ํ ์ฝ๋๋ฅผ ์คํํ๊ณ ์ถ๋ค๋ฉด ApplicationRunner.run()์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. */
public class TestRunner implements ApplicationRunner {
@Autowired
DataSource dataSource;
@Override
public void run(
ApplicationArguments args) throws Exception {
Connection connection = dataSource.getConnection();
log.info("Url: " + connection.getMetaData().getURL());
log.info("UserName: " + connection.getMetaData().getUserName());
// INFO --- [ main] TestRunner : Url: jdbc:h2:mem:testdb
// INFO --- [ main] TestRunner : UserName: SA
}
}
๐ ์ฌ์ฉ๋ฒ1 - TransactionTemplate ์ฌ์ฉ
์คํ๋ง์ IoC, AOP๋ฅผ ์ฌ์ฉํ์ง ์๊ณ , ์ฝ๋๋ก ์ง์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ๋ณดํต TransactionTemplate์ ์ฌ์ฉํฉ๋๋ค. (๊ธฐ์ตํ์ธ์, ์ด ๊ฐ์ฒด๋ ๋ด๋ถ์์ PlatformTransactionManager๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.)
@Service
public class UserService {
@Autowired
private TransactionTemplate template;
public Long registerUser(User user) {
Long id = template.execute(status -> {
// SQL ์คํ
return id;
});
}
}
์ด ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ์ ์ฅ์ ์ด ์์ต๋๋ค.
- ์คํ๋ง IoC๋ฅผ ํตํด ์ค์ ์์ญ๊ณผ DB์คํ์ฝ๋๊ฐ ๋ถ๋ฆฌ๋์์ต๋๋ค. ์ค์ ์ ์์ ํ ํ์๊ฐ ์์ต๋๋ค.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ ๊ฐ๋ฐ์๊ฐ ๊ด๋ฆฌํ์ง ์์ต๋๋ค. ๊ฐ๋ฐ์๋ ํธ๋์ญ์ ์ฝ๋ฐฑ ๋ฉ์๋๋ง ๊ตฌํํ๋ฉด ๋ฉ๋๋ค.
- ์ ์์ค์ SQLExceptions์ ์คํ๋ง์์ ์ก์ ์ถ์ํ๋ ๋ฐํ์ ์์ธ๋ก ํฌ์ฅํด์ค๋๋ค.
๋ฌผ๋ก @Transcational์ด ์๋นํ ํธํ๊ธฐ ๋๋ฌธ์, ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๋๋๋ค.
ํ์ง๋ง ๋ค์ด๋๋ฏน ํ๋ก์์ ์๋ฆฌ์, @Transcational์ ๊ฒฝ์ฐ ํด๋์ค๋ ๋ฉ์๋ ๋จ์๋ก ๋ฐ์ ๊ฑธ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด [์ํ๊ตฌ๋งค-์ด๋ฉ์ผ๋ฐ์ก]์ธ๋ฐ, ์ด๋ฉ์ผ ๋ฐ์ก์ด ์ทจ์๋์๋ค๊ณ ํธ๋์ญ์
์ ์ํด ๋กค๋ฐฑ๋๋ฉด ์๋ฉ๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ @Transcational ์ผ๋ก ํด๊ฒฐํ๊ธฐ์ ์ ๋งคํ ์ํฉ์ด ๋์ต๋๋ค. DIP๋ฅผ ๋ฌด์ํ๊ณ ๋๋ฉ์ธ์ ๋ถ์ธ๋ค๊ฑฐ๋..
public class TransactionInvoker {
private A1Dao a1dao;
private A2Dao a2dao;
@Transactional
public void doInternalTransaction() throws Exception{
a1dao.insertA1();
a2dao.insertA2();
}
}
- ํ๋ก์์ ํน์ฑ์, ํธ๋์ญ์ ์ ์ธ๋ถ์์ doInternalTranscation()์ ํธ์ถํ ๋๋ง ๊ฑธ๋ฆฝ๋๋ค.
- ์คํ๋ง ํธ๋์ญ์ ์ ๋ฉ์๋ ์์์ ์ปค๋ฅ์ ์ด ์์ฑ๋๊ณ , ๋ฉ์๋ ์ข ๋ฃ์์ ์ปค๋ฅ์ ์ ํ์ ๋ฐํํฉ๋๋ค.
ํธ๋์ญ์ ์ ์ธ๋ถ์ ์ผ๋ก ๊ฑธ๊ณ ์ถ๋ค๋ฉด, ์๋์ ๊ฐ์ด TranscationTemplate์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
@Transcational์์ ์ง์ํ๋ ์ต์ ์ ๋น์ฐํ Template์๋ ์์ต๋๋ค.
propagation : ํธ๋์ญ์ ์ ํ ๊ท์น ์ค์
isolation : ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ๋ ๋ฒจ ์ค์
readOnly : ์ฝ๊ธฐ ์ ์ฉ ์ฌ๋ถ ์ค์
rollbackFor : ํธ๋์ญ์ ์ ๋กค๋ฐฑํ ์์ธ ํ์ ์ค์
noRollbackFor : ํธ๋์ญ์ ์ ๋กค๋ฐฑํ์ง ์์ ์์ธ ํ์ ์ค์
timeout : ํธ๋์ญ์ ํ์์์ ์๊ฐ ์ค์
public class TransactionInvoker {
private A1Dao a1dao;
private A2Dao a2dao;
private TransactionTemplate transactionTemplate;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
// @Transcational(...)์ ์ค์ ์ ์ด๋ ๊ฒ ํ ์ ์๋ค.
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
this.transactionTemplate.setTimeout(30);
}
private void doInternalTransaction() throws Exception {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
try {
a1dao.insertA1();
a2dao.insertA2();
} catch (Exception e) {
status.setRollbackOnly(); // ์ฝ๋ฐฑ์์ ์คํ๋ ์ฝ๋๋ง ๋กค๋ฐฑ
}
}
});
}
}
์ฐธ๊ณ ๋ก ๋ฐํ๊ฐ์ด ์๋ ํธ๋์ญ์ (TranscationCallback)์ .execute() ๋ฉ์๋์ ๋ฐํ๊ฐ์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค.
๋๋คํํ์์ด ์ฌ์ฉ ๊ฐ๋ฅํ๊ฒฝ์ฐ, ์๋์ ๊ฐ์ด ์์ฑํ๋ฉด ๋ ๊ฐ๊ฒฐํฉ๋๋ค.
transactionTemplate.execute((TransactionCallback) status -> {
try {
a1dao.insertA1();
a2dao.insertA2();
} catch (Exception e) {
status.setRollbackOnly();
}
return "Hello"; // tanscationTemplate.execute() ์ ๋ฐํ๊ฐ
});
์ด๋ฐ ๊ฒฝ์ฐ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
// ํ๋ก์์ ๋ค๋ฅด๊ฒ ์ํ๋ ์ง์ ๋ง ๊ฑธ ์ ์์ต๋๋ค. ๋ง ๊ทธ๋๋ก ์ง์ ํธ๋์ญ์
์ ๊ฑฐ๋๊ฑฐ๋๊น์.
transactionTemplate.execute {
val person = Person(name = "Suhwan Jee")
personRepository.save(person) /* Spring Data JPA */
try {
transactionTemplate.execute {
throw Exception("some unexpected exception")
}
} catch (e: Exception) {
}
}
๋ฌผ๋ก @Transcational์ด๋ TranscationTemplate์ด๋ ๊น๋ณด๋ฉด PlatformTxManager๋ก ์คํํ๋๊ฑด ๋๊ฐ์ต๋๋ค.
๋ฐ๋ก ๋์ํ๋๊ฒ ์๋๋๋ค. + Spring Transcation Template์ ์ฌ์ฉํ ๋ ์ค์ํ๊ธฐ ์ฌ์ด ๋ถ๋ถ๋ค
// TranscationTemplate์ ๋ด๋ถ์ฝ๋
public class TransactionTemplate extends DefaultTransactionDefinition
implements TransactionOperations, InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
@Nullable
private PlatformTransactionManager transactionManager;
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager)this.transactionManager).execute(this, action);
} else {
// PlatfromTranscationManger.getTransaction()์ ํตํด ํธ๋์ญ์
์ ๊ด๋ฆฌํฉ๋๋ค.
TransactionStatus status = this.transactionManager.getTransaction(this);
Object result;
try {
result = action.doInTransaction(status);
} catch (Error | RuntimeException var5) {
this.rollbackOnException(status, var5);
throw var5;
} catch (Throwable var6) {
this.rollbackOnException(status, var6);
throw new UndeclaredThrowableException(var6, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
@ ํธ๋์ญ์ ์ด๋ ธํ ์ด์ ์ฌ์ฉ (@EnableTransactionManagement)
๋๋ถ๋ถ์ ๊ฐ๋ฐ์๋ค์ ์ด ๋ฐฉ์์ ์ ํธํฉ๋๋ค. @Transactional์ด ๋ฌ๋ฆฐ ๋ฉ์๋๋ ํธ๋์ญ์ ์ฒ๋ฆฌ๊ฐ ๋๋๊ฑฐ์ฃ .
@Service
public class UserService {
@Transactional
public Long registerUser(User user) {
// SQL ์คํ
userDao.save(user);
return id;
}
}
- ํธ๋์ญ์
๋งค๋์ง๋จผํธ๋ฅผ ์ฌ์ฉํ๊ธฐ์ํด์๋ ์ปจํ
์ด๋ ๊ฐ์ฒด์ @EnableTransactionManagement๋ฅผ ๋ถ์ฌ์ผํฉ๋๋ค.
(์ต๊ทผ์๋ ์คํ๋ง๋ถํธ์ @AutoConfiguration์์ ๊ธฐ๋ณธ ํฌํจ๋์ด์์ต๋๋ค. ๊ทธ๋์ ์๋ตํด๋ ๋์ํ๋๊ฑฐ์ฃ .)
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean // ๋ชจ์๋ง ๋ค๋ฅผ๋ฟ, 1๋ฒ ๋ฐฉ์๊ณผ ๋๊ฐ์ ํธ๋์ญ์
๋งค๋์ ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
public PlatformTransactionManager txManager() {
return yourTxManager; // more on that later
}
}โ
- ๊ทธ๋ฌ๋ฉด ์คํ๋ง์์๋ @Transactional ์ด๋ ธํ ์ด์ ์ด ๋ฌ๋ฆฐ public ๋ฉ์๋์ ๋ํด ์๋์ ๊ฐ์ ํธ๋์ญ์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ฌ ์คํํด์ค๋๋ค.
// ์ค์ ์คํ๋ง ๋ด๋ถ์์ ๋์ํ๋ JDBC ์ฝ๋
public class UserService {
public Long registerUser(User user) {
Connection connection = dataSource.getConnection();
try (connection) {
connection.setAutoCommit(false);
connection.commit();
} catch (SQLException e) {
connection.rollback();
}
}
}
๋ฌผ๋ก ์คํ๋ง์ด ํ๋ก์์ ํน์ dataSource๋ฅผ ์ง์ ์ฌ์ฉํ์ง ์์ต๋๋ค.
PlatfromTxManager๋ฅผ ํตํด DIP๋ฅผ ์ ์ฉ์์ผ๋จ์ฃ . ๊ตฌํ์ฒด๋ฅผ ์์ ๋กญ๊ฒ ๋ฐ๊ฟ ์ ์์ต๋๋ค.
# ์คํ๋ง์ด ํธ๋์ญ์ ์ฝ๋๋ฅผ ๋ด ๊ฐ์ฒด์ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ
์ด์ ์ด์ด ๋๋์ด ์ค์์ฃ ? ๋ค. ์คํ๋ง์์๋ UserService ๊ฐ์ฒด์ ํ๋ก์์ ํธ๋์ญ์ ์ฝ๋๋ฅผ ์ฝ์ ํฉ๋๋ค. ์์์ ๋ฐฐ์ด ์คํ๋ง AOP๋ฅผ ์ฌ์ฉํด์ ๋ง์ด์ฃ .
๋ค๋ง ์ฌ๊ธฐ์์ ํ๊ฐ์ง ์ฐจ์ด์ ์ ํ๋ก์ ์์ฒด์์ ํธ๋์ญ์ ์ฝ๋๋ฅผ ์คํํ๋ ๊ฑด ์๋๋ ๊ฒ์ ๋๋ค. ์คํ๋ง AOP๋ ํ๋ก์ ๊ฐ์ฒด์ ๋ค์๊ณผ ๊ฐ์ด [Transaction Manager ๊ฐ์ฒด]์๊ฒ ์์ํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
- [Transaction Manager ๊ฐ์ฒด]์๋ doBegin() ๊ณผ doCommit() ๋ฉ์๋๊ฐ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์์ํฉ๋๋ค.
// ์ดํด๋ฅผ ๋๊ธฐ์ํ ํธ๋์ญ์
๋งค๋์ ๊ฐ์ฒด์ ์ฝ๋. ์ค์ ์ฝ๋๋ ํจ์ฌ ๋ณต์กํ๋ค!
public class DataSourceTransactionManager implements PlatformTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
//... ๋ณต์กํ ๊ณผ์ ์๋ต
Connection newCon = obtainDataSource().getConnection();
newCon.setAutoCommit(false);
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
//... ๋ณต์กํ ๊ณผ์ ์๋ต
Connection connection = status.getTransaction().getConnectionHolder().getConnection();
try {
con.commit();
} catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
}
@ ์ฆ ์ ๋ฆฌํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
- ์คํ๋ง์ @Transactional ์ด๋ ธํ ์ด์ ์ ๊ฐ์ง ๋ฉ์๋๋ฅผ ๋ฐ๊ฒฌํ๋ฉด, ๋ค์ด๋๋ฏน ํ๋ก์๋ฅผ ๋ง๋ญ๋๋ค.
- ํด๋น ํ๋ก์ ๊ฐ์ฒด๋ [TransactionManager ๊ฐ์ฒด]์๊ฒ ํธ๋์ญ์ ๋์์ ์์ํ๋ ์ฝ๋๋ฅผ ๊ฐ์ง๋๋ค.
- ํธ๋์ญ์ ๋งค๋์ ๋ ์ฒ์์ ๋ดค๋ ๊ฒ ์ฒ๋ผ JDBC ์ฝ๋๋ฅผ ํตํด ํธ๋์ญ์ ์ ์คํํฉ๋๋ค.
์คํ๋ง์ @Transcational์ ๋ง๋ฒ์ ํค์๋๊ฐ ์๋๋๋ค. ๊ฒฐ๊ตญ JDBC๋ก ๋์ํฉ๋๋ค. ์ด๋ฅผ ์คํ๋ง์์๋ @Transactional ํ๊ทธ๋ฅผ ์ด์ฉํ ๋ ผ๋ฆฌ์ ํธ๋์ญ์ ์ ์ฌ์ฉํ๊ณ , ์ค์ ๋ฌผ๋ฆฌ์ ํธ๋์ญ์ ์ JDBC๋ก ์ฒ๋ฆฌ๋๋ค๋ผ๊ณ ํํํ๊ธฐ๋ ํฉ๋๋ค.
# @Transactional์ Propagtion์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ
์ฌ์ค ํธ๋์ญ์ ์ isolation level์ JDBC์์๋ ๊ฐ๋จํ๊ฒ ์ค์ ํ ์ ์์ต๋๋ค.
๋ค๋ง ํธ๋์ญ์ ๋์ค isolation level์ ์ ํํ๊ณ ์ ํ๋ค๋ฉด JDBC ๋๋ผ์ด๋ฒ์์ ๊ธฐ๋ฅ์ด ์ง์๋๋์ง ํ์ธํด์ผ ํฉ๋๋ค.
// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
๊ทธ๋ ๋ค๋ฉด Propagation์ ๋์ฒด ์ด๋ป๊ฒ JDBC๋ก ๋ง๋ ๊ฒ์ผ๊น์? ๋ชจ๋ ๊ฒ์ ๋น๋ฐ์ ํ๋ก์๋ฅผ ํตํ Lazyํ ๋์์ ์์ต๋๋ค.
# (๋ฒ์ธ) @Transcational(readOnly = true)์ ๋์
์ฑ๋ฅ์ ํฅ์์ํค๋ @Transcational(readOnly=true)๋ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ๋์ํ๋ ๊ฑธ๊น?
๋ฌผ๋ก readOnly๋ฅผ ๋จ๋ค๊ณ ํด์ ํน๋ณํ๊ฒ ๋ค๋ฅธ ๋์์ด ์ผ์ด๋์ง ์๋๋ค.
์ด์ฐจํผ @Transcational์ ์ฌ์ฉํ๋ ์์ ์ setAutoCommit์ด false๊ฐ ๋๋ฉด์ ์ฌ๋ฌ ์์ ์ ํ ํธ๋์ญ์ ์ ๋ฌถ๊ธฐ ๋๋ฌธ์ด๋ค.
// ์ดํด๋ฅผ ๋๊ธฐ์ํ ํธ๋์ญ์
๋งค๋์ ๊ฐ์ฒด์ ์ฝ๋. ์ค์ ์ฝ๋๋ ํจ์ฌ ๋ณต์กํ๋ค!
public class DataSourceTransactionManager implements PlatformTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
//... ๋ณต์กํ ๊ณผ์ ์๋ต
Connection newCon = obtainDataSource().getConnection();
newCon.setAutoCommit(false);
}
...
}
๋ค๋ง readOnly ๋ผ๋ ํํธ๋ฅผ ์ฃผ๋ฉด, ์คํ๋ง์์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํ ์ถ๊ฐ์ ์ธ ์์ ์ ์ฝ๊ฐ ํ๋ค.(์ฑ๋ฅํฅ์์ด ํฌ์ง๋ ์๋ค.)
JDBC ๋๋ผ์ด๋ฒ์ ๋ฐ๋ผ ๋ค๋ฅด์ง๋ง. readOnly๋ผ๊ณ ํ๋จ๋๋ฉด ์ฟผ๋ฆฌ๋ฅผ ์ต์ ํํ๋ ๊ธฐ๋ฅ๋ ์๋ค๊ณ ํ๋ค. ์ฟผ๋ฆฌ ํํธ ๊ฐ์ ๋๋
ํนํ JPA์ ํจ๊ป ์ฌ์ฉํ ๋, SpringDataJpa์์๋ readOnly๊ฐ ์ค์ ๋์ด์๋ค๋ฉด ์์์ฑ ์ปจํ ์คํธ๋ฅผ ํ๋ฌ์ํ์ง ์๋๋ค.
์ฆ ํ๋ฌ์ ๋ชจ๋๊ฐ ๊ฐ์ ๋ก Manual๋ก ๋ฐ๋์ด๋ฒ๋ฆฐ๋ค. (์ฐธ๊ณ ๋ก Manual์ ๊ณผ๊ฑฐ์ด๋ฆ์ Never์๋ค. ํ๋ฌ์๋ฅผ ํ์ง ์์.)
entityManager.setFlushMode(FlushModeType.MANUAL);
์ข ๋ ์ฝ๊ฒ๋งํ๋ฉด, ์ฝ๊ธฐ ์์
๋ง ๊ฐ๋ฅํ ๋ฟ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ ๋ด์ญ์ ๋ณด๋ด์ง ์๋๋ค.
(2022.08.15 ์ถ๊ฐ)
* ์ฐธ๊ณ ๋ก ๋ํฐ์ฒดํน๊ณผ flush๋ฅผ ์ํ๋ค๊ณ ํ์ง, ํธ๋์ญ์
์ readOnly๊ฐ ์์ /์ญ์ ๋ฅผ ๋ง๋ ๊ธฐ๋ฅ์ด ์์ง๋ ์๋ค.
save()๋ฅผ ์คํํด๋ ์ ๋์ํ๊ณ , updated์ ๊ฒฝ์ฐ์๋ ์ง์ flush ์์ผ์ฃผ๋ฉด ์ค์ DB์ ์ฟผ๋ฆฌ๊ฐ ์ฐํ๋ค.
์ฝ๊ฐ ๋น์ทํ ๋งฅ๋ฝ์ผ๋ก, @Transcational( SUPPORTS )๋ก ์ค์ ํ๋ฉด ์ถ๊ฐ๋๋ ํธ๋์ญ์ ๊ด๋ จ JDBC ์ฝ๋๋ ์๋ฌด๊ฒ๋ ์๋ค.
๊ทธ๋์ ์๋์ ๊ฐ์ด ์์ JDBC ์ฝ๋๋ฅผ ์์ด์ ์จ๋ ์ถฉ๋(์์ธ)์์ด ์ ์์ ์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ก๋จ์ ์ ์ ์๋ค.
@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
public long insertTrade(TradeData trade) throws Exception {
//JDBC Code...
}
๊ฒฐ๊ตญ ์ ๋ถ๋ค ์๋ฐ์ JDBC ์ฝ๋๋ก ๋์๊ฐ๋๊ฑฐ๋ผ์ ๊ทธ๋ ๋ค. ๋ด๋ถ๋์์ ์๊ณ ์๋ค๋ฉด ํฌ๊ฒ ๋๋ผ์ด ๋ถ๋ถ๋ ์๋๋ค.
# Spring๊ณผ JPA์์ ๊ฐ์ @Transactional ํ๊ทธ๋ฅผ ์ฌ์ฉํ ์ ์๋ ์ด์
์ด? ์๊ฐํด๋ณด๋ฉด ์ด์ํฉ๋๋ค. ์คํ๋ง์์๋ @Transactional ํ๊ทธ๋ฅผ ํตํ ํ๋ก์ ์์ฑ์ด ์์ง๋ง, JPA์๋ ๋ ๋ฆฝ์ ์ผ๋ก ํด๋น @Transactional ํ๊ทธ๊ฐ ์กด์ฌํ๊ณ , ๋ค๋ฅธ ์ฒ๋ฆฌ๋ฐฉ์์ด ์กด์ฌํฉ๋๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ [์คํ๋ง + JPA] ์กฐํฉ์ ๋ฌธ์ ์์ด ์ฌ์ฉํ๋๋ฐ ์ด๋ป๊ฒ ๋๊ธฐํํด์ ์ฌ์ฉํ๋ ๊ฒ์ผ๊น์?
- ํ์ด๋ฒ๋ค์ดํธ(JPA)๋ ์คํ๋ง์ @Transactional์ ๋์๋ฐฉ์์ ๋ชจ๋ฆ ๋๋ค.
- ์คํ๋ง์ ํ์ด๋ฒ๋ค์ดํธ(JPA)๊ฐ ์ฌ์ฉํ๋ @Transcational์ ๋์๋ฐฉ์์ ๋ชจ๋ฆ ๋๋ค.
public class UserService {
@Autowired
private SessionFactory sessionFactory;
// ํ์ด๋ฒ๋ค์ดํธ(JPA)์ ์ฝ๋
public void registerUser(User user) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
session.close();
}
}
๋๋ผ์ด ์ฌ์ค์, ์ฌ์ค ๋ณ ๋ฌธ์ ๊ฐ ์๋ค๋ ๊ฒ๋๋ค. ์ด๋ฏธ ์คํ๋ง์์ JPA์ ํจ๊ป ์ฌ์ฉํ ์ ์๊ฒ ์๋ํ ํด๋์์ต๋๋ค. ์ด๊ฒ ์ด๋ ต์ง ์๊ฒ ๊ฐ๋ฅํ ๊ฒ์ ํ์ด๋ฒ๋ค์ดํธ๋ ์คํ๋ง์ด๋ ๊ฒฐ๊ตญ ๋ ๋ค JDBC์ ๊ธฐ๋ณธ ๋ฐฉ์์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ด์ฃ .
์ถ์ํํด์ ์ฝ๊ฒ ์ฌ์ฉํ๋๊ฑฐ์ง ๋ง์ ์ด ์๋๋๋ค. ๋ชจ๋ ๊ฒ์ JDBC๋ก ๋์ํฉ๋๋ค
# Spring๊ณผ ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
ํ์ด๋ฒ๋ค์ดํธ์์ ์ ๊ณตํ๋ SessionFactory๋ฅผ ์คํ๋ง์์ ์ฌ์ฉํ๊ณ , @Transactional๋ฅผ ๋ฌ์๋ด ์๋ค.์ด๋ ๊ฒ ์ฌ์ฉํ๋ฉด ์คํ๋ง์์ ์ํ๋ฅผ ๊ด๋ฆฌํ์ง ์๊ณ , ํ์ด๋ฒ๋ค์ดํธ๊ฐ ๊ด๋ฆฌํ๋๋ก ๋์ํฉ๋๋ค.
@Service
public class UserService {
@Autowired
private SessionFactory sessionFactory;
@Transactional // ์คํ๋ง๊ณผ ํ์ด๋ฒ๋ค์ดํธ๊ฐ ๊ฐ์ ํ๊ทธ ์ฌ์ฉ.
public void registerUser(User user) {
sessionFactory.getCurrentSession().save(user);
}
}
์๋ฆฌ๋ ๊ฐ๋จํฉ๋๋ค. ์คํ๋ง์์ ํ๋ก์๋ฅผ ๋ง๋ค ๋ ์คํ๋ง์์ ์ฌ์ฉํ๋ ํธ๋์ญ์ ๋งค๋์ (PlatformTransactionManager)์๊ฒ ์์ํ์ง ์๊ณ ํ์ด๋ฒ๋ค์ดํธ์ ํธ๋์ญ์ ๋งค๋์ (HibernateTransactionManager)์๊ฒ ์์ํ๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ JPA๋ฅผ ํตํด์ ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ํธ๋์ญ์ ๋งค๋์ ๊ฐ์ฒด๋ก (JpaTransactionManager)๋ฅผ ์ฐ๋ฉด ๋์ฃ .
์ด๋ ์๋ ๊ฐ๋ฐ์๊ฐ ์ง์ ํธ๋์ญ์ ๋งค๋์ ๊ฐ์ฒด๋ฅผ ์ ์ ํ๊ฒ ์ง์ ํด์ฃผ์ด์ผํ์ง๋ง, ์ง๊ธ์ ์คํ๋ง ๋ถํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (spring-boot-starter-data-jpa)๋ฅผ ์ฐ๋ฉด ์๋์ผ๋ก Jpa ํธ๋์ญ์ ๋งค๋์ ๋ฅผ ์ฐ๋๋ก ์ค์ ์ ๋ฐ๊ฟ์ค๋๋ค.
์คํ๋ง ๋ด๋ถ ์ฝ๋๋ฅผ ๋ณด์ง์์์ง๋ง, ์ด์ง ๊ฐ์ด์ค์์ง ์๋์? [ํธ๋์ญ์ ๋งค๋์ ๊ฐ์ฒด] ๋ํ IoC๊ฐ ๊ตฌํ๋์ด ์ ์ฐํ๊ฒ ์ฌ์ฉํ๊ณ ์์๊ฑฐ๋ผ๋ ๋๋์ด์. ์ค์ ๋ก ์คํ๋ง์ PlatformTransactionManager ๋ผ๋ ์ธํฐํ์ด์ค์๋ง ์์กดํ๊ณ ์๊ณ , ๊ทธ ๊ตฌํ์ฒด๋ ์ ๋์ ์ผ๋ก ๋ฐ๊ฟ ์ ์์ต๋๋ค. ์คํ๋ง์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ตฌํ์ฒด๋ DataSourceTransactionManager ์ ๋๋ค.
# ๊ฒฐ๋ก
๋ง๋ฒ์ด ์๋๋๋ค. ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ์ฐ๋ ์คํ๋ง์ ์ฐ๋ @Transactional์ ์ฐ๋ ๊ฒฐ๊ตญ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐ์์ JDBC์ ๊ธฐ๋ณธ์ฝ๋ (getConnection, setAutoCommit, Commit)์ผ๋ก ์๋ํฉ๋๋ค.
๋ค๋ง ์ด์ฒ๋ผ ํ๋ก์๋ฅผ ์ฌ์ฉํด์ ์๊ธฐ๋ ์คํ๋ง์ ํธ๋์ญ์ ๋ฌธ์ ๊ฐ ์๋๋ฐ, ์ด๋ ๋ค๋ฅธ ๊ธ์์ ๋ฐ๋ก ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. 2021.08.18 - [Backend/Spring Core] - DB์ @Transactional์ ์ฌ์ฉํ ๋ ์์ฃผ ๋์ค๋ ์ค์
# ๋ ํผ๋ฐ์ค
https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth?fbclid=IwAR1PsHPKHyLGmiKORaTsvXV6EwIwe5f2RTCkz52QLZFnDdI7QzArXLil4PQ
https://jeong-pro.tistory.com/228
# 2022.03.01 ์ถ๊ฐ
๐Spring ์ @Transactional ์ด๋ ธํ ์ด์ ๋ถ์ธ ๋ฉ์๋๋ฅผ ์ด๋ป๊ฒ ์ฐพ๊ณ ํธ๋์ญ์ ์ ์ฒ๋ฆฌํ๋์? ๊ทธ ๋ด๋ถ ๊ตฌํ์ ์์ธํ๊ฒ ์ค๋ช ํด ์ฃผ์ธ์.
์ผ๋จ @Transcational์ ์ปดํฌ๋ํธ ์ค์บ์ ๋์์ด ์๋๋๋ค.
์ด๋ @EnableTranscationManagement ๋ฅผ ๊ธฐ์ ์ผ๋ก, ํ์ ํจํค์ง๋ค์ ํ์ํ๋ค.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({TransactionManagementConfigurationSelector.class})
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
@Import(TransactionManagementConfigurationSelector.class) โก Selector๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ค.
Selector๋ 3๊ฐ์ง ์ข
๋ฅ์ @Transcational ์ด๋
ธํ
์ด์
(javax, spring, aspectj)๋ฅผ ๋ถ๋ฅํ๋ ๊ธฐ๋ฅ์ ๊ฐ์ง๊ณ ์๋ค.
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
public TransactionManagementConfigurationSelector() {
}
protected String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return new String[]{AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[]{this.determineTransactionAspectClass()};
default:
return null;
}
}
private String determineTransactionAspectClass() {
return ClassUtils.isPresent("javax.transaction.Transactional", this.getClass().getClassLoader()) ? "org.springframework.transaction.aspectj.AspectJJtaTransactionManagementConfiguration" : "org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration";
}
}
@EnableTranscationManagement๋ ๊ธฐ๋ณธ์ ์ผ๋ก PlatformTxManager ํ์
์ผ๋ก ๋ฑ๋ก๋ ๋น์ ์ฐพ์์ ์ฌ์ฉํ๋ค.
์คํ๋ง๋ถํธ๋ DataSourceTxManager๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ๋ฑ๋กํด์ฃผ๊ณ , SpringDataJPA ์ฌ์ฉ์ JpaDataSourceTxManager๋ฅผ ๋ฑ๋กํ๋ค. ๋ง์ฝ TranscationTemplate์ฒ๋ผ, ํธ๋์ญ์
๋งค๋์ ์ ๊ตฌํ์ฒด๋ฅผ ๋ฐ๊พธ๊ณ ์ถ๊ฑฐ๋, ์ฌ๋ฌ๊ฐ๋ฅผ ์ด๋ค๋ฉด ์ง์ ์ง์ ํ๋ฉด ๋๋ค.
์ค์ ๊ฐ์ฒด์ TranscationConfigurer ์ธํฐํ์ด์ค๋ฅผ ์ถ๊ฐํ๋ฉด ๋๋ค.
@Configuration
@EnableTransactionManagement
public class DBConfig implements TransactionManagementConfigurer {
// ์๋ต...
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return transactionManger();
}
@Bean
public PlatformTransactionManager transactionManger() {
return new DataSourceTransactionManager(dataSource());
}
์ฐธ๊ณ ๋ก ์คํ๋ง4.2๋ถํฐ๋ ์คํ๋ง ์ด๋ ธํ ์ด์ ์ธ @Transcational("๋น ์ด๋ฆ")์ผ๋ก๋ ๊ตฌํ์ฒด๋ฅผ ๋ฐ๊ฟ ์ ์๋ค.
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
... ์๋ต
}
์ดํ @Transcational์ด ์ ์ฉ๋ ํด๋์ค๋ฅผ ํ๋ก์๋ก ๋ฐ๊ฟ, PlatformTxmanager๋ฅผ ์ฃผ์ ํ๋ ๊ฒ์ด๋ค.
๊ณต์๋ฌธ์์๋ ๋์์์ง๋ง PlatformTxManager๋ ๋ฉ์๋๋ด์์ ์๋ก์ด ์ค๋ ๋๋ฅผ ๋ง๋ค์ด ์์ํ๋ ๊ฒ์ ํธ๋์ญ์ ์ ๊ฑธ์ด์ฃผ์ง ์๋๋ค๊ณ ํ๋ค. (= ๊ทธ ์ค๋ ๋๋ ๋กค๋ฐฑ ์๋จ) ์ด์ฐ๋ณด๋ฉด ๋น์ฐํ ์ฌ์ค.
'๐ฑ Spring Framework > Spring Core' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
DB ํธ๋์ญ์ ๊ณผ ์ปค๋ฅ์ ์ดํดํ๊ธฐ (2) | 2021.08.25 |
---|---|
DB์ @Transactional์ ์ฌ์ฉํ ๋ ์์ฃผ ๋์ค๋ ์ค์ (0) | 2021.08.18 |
์ฝ๊ฒ ์ดํดํ๋ ์คํ๋ง AOP (~์์ฑ์ค~) (0) | 2021.08.18 |
ํ ๋น์์คํ๋ง#2 ์ฑ๊ธํค ๋ ์ง์คํธ๋ฆฌ์ ์ค๋ธ์ ํธ ์ค์ฝํ (0) | 2021.08.14 |
ํ ๋น์ ์คํ๋ง#1 ์ค๋ธ์ ํธ์ ์์กด๊ด๊ณ (0) | 2021.08.14 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev