Dev/Spring

Spring @MVC, Data Access, MyBatis

OK-가자 2021. 10. 15. 11:18

Spring @MVC DATA ACCESS

1. datasource (Connection Pool)

  • JDBC를 통해 DB를 사용하려면, Connection 타입의 DB 연결 객체가 필요하다.
  • 엔터프라이즈 환경에서는 각 요청마다 Connection을 새롭게 만들고 종료시킨다.
  • 애플리케이션과 DB사이의 실제 커넥션을 매번 새롭게 만드는 것은 비효율적이고 성능저하
  • 풀링(pooling) 기법 사용 정해진 개수의 DB Connection Pool에 준비하고 애플리케이션 요청때 마다 꺼내서 할당 하고 돌려받아 pool에 저장.
  • Spring에서는 DataSource를 하나의 독립된 빈으로 등록하도록 강력하게 권장.
  • 엔터프라이즈 시스템에서는 반드시 DB 연결 풀 기능을 지원하는 DataSource를 사용해야 한다.
  • 종류
      스프링에서 제공 : 
              SimpleDriverDataSource, SingleConnectionDataSource
              학습/테스트용.  실제 서비스에서 사용하지 말 것
      아파치 Common DBCP : 
              가장 유명한 오픈소스  DB 커넥션 풀 라이브러리
              http://commons.apache.org/dbcp
      상용 DB 커넥션 풀 :
               스프링 빈으로 등록 가능하고 프로퍼티를 통해 설정이 가능하다면 어떤 것이던지
               사용하면 된다.

[ 실습 ]

  1. 라이브러리 추가 (pom.xml)
<!-- spring jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
  1. Oracle DataSource를 bean으로 등록한다.
<!-- oracle datasource -->
    <bean id="oracleDatasource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="URL" value="jdbc:oracle:thin:@localhost:1521:xe" />
        <property name="user" value="webdb" />
        <property name="password" value="webdb" />
        <property name="connectionCachingEnabled" value="true" />
        <qualifier value="main-db" />
    </bean>
  1. Common DBCP의 DataSource를 bean으로 등록한다. (MySQL인 경우)
<!-- Common DBCP -->
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
<!-- Connection Pool DataSource-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/webdb" />
    <property name="username" value="webdb" />
    <property name="password" value="webdb" />
</bean>

[ 실습 ]
4. 현재 Dao 객체에 datasource 빈을 주입하고 getConnection() 메서드를 대체하고 테스트 합니다.

2. DAO Pattern

  • 데이터 액세스 계층은 DAO 패턴이라 불리는 방식으로 분리하는 것이 원칙.
  • 비즈니스가 단순하거나 없으면, DAO를 서비스 계층과 통합할 수 있다.
  • 데이터 액세스 기술을 외부로 노출시키지 않는다.
  • datasource를 주입(DI)받고를 해야 하고 메서드는 add(), update()등, 단순하고 CRUD에 따르는 일반적인 이름을 사용하도록 한다.

3. 예외처리

데이터 액세스 중에 발생하는 예외는 복구할 수 없다.

  • DAO 외부로 던지는 예외는 런타임 예외(RuntimeException)여야 한다.
  • SQLException은 서비스계층에서 직접 다뤄야 할 이유가 없으므로 RuntimeException 으로 전환해야 한다.
  • 그러나 받아야 하는 경우가 있다면, 의미를 갖는 예외로 전환한다.

4. 템플릿과 API

  • 데이터 액세스 기술을 사용하는 코드는 try/catch/finally 와 반복되는 코드로 작성되는 경우가 많다.

  • 데이터 액세스 기술은 외부의 리소스와 연동을 통해서 이루어 지기 때문에 다양한 예외 상황이 발생할 경우가 많고 예외상황을 종료하고 리소스를 반환하기 위한 코드가 길고 복잡해 지는 경향이 있다. (가독성이 좋지 않다.)

  • 스프링에서는 DI의 응용 패턴인 템플릿/콜백 패턴을 이용해 반복되는 판에 박힌 코드를 피하고 예외 변환과 트랜잭션 동기화를 위한 템플릿을 제공한다.

  • 해당 기술의 데이터 액세스 기술 API( MyBatis API, JDBC API)와 스프링 데이터 액세스 템플릿을 조합하여 사용한다.

5. Spring에서 MyBatis 사용하기

MYBatis3.X (소개)

  1. MyBatis2.x(IBatis)의 후속으로 등장한 ORM 프레임워크이다.
  2. XML를 이용한 SQL과 ORM 을 지원한다.
  3. 본격적인 ORM인 JPA나 Hibernate 처럼 새로운 DB 프로그래밍 패러다임을 이해해야 하는 것은 아니다. ( MyBatis3.x에서는 Mapper 인터페이스를 통해 지원 )
  4. 이미 익숙한 SQL를 그대로 사용하고 JDBC코드의 불편함을 제거
  5. 가장 큰 특징은 SQL을 자바코드에서 분리해서 별도의 XML 파일 안에 작성하고 관리할 수 있는 것이다.
  6. 스프링 3.0부터는 MyBatis3.x(iBatis2.x) 버전에 스프링 데이터 액세스 기술 대부분을 지원한다.( DataSource Bean 사용, 스프링 트랜잭션, 예외 자동변환, 템플릿 )

