For Programmer

Part3 - 스프링 MVC프로젝트의 기본 구성(1) 본문

Spring/스프링 프로젝트

Part3 - 스프링 MVC프로젝트의 기본 구성(1)

유지광이 2020. 9. 2. 16:08
728x90

프로젝트를 진행할때 다음과 같은 네이밍 규칙을 가지고 작성합니다.

-xxxController: 스프링 MVC에서 동작하는 Controller클래스를 설계할 때 사용합니다.

-xxxService, xxxServicelmpl : 비즈니스 영역을 담당하는 인터페이스는 'xxxService' 라는 방식을 사용하고, 인터페이스를 구현한 클래스는 'xxxServicelmpl'이라는 이름을 사용합니다.

-xxxDAO,xxxRepository: DAO(Data-Access-Object)나 Repository(저장소)라는 이름으로 영역을 따로 구성하는 것이 보편적입니다. 다만 이 책의 예제는 별도의 DAO를 구성하는 대신에 MyBatis의 Mapper인터페이스를 사용합니다.

-VO,DTO:VO와 DTO는 일반적으로 유사한 의미로 사용하는 용어로, 데이터를 담고 있는 객체를 의미한다는 공통점이 있습니다. 다만, VO의 경우는 주로 Read Only의 목적이 강하고, 데이터 자체도 Immutable(불변)하게 설계하는 것이 정석입니다. DTO는 주로 데이터 수집의 용도가 좀 더 강합니다. 예를 들어, 웹 화면에서 로그인하는 정보를 DTO로 처리하는 방식을 사용합니다. 테이블과 관련된 데이터는 VO라는 이름을 사용하겠습니다.

 

이 예제에서는 다음과 같이 패키지 구성을 할 것입니다.

org.zerock.config - 프로젝트와관련된 설정 클래스들의 보관 패키지

org.zerock.controller -스프링 MVC의 Controller들의 보관 패키지

org.zerock.service -스프링의 Service 인터페이스와 구현 클래스 패키지

org.zerock.domain -VO,DTO 클래스들의 패키지

org.zerock.persistence -MyBatis Mapper 인터페이스 패키지

org.zerock.exception -웹 관련 예외처리 패키지

org.zerock.aop -스프링의 AOP관련 패키지

org.zerock.security -스프링의 Security 관련 패키지

org.zerock.util -각 종 유틸리티 클래스 관련 패키지

 

프로젝트 SpringLegacyProject 생성 후 기존 Part2에서 했던 pom.xml을 그대로 복사한 후 다음 스프링 관련 라이브러리만 2개와 MyBatis라이브러리 4개를 추가합니다.(프로젝트명:SpringLegacyProjectXMLpart3, 패키지명:org.zerock.controller) 

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
<!-- MyBatis -->
		<!-- 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.mybatis/mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.6</version>
		</dependency>
		<!-- 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/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4 -->
		<dependency>
			<groupId>org.bgee.log4jdbc-log4j2</groupId>
			<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
			<version>1.16</version>
		</dependency>

다음은 SQL Developer를 이용해서 테이블을 생성 한 후 각 게시물마다 고유의 번호를 부여하기 위해 오라클의 시퀀스라는 것을 생성하여 부여합니다. 모든 오라클 명령어는 다음과 같습니다.

create sequence seq_board;

create table tbl_board(
    bno number(10,0),
    title varchar2(200) not null,
    content varchar2(2000) not null,
    writer varchar2(50) not null,
    regdate date default sysdate,
    updatedate date default sysdate
    );
    
alter table tbl_board add constraint pk_board primary key(bno);

 

* 시퀀스는 생성할 때 웬만하면 seq_ 를 앞에 붙여주는것이 좋으며 regdate,updatedate를 생성하는 이유는 레코드의 생성시간과 최종 수정시간을 같이 기록하기 위해서 생성하였다. 따라서 디폴트값으로 현재 시간(sysdate)를 지정하였고 나중에 insert로 데이터를 추가할 때도 자동으로 추가가 되기 때문에 굳이 regdate,updatedate는 추가해줄 필요가 없다. 나머지 제목,작성자,내용,게시글번호(bno) 까지 생성하였다. 

 

마지막으로 bno에 primary key(pk)를 부여하고 있는데 부여할떄는 pk_를 앞에 붙이는 것이 일반적이며 pk를 부여하게되면 게시글번호가 중복이 발생될 수 없다.

