💻 Spring Data Access (JDBC, Transaction)
Spring 프레임워크를 사용하여 애플리케이션을 개발할 때, 데이터베이스와의 연동은 필수적인 부분입니다. 이번 글에서는 Spring의 JDBC를 사용한 데이터 접근과 함께 MyBatis 통합, 트랜잭션 관리 방법, 그리고 데이터베이스 연동 설정을 해보겠습니다.
1. 🌟 Spring JDBC Template
✔️ Spring JDBC Template란?
Spring JDBC Template은 JDBC(Java Database Connectivity) 사용 시 발생하는 보일러플레이트 코드를 대폭 줄여주기 위해 설계된 유틸리티 클래스입니다. JDBC를 사용할 때 반복적으로 사용되는 코드, 예를 들면 데이터베이스 커넥션을 열고 닫는 것, PreparedStatement
와 같은 SQL 구문을 작성하는 것, 예외 처리 등을 자동으로 처리해주어 더 적은 코드로 깔끔하게 데이터베이스 작업을 수행할 수 있게 해줍니다.
📌 JDBC Template 사용 방법
Spring에서 JdbcTemplate
을 사용하는 과정은 매우 간단합니다. 기본적으로 다음과 같은 단계를 따릅니다:
- DataSource 설정을 통해 데이터베이스 커넥션을 구성
JdbcTemplate
객체를 생성하여 데이터베이스와 상호작용
예시 코드
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
위 코드에서 보듯, DataSource
를 통해 데이터베이스 커넥션을 설정하고, 이를 JdbcTemplate
에 전달하여 사용할 수 있습니다.
📌 주요 메서드
queryForObject
: 단일 결과를 반환할 때 사용query
: 여러 행을 반환할 때 사용update
: 삽입, 수정, 삭제를 수행할 때 사용
예시로, queryForObject
를 이용해 단일 결과를 조회하는 코드를 보겠습니다.
String sql = "SELECT name FROM users WHERE id = ?";
String name = jdbcTemplate.queryForObject(sql, new Object[]{id}, String.class);
2. 💡 MyBatis와의 통합 및 XML Mapper 사용
✔️ MyBatis 소개
MyBatis는 SQL을 XML 파일에 정의하고, 자바 객체와 SQL 간의 매핑을 쉽게 해주는 프레임워크입니다. Spring에서 MyBatis를 사용하는 이유는 복잡한 SQL 쿼리와 객체 간의 매핑 작업을 더욱 직관적이고 효율적으로 처리할 수 있기 때문입니다. 특히, SQL을 XML 파일로 분리하여 관리할 수 있는 것이 큰 장점입니다.
📌 MyBatis 의존성 추가 (pom.xml)
MyBatis를 사용하기 위해서는 다음과 같이 의존성을 추가합니다.
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
📌 MyBatis 설정 (application.yml)
MyBatis와 Spring을 연동하려면 간단한 설정이 필요합니다. application.yml
파일에서 데이터베이스 및 MyBatis 설정을 다음과 같이 할 수 있습니다.
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydatabase
username: myuser
password: mypassword
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
config-location: classpath:mybatis-config.xml
mapper-locations: classpath:mappers/*.xml
위 설정을 통해 MyBatis와 MySQL이 연동되며, 쿼리문을 XML 파일로 분리해 사용할 수 있습니다.
✔️ XML Mapper 사용 예시
Mapper 인터페이스를 만들고, SQL 쿼리를 XML 파일로 분리하여 사용합니다. 먼저, 인터페이스를 정의한 다음, 그와 매핑되는 XML 파일에서 SQL을 정의합니다.
📌 Mapper 인터페이스
public interface UserMapper {
User findById(Long id);
List<User> findAll();
}
📌 XML 파일로 쿼리 정의 (UserMapper.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="com.example.mapper.UserMapper">
<!-- 단일 사용자 조회 -->
<select id="findById" parameterType="long" resultType="com.example.model.User">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 모든 사용자 조회 -->
<select id="findAll" resultType="com.example.model.User">
SELECT * FROM users
</select>
</mapper>
XML 파일에서는 SQL 쿼리를 정의하고, #{}
구문을 통해 동적으로 파라미터 값을 주입받습니다. 이를 통해 SQL 쿼리를 자바 코드에서 분리하여 XML 파일로 관리할 수 있습니다.
3. 🔄 트랜잭션 관리
데이터베이스 작업에서 트랜잭션 관리는 매우 중요한 부분입니다. 트랜잭션은 여러 작업을 하나의 단위로 묶어 원자성을 보장하며, 작업 중 하나라도 실패하면 전체 작업을 롤백할 수 있도록 도와줍니다. Spring에서는 트랜잭션을 선언적 또는 프로그래매틱하게 관리할 수 있습니다.
✔️ 선언적 트랜잭션 관리
선언적 트랜잭션 관리는 가장 많이 사용하는 방식으로, 애너테이션이나 XML 설정을 통해 트랜잭션을 관리하는 방식입니다. 가장 많이 사용하는 애너테이션은 @Transactional
입니다. 메서드 또는 클래스 레벨에서 이 애너테이션을 붙이면, 해당 메서드 내의 모든 데이터베이스 작업이 트랜잭션으로 처리됩니다.
예시
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, Double amount) {
// 출금
accountRepository.debit(fromAccountId, amount);
// 입금
accountRepository.credit(toAccountId, amount);
}
위 코드를 통해 입금과 출금 작업이 하나의 트랜잭션으로 묶이게 되고, 만약 중간에 에러가 발생하면 전체 작업이 롤백됩니다.
✔️ 프로그래매틱 트랜잭션 관리
프로그래매틱 트랜잭션 관리는 코드에서 직접 트랜잭션을 관리하는 방법입니다. PlatformTransactionManager
와 TransactionTemplate
을 사용하여 트랜잭션을 시작, 커밋 또는 롤백할 수 있습니다. 선언적 트랜잭션과 달리, 코드 내에서 트랜잭션의 범위를 명시적으로 정의할 수 있습니다.
예시
public void transferMoney(Long fromAccountId, Long toAccountId, Double amount) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 출금
accountRepository.debit(fromAccountId, amount);
// 입금
accountRepository.credit(toAccountId, amount);
transactionManager.commit(status); // 트랜잭션 커밋
} catch (Exception e) {
transactionManager.rollback(status); // 트랜잭션 롤백
throw e;
}
}
4. ⚙️ 데이터베이스 연동 설정 (DataSource 설정, HikariCP 등)
Spring에서 데이터베이스와의 연동을 위해서는 DataSource
설정이 필요합니다. DataSource
는 데이터베이스 커넥션을 관리하며, Spring Boot에서는 기본적으로 HikariCP가 기본 연결 풀(Connection Pool)로 제공됩니다. HikariCP는 성능이 뛰어나고 경량화된 커넥션 풀로 많은 애플리케이션에서 사용됩니다.
✔️ HikariCP 설정 예시
Spring Boot에서는 간단하게 application.yml
파일에서 HikariCP 설정을 할 수 있습니다.
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
이 설정을 통해 HikariCP를 이용한 데이터베이스 커넥션 풀링을 구성할 수 있습니다.
5. ⚙️ 두 개의 데이터베이스 사용하기 (Multiple DataSources)
Spring Boot에서는 두 개 이상의 데이터베이스를 사용하여 각각 독립적으로 트랜잭션을 관리할 수 있습니다. 여기서는 두 개의 DataSource
를 설정하고, 각각의 데이터베이스에 대해 트랜잭션을 어떻게 처리할 수 있는지 설명하겠습니다.
📌 두 개의 데이터베이스 설정 (application.yml)
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/primarydb
username: primary_user
password: primary_password
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/secondarydb
username: secondary_user
password: secondary_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
primary:
mapper-locations: classpath:mappers/primary/*.xml
secondary:
mapper-locations: classpath:mappers/secondary/*.xml
위 설정은 각각 Primary 데이터베이스와 Secondary 데이터베이스에 대한 연결 정보를 제공합니다.
✔️ Primary DataSource 설정
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
@Primary
@Bean(name = "primarySqlSessionTemplate")
public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
✔️ Secondary DataSource 설정
@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfig {
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
@Bean(name = "secondarySqlSessionTemplate")
public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
6. 🔄 두 개의 데이터베이스 트랜잭션 관리
✔️ 기본 트랜잭션 관리 문제
두 개의 데이터베이스를 사용하는 경우, 일반적으로 @Transactional
애너테이션을 사용하면 하나의 트랜잭션 매니저만 적용됩니다. 예를 들어 하나의 서비스에서 두 개의 데이터베이스를 동시에 사용하는 경우, 한 데이터베이스에서 에러가 발생해도 다른 데이터베이스의 변경 사항은 롤백되지 않을 수 있습니다.
이 문제를 해결하기 위해 ChainedTransactionManager를 이용하여 두 개의 트랜잭션을 동기화할 수 있습니다.
✔️ ChainedTransactionManager 설정
ChainedTransactionManager는 여러 개의 트랜잭션 매니저를 묶어 하나의 트랜잭션처럼 동작하게 만드는 방법입니다. 이를 통해 두 개 이상의 데이터베이스에서 발생하는 작업을 하나의 트랜잭션으로 처리할 수 있습니다.
ChainedTransactionManager 설정 예시
@Configuration
public class ChainedTransactionManagerConfig {
@Bean(name = "chainedTransactionManager")
public ChainedTransactionManager transactionManager(
@Qualifier("primaryTransactionManager") PlatformTransactionManager primaryTransactionManager,
@Qualifier("secondaryTransactionManager") PlatformTransactionManager secondaryTransactionManager) {
return new ChainedTransactionManager(primaryTransactionManager, secondaryTransactionManager);
}
}
✔️ ChainedTransactionManager를 이용한 트랜잭션 적용
이제 ChainedTransactionManager를 설정한 후, 서비스 레이어에서 두 개의 데이터베이스를 호출할 때 트랜잭션을 적용할 수 있습니다.
예시 서비스 클래스
@Service
public class UserService {
@Autowired
private PrimaryUserMapper primaryUserMapper;
@Autowired
private SecondaryUserMapper secondaryUserMapper;
@Autowired
private ChainedTransactionManager chainedTransactionManager;
@Transactional(transactionManager = "chainedTransactionManager")
public void createUserInBothDB(User user) {
primaryUserMapper.insertUser(user); // Primary DB에 사용자 추가
secondaryUserMapper.insertUser(user); // Secondary DB에 사용자 추가
}
}
위 코드에서는 ChainedTransactionManager
를 통해 두 데이터베이스의 트랜잭션을 묶어 처리하게 됩니다. 하나의 데이터베이스에서 에러가 발생하면 전체 트랜잭션이 롤백됩니다.
💡 요약
- Spring JDBC Template은 복잡한 JDBC 작업을 간소화해주는 유틸리티로, 데이터베이스와의 상호작용을 쉽게 할 수 있게 도와줍니다.
- MyBatis는 SQL과 자바 객체 간의 매핑을 지원하는 프레임워크로, SQL 쿼리를 XML 파일로 분리하여 관리할 수 있습니다.
- 트랜잭션 관리는 선언적 방식과 프로그래매틱 방식으로 나뉘며, 상황에 맞게 선택할 수 있습니다.
- 데이터베이스 연동 설정에서 HikariCP와 같은 커넥션 풀을 사용하여 성능 최적화를 도모할 수 있습니다.
- 두 개의 데이터베이스를 사용하는 경우, 각각의 DataSource와 트랜잭션 매니저를 설정해야 하며, 트랜잭션을 묶어 관리하려면 ChainedTransactionManager를 사용할 수 있습니다.
- ChainedTransactionManager를 사용하면 두 개 이상의 데이터베이스에서 하나의 트랜잭션처럼 작업을 처리할 수 있어, 데이터 일관성을 유지할 수 있습니다.
'Java' 카테고리의 다른 글
[JAVA] Java에서 예외 처리방법 (7) | 2024.11.13 |
---|---|
[JAVA] 프로젝트에서 PMD를 이용한 소스 품질 검사 (0) | 2024.11.11 |
[JAVA] Spring Boot Redis 캐시 사용법 (0) | 2024.09.20 |
[JAVA] Spring 돌아보기 Part.2 #AOP (0) | 2024.09.12 |
[JAVA] Spring 돌아보기 Part.1 #IoC #DI #싱글톤 (0) | 2024.09.10 |