[ KOSTA 교육 41일차 ] Spring DAO 복습 | JdbcTemplate, MyBatis, Hibernate, JPA | DataSource | Controller, Service, DAO의 역할 | MyBatis - SqlSessionFactoryBean, TransactionManager

junjun·2024년 6월 25일
0

KOSTA

목록 보기
39/48

Spring DAO

  • Spring DAO
    • Spring 의 여러 모듈 중 하나입니다.
    • Persistence Layer에서 지원해주는 기능을 모두 지닙니다.
    • ORM ( MyBatis, Hibernate, JPA )과의 연동을 지원합니다.

여러 DB 접근 기술

JdbcTemplate

  • spring-jdbc 모듈에 포함되어 있습니다.
  • 스프링에서 제공하는 JDBC를 좀 더 쉽고 간편하게 사용하게 만들어주는 클래스입니다.
  • JDBC를 직접 사용할 때와 대비해, 트랜잭션 관리 및 리소스 관리를 자동으로 해줍니다.
  • 사용하기 위해서 DataSource 를 의존성으로 주입해주어야 합니다.
  • 템플릿 콜백 메서드 패턴을 사용하여 JDBC를 직접 사용할 때 발생하는 대부분의 반복작업을 대신 처리해줍니다.
  • 커넥션 획득 / Statement를 준비하고 실행 / 결과를 반복하도록 루프를 실행 / 커넥션 종료, Statement, ResultSet 종료 / 트랜잭션을 다루기 위한 커넥션 동기화 / 예외 발생 시 스프링 예외 변환기 실행 등의 작업을 해줍니다.

MyBatis ( 구. iBatis )

  • Java 개발자들이 DB를 더 쉽게 다룰 수 있도록 도와주는 오픈소스 ORM(Obect-Relational Mapping) 프레임워크

    • iBatis 오픈 소스 -> 구글 인수 -> MyBatis
  • DB Query와 프로그래밍 언어 코드를 분리하여 유지보수성과 생산성을 높여줍니다.

  • cf. Hibernate : Java Persistence API의 스펙을 구현체로, 직접 사용 시 MyBatis와 비교해서 설정이 바뀌는 것 외에 큰 차이가 없습니다. ( = 물론 객체를 다룸으로써 SQL을 자동생성해주는 것에서 차이가 있습니다. )

MyBatis 주요 장점

  • 유연성 : SQL 쿼리를 직접 작성 가능하여 유연하고, 동적 쿼리를 작성할 수 있습니다.

  • 간결성 & 유지보수성 : MyBatis는 SQL 쿼리와 프로그래밍 언어 코드를 분리하기에, 코드가 간결해지고 SQL변경 시 Mapper XML 파일만 변경해주면 되므로 유지보수에 유리합니다.

  • 성능 : MyBatis는 캐시 기능을 제공하며, 이를 통해 DB 연산 속도를 높일 수 있습니다.

  • 다양한 DB 지원 : 다양한 DB에 대한 연결을 지원합니다.

대표 장점 : 동적 쿼리
  • 동적 쿼리는, 어플리케이션 런타임에 조건에 따라 SQL 쿼리를 동적으로 생성하는 것입니다.

  • DB의 검색 조건이나 결괏값 등이 동적으로 변화할 때 유용하게 사용됩니다.

  • MyBatis에서는 동적 쿼리를 작성하기 위해 <if>, <choose>, <when>, <otherwise>, <foreach> 등의 태그를 사용할 수 있습니다.

  • 아래와 같이 SQL을 생성 시, 프로그래밍 코드 처럼 조건문/반복문을 적용하여 동적으로 필요한 SQL을 생성하여 적용할 수 있습니다.

<!-- select 문 동적쿼리 (조건문) -->
<select id="findActiveBlogLike" parameterType="Blog" resultType="Blog">
  SELECT 
  	* 
  FROM 
  	BLOG 
  WHERE
  	state = 'ACTIVE'
  <choose>
      <when test="title != null">
        AND title like #{title}
      </when>
      <when test="author != null and author.name != null">
        AND author_name like #{author.name}
      </when>
      <otherwise>
        AND featured = 1
      </otherwise>
 </choose>
</select>

<!-- select 문 동적쿼리 (반복문) -->
<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM	POST P
  WHERE ID IN
  <foreach item="item" index="index" collection="list"
           open="(" separator="," close=")">
   		#{item}
  </foreach>
</select>