또한 오라클은 MySql과 다르게 Commit을 수동으로 해주어야 하기 때문에 F11키를 이용하여 수동으로 Commit을 꼭해주어야 한다.

 

데이터베이스 관련 설정 및 테스트

xml설정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"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- Root Context: defines shared resources visible to all other web components -->

	<!-- 스캔할 bean들이 모여있는 패키지를 지정한다. -->
	<context:component-scan base-package="org.zerock.sample"></context:component-scan>

	<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="scott"></property>
		<property name="password" value="tiger"></property>
	</bean>

	<!-- HikariCP configuration -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
		<constructor-arg ref="hikariConfig" />
	</bean>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<mybatis-spring:scan base-package="org.zerock.mapper" />

</beans>

 

+ PART 1에서 작성된 log4jdbc.log4j2.properties 파일을 추가해 줍니다.(src/main/resource)

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

+ PART 1에서 작성했던 DataSourceTests 클래스와 JDBCTests클래스를 테스트 패키지에 추가합니다. 이유는 웹개발 이전에 테스트를 통해 반드시 확인해야하기 때문입니다.

DataSourceTests.java

package org.zerock.persistence;

import static org.junit.Assert.fail;

import java.sql.Connection;

import javax.sql.DataSource;

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.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
//Java 설정을 사용하는 경우
//@ContextConfiguration(classes= {RootConfig.class})
@Log4j
public class DataSourceTests {

	@Setter(onMethod_ = { @Autowired })
	private DataSource dataSource;

	@Setter(onMethod_ = { @Autowired })
	private SqlSessionFactory sqlSessionFactory;

	@Test
	public void testMyBatis() {

		try (SqlSession session = sqlSessionFactory.openSession(); Connection con = session.getConnection();) {

			log.info(session);
			log.info(con);

		} catch (Exception e) {
			fail(e.getMessage());
		}

	}

	@Test
	public void testConnection() {

		try (Connection con = dataSource.getConnection()) {

			log.info(con);

		} catch (Exception e) {
			fail(e.getMessage());
		}
	}
}

JDBC.java

package org.zerock.persistence;

import static org.junit.Assert.fail;

import java.sql.Connection;
import java.sql.DriverManager;

import org.junit.Test;

import lombok.extern.log4j.Log4j;

@Log4j
public class JDBCTests {

	static {
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Test
	public void testConnection() {

		try (Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "scott", "tiger")) {
			log.info(con);
		} catch (Exception e) {
			fail(e.getMessage());
		}
	}

}

Java 설정을 이용하는 경우의 프로젝트 구성

Java설정을 이용한다면 pom.xml의 라이브러리 추가는 동일하지만 XML을 대신하는 클래스 파일이 필요합니다. 앞에서 다 설명했기 때문에 그대로 복사 붙여넣기만 하면됩니다. 

 

RootConfig.java

package org.zerock.config;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@ComponentScan(basePackages = "org.zerock.sample")
@MapperScan(basePackages = "org.zerock.mapper")
public class RootConfig {

	@Bean
	public DataSource dataSource() {
		HikariConfig hikariConfig = new HikariConfig();
//		hikariConfig.setDriverClassName("oracle.jdbc.driver.OracleDriver");
//		hikariConfig.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:XE");

		hikariConfig.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
		hikariConfig.setJdbcUrl("jdbc:log4jdbc:oracle:thin:@localhost:1521:xe");
		hikariConfig.setUsername("scott");
		hikariConfig.setPassword("tiger");

		HikariDataSource dataSource = new HikariDataSource(hikariConfig);

		return dataSource;
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
		sqlSessionFactory.setDataSource(dataSource());
		return sqlSessionFactory.getObject();
	}

}

ServletConfig.java

package org.zerock.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc
@ComponentScan(basePackages = { "org.zerock.controller", "org.zerock.exception" })
public class ServletConfig implements WebMvcConfigurer {

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {

		InternalResourceViewResolver bean = new InternalResourceViewResolver();
		bean.setViewClass(JstlView.class);
		bean.setPrefix("/WEB-INF/views/");
		bean.setSuffix(".jsp");
		registry.viewResolver(bean);
	}

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {

		registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
	}

}

WebConfig.java

package org.zerock.config;

