Notice
Recent Posts
Recent Comments
Link
개발 무지렁이
[Spring] JDBC API 로직의 반복과 이를 해결하는 Spring의 JdbcTemplate 본문
🦉 DataSource
: 데이터베이스와 연결할 수 있는 정보가 들어있는 객체를 말한다.
(⚠️ Spring이 DataSource를 주입해준다)
: 데이터베이스와 연결할 수 있는 정보가 들어있는 객체를 말한다.
(⚠️ Spring이 DataSource를 주입해준다)
📜 SpringConfig.java
@Configuration // Spring Bean으로 관리된다.
public class SpringConfig {
DataSource dataSource;
@Autowired
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new JdbcMemberRepository(dataSource);
}
}
📜 JdbcMemberRepository.java
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
private void close(Connection conn, PerparedStatement pstmt, ResultSet rs) {
try {
if(rs != null) {
rs.close();
}
} catch(SQLException e) {
e.printStackTrace();
}
try {
if(pstmt != null) {
pstmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn != null) {
close(conn);
}
} catch(SQLException e) {
e.printStackTrace();
}
}
@Override
public List<Member> findAll() {
String sql = "select * from member";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
List<Member> members = new ArrayList<>();
while(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
members.add(member);
}
return members;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Optional<Member> findByName(Long id) {
String sql = "select * from member where id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.preparedStatement(sql);
pstmt.setString(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);
} else {
return Optional.empty();
}
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate(); // db에 DML 쿼리가 날라간다
rs = pstmt.getGeneratedKeys();
if(rs.next()) {//값을 추출
member.setId(rs.getLong(1));
} else {
throw new SQLException("id 조회 실패");
}
return member;
} catch(Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
}
🦉 Statement 클래스
: 기본적인 SQL문을 실행하는데 사용하지만,
SQL INJECTION 공격에는 취약하다.
⚠️ 해당 클래스는 2가지 하위 클래스로 나뉜다.
(1) PreparedStatement
: 미리 컴파일된 SQL문을 실행하는데 사용된다.
데이터바인딩이 파라미터화되어 메서드를 통해 이루어지므로, 다른 연산자가 들어갈 수 없고,
이로 인해, SQL INJECTION 공격을 예방할 수 있다.
(2) CallableStatement
: 저장 프로시저를 실행하는데 사용된다.
데이터베이스에서 미리 정의된 일련의 SQL명령문을 실행하는데 사용된다.
🦉 Statement 클래스의 내장 메서드
.executeQuery(String sql)
: SELECT문을 실행하고 결과집합(ResultSet)을 반환
.executeUpdate(String sql)
: INSERT, UPDATE, DELETE와 같은 DML 쿼리를 실행하고,
영향을 미친 행의 수를 반환
.execute(String sql)
: 임의의 SQL문 실행
데이터베이스에서 지원하지 않는 특수한 작업을 수행하는데 사용
⚠️ Statement.RETURN_GENERATED_KEYS
:INSERT 쿼리를 실행한 후에 자동으로 생성된 key를 알고 싶을 때
🏁 플래그를 사용하여 설정
⚠️ pstmt.getGeneratedKeys() 메서드를 호출해 ResultSet을 반환받고, 여기서 getLong()을 통해 추출한다.
: 기본적인 SQL문을 실행하는데 사용하지만,
SQL INJECTION 공격에는 취약하다.
⚠️ 해당 클래스는 2가지 하위 클래스로 나뉜다.
(1) PreparedStatement
: 미리 컴파일된 SQL문을 실행하는데 사용된다.
데이터바인딩이 파라미터화되어 메서드를 통해 이루어지므로, 다른 연산자가 들어갈 수 없고,
이로 인해, SQL INJECTION 공격을 예방할 수 있다.
(2) CallableStatement
: 저장 프로시저를 실행하는데 사용된다.
데이터베이스에서 미리 정의된 일련의 SQL명령문을 실행하는데 사용된다.
🦉 Statement 클래스의 내장 메서드
.executeQuery(String sql)
: SELECT문을 실행하고 결과집합(ResultSet)을 반환
.executeUpdate(String sql)
: INSERT, UPDATE, DELETE와 같은 DML 쿼리를 실행하고,
영향을 미친 행의 수를 반환
.execute(String sql)
: 임의의 SQL문 실행
데이터베이스에서 지원하지 않는 특수한 작업을 수행하는데 사용
⚠️ Statement.RETURN_GENERATED_KEYS
:INSERT 쿼리를 실행한 후에 자동으로 생성된 key를 알고 싶을 때
🏁 플래그를 사용하여 설정
⚠️ pstmt.getGeneratedKeys() 메서드를 호출해 ResultSet을 반환받고, 여기서 getLong()을 통해 추출한다.
🦉 DataSourceUtils
: Spring에서 제공하는 유틸리티 클래스 중 하나로,
데이터베이스 연결/관리 작업을 편하게 수행하도록 하는 클래스를 말한다.(🧩 코드 간소화)
(⚠️ Spring JDBC에서 사용)
.getConnection(DataSource dataSource)
: 데이터베이스 연결을 얻는다
.releaseConnection(Connection conn, DataSource dataSource)
: 데이터베이스 연결을 안전하게 해제 및 반환
.doGetConnection(DataSource dataSource)
: 내부적인 메서드로 실제 데이터베이스 연결을 가져온다.
(개발자가 호출하는 메서드가 아님)
.doReleaseConnection(Connection conn, DataSource dataSource)
: 내부적인 메서드 실제 데이터베이스 연결을 안전하게 해제 및 반환
(개발자가 호출하는 메서드가 아님)
.doBegin(TransactioniInfo txInfo)
: 스프링의 트랜잭션 관리를 시작
.doCommit(TransactionInfo txInfo)
: 스프링의 트랜잭션 관리를 커밋
.doRollback(TransactionInfo txInfo)
: 스프링의 트랜잭션 관리를 롤백
: Spring에서 제공하는 유틸리티 클래스 중 하나로,
데이터베이스 연결/관리 작업을 편하게 수행하도록 하는 클래스를 말한다.(🧩 코드 간소화)
(⚠️ Spring JDBC에서 사용)
.getConnection(DataSource dataSource)
: 데이터베이스 연결을 얻는다
.releaseConnection(Connection conn, DataSource dataSource)
: 데이터베이스 연결을 안전하게 해제 및 반환
.doGetConnection(DataSource dataSource)
: 내부적인 메서드로 실제 데이터베이스 연결을 가져온다.
(개발자가 호출하는 메서드가 아님)
.doReleaseConnection(Connection conn, DataSource dataSource)
: 내부적인 메서드 실제 데이터베이스 연결을 안전하게 해제 및 반환
(개발자가 호출하는 메서드가 아님)
.doBegin(TransactioniInfo txInfo)
: 스프링의 트랜잭션 관리를 시작
.doCommit(TransactionInfo txInfo)
: 스프링의 트랜잭션 관리를 커밋
.doRollback(TransactionInfo txInfo)
: 스프링의 트랜잭션 관리를 롤백
𖠃 🌱 Spring의 JdbcTemplate 기능 제공 + SimpleJdbcInsert
: 데이터베이스에 안전하게 Access하고,
편하게 sql문을 날릴 수 있게 도와주는 클래스를 말한다.
🎯 JDBC API에서 반복 코드 제거
🎯 SimpleJdbcInsert: Insert 쿼리를 짤 필요가 없다.
편하게 sql문을 날릴 수 있게 도와주는 클래스를 말한다.
🎯 JDBC API에서 반복 코드 제거
🎯 SimpleJdbcInsert: Insert 쿼리를 짤 필요가 없다.
📜 JdbcTemplateMemberRepository.java
public class JdbcTemplateMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcTemplateMemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
// Number: 숫자값을 나타내는 추상클래스
// executeAndReturnKey(): 데이터베이스에 데이터를 삽입하고, 자동으로 생성된 키를 반환하는 메서드
// MapSqlParameterSource: 데이터베이스에 삽입할 파라미터를 나타내는 객체
member.setId(key.longValue());
return member;
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
}
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
@Override
public Optional<Member> findById(Long id) {
List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
return result.stream().findAny(); //첫번째로 찾은 요소를 반환
}
}
🦉 RowMapper
: JDBC의 ResultSet을 Java객체로 매핑하는데 사용되는 인터페이스를 말한다.
(데이터베이스 -> 자바객체로의 변환작업을 🧩 간소화)
(T: 변환하려는 자바객체의 타입)
⚠️ mapRow(ResultSet rs, int rowNum), rowNum: 현재행의 인덱스
: JDBC의 ResultSet을 Java객체로 매핑하는데 사용되는 인터페이스를 말한다.
(데이터베이스 -> 자바객체로의 변환작업을 🧩 간소화)
(T: 변환하려는 자바객체의 타입)
⚠️ mapRow(ResultSet rs, int rowNum), rowNum: 현재행의 인덱스
'Backend > 스프링' 카테고리의 다른 글
[Spring] Spring의 설계 철학과 느슨한 결합을 만들어주는 Spring Container의 DI와 인터페이스 지향 (0) | 2023.09.19 |
---|---|
[Spring] 라이브러리의 의존관계를 관리하는 빌드툴과 기초 라이브러리 (0) | 2023.09.11 |
[Spring] 개발 및 테스트환경에 적합한 경량화된 DBMS, H2 (0) | 2023.09.10 |
[Spring] 의존성 주입(DI)에 의한 의존관계 조립 (0) | 2023.09.09 |
[Spring] Spring Framework Transaction과 AOP, Transaction Manager (0) | 2023.05.10 |
Comments