<!-- update 문 동적쿼리 (조건문) -->
<update id="updateAuthorIfNecessary" parameterType="domain.blog.Author">
  	update userinfo
  		<set>
          <if test="username != null">username=#{username},</if>
          <if test="password != null">password=#{password},</if>
  		</set>
  where id = #{id}
</update>

MyBatis 사용해보기

1. MyBatis를 사용하기 위한 Dependencies 설정

1) spring-core

  • Spring Framework의 근본/핵심 부분으로 DI를 지원하는 기능을 담당합니다.

  • Core Container 안에 Beans, Core, Context, Expression Language 모듈이 있습니다.

  • Core는 DI, IoC를 담당하는 스프링 내 서브 프레임워크입니다.

  • Spring Beans가 이를 구현하여 DI, IoC 기능을 구현합니다.

  • Spring Context는 Core와 Beans를 기반으로 ㅎ여, 정의된 객체에 엑세스할 수 있는 매개물을 제공합니다. Context의 핵심 인터페이스는 ApplicationContext입니다.

  • Spring SpEL을 통해 간단한 문법을 통해 앱에 필요한 데이터나 설정 값을 가져올 수 있습니다.

2) spring-jdbc

  • Spring Framework에서 제공하는 JDBC 모듈

      1. DataSource 설정 기능
      1. SQL 실행
      1. 예외처리 -> JDBC 예외를 처리해서 더 많은 예외정보 제공
      1. ORM 프레임워크와 연동 기능 제공 ( DB Table과 자바 객체 매핑 가능 )
  • Java EE가 제공하는 JDBC 자체를 사용하는 것보다 좀 더 편리하게 사용 가능 ( ex. jdbcTempalte )

3) mybatis

  • MyBatis 자체의 의존성

4) mybatis-spring

  • MyBatis와 Spring을 연결하기 위한 의존성

2. 데이터베이스 설정

  • MyBatis는 JDBC를 통해 DB 연결을 진행합니다.

  • 이를 위한 DB 연결 정보를 설정하고, 해당 설정은 DataSource라는 커넥션 풀 제공 클래스에서 진행합니다.

  • 이러한 DataSource를 스프링 빈으로 설정하기 위해, servlet-context.xml 에 다음과 같은 설정을 추가합니다.

<!-- servlet-context.xml -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
  <property name="username" value="it"/>
  <property name="password" value="0000"/>
</bean>

