Chapter 8 내용
- DataSource 설정
- JdbcTemplate을 이용한 쿼리 실행
- DB 관련 익셉션 변환 처리
- 트랜잭션 처리
MYSQL과 연동(MySQL + Gradle + JDBC)
1. build.gradle에 추가(jdbc)
1
2
3
4
|
implementation group: 'org.apache.tomcat', name: 'juli', version: '6.0.26'
implementation group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '7.0.19'
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.19'
implementation group: 'org.springframework', name: 'spring-jdbc', version: '5.2.3.RELEASE'
|
cs |
2. DB 테이블 생성, 데이터 삽입하기
member 테이블 생성 후, 몇가지 데이터를 넣어줬다.
3. DataSource 설정
- JDBC API는 DriverManager 외에 DataSource를 이용해서 DB연결을 구한다.
@Configuration
public class DbConfig {
@Bean(destroyMethod = "close")
public DataSource dataSource() {
DataSource ds = new DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");
ds.setUsername("root");
ds.setPassword("비밀번호");
ds.setInitialSize(2);
ds.setMaxActive(10);
ds.setTestWhileIdle(true);
ds.setMinEvictableIdleTimeMillis(60000 * 3);
ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
return ds;
}
}
- 스프링 설정 파일에 Bean으로 db연결 정보를 등록해 준다.
- setUrl에는 mysql 주소와, DB스키마 이름을 넣으면 된다., 위의 코드에서 스키마 이름은 test이다.
- setUsername과 setPassword는 DB를 설치했을 때 설정한 계정 정보
4. DBQuery 작성
public class DbQuery {
private DataSource dataSource;
public DbQuery(DataSource dataSource) {
this.dataSource = dataSource; //구현해 놓은 dataSource 의존성 주입받음
}
public int count() {
Connection conn = null;
try {
conn = dataSource.getConnection(); //dbConnetcion 구하기
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select count(*) from MEMBER")) {
rs.next();
return rs.getInt(1);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (conn != null)
try {
conn.close();
} catch (SQLException e) {
}
}
}
}
public class MainUsingDbQuery {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DbConfig.class,
DbQueryConfig.class);
DbQuery dbQuery = ctx.getBean(DbQuery.class);
int count = dbQuery.count();
System.out.println(count);
ctx.close();
}
}
[결과] 2
JDBCTemplate을 이용한 쿼리 실행
spring을 사용하면 DataSource, Connection등을 사용하지 않고, jdbcTemplate을 이용해서 쿼리를 실행할 수 있다.
jdbcTemplate 객체를 생성하고, DataSource를 주입받으면 된다.
public class MemberDao {
private JdbcTemplate jdbcTemplate;
public MemberDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
- JdbcTemplate 코드 < 더보기 >
public class MemberDao {
private JdbcTemplate jdbcTemplate;
public MemberDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public Member selectByEmail(String email) {
List<Member> results = jdbcTemplate.query(
"select * from MEMBER where EMAIL = ?",
new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member(
rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE").toLocalDateTime());
member.setId(rs.getLong("ID"));
return member;
}
}, email);
return results.isEmpty() ? null : results.get(0);
}
public void insert(Member member) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con)
throws SQLException {
// 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
PreparedStatement pstmt = con.prepareStatement(
"insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE) " +
"values (?, ?, ?, ?)",
new String[] { "ID" });
// 인덱스 파라미터 값 설정
pstmt.setString(1, member.getEmail());
pstmt.setString(2, member.getPassword());
pstmt.setString(3, member.getName());
pstmt.setTimestamp(4,
Timestamp.valueOf(member.getRegisterDateTime()));
// 생성한 PreparedStatement 객체 리턴
return pstmt;
}
}, keyHolder);
Number keyValue = keyHolder.getKey();
member.setId(keyValue.longValue());
}
public void update(Member member) {
jdbcTemplate.update(
"update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?",
member.getName(), member.getPassword(), member.getEmail());
}
public List<Member> selectAll() {
List<Member> results = jdbcTemplate.query("select * from MEMBER",
(ResultSet rs, int rowNum) -> {
Member member = new Member(
rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE").toLocalDateTime());
member.setId(rs.getLong("ID"));
return member;
});
return results;
}
public int count() {
Integer count = jdbcTemplate.queryForObject(
"select count(*) from MEMBER", Integer.class);
return count;
}
}
마찬가지로 의존성 주입을 위해서 설정 파일에 Bean으로 등록해준다.
@Bean
public MemberDao memberDao() {
return new MemberDao(dataSource());
}
생성한 JdbcTemplate 코드를 필요할 때 꺼내서 쓰기면 하면 된다.
INSERT 쿼리 실행 시 KeyHolder를 이용해서 자동 생성 키 값 구하기
MySQL에서는 AUTO_INCREMENT 칼럼을 지정해서 자동 증가 칼럼을 만들 수 있다.
CREATE TABLE `member` (
`ID` int NOT NULL AUTO_INCREMENT,
`EMAIL` varchar(255) DEFAULT NULL,
`PASSWORD` varchar(100) DEFAULT NULL,
`NAME` varchar(100) DEFAULT NULL,
`REGDATE` datetime DEFAULT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `EMAIL` (`EMAIL`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
- JdbcTemplate은 KeyHolder을 사용해서 자동으로 생성된 키값을 구할 수 있다.
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator(){…생략}, keyHolder);
- GeneratedKeyHolder 객체 생성(KeyHolder 구현 클래스)
- PreparedStatement 객체 생성 후, 두번째 파라미터로 자동 생성 되는 키 칼럼 목록(ID) 전달 ( new String[] {“ID”})
- update()에는 자동 생성된 키값을 KeyHolder 에 보관한다.
트랜잭션 처리
- 트랜잭션은 여러 쿼리를 논리적으로 하나의 작업으로 묶어주고, 묶인 쿼리 중 하나라도 실패하면 전체 쿼리를 실패로 간주하고 실패 이전에 실행된 쿼리를 취소한다.
- (commit과 rollback)실행
-JDBC는 Connectin의 setAutoCommit(flase)를 이용해서 트랜잭션을 시작하고 commit(),rollback()을 이용한다.
단점) 개발자가 코드 누락을 하기 쉽고, 중복이 반복되는 문제가 있음.
Connection conn = null;
try{
...
conn.setAutoCommit(false); // 트랜잭션 범위 시작
... 쿼리실행
conn.commit(); // 트랜잭션 범위 종료: 커밋
}
catch(SQLException ex){
if(conn != null)
// 트랜잭션 범위 종료: 롤백
try{ conn.rollback(); } catch (SQLException e){}
}
finally{
if(conn!= null)
try{ conn.close(); } catch(SQLException e){}
}
@Transactional
트랜잭션을 이용하려면, 트랜잭션 범위에서 실행하고 싶은 메서드에 @Transactional 애노테이션만 붙이면 된다.
@Transactional
public void changePassword(String email, String oldPwd, String newPwd) {
Member member = memberDao.selectByEmail(email);
if (member == null)
throw new MemberNotFoundException();
member.changePassword(oldPwd, newPwd);
memberDao.update(member);
}
- @Transactional이 붙은 chagePassword() 내에서 실행하는 쿼리는 한 트랜잭션에 묶이며, 하나라도 실패 시 롤백된다.
- @Transactional 애노테이션이 제대로 동작하려면 스프링 설정에 두가지를 설정해야한다.
1. PlatformTransactionManager 빈설정
2. @Transcational 애노테이션 활성화 설정( @EnableTransactionManagement)
@Configuration
@EnableTransactionManagement
public class AppCtx {
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource());
return tm;
}
(코드생략)
}
REFERENCE
초보 웹 개발자를 위한 스프링 5 프로그래밍 입문(최범균)
'공부 > Spring' 카테고리의 다른 글
[Spring]Chapter 11 - 요청 매핑, 커맨드 객체, 리다이렉트, 폼 태그, 모델 (0) | 2021.07.11 |
---|---|
[Spring] Chapter 9-10장 Intellij에서 스프링 MVC 시작하기(Spring+ Gradle + MVC) (3) | 2021.07.11 |
[Spring] Chapter 07 - Spring AOP (0) | 2021.06.13 |
[Spring] Chapter 06 - 빈라이클사이클과 범위 (0) | 2021.06.13 |
[Spring] Chapter 05 - 컴포넌트 스캔 (0) | 2021.06.13 |