Categories
웹개발/Mybatis
MyBatis SqlSessionFactory SqlSession 차이, 역할과 생명주기 정리
MyBatis 설정에서 SqlSessionFactory, SqlSession, SqlSessionTemplate, SqlSessionFactoryBean이 한꺼번에 보이면 먼저 역할을 나눠야 한다. 어떤 객체가 설정을 만들고, 어떤 객체가 SQL을 실행하고, Spring에서는 왜 직접 SqlSession을 열지 않는지 구분하지 못하면 설정 오류를 엉뚱한 곳에서 찾게 된다.
이 차이를 정리하지 않으면 Mapper가 주입되지 않거나, 트랜잭션이 기대대로 묶이지 않거나, 레거시 코드에서 직접 openSession()을 호출하는 이유를 이해하기 어렵다. 핵심은 간단하다. SqlSessionFactory는 세션을 만드는 공장이고, SqlSession은 실제 SQL을 실행하는 작업 단위다. Spring 환경에서는 이 작업 단위를 SqlSessionTemplate이 대신 관리한다.
핵심 결론: SqlSessionFactory는 SQL 실행 환경을 만들고, SqlSession은 실제 SQL 실행 통로이며, Spring에서는 SqlSessionTemplate이 세션 생명주기를 대신 관리한다.
확인 순서: DataSource → SqlSessionFactoryBean → SqlSessionFactory → SqlSessionTemplate → Mapper 인터페이스 → 트랜잭션 경계.
먼저 전체 흐름을 잡는다
MyBatis는 JDBC 코드를 직접 반복하지 않도록 SQL 실행 과정을 대신 처리해 주는 프레임워크다. JDBC, JPA와 역할 차이가 먼저 헷갈린다면 MyBatis 비교를 먼저 보고 오는 편이 좋다. 내부 흐름만 놓고 보면 결국 DB 연결을 얻고, SQL을 실행하고, 결과를 객체로 매핑한다. 이 흐름을 Spring에서 구성하려면 몇 가지 객체가 필요하다.
- DataSource: DB 연결을 제공한다.
- SqlSessionFactoryBean: Spring 설정에서 SqlSessionFactory를 만들어 주는 FactoryBean이다.
- SqlSessionFactory: SqlSession을 생성하는 MyBatis 핵심 객체다.
- SqlSession: SQL 실행, commit, rollback, close를 담당하는 작업 단위다.
- SqlSessionTemplate: Spring에서 SqlSession을 안전하게 쓰도록 감싼 객체다.
- Mapper 인터페이스: 개발자가 호출하는 DAO 역할의 인터페이스다.
이 중 개발자가 평소 가장 많이 만나는 것은 Mapper 인터페이스다. 하지만 Mapper가 동작하려면 뒤에서 SqlSessionFactory와 SqlSessionTemplate이 준비되어 있어야 한다. MapperScan 설정이 맞지 않거나 SqlSessionFactoryBean 설정이 빠지면 Mapper 인터페이스는 Bean으로 잡히지 않는다. 이 문제는 MapperScan을 확인할 때 같이 보면 좋다.
SqlSessionFactory는 무엇인가
세션을 만드는 공장이다
SqlSessionFactory는 이름 그대로 SqlSession을 만드는 공장이다. Mapper XML 위치, MyBatis 설정, TypeAlias, TypeHandler, DataSource 같은 정보를 바탕으로 SQL 실행 환경을 구성한다. 한 번 만들어진 뒤에는 보통 애플리케이션 전체에서 공유된다.
중요한 점은 SqlSessionFactory 자체가 매번 SQL을 직접 실행하는 객체가 아니라는 것이다. SQL 실행은 SqlSession이 담당한다. SqlSessionFactory는 필요한 설정을 들고 있다가, SQL을 실행할 수 있는 SqlSession을 만들어 준다.
SqlSessionFactory factory = ...;
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUser("u100");
}
순수 MyBatis 코드에서는 이런 식으로 openSession()을 직접 호출한다. 이 경우 세션을 열고 닫는 책임이 개발자에게 있다. close를 빠뜨리면 리소스 문제가 생기고, commit/rollback 처리도 직접 신경 써야 한다.
Spring에서는 SqlSessionFactoryBean으로 만든다
Spring 환경에서는 보통 SqlSessionFactoryBean을 통해 SqlSessionFactory를 만든다. 이름이 비슷해서 헷갈리지만, SqlSessionFactoryBean은 Spring 설정 객체이고, 그 결과로 만들어지는 것이 SqlSessionFactory다.
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:/mapper/**/*.xml")
);
return factoryBean.getObject();
}
이 설정에서 DataSource가 잘못되면 DB 연결이 안 되고, mapperLocations가 잘못되면 Mapper XML을 못 찾는다. MyBatis 전체 연동 흐름이 아직 헷갈린다면 MyBatis 연동 설정에서 DataSource, SqlSessionFactory, MapperScan 순서를 먼저 잡는 편이 좋다.
자주 놓치는 원인: Mapper 오류처럼 보여도 실제 원인은 SqlSessionFactoryBean이 Mapper XML을 못 읽었거나 DataSource가 제대로 연결되지 않은 경우가 많다.
SqlSession은 무엇인가
SQL 실행 작업 단위다
SqlSession은 실제 SQL을 실행하는 객체다. select, insert, update, delete를 호출하고, 트랜잭션을 commit하거나 rollback하고, 작업이 끝나면 close한다. 순수 MyBatis에서는 SqlSession을 직접 다루기 때문에 생명주기 관리가 중요하다.
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insertUser(user);
session.commit();
} catch (Exception e) {
// 필요하면 rollback 처리
throw e;
}
하지만 Spring 환경에서는 보통 이렇게 직접 쓰지 않는다. Spring 트랜잭션과 함께 사용할 때는 SqlSessionTemplate이 현재 트랜잭션에 맞는 SqlSession을 연결해 준다. 개발자는 Mapper 인터페이스만 호출하면 된다.
직접 열면 생기는 문제
Spring 프로젝트에서 아무 생각 없이 openSession()을 직접 호출하면 트랜잭션 경계가 어긋날 수 있다. 예를 들어 서비스 메서드에 @Transactional이 붙어 있어도, 직접 연 SqlSession이 Spring이 관리하는 세션과 다르면 기대한 트랜잭션에 묶이지 않을 수 있다.
@Service
public class UserService {
private final SqlSessionFactory sqlSessionFactory;
@Transactional
public void save(User user) {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insertUser(user);
session.commit(); // Spring 트랜잭션과 따로 움직일 수 있음
}
}
}
이런 코드는 겉으로는 동작해도 트랜잭션 관리가 섞인다. Spring을 쓰는 프로젝트라면 Mapper 인터페이스를 주입받아 호출하고, commit/rollback은 Spring 트랜잭션에 맡기는 편이 일반적이다.
SqlSessionTemplate은 왜 필요한가
Spring에서 세션 생명주기를 대신 관리한다
SqlSessionTemplate은 Spring과 MyBatis를 연결할 때 핵심이 되는 객체다. 내부적으로 SqlSession을 사용하지만, 개발자가 직접 open/close를 하지 않아도 되도록 감싼다. 현재 스레드의 트랜잭션 상황에 맞춰 적절한 SqlSession을 연결하고, 작업이 끝나면 정리한다.
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
Mapper 인터페이스를 호출할 때도 결국 이 SqlSessionTemplate이 뒤에서 SQL 실행을 처리한다. 그래서 Spring MyBatis 프로젝트에서는 직접 SqlSession을 열기보다 Mapper를 주입받아 사용하는 패턴이 많다.
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional
public void save(User user) {
userMapper.insertUser(user);
}
}
이 방식은 코드가 짧아서 좋은 것이 아니라, 세션과 트랜잭션 경계를 Spring이 일관되게 관리할 수 있어서 좋다. SI나 공공 프로젝트에서 레이어가 많은 구조일수록 이런 일관성이 중요하다.
역할을 표로 정리하기
| 구분 | 역할 | 개발자가 직접 다루는 정도 |
|---|---|---|
| DataSource | DB 연결 제공 | 설정에서 다룸 |
| SqlSessionFactoryBean | Spring에서 SqlSessionFactory 생성 | 설정에서 다룸 |
| SqlSessionFactory | SqlSession 생성 | 직접 호출은 드묾 |
| SqlSession | SQL 실행 작업 단위 | Spring에서는 직접 사용 지양 |
| SqlSessionTemplate | SqlSession 생명주기와 트랜잭션 연동 | 설정 또는 내부 동작으로 사용 |
| Mapper 인터페이스 | 개발자가 호출하는 SQL 실행 입구 | 가장 자주 사용 |
Mapper 호출은 실제로 어떻게 이어지는가
개발자가 서비스에서 userMapper.selectUser()를 호출하면 겉으로는 인터페이스 메서드를 부르는 것처럼 보인다. 하지만 내부적으로는 Mapper 프록시가 만들어지고, 이 프록시가 SqlSession을 통해 SQL을 실행한다.
- Spring이 Mapper 인터페이스를 Bean으로 등록한다.
- Mapper 프록시가 생성된다.
- 서비스에서 Mapper 메서드를 호출한다.
- SqlSessionTemplate이 현재 트랜잭션에 맞는 SqlSession을 사용한다.
- Mapper XML 또는 어노테이션 SQL이 실행된다.
- 결과가 resultType 또는 resultMap 기준으로 매핑된다.
마지막 단계에서 결과 매핑이 어긋나면 객체는 만들어져도 필드가 null일 수 있다. 이 문제는 SqlSessionFactory나 SqlSession의 역할 문제가 아니라 매핑 기준 문제다. 이런 경우는 MyBatis 매핑 차이를 같이 확인하는 편이 빠르다.
실무에서 자주 헷갈리는 상황
Mapper XML을 못 찾는 경우
Mapper 인터페이스는 Bean으로 잡혔는데 실행 시점에 statement를 못 찾는 경우가 있다. 이때는 SqlSessionFactoryBean의 mapperLocations가 실제 XML 위치를 포함하는지 확인해야 한다.
factoryBean.setMapperLocations(
resolver.getResources("classpath:/mapper/**/*.xml")
);
빌드 결과물에 XML이 포함되지 않았거나, 경로가 classpath:/mappers/인데 설정은 classpath:/mapper/로 되어 있으면 실행 시점에 문제가 난다. 이 오류는 Mapper 인터페이스 코드만 봐서는 해결되지 않는다.
Mapper 인터페이스가 Bean으로 안 잡히는 경우
Mapper 인터페이스 자체가 주입되지 않는다면 SqlSessionFactory보다 먼저 MapperScan 범위를 본다. 인터페이스가 있는 패키지가 스캔 대상에 들어가야 한다.
@MapperScan("com.example.project.mapper")
@Configuration
public class MyBatisConfig {
}
패키지 구조를 바꿨는데 MapperScan 경로를 그대로 두면 기존 Mapper는 동작하고 새 Mapper만 주입되지 않는 상황도 생긴다. 이럴 때는 설정 파일과 실제 패키지 경로를 같이 봐야 한다.
트랜잭션이 기대대로 롤백되지 않는 경우
서비스에 @Transactional이 있는데 일부 SQL만 반영되거나 rollback이 기대와 다르게 보이면 직접 연 SqlSession이 있는지 확인한다. Spring이 관리하는 트랜잭션 흐름 밖에서 commit을 호출하면 전체 트랜잭션 기준과 어긋날 수 있다.
일반적인 Spring MyBatis 프로젝트에서는 Mapper를 주입받아 호출하고, 트랜잭션은 서비스 계층에서 관리한다. SqlSession을 직접 열어야 하는 특수한 경우가 아니라면 이 패턴을 유지하는 것이 안전하다.
주의할 점: Spring 프로젝트에서 SqlSession을 직접 열면 코드가 더 명확해지는 것이 아니라, 트랜잭션과 리소스 관리 책임이 개발자 코드로 넘어올 수 있다.
설정 예제로 보는 기본 구조
아래는 Spring Java Config 기준의 단순 예시다. 프로젝트에 따라 패키지명과 XML 위치는 달라진다.
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:/mapper/**/*.xml")
);
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
이 구조에서 문제가 나면 순서대로 보면 된다. DataSource가 정상인지, Mapper XML 경로가 맞는지, MapperScan 패키지가 맞는지, 트랜잭션 설정이 서비스 계층에 적용되는지 확인한다.
실무에서 바로 쓰는 점검 순서
- Mapper가 주입되지 않으면 MapperScan 패키지부터 확인한다.
- SQL statement를 못 찾으면 mapperLocations와 XML namespace를 확인한다.
- DB 연결 오류가 나면 DataSource와 SqlSessionFactoryBean 설정을 본다.
- 트랜잭션이 이상하면 직접 openSession을 호출한 코드가 있는지 찾는다.
- 조회 결과 필드가 비면 resultType/resultMap과 컬럼 alias를 확인한다.
- Spring 환경에서는 Mapper 인터페이스 호출을 기본 패턴으로 둔다.
실무에서 바로 쓰는 팁
- SqlSessionFactory는 애플리케이션 공용 설정 객체로 본다. 매 요청마다 새로 만드는 대상이 아니다.
- SqlSession은 작업 단위로 본다. 순수 MyBatis에서는 직접 열고 닫지만, Spring에서는 보통 직접 다루지 않는다.
- SqlSessionTemplate을 Spring 연동의 실행 통로로 이해한다. Mapper 호출 뒤에서 세션과 트랜잭션을 연결한다.
- Mapper 오류를 Mapper 코드만 보고 판단하지 않는다. 스캔 범위, XML 경로, namespace, DataSource를 함께 봐야 한다.
- 레거시 코드의 openSession 호출은 트랜잭션 기준을 확인한다. 직접 commit하는 코드가 있으면 Spring 트랜잭션과 분리될 수 있다.
자주 묻는 질문
- Q. SqlSessionFactory와 SqlSession의 가장 큰 차이는 무엇인가요? A. SqlSessionFactory는 SqlSession을 만드는 공장이고, SqlSession은 실제 SQL을 실행하는 작업 단위입니다.
- Q. Spring에서는 SqlSession을 직접 사용하면 안 되나요? A. 반드시 안 되는 것은 아니지만 일반적으로 Mapper 인터페이스와 SqlSessionTemplate을 통해 사용하는 편이 안전합니다. 트랜잭션과 리소스 관리를 Spring에 맡길 수 있기 때문입니다.
- Q. SqlSessionFactoryBean은 SqlSessionFactory와 같은 건가요? A. 아닙니다. SqlSessionFactoryBean은 Spring에서 SqlSessionFactory를 만들어 주는 설정용 FactoryBean입니다.
- Q. Mapper가 Bean으로 안 잡히면 무엇을 봐야 하나요? A. MapperScan 패키지, Mapper 인터페이스 위치, 설정 클래스 로딩 여부를 먼저 확인합니다.
- Q. Mapper XML을 못 찾는 오류는 어디서 확인하나요? A. SqlSessionFactoryBean의 mapperLocations 설정과 실제 XML 파일 위치, 빌드 결과물 포함 여부, namespace를 확인합니다.
- Q. SqlSessionTemplate은 꼭 필요한가요? A. Spring MyBatis 연동에서는 사실상 핵심 역할을 합니다. 현재 트랜잭션에 맞는 SqlSession 사용과 리소스 정리를 안전하게 처리합니다.
정리
SqlSessionFactory와 SqlSession의 차이는 "설정으로 세션을 만드는 객체"와 "실제로 SQL을 실행하는 작업 단위"의 차이다. 여기에 Spring이 들어오면 SqlSessionTemplate이 세션 생명주기와 트랜잭션 연동을 대신 관리한다.
따라서 Spring MyBatis 프로젝트에서는 SqlSession을 직접 열기보다 Mapper 인터페이스를 주입받아 호출하는 구조가 기본이다. 문제가 생기면 Mapper 코드만 보지 말고 DataSource, SqlSessionFactoryBean, mapperLocations, MapperScan, 트랜잭션 경계를 순서대로 확인해야 한다.
MyBatis 설정 오류를 줄이려면 객체 이름을 외우기보다, 누가 세션을 만들고 누가 SQL을 실행하며 누가 생명주기를 닫는지 흐름으로 보는 편이 오래 간다.
'웹개발 > Mybatis' 카테고리의 다른 글
| MyBatis mybatis-config.xml Mapper XML 차이, 무엇을 어디에 써야 할까 (0) | 2026.07.09 |
|---|---|
| MyBatis #{} ${} 차이, SQL Injection과 Oracle 동적쿼리 기준 정리 (0) | 2026.07.08 |
| @MapperScan 설정과 Mapper 인터페이스가 Bean으로 안 잡힐 때 확인할 부분 (0) | 2026.06.29 |
| Spring MyBatis 연동 설정 완전 정리 (Maven/Gradle) (0) | 2026.06.29 |
| MyBatis란? JDBC·JPA와 비교하고 공공 프로젝트에서 쓰는 이유 (0) | 2026.06.28 |