import javax.servlet.ServletRegistration;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

	// 프로젝트에서 사용할 Bean들을 정의하기 위한 클래스를 지정한다.
	@Override
	protected Class<?>[] getRootConfigClasses() {

		return new Class[] { RootConfig.class };
	}

	// Spring MVC 프로젝트 설정을 위한 클래스를 지정한다.
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[] { ServletConfig.class };
	}

	// DispatcherServlet에 매핑할 요청 주소를 셋팅한다.
	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

	@Override
	protected void customizeRegistration(ServletRegistration.Dynamic registration) {
		registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");
	}
}

 위의 작업 후에는 XML에서 했던것과 같이 Oracle JDBC Driver의 설정과, log4jdbc.log4j2.properties 등을 추가합니다. 프로젝트 구성 후에는 테스트 코드를 통해서 정상적인지 확인하고, Tomcat을 통해서 실행 가능한지 확인해야합니다.

 

영속/비즈니스 계층의 CRUD 구현

코드를 이용해서 데이터에 대한 CRUD(Create,Read,Update,Delete) 작업을 진행합니다.

영속 계층의 작업은 항상 다음과 같은 순서로 진행합니다.

-테이블의 칼럼 구조를 반영하는 VO(Value Object)클래스의 생성

-MyBatis의 Mapper 인터페이스의 작성/XML 처리

-작성한 Mapper인터페이스의 테스트

 

VO클래스의 작성

VO클래스를 생성하는 작업은 테이블 설계를 기준으로 작성하면 됩니다. 현재 tbl_board테이블의 구성은 아래와 같습니다.

프로젝트에 org.zerock.domain 패키지를 생성하고,BoardVO 클래스를 정의합니다.

 

BoardVo.java

package org.zerock.domain;

import java.util.Date;

import lombok.Data;

@Data
public class BoardVO {
	
	private Long bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private Date updateDate;
}

Mapper 인터페이스

 

root-context.xml의 일부(org.zerock.mapper 패키지를 조사하도록 설정)

<mybatis-spring:scan base-package="org.zerock.mapper" />

org.zerock.mapper - BoardMapper 인터페이스 생성

 

BoardMapper.java

package org.zerock.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;

public interface BoardMapper {

	@Select("select * from tbl_board where bno>0")
	public List<BoardVO> getList();
}

위의 BoardMapper 클래스를 가지고 Test를 해볼 수 있다. org.zerock.mapper - BoardMapperTests 클래스 생성 

 

BoardMapperTests.java

package org.zerock.mapper;

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.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
// Java Config
// @ContextConfiguration(classes = {org.zerock.config.RootConfig.class} )
@Log4j
public class BoardMapperTests {

	@Setter(onMethod_ = @Autowired)
	private BoardMapper mapper;

	@Test
	public void testGetList() {

		mapper.getList().forEach(board -> log.info(board));

	}

}

test실행결과(test할때는 꼭 해당프로젝트에 ojdbc6.jar을 외부라이브러리로 추가해주어야한다.) tomcat서버의 jdbc폴더에 넣었다고 해도 test할 때는 직접 추가해주어야 오류발생하지 않음)

Mapper XML 파일

 src/main/resource 폴더 내부에 org/zerock/mapper 단계로 해당 이름순으로 폴더를 생성하고 XML파일을 작성합니다.

 

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="org.zerock.mapper.BoardMapper">


	<select id="getList" resultType="org.zerock.domain.BoardVO">
	<![CDATA[
		select * from tbl_board where bno > 0 
		]]>
	</select>
	
</mapper>

*XML을 작성할 때는 반드시 <mapper>의 namespace 속성값을 Mapper 인터페이스와 동일한 이름을 주는 것에 주의하고 <select>태그의 id속성 값은 메서드의 이름과 일치하게 작성합니다.resultType의 속성값은 select 쿼리의 결과를 특정 클래스의 객체로 만들기 위해서 설정합니다. XML에 사용한 CDATA부분은 XML에서 부등호(특수문자)를 사용하기 위해 사용합니다. 참고로 XML을 사용하게 될 경우 어노테이션에서 사용한 SQL문은 제거해도 됩니다.

//@Select("select * from tbl_board where bno>0")
	public List<BoardVO> getList();

-> 똑같이 테스트를 실행하여 데이터베이스와 잘 연결이 되는지 확인합니다.

 

....다음 편에서 계속

728x90
Comments