xxxController: 스프링 MVC에서 동작하는 Controller 클래스
xxxSerivce, xxxServiceImpl: 비즈니스 영역을 담당하는 인터페이스는 ‘xxxService’라는 방식을 사용하고, 인터페이스를 구현한 클래스는 ‘xxxServiceImpl’이라는 이름을 사용(실제 reference 변수는 'xxxService'의 변수 사용)
xxxDAO, xxxRepository: DAO(Data-Access-Object)나 Repository(저장소)라는 이름으로 영역을 따로 구성하는 것이 보편적. 예제에서는 별도의 DAO를 구성하는 대신에 MyBatis의 Mapper 인터페이스를 활용.
VO, DTO: VO의 경우는 주로 Read Only의 목적이 강하고, 데이터 자체도 Immutable(불변)하게 설계. DTO는 주로 데이터 수집의 용도
VO : 객체를 상수화 시켜 변하지 않게 한다. 그럼 왜씀? 메소드간 데이터를 ㄱ전달할떄 덩어리로 묶어서 전달하는 용도
DTO : DB에서 데이터 값 받아서 전달하는 용도(DB쪽 용도)
비즈니스를 단위별로 구분하고 다시 내부에서 Controller 패키지, Service 패키지 등으로 다시 나누는 방식을 이용한다.
R-C-(UD)
Project name : ex02
패키지의 마지막 단어가 context root가 된다.
컨텍스트의 이름은 겹치지 않도록 웹프로젝트의 이름으로 하는 경우가 많다.
프로젝트를 생성한 후 제일 먼저 Framwork의 버전을 맞추는 것이 원칙이다.
web.xml
에 인코딩 필터 매핑 추가 <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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
.jsp
에 한글 설정 코드 추가<%@ page session="false" language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
설정을 마친 후 기본적으로 동작하는지 확인한다.
(HomeController.java와 home.jsp는 환경설정이 모두 끝난 후 지우도록 한다)
<java-version>1.6</java-version>
→ 11
3.1.1 REALEASE → 5.0.7 REALEASE
<source>11</source>
<target>11</target>
Maven>Project Update 후 버전이 제대로 수정되었는지 다음과 같이 확인한다.
<dependency>
수정<artifactId>javax.servlet-api</artifactId>
로 변경
<version>3.1.0</version>
로 변경
<Dependency>
추가https://mvnrepository.com/artifact/org.projectlombok/lombok/1.18.0
depency를 복사해서 pom.xml에 추가
<version>1.2.17</version>
로 변경
<scope>runtime</scope>
삭제 또는 추석처리
https://mvnrepository.com/artifact/org.springframework/spring-test/5.0.7.RELEASE
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
<scope>test</scope>
</dependency>
https://mvnrepository.com/artifact/org.springframework/spring-jdbc/5.0.7.RELEASE
https://mvnrepository.com/artifact/org.mybatis/mybatis/3.4.6
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
https://mvnrepository.com/artifact/org.mybatis/mybatis-spring/1.3.2
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.8</version>
</dependency>
https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4/1.16
<!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4 -->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
<version>1.16</version>
</dependency>
src/main/resources
에 추가한다. 밑에도 src폴더가 있으니 올바른 경로를 찾아서 추가하도록 한다.
파일내용
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
(hikari오류때문에 추가한건데 왜 난 오류가 안났지)
<!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.3.0.0</version>
</dependency>
<bean>
추가하기<?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">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<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="book_ex"></property>
<property name="password" value="book_ex"></property>
</bean>
</beans>
오류가 없다면 다음과 같이 Beans Graph에 작성한 bean들이 보여야한다.
destroy-method에 close추가
위와같이 자동으로 추가된 것을 확인할 수 있다.
<?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">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<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="book_ex"></property>
<property name="password" value="book_ex"></property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close" primary="false">
<constructor-arg ref="hikariConfig"></constructor-arg>
</bean>
</beans>
<bean>
추가위에서 Name의 dataSource는 내부적으로 정해져서 변경할 수 없다.
Ref는 이 부분의 dataSource를 참조한다는 뜻이다.
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close" primary="false">
<constructor-arg ref="hikariConfig"></constructor-arg>
</bean>
STS(Spring Tool Suite)는 Spring Framework를 위한 특화된 IDE(Integrated Development Environment)입니다. "dataSource"는 데이터베이스와의 커넥션을 관리하는 객체로, 일반적으로 Spring Framework 내에서 JDBC(Java Database Connectivity)나 JPA(Java Persistence API) 등을 사용할 때 설정합니다. 이 객체는 데이터베이스와의 실제 연결을 캡슐화하고 커넥션 풀링 같은 고급 기능을 제공합니다.
dataSource의 역할은 크게 아래와 같습니다:
커넥션 풀링: 여러 사용자가 동시에 데이터베이스에 접근할 때, 매번 새로운 연결을 생성하는 것은 비효율적입니다. dataSource는 여러 커넥션을 미리 만들어 놓고, 필요할 때 커넥션을 제공하고 반환받는 역할을 합니다.
트랜잭션 관리: 데이터베이스 작업을 트랜잭션 단위로 처리할 수 있도록 도와줍니다.
추상화: dataSource는 데이터베이스 엔진에 대한 실제 연결 정보를 추상화하여, 애플리케이션 코드에서는 데이터베이스 연결을 직접 다루지 않아도 됩니다. 이로 인해 데이터베이스가 변경되더라도 코드의 큰 수정 없이 dataSource 설정만 변경하면 됩니다.
에러 핸들링: 연결 실패, 쿼리 오류 등 다양한 데이터베이스 관련 오류를 캡쳐하고 적절히 처리할 수 있습니다.
Spring Framework에서 dataSource는 보통 XML 설정 파일, 자바 설정 파일, 또는 애노테이션을 통해 설정됩니다. 예를 들면, 아래와 같이 XML 파일에서 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/mydb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
여기에서 BasicDataSource
는 Apache Commons DBCP 라이브러리에서 제공하는 데이터 소스 클래스입니다. 이 설정을 통해 MySQL 데이터베이스에 연결을 설정합니다. 이후에 이 dataSource
빈은 JdbcTemplate 나 JPA의 EntityManager와 같은 데이터 액세스 객체에 주입될 수 있습니다.
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close" primary="false">
<constructor-arg ref="hikariConfig"></constructor-arg>
</bean>
이 설정은 HikariCP라는 커넥션 풀 라이브러리를 사용하여 dataSource
를 설정한 예시입니다. HikariCP는 고성능 데이터베이스 커넥션 풀 라이브러리로 널리 사용되고 있습니다.
설정 내용을 간단히 설명하면 다음과 같습니다:
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" primary="false">
: 이 부분에서 Spring Bean을 정의하고 있습니다. id
는 Bean의 식별자이며, class
는 이 Bean이 어떤 클래스의 인스턴스를 생성할 것인지 명시합니다. destroy-method="close"
는 이 Bean이 소멸될 때 실행할 메소드를 지정하고, primary="false"
는 이 dataSource가 primary bean이 아니라는 것을 명시합니다. 여러 개의 dataSource를 가질 경우 어느 것이 주된(primary) dataSource인지 설정할 수 있습니다.<constructor-arg ref="hikariConfig"></constructor-arg>
: HikariDataSource의 생성자에 주입할 인자를 설정합니다. ref="hikariConfig"
는 다른 Bean이나 설정에서 정의된 hikariConfig
를 참조하여 생성자에 주입하는 것을 의미합니다. 즉, HikariDataSource의 설정은 hikariConfig
라는 다른 Bean에서 가져옵니다.이렇게 설정하면 Spring 애플리케이션 내에서 dataSource
라는 이름으로 이 Bean을 참조할 수 있습니다. 이 Bean은 일반적으로 JdbcTemplate
, EntityManager
등과 같은 데이터베이스 작업을 수행하는 다른 객체에 주입됩니다. 이를 통해 데이터베이스 연결과 관련된 모든 세부 설정이나 커넥션 관리는 HikariCP에 위임하고, 애플리케이션 로직에서는 비즈니스 로직에만 집중할 수 있습니다.
STS 종료후 문제가 발생한 부분의 repository를 날리고 재시작
.m2\repository\
위와 같이 버전을 지정해서 변경하면 메이븐은 이 버전이 레퍼지토리에 있는지 확인하고 없으면 해당 버전을 다운받고 적용한다. 충돌 방지를 위해 버전을 변경한 후에는 서버를 닫고 clean하고 (캐싱 삭제) 재시작을 하도록 한다.
반복적인 실행으로 여러 개의 데이터 생성 및 확인 및 commit
Namespaces 에서 mybatis-spring 체크
체크를 하면 <mybatis-spring:scan base-package=""/>
을 쓸 수 있다.
<mybatis-spring:scan base-package="com.zerock.mapper"/>
을 추가하면 com.zerock.mapper를 스캔해서 mapper를 찾는다.
프로젝트에 com.zerock.domain패키지 생성, BoardVO 클래스 정의한다.
BoardMapper.java
package com.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import com.zerock.domain.BoardVO;
public interface BoardMapper {
@Select("select * from tbl_board")
public List<BoardVO> getList();
}
BoardMapper 인터페이스를 작성할 때는 이미 작성된 BoardVO 클래스를 적극적으로 활용해서 필요한 SQL을 어노테이션의 속성값으로 처리할 수 있다. SQL을 작성할 때는 반드시 ';'이 없도록 작성해야 한다.
작성된 BoardMapper 인터페이스를 테스트 할 수 있게 테스트 환경인 'src/test/java'에 'com.zerock.mapper'패키지를 작성하고 BoardMapperTests 클래스를 추가한다.
package com.zerock.mapper;
import java.util.List;
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 com.zerock.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_=@Autowired)
private BoardMapper mapper;
@Test
public void testGetList() {
mapper.getList().forEach(board -> log.info(board));
}
}
BoardMapperTests 클래스는 스프링을 이용해서 BoardMapper 인터페이스의 구현체를 주입받아서 동작한다.
정상적으로 테스트가 진행되면 위와 같은 결과가 나온다.
mapper의 쿼리부분을 삭제한다.
패키지 생성 후 xml파일을 생성한다.
<?xml version="1.0" encoding="${encoding}"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
window>preference>templet
xml파일을 설정할때 틀리지 않고 기본적인 코드를 작성할 수 있도록 돕는 설정이다.
id - 어떤 메서드와 연결되어 있는지 알려주는 속성
완성된 BoardMapper.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="com.zerock.mapper.BoardMapper">
<select id="getList" resultType="com.zerock.domain.BoardVO">
<![CDATA[
select * from tbl_board
]]>
</select>
</mapper>
<mapper>
의 namespace 속성값을 Mapper 인터페이스와 동일한 이름을 주는 것에 주의한다. <select>
태그의 id 속성값은 메서드의 이름과 일치하게 작성한다. resultType 속성값은 select 쿼리의 결과를 특정 클래스의 객체로 만들기 위해서 설정한다. XML에 사용한 <![CDATA[]]>
부분은 XML에서 부등호를 사용하기 위해서 사용한다
XML에 SQL문 처리 후에는
public interface BoardMapper {
//@Select("select * from tbl_board")
public List<BoardVO> getList();
}
위와 같이 BoardMapper 인터페이스의 SQL을 제거한다.
MyBatis는 내부적으로 JDBC의 PreparedStatement를 활용하고 필효한 파라미터를 처리하는 '?'에 대한 치환은 '#{속성}'을 이용해서 처리한다.
무조건 시퀀스를 pk로 쓰진 않는다.
<insert id="insert">
<![CDATA[
insert into tbl_board (bno, title, content, writer)
values (seq_board.nextval, #{title}, #{content}, #{writer})
]]>
</insert>
<insert id="insertSelectKey">
<selectKey keyProperty="bno" order="BEFORE"
resultType="long">
<![CDATA[
select seq_board.nextval from dual
]]>
</selectKey>
<![CDATA[
insert into tbl_board (bno, title, content, writer)
values (#{bno}, #{title}, #{content}, #{writer})
]]>
</insert>
BoardMapper.java
public interface BoardMapper {
//@Select("select * from tbl_board")
public List<BoardVO> getList();
public void insert(BoardVO board);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
}
BoardMapper.xml
<select id="read" resultType="com.zerock.domain.BoardVO">
<![CDATA[
select * from tbl_board where bno = #{bno}
]]>
</select>
BoardMapperTests
@Test
public void testRead() {
BoardVO board = mapper.read(10L);
log.info(board);
}
영향을 받은 갯수만큼의 리턴이 돌아온다.
BoardMapper.java
public interface BoardMapper {
//@Select("select * from tbl_board")
public List<BoardVO> getList();
public void insert(BoardVO board);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
public int delete(Long bno);
}
BoardMapper.xml
<delete id="delete">
<![CDATA[
delete from tbl_board where bno = #{bno}
]]>
BoardMapperTests
@Test
public void testDelete() {
log.info("delete lines : " + mapper.delete(10L));
}
만약 위의 테스트를 한번 더 진행하면, DB에 데이터가 없기 때문에 다음과 같이 출력된다.
update 처리를 할 때 미리 where절을 설정하도록 한다.
BoardMapper.java
public interface BoardMapper {
//@Select("select * from tbl_board")
public List<BoardVO> getList();
public void insert(BoardVO board);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
public int delete(Long bno);
public unt update(BoardVO board);
}
BoardMapper.xml
<update id="update">
<![CDATA[
update tbl_board
set title = #{title},
content = #{content},
writer = #{writer},
updateDate = sysdate
where bno = #{bno}
]]>
</update>
BoardMapperTests
@Test
public void testUpdate() {
BoardVO board = new BoardVO();
board.setBno(6L);
board.setTitle("제목 수정");
board.setContent("내용 수정");
board.setWriter("이름 수정");
int count = mapper.update(board);
log.info("UPDATE COUNT : " + count);
}