🌿MyBatis
- 영속성 계층 (데이터베이스 조작 계층)
Mybatis는 자바 오브젝트와 SQL사이의 자동 매핑 기능을 지원하는 ORM(Object relational Mapping) 프레임워크이다.
MyBatis는 Spring 기술이 아닌 독립적인 기술이지만, Spring에서 도입을 해서 사용할 수 있도록 연동되어 있다.
MyBatis는 SQL을 별도의 파일을 분리해서 관리하게 하며, 응용프로그램과 데이터베이스를 연결하는 JDBC 같은 역할을 한다. 단, SQL을 그대로 사용할 수 있다는 점에서 JDBC을 사용할 때의 불편함을 해결했다.
JDBC에 대해서는 위 글을 참고한다.
Spring Lagacy Project
Spring Legacy라는 말은 없다. Spring Legacy Project가 아닌 Spring "Lagacy Project"이다.
Spring io에서도 개발자들의 Spring 초기 진입이 어렵다는 걸 알고 있었다. 그래서 Spring은 초반 세팅 과정을 생략하고 자동화하는 작업을 만들었는데, 이게 Spring Boot이다.
Spring을 시작할 때 도움을 준다는 뜻에서 Boot라는 게 붙었지만, Spring과 Spring Boot는 다르지 않다. 마찬가지로 Spring Lagacy를 사용한다면 Spring을 사용하는 것이다.
Lagacy는 뒤떨어지고 안 좋다는 의미가 아니다. Spring이 세팅하는 방법을 Boot 체제로 바꾸려고 하기 때문에 이전에 사용했다는 의미에서 붙인 것이다.
🌿Spring Mybatis
1. XML Mapper를 사용하는 방법
2. Interface Mapper를 사용하는 방식
Spring Mybatis는 스프링 프로젝트 중 하나이다.
둘 다 골고루 쓰이긴 하지만, XML이 보다 넓은 범위에서 쓰이고 있다.
프로젝트 설정
1. pom.xml
- JDK 11
- Spring 5.0.7.RELEASE
위 글을 참고하여 pom.xml을 수정하고, Spring을 설정한다.
2. pom.xml
- MyBatis에 의존성 추가
- ojdbc6.jar
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
pom.xml에 위 코드를 추가한다.
같은 MyBatis를 사용하더라도 jar파일이 다양하기 때문에 사람들마다 가져오는 dependency가 다르다.
MyBatis는 내부적으로 JDBC를 사용하기 때문에 ojdbc가 필요하다.
그런데 ojdbc는 Maven으로 설치할 수 없다. 이는 오라클이 Maven사에 왜 ojdbc를 Maven에서 설치할 수 있냐고 소송을 걸었는데 Maven이 졌기 때문이다.😭
그래서 ojdbc만은 이전 방식으로 BuildPath로 가져오거나 lib폴더에 넣어서 설치해야 한다.
WEB-INF 폴더에 lib 폴더를 만들어서 ojdbc6.jar 파일을 넣어주었다.
log4j 버전 업
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
위 코드를 아래 코드로 바꿔주었다.
junit 버전 업
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
위 코드를 아래 코드로 바꿔주었다.
Lombok
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
스프링 프레임워크
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
log4j
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
<version>1.16</version>
</dependency>
로그를 남기기 위해 log4j를 추가했다.
🍃pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>mybatis</artifactId>
<name>MyBatisTest</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>11</java-version>
<org.springframework-version>5.0.7.RELEASE</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
<version>1.16</version>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- @ResponseBody 사용 -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.0</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>11</source>
<target>11</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
root-context.xml
<!-- MyBatis -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations" value="classpath*:mapper/*.xml"></property>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sessionFactory"></constructor-arg>
</bean>
MyBatis의 연결 작업을 누가 담당하고 있는지 알려 주어야 한다.
아래에서 HikariCP를 사용하므로 이를 프로퍼티로 작성한다.
classpath*:는 소스 폴더를 가리키는 말이다. 그리고 mapper/*.xml는 mapper 폴더 안의 모든 xml파일을 의미한다.
mapper(xml)을 이용해서 DB 작업을 하기 때문에 경로 설정을 해준 것이다.
JDBC 연결 테스트
업무를 구현하려는 게 아니라 테스트를 위한 구현을 해보도록 하자.
Spring은 테스트를 위한 패키지가 따로 있다.
com.test.persistence 패키지에 JDBCTest.java 파일을 생성하였다.
package com.test.mybatis.persistence;
import static org.junit.Assert.assertNotNull;
import java.sql.Connection;
import java.sql.DriverManager;
import org.junit.Test;
import jdk.internal.org.jline.utils.Log;
import lombok.extern.log4j.Log4j;
@Log4j
public class JDBCTest {
@Test
public void testConnection() {
try {
Class.forName("oracle.jdbc.driver.OracleDriver"); //드라이버 로딩
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "hr", "java1234");
assertNotNull(conn);
System.out.println(conn.isClosed());
log.info(conn.isClosed()); // log
log.warn("경고 메시지");
log.error("에러 메시지");
} catch (Exception e) {
// TODO: handle exception
}
}
}
메서드 이름은 마음대로 해도 되지만, test로 시작해서 만드는 경우가 많다.
새로 만든 프로젝트가 오라클에 제대로 접속이 되었다는 것을 알 수 있다.
JUnit
일반 클래스에 메서드를 하나 만든 다음에 @Test를 붙인 게 JUnit이라는 기능이다.
메인 메서드가 없음에도 불구하고 @Test 어노테이션이 붙은 것을 실행해 준다.
Log4j
Log.info(conn.isClosed()); // log
Log.warn("경고 메시지");
Log.error("에러 메시지");
Log4j는 로그 메시지를 전용으로 출력한다.
🌿Connection 객체 생성
JDBC에서 DBUtil.open()을 하면 DB가 연결되고, Connection 객체 생성이 반복되었었다.
작업양이 많아지고 팀 작업이 되면 닫히지 않은 Connection이 차지하는 메모리 용량이 늘어나서 문제가 생길 수 있다. 즉, 필요 없이 연결이 지속되는 게 문제가 되는 것이다.
그런데 어느 시점에서 DB를 해제할지를 설정하는 것이나 DB를 해제하는 것이나 손이 많이 가는 작업이다.
이러한 해결책으로 커넥션 풀(Connection Pool)을 사용한다.
이 DB에 연결하면 관리되지 않는 Connection 객체가 메모리를 차지하고 성능을 저하하게 하므로 사용 완료된 Connection을 close()하는 과정이다.
🌿Connection Pool
- Commons DBCP
- Tomcat DBCP
- HikariCP: 스프링 부트 2.0부터 기본으로 사용한다.
DBCP는 DataBase Connection Pool를 의미한다.
Commons DBCP
- pom.xml에서 commons-dbcp 의존성 추가
가장 대중화된 공식 DBCP 중 하나이다.
pom.xml
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
pom.xml에 Commons DBCP를 추가한다.
root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Commons DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@Localhost:1521:xe"></property>
<property name="username" value="hr"></property>
<property name="password" value="java1234"></property>
</bean>
</beans>
이번에는 root-context.xml을 수정한다. 이때 만든 bean은 데이터의 커넥션을 관리하게 된다.
커넥션을 만들고 관리하게 하려면 연결하는 작업을 해야 하므로 몇 가지 정보를 property로 알려줘야 한다.
DBCPTest.java
package com.test.mybatis.persistence;
import static org.junit.Assert.assertNotNull;
import java.sql.Connection;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class DBCPTest {
@Autowired
private DataSource dataSource;
@Test
public void testTestConnection() {
assertNotNull(dataSource);
try {
//Connection conn = DBUtil.open();
Connection conn = dataSource.getConnection();
log.info(conn.isClosed());
} catch (Exception e) {
// TODO: handle exception
}
}
}
잦은 접속과 잦은 사용을 하더라도 Connection을 무한대로 생성하지 않고, 10개를 돌려가면서 사용한다.
그래서 데이터베이스가 뻗는 일이 잘 발생하지 않게 된다.
🍃HikariCP
- pom.xml에서 HikariCP 의존성 추가
Spring Boot로 넘어가면 HikariCP를 사용하게 된다.
pom.xml
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.4</version>
</dependency>
root-context.xml
<!-- HikariCP -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="jdbcUrl" value="jdbc:oracle:thin:@Localhost:1521:xe"></property>
<property name="username" value="hr"></property>
<property name="password" value="java1234"></property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg ref="hikariConfig"></constructor-arg>
</bean>
jdbcUrl가 바뀐 것 말고는 hikariConfig의 property는 완전히 똑같다.
destroy-method="close"는 데이터 소스가 사라질 때 자원을 모두 해제하기 위해 사용한다.
test.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">
<!-- namespace="파일명" -->
<mapper namespace="test">
<!-- MyBatis는 SQL을 XML 파일에서 관리 -->
<!--
<select id=""></select>
<insert id=""></insert>
<update id=""></update>
<delete id=""></delete>
-->
<select id="time" resultType="String">
select sysdate from dual
</select>
</mapper>
namespace는 패키지 같은 것이어서 마음대로 적어도 된다. 단, member.board라고 작성하더라도 부모 자식 관계가 아니라 하나이다.
보통 파일명으로 하는 게 헷갈리지 않기 때문에 많이 사용한다.
JDBC는 SQL을 Java 파일에서 문자열로 관리했는데, MyBatis는 SQL을 XML 파일에서 관리한다.
MapperTest.java
제대로 작동하는지 쿼리를 날려보도록 하자.
만약 쿼리가 제대로 작동한다면 MyBatis와 HikariCP도 작동한다는 것을 의미한다.
package com.test.mybatis.persistence;
import static org.junit.Assert.assertNotNull;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class MapperTest {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Test
public void testQuery() {
assertNotNull(sqlSessionFactory);
//SqlSessionTemplate > SQL 실행 (Statement 역할)
SqlSession session = sqlSessionFactory.openSession();
//session.executeQuery("select")
String time = session.selectOne("test.time"); //쿼리 결과
log.info(time);
}
}
SqlSession 타입으로 sqlSessionFactory의 openSession()를 받아온다. 이게 Statement 역할을 하여 쿼리를 날릴 수 있다.
root-context.xml의 bean을 보면 xml의 위치를 알고 있다. 이 말은 xml 안의 쿼리의 존재를 알고 있다는 의미이다. 그래서 쿼리를 참조하기만 하면 사용할 수 있으므로 이때 쿼리에 있는 id를 작성한다.
나중에는 xml 파일을 많이 만들기 때문에 어느 파일에 있는 쿼리인지도 알려주어야 한다. 그래서 namespace를 파일명으로 작성한 것이다.
🌿MyBatis의 활용
com.test.controller
- MyBatisController.java
com.test.persistence
- MyBatisDAO.java (I)
- MyBatisDAOImpl.java (C)
com.test.domain
- MyBatisDTO.java
src/main/resources > mapper
- mybatis.xml
views
- list.jsp
- add.jsp
- addok.jsp
MyBatisTest
- script.sql
전체 코드
servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!--
스프링이 "com.test.mybatis" 패키지와 자식 패키지를 탐색해서 그 안의 @Controller를 인식
-->
<context:component-scan base-package="com.test.mybatis" />
<context:component-scan base-package="com.test.controller" />
<context:component-scan base-package="com.test.persistence" />
</beans:beans>
성능 문제 때문에 스캔 범위는 되도록 좁힐 수 있는 만큼 좁혀야 한다.
현재 controller와 persistence를 인식할 수 있도록 범위를 추가한 상태이다.
root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Commons DBCP -->
<!--
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"></property>
<property name="username" value="hr"></property>
<property name="password" value="java1234"></property>
</bean>
-->
<!-- HikariCP -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<!--
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="jdbcUrl" value="jdbc:oracle:thin:@Localhost:1521:xe"></property>
-->
<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
<property name="jdbcUrl" value="jdbc:log4jdbc:oracle:thin:@localhost:1521:xe"></property>
<property name="username" value="hr"></property>
<property name="password" value="java1234"></property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<constructor-arg ref="hikariConfig"></constructor-arg>
</bean>
<!-- MyBatis -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations" value="classpath*:mapper/*.xml"></property>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sessionFactory"></constructor-arg>
</bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 인코딩 필터 등록하기(클래스는 이미 존재함) -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<servlet-name>appServlet</servlet-name>
</filter-mapping>
</web-app>
Spring에서 인코딩 클래스가 CharacterEncodingFilter가 있기 때문에 이를 그대로 사용한다.
인코딩 형식을 parameter로 넘겨서 어떻게 인코딩할 것인지를 알려준다.
servlet-name에 주소를 알려주는 게 아니라 서블릿 등록시킨다.
MyBatisController.java
package com.test.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import com.test.domain.MyBatisDTO;
import com.test.persistence.MyBatisDAO;
import com.test.persistence.MyBatisDAOImpl;
@Controller // F3키는 도움말
public class MyBatisController { // CRUD 작업하기
// 의존객체로 하는방법
// 1. 멤버변수로 만들기
@Autowired // 의존 주입
private MyBatisDAO dao;
// 2. xml이나 setter로 주입해줘야 한다. -> xml대신 annotation을 써주면 전부 해결(@Autowired)
@GetMapping(value = "/test.do") // http://localhost:8100/mybatis/test.do
public String test() {
System.out.println("test");
// 밑에처럼 말고 의존 객체로 하겠다 -> Spring DI
// MyBatisDAO dao = new MyBatisDAOImpl();
dao.test();
return "list";
}// test
@GetMapping(value = "/add.do")
public String add() {
return "add";
}
@PostMapping(value = "/addok.do")
public String addok(MyBatisDTO dto, Model model) {
int result = dao.add(dto);
model.addAttribute("result", result);
return "addok";
}
}
컨트롤러를 만들고 Spring의 혜택을 얻기 위해서 의존 객체 DAO를 직접 만들지 않고 Spring에게 만들어 달라고 했다.
직접 만드는 구문을 없앤 대신에 생성자 또는 Setter를 만들어야 했는데, 이를 어노테이션 설정으로 바꿨기 때문에 업무가 달라지게 되었다.
의존 주입을 해서 집어넣을 변수를 하나 만들고 @Autowired을 사용하였고, 해당 클래스가 인식 가능하게 해야 하기 때문에 @Repositiory 어노테이션을 붙여 주었다.
@Component라는 말은 특징이 아무것도 없는 이름이기 때문에 Spring은 @Component를 베이스로 깔고, 성격에 따라 어노테이션을 만들었다. 그 중에 하나가 @Controller이고, 그 중에 하나가 @Repository인 것이다.
DB를 다루는 파일에 @Component 대신에 @Repository를 사용하고, 서비스 업무를 담당하면 @Service를 사용한다.
MyBatisDAO.java (I)
package com.test.persistence;
import com.test.domain.MyBatisDTO;
public interface MyBatisDAO {
void test();
int add(MyBatisDTO dto);
}
JDBC
1. Connection
2. Statement
3. ResultSet
MyBatis
1. SqlSessionTemplate
a. 반환값 X
- stat.executeUpdate()
- ✔️template.insert()
- ✔️template.update()
- ✔️template.delete()
b. 반환값 O
- stat.executeQuery()
- ✔️template.selectOne() > 결과셋 레코드가 1개일 때 사용
- ✔️template.selectList() > 결과셋 레코드가 N개일 때 사용
JDBC에서 했던 3가지 업무를 MyBatis에서는 SqlSessionTemplate가 혼자서 한다. 물론 쿼리를 날린다는 점에서 2번에 가깝긴 하다.
this.template.insert를 할 때 쿼리를 달라는 의미는 쿼리의 아이디를 달라는 의미이다. 결과에 따라 1또는 0을 돌려주기 때문에 변수를 만들 필요 없이 바로 return한다.
MyBatisDAOImpl.java (C)
package com.test.persistence;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.test.domain.MyBatisDTO;
//자동으로 <bean>태그가 안 되기 때문에 @Repository를 붙인다.
//이 안에 Component 어노테이션이 있으면 <bean>으로 등록이 된다는 의미이다.
@Repository //@Conponent 로 넓게 써도 상관은 없지만 무슨 역할인지 좀더 알고 싶을때 더 세세하게 적는것.
public class MyBatisDAOImpl implements MyBatisDAO{ // 자식과 부모 연결
@Autowired //이거 쓰면 의존 주입이 따로 필요없다
private SqlSessionTemplate template;
@Override
public void test() {
System.out.println(template==null);
}
@Override
public int add(MyBatisDTO dto) {
/*
JDBC
1. Connection
2. Statement
3. ResultSet
MyBatis
1. SqlSessionTemplate
a. 반환값 x (원래는 stat.executeUpdate())
- template.insert()
- template.update()
- template.delete()
b. 반환값 o (원래는 stat.executeQuery())
- template.selectOne() : 결과셋 레코드가 1개일때 사용.
- template.selectList() : 결과셋 레코드가 N개일때 사용.
*/
//this.template.insert("매퍼 네임스페이스.쿼리 id", 인자값)
//this.template.insert("mybatis.add", dto); //따로 안꺼내고 dto전체 넘겨주기
return this.template.insert("mybatis.add", dto);
}
}
SqlSessionTemplate에 대한 인스턴스를 만들어서 사용한다.
이는 이미 Spring이 읽어들인 설정이므로 getBean만 하면 된다. @Autowired를 하면 getBean을 하는 것과 동일하다.
- HikariConfig > HikariDataSource > SqlSessionFactoryBean > SqlSessionTemplate > MyBatisDAOImpl > MyBatisController
SqlSessionTemplate은 root-context.xml에서 설정되어 있다. 이러한 과정은 위와 같이 표현할 수 있다.
연쇄적으로 의존관계에 있는 것들이 거꾸로 올라가면서 생성된다. 개발자는 관계만 세팅해주면 DI라는 관계에 의해서 나머지는 연쇄적으로 만들어진다. 즉, 컨트롤러가 만들어지는 순간 객체 6개가 한 번에 만들어지는 것이다.
MyBatisDTO.java
package com.test.domain;
import lombok.Data;
@Data
public class MyBatisDTO {
private String seq;
private String name;
private String age;
private String address;
private String gender;
}
mybatis.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="mybatis">
<insert id="add" parameterType="com.test.domain.MyBatisDTO">
insert into tblMyBatis(seq, name, age, address, gender)
values (seqMyBatis.nextVal, #{name}, #{age}, #{address}, #{gender})
<!-- #{ } == ? -->
</insert>
</mapper>
쿼리의 id는 메서드의 이름으로 짓는 게 알아보기 쉽다.
내부적으로 JDBC이기 때문에 세미콜론(;)이 있으면 오류가 발생하므로 지운다.
매개변수 이름을 적지만, 실제로는 getter를 적은 것이다.
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<!-- list.jsp -->
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
</script>
</body>
</html>
add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<!-- add.jsp -->
<h1>입력</h1>
<form method="POST" action="/mybatis/addok.do">
<table class="vertical">
<tr>
<th>이름</th>
<td><input type="text" name="name" required class="short"></td>
</tr>
<tr>
<th>나이</th>
<td><input type="text" name="age" required class="short"></td>
</tr>
<tr>
<th>주소</th>
<td><input type="text" name="address" required class="full"></td>
</tr>
<tr>
<th>성별</th>
<td>
<select name="gender">
<option value="m">남자
<option value="f">여자
</select>
</td>
</tr>
</table>
<div>
<button>등록하기</button>
</div>
</form>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
</script>
</body>
</html>
되도록 Spring의 혜택을 받을 수 있도록 input 태그의 name을 지정할 때 다음의 조건을 만족해야 한다.
1. DB > 테이블의 컬럼명
2. DTO > 멤버변수명
3. HTML > 태그 name
위 3개를 똑같이 만드는 게 GOAT이다. 그러면 고민없이 Spring의 모든 혜택을 누릴 수 있다.
addok.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<!-- addok.jsp -->
<h1>결과</h1>
<div>${result}</div>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
</script>
</body>
</html>
script.sql
-- MyBatisTest > script.sql
drop table tblMyBatis;
drop sequence seqMyBatis;
create table tblMyBatis (
seq number primary key,
name varchar2(50) not null,
age number not null,
address varchar2(100) not null,
gender char(1) not null
);
create sequence seqMyBatis;
insert into tblMyBatis (seq, name, age, address, gender)
values (seqMyBatis.nextVal, 'Isaac', 24, '서울시 강남구 역삼동', 'm');
select * from tblMyBatis;
commit;