JDBC 추가하기
pom.xml <!-- 프로젝트의 의존성 라이브러리를 정의한다. --> <dependencies> <!-- 오라클 데이터베이스연동을 지원하는 jdbc 드라이버 라이브러리 의존성 추가 --> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc11</artifactId> <version>21.8.0.0</version> </dependency> <!-- spring-jdbc 라이브러리 의존성 정의 * spring-jdbc 라이브러리 의존성의 spring-jdbc, spring-tx 라이브러리 의존성도 포함한다. * jdbc 기반의 데이터베이스 엑세스 작업을 쉽게 구현하도록 지원하는 라이브러리다. --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.24</version> </dependency> </dependencies>
- 스프링의 데이터베이스 엑세스 작업은 jdbc 연동, ibatis/mybatis 연동, JPA/hibernate 연동 등 다양한 데이터베이스 엑세스 기술과 연동해서 데이터베이스 엑세스 작업을 구현할 수 있다.
- 스프링의 데이터베이스 엑세스 작업 특징
* 다양한 데이터베이스 엑세스 라이브러리와의 연동을 지원한다.
(jdbc, ibatis/mybatis, JPA/hibernate 등)
* 일관된 데이터베이스 엑세스 예외처리를 지원한다.
- 모든 데이터베이스 엑세스 오류에 대해서 DataAccessException 예외를 발생시킨다.
- DataAccessException 클래스는 RuntimeException의 하위 클래스여서 예외처리를 강제하지 않는다. (try ~ catch 불필요)
- 데이터베이스의 종류와 상관없이 오류 상황에 맞는 적절한 예외 객체를 발생시킨다.
- 예외 클래스의 이름도 예외상황에 맞는 이름이기 때문에 오류 상황을 더 쉽게 파악할 수 있다.
* 데이터베이스와 연결을 유지하는 Connection객체는 Connection pool을 이용해서 획득하고, 사용 후 반납한다.
- Connection Pool은 데이터베이스와 연결을 유지하는 Connection 객체를 관리하는 객체다.
- Connection Pool객체를 스프링 컨테이너가 생성하게 하거나, 생성된 Connection Pool을 스프링 컨테이너에 등록한다.
* 선언적 트랜잭션 처리를 지원한다.
- 프로그램 코딩 없이 간단한 설정만으로 트랜잭션 처리 기능을 지원받을 수 없다.
* spring-jdbc는 데이터베이스 엑세스를 지원하는 JdbcTemplate 객체를 제공한다.
* JdbcTemplate는 spring-jdbc의 핵심 객체다.
* JdbcTemplate는 INSERT, UPDATE, DELETE, SELECT를 실행하는 메소드를 제공한다.
* 사용자 정의 DAO 클래스는 JdbcTemplate 객체를 의존성 주입받아서 데이터베이스 엑세스 작업을 수행한다.
* jdbc 드라이버 라이브러리 의존성 추가
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<version>21.8.0.0</version>
</dependency>
* spring-jdbc 라이브러리 의존성 추가
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.24</version>
</dependency>
<bean id="connectionPool" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${db.driver-class-name}"></property>
<property name="url" value="${db.url}"></property>
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="connectionPool"></property>
</bean>
<context:annotation-config />
<context:component-scan base-package="com.sample" />
@Repository
public class UserDao {
@Autowired
private JdbcTemplate template;
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
UserDao.java
package com.sample.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import com.sample.vo.User;
@Repository
public class UserDao {
@Autowired
private JdbcTemplate template;
public void insertUser(User user) {
String sql = "insert into web_users(user_id, user_password, user_name, user_email) values (?,?,?,?)";
template.update(sql, user.getId(), user.getPassword(), user.getName(), user.getEmail());
}
public void updateUser(User user) {
String sql = "update web_users set user_password = ?, user_updated_date = sysdate where user_id = ?";
template.update(sql, user.getPassword(), user.getId());
}
/*
* 객체가 여러 개 얻어 질 때 : template.query
* 객체가 하나 얻어 질 때 (primary key 일 때) : template.queryForObject(sql, new UserRowMapper(), 들어갈 값)
*/
public List<User> getAllUser() {
String sql = "select * from web_users";
return template.query(sql, new UserRowMapper());
}
public User getUserById(String id) {
String sql = "select * from web_users where user_id = ? ";
return template.queryForObject(sql, new UserRowMapper(), id);
}
public User getUserByEmail(String email) {
String sql = "select * from web_users where user_email = ?";
return template.queryForObject(sql, new UserRowMapper(), email);
}
// Select문의 실행 후 획득된 ResultSet을 어떻게 처리할지 구현한 객체다.
private class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getString("user_id"));
user.setPassword(rs.getString("user_password"));
user.setName(rs.getString("user_name"));
user.setEmail(rs.getString("user_email"));
user.setEnabled(rs.getString("user_enabled"));
user.setUpdatedDate(rs.getDate("user_updated_date"));
user.setCreatedDate(rs.getDate("user_created_date"));
return null;
}
}
}
... = 가변길이 매개변수
UserService
package com.sample.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sample.dao.UserDao;
import com.sample.vo.User;
@Service
public class UserService {
@Autowired
private UserDao userDao;
// 회원가입
public void registeruser(User user) {
}
// 비밀번호 변경
public void changeUserPassword(String userId, String oldPassword, String passowd) {
}
}
- SQL Mapping Framework다.
* SQL Mapping Framework는
SQL 실행에 필요한 정보를 포함하고 있는 객체
SQL 실행 결과를 저장하는 객체를 SQL과 매핑하면 해당 SQL을 실행시키고, 결과값을 반환하는 프레임워크다.
- ibatis의 후속 버전이다.
* ibatis와 많은 부분이 유사하다.
- mybatis의 주요 구성요소
Mapper 인터페이스
* 데이터베이스 엑세스 작업이 정의된 인터페이스다.
* 개발자가 작성한다.
* 예시
public interface userMapper {
void insertUser(User user);
void updateUser(User user);
List<User>getAllUsers();
User getUserById(String userId);
User getUserByEmail(String email);
}
package com.sample.mapper;
public interface PostMapper {
void insertPost(Post post);
int getTotalRows(Map<String, Object> param);
List<PostListDto> getPosts(Map<String, Object>param);
PostDetailDto getPostDetailByNo(int postNo);
Post getPostByNo(int postNo);
void updatePost(Post post);
}
Mapper 파일
- SQL과 객체 매핑 정보가 정의된 XML 파일이다.
- Mapper 인터페이스의 전체이름을 namespace로 설정한다.
users.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sample.mapper.UserMapper">
<insert id="insertUser" parameterType="com.sample.vo.User">
insert into web_users (user_id, user_password, user_name, user_email)
values (#{id}, #{password}, #{name}, #{email})
</insert>
<select id="getUserById" parameterType="string" resultType="com.sample.vo.User">
select
user_id as id,
user_password as password,
user_name as name,
user_email as email,
user_enabled as enabled,
user_created_date as createdDate,
user_updated_date as updatedDate
from
web_users
where
user_id = #{value}
</select>
</mapper>
posts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sample.mapper.PostMapper">
<select id="getTotalRow" parameterType="map" resultType="int">
select
count(*)
from
web_posts
where
post_disabled = 'N'
<if test="opt != null and keyword != null">
<choose>
<when test="opt == 'title'">
and post_title like '%' || #{keyword} || '%'
</when>
<when test="opt == 'writer'">
and post_user_id in (select user_id from web_users where user_name = #{keyword})
</when>
<when test="opt == 'content'">
and post_content like '%' || #{keyword} || '%'
</when>
</choose>
</if>
</select>
</mapper>
ibatis와 mybatis
- parameterClass -> parameterType, resultClass -> resultType으로 변경되었다. - #파라미터명# -> #{파라미터명}로 변경되었다. - dynamic 쿼리에서 <if>, <choose> ~ <when> ~ <otherwise>, <foreach> 태그를 사용해서 조건식처리, 반복처리가 가능해졌다. - dynamic 쿼리에서 <where>, <set> 태그를 사용해서 불필요한 and와 ,를 제거할 수 있게 되었다.
Mapper 인스턴스
- Mapper 인터페이스를 구현한 객체다.
- 개발자가 구현하지 않는다.
- mybatis와 mybatis-spring 라이브러리에서 Mapper인터페이스를 구현한 객체를 자동으로 생성하고, 스프링 컨테이너의 빈으로 등록시킨다.
public interface PostMapper {
void insertPost(Post post);
int getTotalRows(Map<String, Object> param);
List<PostListDto> getPosts(Map<String,Object> param);
PostDetailDto getPostDetailByNo(int postNo);
Post getPostbyNo(int postNo);
void updatePost(Post post);
}
Mapper 인스턴스
public class PostmapperInstance implements PostMapper {
@Autowired
private SqlSessionFactory sqlSessionFactory;
public void insertPost(Post post) {
SqlSession session = sqlSessionFactory.openSession();
session.insert("com.sample.mapper.PostMapper.insertPost", post);
}
public int getTotalrow(Map<String, Object> param) {
SqlSession session = SqlSessionFactroy.openSession();
return session.selectOne("com.sample.mapper.Postmapper.getTotalRows", param);
session.close();
}
}
<T> T selectOne(String id, Object parameter)
결과값이 하나 조회되는 SELECT 문을 실행시키고, 값을 반환한다.
<E> List<E> selectList(String ind, Object parameter)
결과값이 여러 개 조회되는 SELECT 문을 실행히시키고, 값을 반환한다.
int insert(String ind, Object parameter)
INSERT문을 실행시키고, 추가된 행의 개수를 반환한다.
int update(String ind, Object parameter)
UPDATE문을 실행시키고, 변경된 행의 개수를 반환한다.
int delete(String ind, Object parameter)
DELETE문을 실행시키고, 삭제된 행의 개수를 반환한다.
void commit()
INSERT, UPDATE, DELETE 문의 실행결과를 테이블에 영구적으로 반영시킨다.
void rollback()
INSERT, UPDATE, DELETE 문의 실행의 테이블 반영을 취소시킨다.
void close()
SqlSession 객체를 폐기시킨다.
<T> T getMapper(Class<T> type)
type에서 지정한 Mapper 인터페이스를 구현한 Mapper 인스턴스를 반환한다.
예시)
Mapper 인터페이스 정의
public interface UserMapper {
void insertUser(User user)
}
Mapper 파일 정의
<mapper namespace="com.smple.mapper.UserMapper">
<insert id="insertUser" parameterType="com.sample.vo.User">
insert into web_users (user_id, user_password, user_name, user_email)
</insert>
</mapper>
Mapeer 인스턴스 획득하기
SqlSession session = sqlSessionFactory.openSession();
Usermapper instance = session.getMapper(UserMapper.class);
* instance 변수에는 UserMapper 인터페이스를 구현한 객체가 대입된다.
*mybatis-spring 라이브러리에는 모든 Mapper인터페이스를 스캔해서 Mapper 인스턴스를 생성하고, 스프링 컨테이너의 빈으로 등록시킨다.
mybatis 매퍼파일의 where 태그
================================================================== = <where> 태그를 사용하지 않는 경우 ================================================================== select * from web_products where <if test="deleted != null"> product_deleted = #{deleted} </if> <if test="minPrice > 0"> and product_price >= #{minPrice} </if> <if test="maxPrice > 0"> and product_price <= #{maxPrice} </if> <if test="categoryNo != null"> and product_category_no = #{categoryNo} </if> -------------------------------------------------------- Map<String, Object> param = new HashMap<>(); param.put("deleted", 'N'); param.put("minPrice", 10000); select * from web_products where product_deleted = #{deleted} and product_price >= #{minPrice} -------------------------------------------------------- Map<String, Object> param = new HashMap<>(); select * from web_products where -- 문법 오류가 발생한다. -------------------------------------------------------- Map<String, Object> param = new HashMap<>(); param.put("minPrice", 10000); param.put("maxPrice", 50000); select * from web_products where and product_price >= #{minPrice} -- 문법 오류가 발생한다. and product_price <= #{maxPrice} -------------------------------------------------------- ================================================================== = <where> 태그를 사용하는 경우 ================================================================== select * from web_products <where> <if test="deleted != null"> product_deleted = #{deleted} </if> <if test="minPrice > 0"> and product_price >= #{minPrice} </if> <if test="maxPrice > 0"> and product_price <= #{maxPrice} </if> <if test="categoryNo != null"> and product_category_no = #{categoryNo} </if> </where> -------------------------------------------------------- Map<String, Object> param = new HashMap<>(); param.put("deleted", 'N'); param.put("minPrice", 10000); select * from web_products where product_deleted = #{deleted} and product_price >= #{minPrice} * <where>과 </where>사이에 동적으로 추가되는 조건이 하나라도 있으면 where 키워드가 추가된다. -------------------------------------------------------- Map<String, Object> param = new HashMap<>(); select * from web_products * <where>과 </where>사이에 동적으로 추가되는 조건이 하나도 없으면 where 키워드는 추가되지 않는다. -------------------------------------------------------- Map<String, Object> param = new HashMap<>(); param.put("minPrice", 10000); param.put("maxPrice", 50000); select * from web_products where product_price >= #{minPrice} and product_price <= #{maxPrice} * <where>과 </where>사이에 동적으로 추가되는 조건이 있고, 첫번째 조건에 and 키워드가 있으면, and를 제거한다. --------------------------------------------------------