Day 84

ChangWoo·2023년 1월 2일
0

중앙 HTA

목록 보기
28/51

Spring

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 연동하기

* spring-jdbc는 데이터베이스 엑세스를 지원하는 JdbcTemplate 객체를 제공한다.
* JdbcTemplate는 spring-jdbc의 핵심 객체다.
* JdbcTemplate는 INSERT, UPDATE, DELETE, SELECT를 실행하는 메소드를 제공한다.
* 사용자 정의 DAO 클래스는 JdbcTemplate 객체를 의존성 주입받아서 데이터베이스 엑세스 작업을 수행한다.

1. 필요한 라이브러리 의존성 추가 (pom.xml)

	* 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>

2. 스프링 빈 설정 파일에 Connection Pool 객체 설정하기 (context.xml)

	 <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>

3. spring-jdbc 라이브러리가 지원하는 JdbcTemplate 객체를 스프링 컨테이너에 등록하기

	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="connectionPool"></property>
	</bean>

4. DAO 클래스와 Service 클래스를 스캔에서 스프링 컨테이너가 객체 생성하고, 의존하는 객체를 자동 의존성 주입시키기

	<context:annotation-config />
	<context:component-scan base-package="com.sample" />

5. 데이터베이스 엑세스 작업을 구현하는 DAO에 JdbcTemplate 객체를 의존성 주입받을 수 있도록 구현하기

	@Repository
	public class UserDao {
		@Autowired
		private JdbcTemplate template;

	}		

6. 핵심 업무로직을 구현하는 Service에 DAO 객체를 의존성 주입받을 수 있도록 구현하기

	@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) {
		
	}
	
}

  • 수동 의존성주입과 자동 의존성 주입을 혼용해서 사용하는 경우가 있다.
  • DB엑세스에 따라 UserDao 혹은 JdbcTemplate은 변경될 수 있다.
  • 그러나 핵심 업무로직이 구현되어 있는 Service는 절대절대 변경될 수 없다.

  • 객체의 주소값을 전달 받는다.
  • 전달받은 주소값 덕분에 UserDao 객체의 메소드를 호출할 수 있다.

MyBatis

- 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인터페이스를 구현한 객체를 자동으로 생성하고, 스프링 컨테이너의 빈으로 등록시킨다.
  • 예시
    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();
    }
 }
  • mybatis의 주요 API
    • 개발자가 직접 mybatis의 주요 API를 사용해서 코딩하는 경우는 절대 없다.
    • SqlSessionFactoryBuilder
      • mybaits 환경설정 파일을 읽어서 SqlSessionFactory 객체를 생성한다.
    • SqlSessionFactory
      • SqlSession 객체를 제공한다.
      • 주요 메소드
        SqlSession openSession() : 새로운 SqlSession 객체를 반환한다.
    • SqlSession
      - SQL을 실행하는 메소드를 제공하는 객체다.
      • SQL을 실행할 때마다 SqlSessionFactory의 openSession() 메소드를 실행해서 새로운 SqlSession 객체를 획득해서 사용하고, SQL 실행이 완료되면 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를 제거한다.
--------------------------------------------------------
profile
한 걸음 한 걸음 나아가는 개발자

0개의 댓글