3. MyBatis 자체의 설정 => 이를 Spring에서 진행해줄 수도 있습니다. ( mybatis-context.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>

  	<!-- db.properties -->
	<properties resource="oracle.properties" />
	
  	<!-- DBCP -->
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />  <!-- MANAGED -->
			<dataSource type="POOLED">
				<property name="driver"   value="${lec.driver}" />
				<property name="url"      value="${lec.url}" />
				<property name="username" value="${lec.username}" />
				<property name="password" value="${lec.userpw}" />
			</dataSource>
		</environment>
	</environments>
	
	<!-- mapper -->
	<mappers>
		<mapper resource="board-map-lec08.xml" />		
	</mappers>
  		
	<!-- TypeAliases -->
	<typeAliases>
		<typeAlias alias="userVO" type="com.lec08.dao.BoardVO"  />
	</typeAliases>
  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource"/>
      <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory"/>
    </bean>
</configuration>  
  • 위의 설정을 해석하면 다음과 같습니다.

  • `

    • classpath에 존재하는 oracle.properties 라는 이름의 프로퍼티 파일을 참조합니다.
  • <typeAlias alias="userVO" type="com.lec08.dao.BoardVO"/>

    • BoardVO 라는 클래스 타입을 userVO라는 이름으로 별칭을 준다.
  • <environments... <transactionManager type="JDBC" />

    • MyBatis 에서 사용할 트랜잭션 매니저 설정

4. MyBatis 매퍼 작성 및 등록

4-1. MyBatis Mapper ( Java )

  • MyBatis Mapper는 DB 쿼리 <-> 자바 메서드를 매핑하는 역할

  • 데이터베이스에 접근하기 위한 SQL 쿼리를 작성하고, 이를 실행하는 자바 메서드 정의

  • XML의 SQL쿼리와 자바 메서드를 연결할 자바 인터페이스 = Mapper

@Mapper // 등록
// 작성
public interface UserMapper {
	User getUserById(int id);
    void insertUser(User user);
    void updateUser(User user);
    void deleteUser(int id);
}

4-2. MyBatis Mapper ( XML )

  • Mapper 속 메서드 별로 SQL 쿼리문을 작성해줍니다.

  • 메서드 이름 = XML에서 SQL의 id 와 같습니다.

<!-- user-mapper.xml -->

<mapper namespace="com.example.mapper.UserMapper">
  	<select id="getUserById" parameterType="int" resultType="com.example.model.User">
      	SELECT * FROM users WHERE id = #{id}
  </select>
  
  <insert id="insertUser" parameterType="com.example.model.User">
    INSERT INTO users (name, email) VALUES (#{name}, #{email})
  </insert>
  
  <!-- 다른 메서드들에 대한 쿼리도 추가 가능 -->
</mapper>

5. Mapper 인터페이스 (정확히는 이 인터페이스의 구현체) 를 스프링 Bean으로 등록하고 다른 빈 ( ex. @Service ) 주입받아 사용합니다.

@Service
public class UserService {
	private final UserMApper userMapper;
    
    public UserService(UserMapper userMapper){
    	this.userMapper = userMapper;
    }
    
    // User Find
    public User getUserById(int id){
    	return userMApper.getUserById(id);
    }
    
    // User Registration
    public void insertUser(User user){
    	userMapper.insertUser(user);
    }
}

DataSource

  • 현재 수업에서 Connection Pool을 Apache Commons Pool, 즉 Tomcat이 제공해주도록 했는데 이 커넥션 풀을 관리해줄 대상이 필요합니다.
    • 어제 수업에서 JNDI를 통해 순수 자바 코드로 Connection Pool을 사용하려 했지만 실패했습니다.
    • Tomcat의 context.xml에 자원을 등록하고, 우리 웹 어플리케이션의 web.xml에서 ( = Spring 사용 전, Java EE 기술만 사용 ) Servlet을 통해 리소스를 당겨서 쓸 수 있었습니다.
      • WAS에 DataSource를 등록할 때, Tomcat이 바라볼 수 있는 webapp/WEB-INF 경로 안에 context.xml에 해당 DBCP 설정을 추가해주었습니다.
      • cf. Java App 자체의 자원을 관리하는 classpath는 /src/main/resources 입니다.
      • EL 표현으로 프로퍼티를 등록하여, DataSource를 가져올 수 있었습니다.
    • Spring을 사용하기에 해당 커넥션 풀에 대한 설정은 DispatcherServlet에게 설정을 넘겨주는, servlet-context.xml 에 설정해주면 되었습니다.
  • App은 커넥션 풀을 직접 사용하는 것이 아닌, DataSource를 거쳐서 사용할 수 있게 됩니다. ( getConnection() )

  • JDBC 커넥션을 얻는 방법은 여러가지가 있습니다.

      1. JDBC DriverManager를 통해 신규 커넥션 생성 ( = DB_URL, DB_ID, DB_PW 입력 )
      1. DataSource ( ex. OracleDataSource, HikariCP )등의 커넥션 풀을 통해 커넥션을 획득해도 됨.
  • 기존의 App은 JDBC의 추상에 의존하여 커넥션을 가져왔지만 ( JDBC DriverManager ), 커넥션 풀이라는 강력한 기술을 사용하기 위해 Java는 javax.sql.DataSource 라는 추상화된 데이터 소스 인터페이스를 제공한다.

  • 개발자는 DBCP2 커넥션 풀 / HikariCP 커넥션 풀 등의 구체 클래스에 의존하지 않고, DataSource 인터페이스에만 의존하도록 App 로직을 짜면 된다.

( 단, DriverManager로 새로운 커넥션을 생성하는 코드는 DataSource를 사용하지 않는다. 위의 그림의 세번째의 그림은 좀 잘못된 그림 같다 )

DataSource, TransactionManager

  • DataSource를 통해 DataBase Connection Pool에 접근 가능합니다. 이 때, 해당 DataSourceTransactionManager가 관리해줍니다.

SQLSession, SqlSessionFactoryBean & SqlSessionTemplate

  • MyBatis의 가장 핵심적인 객체입니다.

  • SQLSessionFactory는 SQLSession을 만들어냅니다.

  • 개발에서 SQLSession을 통해 DB Connection을 생성하고, 원하는 SQL을 전달하고 결과를 리턴받습니다.

<!-- SQLSessionFactory -->
<bean id="sqlSessionFactory"
      class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"></property>
</bean>
  • mybatis-spring 라이브러리 클래스의 SqlSessionFactoryBean을 통해 SQLSessionFactory를 스프링 빈으로 등록합니다.
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception{
	SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
    sqlSessionFactory.setDataSource(dataSource());
    return (SqlSessionFactory) sqlSessionFactory.getObject();
}

  • 자바 코드 방식으로 등록 ( @Configuration + @Bean )

CLOB, BLOB

  • CLOB : DB에 Character형태의 문자열 집합 저장, 각 컬럼당 4GB 차지

  • BLOB : DB에 Binary형태의 데이터 집합 저장, 각 컬럼당 4GB 차지

  • 게시글 작성 같은 경우, 게시글 데이터가 긴 경우 CONTENT1, CONTENT2, CONTENT3.. 이렇게 나눔

    • 데이터가 너무 많을 경우 FE 단에서 ALERT 창 띄워서 그만 쓰라 해야한다.
-----------------------------------------------------------------
 스프링 DAO(Data Access Object) 
	- 데이터 액세스 계층의 코드 작성을 단순화하고 일관된 데이터 접근 방법을 제공
	- JDBC, ORM(Hibernate, Mybatis, ...), JPA 등의 다양한 데이터 접근 기술  제공
	- JdbcTemplate(단순) 제공
	- ORM(Object Relation Mapping) : 자바객체-테이블 매핑
	- JPA(Java Persistence API) : ORM을 위한 표준 인터페이스
-----------------------------------------------------------------


---------------------------------
DBCP(DB Connection Pool)
---------------------------------
데이터베이스와 연결된 커넥션을 미리 만들어 Pool속에 저장
필요할 때 풀에서 커넥션을 가져다 사용
다 쓴 후에 다시 풀에 반환


---------------------------------
pom.xml에  <repositories> 추가
---------------------------------
	<properties>
		~~~ 생략 ~~~ 
	</properties>
	<repositories>
	    <repository>
	        <id>oracle-repo</id>
	        <url>https://maven.oracle.com</url>
	        <releases>
	            <enabled>true</enabled>
	        </releases>
	    </repository>
	</repositories>
	<dependencies>
		~~~ 생략 ~~~
	</dependencies>


---------------------------------
pom.xml에  <dependency> 추가
---------------------------------
		<!--  DBCP -->
	    <!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
		<dependency>
		    <groupId>commons-dbcp</groupId>
		    <artifactId>commons-dbcp</artifactId>
		    <version>1.4</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/commons-pool/commons-pool -->
		<dependency>
		    <groupId>commons-pool</groupId>
		    <artifactId>commons-pool</artifactId>
		    <version>1.6</version>
		</dependency>
		
	    
	    <!-- mapper 관련 -->	
		<dependency>
		    <groupId>javax.annotation</groupId>
		    <artifactId>javax.annotation-api</artifactId>
		    <version>1.3.2</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.6</version>
		</dependency>
		<!--  Mybatis-spring  -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.3.0</version>
		</dependency>
		
		<!-- Spring -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-ibatis</artifactId>
		    <version>2.0.8</version>
		    <scope>provided</scope>
		</dependency>
		<dependency>
		    <groupId>org.apache.ibatis</groupId>
		    <artifactId>ibatis-sqlmap</artifactId>
		    <version>2.3.4.726</version>
		</dependency>
    	
    	

------------------------------------------------
1. DataSource 설정 방법 :: Java/Web 프로젝트인 경우
 - /webapp/WEB-INF/web.xml
 - tomcat/conf/context.xml
------------------------------------------------
tomcat/conf/context.xml
	<!-- 데이터 리소스 설정 -->  
    <Resource name="MY_tomcat_ds"
              auth="Container"
              type="javax.sql.DataSource"
              maxTotal="30"
              maxIdle="5"
              maxWaitMillis="10000"
              driverClassName="oracle.jdbc.driver.OracleDriver"
              url="jdbc:oracle:thin:@127.0.0.1:1521:XE"
              username="it"
              password="0000"/>
              
   /webapp/WEB-INF/web.xml
   <!-- 데이터 리소스 설정 이름 "MY_tomcat_ds"이 일치해야 함 -->  
		:: context.xml				<Resource name="MY_tomcat_ds"> 
		:: web.xml					<res-ref-name>MY_tomcat_ds</res-ref-name> 		
	<resource-ref>
	    <description>My DataSource TEST</description>
	    <res-ref-name>MY_tomcat_ds</res-ref-name>
	    <res-type>javax.sql.DataSource</res-type>
	    <res-auth>Container</res-auth>
	</resource-ref>
    

------------------------------------------------ ***** 해당 방식 사용
2. DataSource 설정 방법 :: Spring 프로젝트인 경우  
 - /webapp/WEB-INF/web.xml
 - /webapp/WEB-INF/spring/lec08-servlet-context.xml	
 - /src/main/resources/oracle.properties
------------------------------------------------

	oracle.properties 파일 설정 정보
	
	lec.driver=oracle.jdbc.driver.OracleDriver
	lec.url=jdbc:oracle:thin:@localhost:1521:XE
	lec.username=it
	lec.userpw=0000

	
	lec08-servlet-context.xml	
	<!-- datasource : 프로퍼티 파일을 사용한 형태 -->
	<context:property-placeholder location="classpath:oracle.properties" />
	<bean id="MY_tomcat_ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
	    <property name="driverClassName" 	value="${lec.driver}" />
	    <property name="url" 				value="${lec.url}" />
	    <property name="username" 			value="${lec.username}" />
	    <property name="password" 			value="${lec.userpw}" /> 
	</bean>

	<!-- datasource : 하드코딩한 형태
	<bean id="MY_tomcat_ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
	    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
	    <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
	    <property name="username" value="it" />
	    <property name="password" value="0000" /> 
	</bean> -->
		
	
	
-----------------------------------------------------------------
 실행 
----------------------------------------------------------------- 		
CtxCallTest.java 
http:localhost:{port}/{context_path}/board_list
	
	
	

#################################################################################

----------------------------------------------------------
*** JdbcTemplate
----------------------------------------------------------
public Post updatePost(Long id, Post updateParam) {
	String sql = "UPDATE post SET title=?, content=?, MODIFIED_DATE=? where id=?";
	template.update(sql, 
    	updateParam.getTitle(), 
        updateParam.getContent(), 
        updateParam.getModifiedDate(),
        id);
}
----------------------------------------------------------
*** JPA
----------------------------------------------------------
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
	//slect * from user where uname = ?
    List<User> findByName(String name);
}

----------------------------------------------------------
*** ORM (Mybatis)
----------------------------------------------------------
package com.lec09.orm;
public class MemberVO {
    private int id;
    private String name;
    private String email;
    private Date regdate;
    // getters and setters
}

package com.lec09.orm.mapper;
public interface MemberMapper {
    void myMethodName__insert(MemberVO mvo);
}
 
'MemberMapper.xml' 
<mapper namespace="com.lec09.mapper.MemberMapper">
    <insert id="myMethodName__insert" parameterType="com.lec09.orm.MemberVO">
        INSERT INTO member (name, email) VALUES (#{name}, #{email})
    </insert>
</mapper>
 
@Service
public class MemberServiceImpl {
	@Autowired
    private  MemberMapper memberMapper;

    public void addMember(MemberVO prmVO) {
        memberMapper.myMethodName__insert(prmVO);
    }
}

----------------------------------------------------------
*** ORM (Hibernate)
----------------------------------------------------------
@Data
@Table(name = "Member")
@Entity
public class Board implements java.io.Serializable {

    @Id
    @Column(name = "seq")
    @GenenratedValue(strategy = GenerationType.AUTO)
    private int seq;

    @Column(name = "title", nullable = false)
    private String title;

    public Board() {}

    public Board(String title) {
        this.title = title;
    }
}

hibernate-board-mapper.xml

@Temporal(TemporalType.TIME)
private Date playTime;

@Temporal(TemporalType.DATE)
private Date added;


<hibernate-mapping>
    <class name="com.lec09.orm.BoardVO" table="board">
        <meta attribute="class-description">
            Represents a single playable track in the music database.
            @author Jum Elliott (with help from Hibernate)
        </meta>

        <id name="seq" type="int" column="seq">
            <meta attribute="scope-set">protected</meta>
            <generataor class="native">
        </id>

        <property name="title" type="string" not-null="true"/>
    </class>
</hibernate-mapping>

public class ServiceImpl {
	SessionFactory sessionFactory = HibernateUtil5.getSessionFactory();
	SessionFactory session = sessionFactory.openSession();
	Transaction tx = null;
		
	public void svcMethodd() {
		try {
		    tx = session.beginTransaction();	
		    // 쿼리 작성
		    tx.commit();
		} catch (RuntimeException e) {
		    if (tx != null) tx.rollback(); 
		} finally {
		    session.close();
		}
		sessionFactory.close();
	}
}
----------------------------------------------------------

0개의 댓글