MYBatis3.X (스프링에서 사용하기)

  1. MyBatis의 DAO는 SQLSession 인터페이스를 구현한 클래스의 객체를 DI받아 사용한다.
  2. MyBatis의 DAO는 SQLSessionDaoSupport 추상 클래스를 상속받아 구현하기도 한다.
  3. 그리고 Mapper 인터페이스를 통한 OR 매핑 기능을 지원한다.
  4. 이 중에 SQLSession 인터페이스를 구현한 클래스의 객체의 DI 방식을 주로 사용하게 된다.
    SQLSession 인터페이스를 구현한 SQLSessionTemplate 클래스를 사용한다.

MYBatis3.X ( 설정하기 – 라이브러리 추가 )

  1. 라이브러리 추가( pom.xml )
<!-- MyBatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.2.2</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.2.0</version>
</dependency>

MyBatis3.X (설정하기 – Bean 설정 )

  1. SqlSessionFactoryBean 설정 ( applicationContext.xml )
<!-- MyBatis SqlSessionFactoryBean --> 
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 
    <property name="dataSource" ref="oracleDatasource" /> 
    <property name="configLocation" value="classpath:mybatis/configuration.xml" /> 
</bean>
  1. SqlSessionTemplete 설정 ( applicationContext.xml )
<!-- MyBatis SqlSessionTemplate --> 
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
  1. DAO 에서는 SqlSessionTemplate를 DI 한다.
public class EmailListDao {
    @Autowired
    private SqlSession sqlSession;

    . . .
}

MyBatis3.X (설정하기 – 설정 파일과 매핑파일 )

  1. MyBatis 설정 파일( configuration.xml )
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        </typeAliases>    
    <mappers>
        <mapper resource="mybatis/mappers/emaillist.xml" />
    </mappers>
</configuration>
  1. sql 매핑 파일( emaillist.xml )
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="emaillist">
</mapper>

MyBatis3.X ( SQL 작성 )

select 하기

  1. 결과의 칼럼 이름과 resultType의 Class의 필드 명이 다른 경우
<resultMap type="com.bit2015.emaillist3.vo.EmailListVo" id="resultMapList">
    <result column="no" property="no"/>
    <result column="first_name" property="firstName"/>
    <result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
</resultMap>

<select id="list" resultMap="resultMapList">
    select no,
           first_name,
           last_name,
           email
            from email_list
        order by no desc
</select>
  1. 결과의 칼럼 이름과 resultType의 Class의 필드 명이 다른 경우( resultMap를 사용안함)
<select id="list2" resultType="com.bit2015.emaillist3.vo.EmailListVo">
    select no,
           first_name as firstName,
           last_name as lastName,
           email
            from email_list
        order by no desc
</select>
  1. resultType, parameterType의 이름은 configuration.xml alias를 사용해 짧게 줄인다.
<typeAliases>
    <typeAlias alias="EmailListVo" type="com.bit2015.emaillist3.vo.EmailListVo" />
</typeAliases>

resultType 의 클래스가 존재하지 않을 경우 map를 사용한다.

<select id="joinlist" resultType="map">
    select  a.no, a.title, a.reg_date, b.no, b.name, b.email 
      from  board a,
            member b
     where  a.member_no = b.no
  order by    a.reg_date desc
</select>

Map으로 리턴 되고 칼럼 이름이 대문자로 Map의 Key가 된다.

파라미터 바인딩

  1. 객체를 사용한 여러 파라미터 바인딩
<insert id="insert" parameterType="EmailListVo">
   insert
     into email_list
   values ( email_list_no_seq.nextval, #{firstName }, #{lastName }, #{email } )
</insert>
  1. 파라미터가 하나인 바인딩
<select id="insert" parameterType="int">
   select no,
          name,
    message,
    to_char( reg_date, 'yyyy-MM-dd hh:mi:ss' ) as regDate
     from guestbook
    where no=#{no }
</select>

  • #{no }는 임의 지정해도 상관없다.
  • int 는 내장된 alias 이다. (byte, short, long, int, integer, double, float, boolean, string)

파라미터 바인딩

파라미터 클래스가 존재하지 않고 여러 값을 파라미터로 넘겨야 하는 경우

<select id="getId" parameterType="map" resultType=“string">
      select id from member where name=#name# and ssn=#ssn#
</select>

-----------------------------------------------------------------------------

jMap map = new HashMap();
map.put("name", "홍길동");
map.put("ssn", "1234561234567");

String id2 = (String)sqlSession.selectOne( "getId", map );

Insert 후, 새로들어 간 row의 Primary Key를 받아야 하는 경우


<insert id="insert" parameterType="GuestbookVo">
    <selectKey keyProperty="no" resultType="long" order="BEFORE">
        select guestbook_seq.nextval from dual
    </selectKey>
    <![CDATA[    
    insert
            into guestbook
          values ( #{no }, #{name }, #{password }, #{message }, SYSDATE )
        ]]>
</insert>