📌 1. JDBC
JDBC(Java DataBase Connectivity)는 Java와 데이터베이스를 연결하기 위한 Java 표준 인터페이스이다. 여러 DB 드라이버를 제공하기 때문에 MySQL, MariaDB, PostgreSQL 등 다양한 DB와 연결이 가능하며 Java 표준이기 때문에 JVM 위에서 운영되는 애플리케이션에서 자유롭게 사용할 수 있다.
데이터 접근 순서 : Controller -> Service -> DAO -> JDBC -> DB
JDBC의 주요 기능
- 데이터베이스 연결 관리 : 다양한 데이터베이스에 연결할 수 있는 일관된 방법을 제공한다.
- SQL 문 실행 : SQL 쿼리 및 업데이트 문을 실행할 수 있다.
- 결과 집합 처리 : 쿼리 실행 결과를 처리하고 데이터베이스로부터 데이터를 검색할 수 있다.
- 트랜잭션 관리 : 데이터베이스 트랜잭션을 처리하고 커밋 및 롤백 작업을 수행할 수 있다.
JDBC의 장점
- 데이터베이스의 독립성 : 동일한 코드 베이스로 다른 데이터베이스 시스템과 상호작용하므로 데이터베이스 변경 시 코드 변경 최소화
- 표준 API 제공 : 표준화된 API를 제공하므로, 이를 사용하여 일관된 방식으로 데이터베이스 작업을 수행할 수 있다.
- 풍부한 기능 : JDBC는 데이터 삽입, 업데이트, 삭제, 쿼리 실행, 트랜잭션 관리 등의 다양한 데이터베이스 작업을 지원한다. 또한, 배치 처리, 데이터 스트림 처리, 저장 프로시저 호출 등 고급 기능도 제공한다.
- 확장성 : JDBC는 드라이버 기반의 구조로 되어 있어 해당 드라이버만 있으면 쉽게 확장할 수 있다.
- 광범위한 지원 : 대부분의 관계형 데이터베이스 시스템이 JDBC 드라이버를 제공하므로, JDBC를 사용하면 거의 모든 주요 데이터베이스 시스템과 연동 가능하다.
JDBC의 단점
- 직접적인 연결 관리 : 데이터베이스 연결, 명령 실행, 결과 처리, 연결 해제 등의 모든 작업을 직접 관리해야 한다.
- 복잡한 예외 처리
- 보일러플레이트 코드 : JDBC는 많은 양의 보일러플레이트 코드(반복적이고 장황한 코드)를 요구한다. 코드의 가독성을 낮추고 유지 보수를 어렵게 만든다.
- 객체 관계 매핑 (ORM) 부재 : JDBC는 객체와 관계형 데이터베이스 간의 매핑을 제공하지 않는다. 이를 위해서는 Hibernate, JPA 같은 ORM 도구를 추가로 사용해야 한다.
package himedia.spring.ver2.repository;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import himedia.spring.ver2.dto.Member;
// @Primary
// @Repository
public class MemberJdbcRepository implements MemberRepository {
// [DI] 의존성 주입 /////////////////////////////////////////
// [방법 1] 생성자를 통한 의존성 주입 ======================
private final DataSource dataSource;
@Autowired
public MemberJdbcRepository(DataSource dataSource) {
this.dataSource = dataSource;
System.out.println("[repository] MemberJdbcRepository 실행됨!!!");
}
// [방법 2] 필드를 통한 의존성 주입 ===========================
// @Autowired private final DataSource dataSource;
//
// public MemberJdbcRepository() {
// System.out.println("[repository] MemberJdbcRepository 실행됨!!!" + " ==> field를 통한 의존성 주입");
// }
@Override
public Long save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
if(rs.next()) {
member.setId(rs.getLong(1));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(pstmt != null)
pstmt.close();
if(conn != null)
conn.close();
} catch(Exception e2) {
e2.printStackTrace();
}
}
return member.getId();
}
@Override
public Optional<Member> findById(Long id) {
String sql = "select * from member where id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(rs != null)
rs.close();
if(pstmt != null)
pstmt.close();
if(conn != null)
conn.close();
} catch(Exception e2) {
e2.printStackTrace();
}
}
return Optional.empty();
}
@Override
public Optional<Member> findByName(String name) {
String sql = "select * from member where name = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
rs = pstmt.executeQuery();
if (rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(rs != null)
rs.close();
if(pstmt != null)
pstmt.close();
if(conn != null)
conn.close();
} catch(Exception e2) {
e2.printStackTrace();
}
}
return Optional.empty();
}
@Override
public List<Member> findAll() {
String sql = "select * from member";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<Member> members = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
members = new ArrayList<>();
while (rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
members.add(member);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if(rs != null)
rs.close();
if(pstmt != null)
pstmt.close();
if(conn != null)
conn.close();
} catch(Exception e2) {
e2.printStackTrace();
}
}
return members;
}
}
1. DataSource (의존성 주입 : Dependency Injection)
DataSource는 데이터베이스 연결을 관리하는 표준 방법이다. 스프링 프레임워크에서 DataSource를 통해 커넥션 풀을 설정하고 관리할 수 있다. MemberJdbcRepository 에서 DataSource는 의존성 주입을 통해 제공된다. Spring에서는 의존성을 주입을 통해 객체 간의 의존 관계를 설정한다. 이는 보통 어노테이션이나 XML 설정 파일을 통해 이루어진다.
2. CRUD 메서드 (Create (생성), Read(읽기), Update(갱신), Delete(삭제)
CRUD는 대부분 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리 기능인 Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 묶어서 일컫는 말이다.
저장 메서드 ('save') : 동적쿼리 PreparedStatement 를 사용하여 멤버 이름을 데이터베이스에 저장하고 생성된 키를 반환한다.
ID로 멤버 조회 ('findById') : 주어진 ID로 멤버를 조회하고 Optional로 반환한다.
이름으로 멤버 조회 ('findByName') : 주어진 이름으로 멤버를 조회하고 Optional로 반환한다.
모든 멤버 조회 ('findByName') : 모든 멤버를 조회하여 List로 반환한다.
📌 2. Mybatis (마이바티스)
참고 사이트
https://mybatis.org/mybatis-3/sqlmap-xml.html
MyBatis 마이바티스는 자바 프로그래밍 언어에서 SQL 데이터베이스 접근을 간편하게 할 수 있도록 돕는 퍼시스턴트 프레임워크이다. 마이바티스는 SQL 쿼리를 XML 파일이나 어노테이션(annotation)으로 정의하고 이를 통해 데이터베이스와의 상호작용을 쉽게 할 수 있도록 한다.
데이터 접근 순서 : Controller -> Service -> Mapper(DAO) -> Mybatis -> JDBC -> DB
마이바티스의 주요 기능
- SQL Mapping : SQL 쿼리를 XML 파일이나 어노테이션으로 작성할 수 있다. 이를 통해 SQL 쿼리를 자바 코드와 분리할 수 있다.
- 자동 매핑 : 데이터베이스의 결과를 자바 객체로 자동으로 매핑해준다. 이를 통해 반복적인 코드 작성을 줄일 수 있다.
- 동적 SQL : 조건에 따라 동적으로 SQL 쿼리를 생성할 수 있는 기능을 제공한다. 이를 통해 복잡한 쿼리를 유연하게 작성한다.
- 높은 유연성 : 마이바티스는 기본적인 CRUD 기능뿐만 아니라 복잡한 비즈니스 로직도 구현할 수 있는 유연성을 제공한다.
Mybatis 연동 관련
1) SqlSession : MyBatis를 사용하기 위한 기본적인 Java 인터페이스는 SqlSession 이다. SqlSession 인터페이스를 통해 명령어를 실행하고 Mapper를 얻으며, 트랜잭션을 관리한다.
2) SqlSessionFactory : SqlSession 인스턴스를 생성한다. 따라서 SqlSessionFactory를 Bean으로 등록해야 한다. Mybatis 의존성 설정으로 사용할 수 있다.
3) SqlSessionFactoryBean : SqlSessionFactory를 스프링에 잘 녹여내어 Bean으로 등록할 수 있게 한다. Mybatis-Spring 의존성 설정으로 사용할 수 있다.
4) SqlSessionTemplate : Mybatis-Spring 의존성 설정으로 사용할 수 있으며, SqlSession의 구현체이다. 즉, SqlSession을 대체하는 역할을 한다. 생성자의 인자로 SqlSessionFactory를 사용한다.
5) Mapper : DAO와 동일한 기능을 한다. Mapper 사용 시에 SQL문과 Java문을 구분할 수 있다. 이 때, Mapper는 반드시 인터페이스 이어야 하고 Bean 으로 등록해야 한다.
다음은 Mybatis 설정하여 데이터베이스 연결한 예시이다.
<!-- MyBatis 설정 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:mappers/*.xml" />
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="himedia.spring.ver2.repository" />
</bean>