@Controller
@RequestMapping(value = "/brokenSVC")
public class testController {
@Autowired
private BrokenService testService;
@GetMapping(value = "test")
public String home(Model model) {
System.out.println(testService.getdata().get(0).getDate());
return "testpage";
}
}
- Service 생성
다음과 같은 Service를 작성한다.
@Service
public class BrokenService {
@Autowired
private testDAO testdao;
public List<Test_Model> getData() {
return testdao.getData();
}
}
- DAO 생성
다음과 같은 DAO를 작성한다.
@Repository
public class BrokenDAO {
@Autowired private SqlSession sqlSession;
private static final String Namespace = "test.main.mapper.rmcMapper"; //rmcMapper가 있는 위치
public List<Test_Model> getData() {
return sqlSession.selectList(Namespace+".getData");
}
}
4. 결론
mybatis를 사용할 경우 기존 방법은 DAO에 쿼리문이 섞여있다는 단점이 있었는데 쿼리를 완벽하게 분리하여 사용할 수 있다는 장점이 있다.
다음과 같은 spring MVC 아키텍처에서 우리는 controller와 view부분만 만들면 되며 나머지 부분은 스프링에서 제공한다. 사용자의 request가 들어올 경우 dispatcher servlet가 요청을 가로채고 handler Mapping에 전달한 후 컨트롤러로 요청이 전달하고 우리가 만든 컨트롤러에 의해 view 네임을 선택하면 view Resolver에 의해 view네임을 가진 jsp를 선택하게 된다.
2. 스프링 MVC 실습하기
사전준비
다음과 같이 pom.xml에 필요한 라이브러리를 추가한다. 게터, 세터를 용이하게 만들어주는 lombok mysql과 연동하기 위한 dbcp, mysql-connector, spring-jdbc를 추가한다. 다음과 같은 deoendency를 추가한다
다음과 같이 Spring Legacy Project의 Spring MVC Project를 클릭하고 helloSpringMVC라는 프로젝트를 생성한다
#2) home.jsp 수정
다음과 같이 기본으로 있는 home.jsp를 수정한다. 클릭할 시 createoffer로, offers로 요청을 보내는 a태그가 2개 있다. 이때 ${pageContext.request.contextPath}는 서버에서 설정된 contextPath를 의미한다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<p> <a href="${pageContext.request.contextPath}/offers">show current offers</a>
<p> <a href="${pageContext.request.contextPath}/createoffer">Add a new offer</a>
</body>
</html>
#3) OffersController 생성하기
다음과 같은 OffersController를 생성한다.
package kr.ac.hansung.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import kr.ac.hansung.model.Offer;
import kr.ac.hansung.service.OffersService;
@Controller
public class OffersController {
@RequestMapping("/offers")
public String showOffers(Model model) {
return "offers";
}
@RequestMapping("/createoffer")
public String createOffer(Model model) {
return "createoffer";
}
}
#4) offers.jsp createoffers.jsp생성하기
jsp코드는 이후에 수정될 예정이니 controlle가 jsp로 제대로 요청을 전달하는지 확인만 할 목적으로 기본 jsp2개를 생성한다
#5) 실행하기
다음과 같이 제대로 매핑이 된 것을 확인할 수 있다.
#6) dao, model, service 생성하기
다음 그림과 같은 구조를 생성하기 위해 dao, model, service를 생성한다. 사용자의 요청이 들어오면 Dispatcher Servlet에서 이를 컨트롤러에 전달해주고 컨트롤러는 이에 맞는 서비스에게 전달하고 서비스는 DAO에게 DAO는 DB에 접근하고 모델을 통해 View로 사용자에게 response 하는 구조이다
OfferDAO
처음 프로젝트 생성할 때 패키 지명을 kr.ac.hansung으로 지정했기 때문에 DAO의 패키지명을 kr.ac.hansung.dao로 패키지를 만들고 내부에 다음과 같은 OfferDAO라는 클래스를 생성한다. @Repository 에노테이션을 통해 DAO클래스임을 명시하고 @Autowired를 통해 DB에 접근할 jdbctemplet을 의존성 주입시킨다.
package kr.ac.hansung.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import kr.ac.hansung.model.Offer;
@Repository
public class OfferDAO {
private JdbcTemplate jdbcTemplateObject;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public int getRowCount() {
String sqlStatement = "select count(*) from offers";
return jdbcTemplateObject.queryForObject(sqlStatement, Integer.class);
}
public Offer getOffer(String name) {
String sqlStatement = "select * from offers where name=?";
return jdbcTemplateObject.queryForObject(sqlStatement, new Object[]{name}, new RowMapper<Offer>() {
@Override
public Offer mapRow(ResultSet rs, int rowNum) throws SQLException {
Offer offer = new Offer();
offer.setId(rs.getInt("id"));
offer.setName(rs.getString("name"));
offer.setEmail(rs.getString("email"));
offer.setText(rs.getString("text"));
return offer;
}
});
}
public List<Offer> getOffers() {
String sqlStatement = "select * from offers";
return jdbcTemplateObject.query(sqlStatement, new RowMapper<Offer>() {
@Override
public Offer mapRow(ResultSet rs, int rowNum) throws SQLException {
Offer offer = new Offer();
offer.setId(rs.getInt("id"));
offer.setName(rs.getString("name"));
offer.setEmail(rs.getString("email"));
offer.setText(rs.getString("text"));
return offer;
}
});
}
public boolean insert(Offer offer) {
String sqlStatement = "insert into offers (name, email, text) values (?,?,?)";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text}) == 1);
}
public boolean update(Offer offer) {
String sqlStatement = "update offers set name=?, email=?, text=? where id=?";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
int id = offer.getId();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text, id}) == 1);
}
}
Offer
kr.ac.hansung.model 패키지를 생성하고 다음과 같은 Offer클래스를 생성한다. lombok의 게터 세터 에노테이션을 통하여 게터 세터를 만든다
package kr.ac.hansung.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Offer {
private int id;
private String name;
private String email;
private String text;
public Offer() {
}
public Offer(String name, String email, String text) {
this.name = name;
this.email = email;
this.text = text;
}
public Offer(int id, String name, String email, String text) {
this.id = id;
this.name = name;
this.email = email;
this.text = text;
}
@Override
public String toString() {
return "Offer [id=" + id + ", name=" + name + ", email=" + email + ", text=" + text + "]";
}
}
OfferService
kr.ac.hansung.service패키지를 생성하고 다음과 같은 OfferService 클래스를 생성한다. Service에노테이션을통해 이클래스가 서비스임을 명시하고 Autowired를 통해 DAO객체를 의존성 주입시킨다.
package kr.ac.hansung.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.ac.hansung.dao.OfferDAO;
import kr.ac.hansung.model.Offer;
@Service
public class OffersService {
private OfferDAO offersDao;
@Autowired
public void setOffersDao(OfferDAO offersDao) {
this.offersDao = offersDao;
}
public List<Offer> getCurrent() {
// TODO Auto-generated method stub
return offersDao.getOffers();
}
}
#7) service-context.xml dao-context.xml 생성하기
기존의 HomeController가 작동할 수 있었던 이유는 web.xml을 보면 알 수 있다. 먼저 에노테이션이 활성화되어있고 ViewResolver를 사용하여 jsp의 이름을 받을 경우 name.jsp로 변경하여 연결해주는 것을 확인할 수 있다. 또한 base-package가 정해져 있어 패키지의 에노테이션을 확인하는것을 알 수 있다. 이처럼 service와 dao의 bean을 생성하고 autowired해주기위해 service-context.xml 와 dao-context.xml을 생성한다.
먼저 src/main/webapp/WEB-INF/spring/appServlet에 다음과 같은 service-context.xml을 생성한다. kr.ac.hansung.service패키지를 스캔하도록 되어있으며 에노테이션을 사용한다고 적혀있다.
다음으로는 service-context.xml과 같은 위치인 src/main/webapp/WEB-INF/spring/appServlet에 다음과 같은 dao-context.xml을 생성한다. dao-context에는 dbcp를 활용하여 DB에 접근하게 할 dataSource Bean이 정의되어있다. 또한 placeholder로 dbcp에서 참고할 properties파일의 위치가 정의되어있다.
src/main/webapp/WEB-INF에 props라는 폴더를 생성하고 다음과 같은 jdbc.properties 파일을 생성한다. 이는 dao-context.xml에서 dbcp를 이용하여 db에 접근할 때 사용하는 파일으로서 다음과 같다.
jdbc.username = 아이디를 입력
jdbc.password = 비밀번호를 입력
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/csemall
#9) web.xml 수정하기
이렇게 dao와 service를 생성하였어도 실행할 경우 에러가 발생한다. 이는 스프링에서 dao-context.xml와 service-context.xml을 바라보고 있지 않기 때문이다. 따라서 다음과 같이 web.xml을 수정한다. 기존에는 root-context.xml만 바라보고 있었지만 생성한 service-context.xml과 dao-context.xml을 추가해줌으로써 스프링이 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 http://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
/WEB-INF/spring/appServlet/service-context.xml
/WEB-INF/spring/appServlet/dao-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>
</web-app>
#10) offers.jsp수정
DB에 존재하는 정보를 받아오는 jsp를 구성하기 위해 다음과 같이 offers.jsp를 수정한다.
<%@ page language="java" contentType="text/html; charset=US-ASCII"
pageEncoding="US-ASCII"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Insert title here</title>
</head>
<body>
<c:forEach var="offer" items="${offers}">
<p>
<c:out value="${offer}">
</c:out>
</p>
</c:forEach>
</body>
</html>
1. 사용자는 /으로 접속하게 되면 HomeController에 의해 home.jsp로 접속하게 된다.
2. 사용자가 offers로 요청을 보내게 되면 디스패쳐 서블릿에 의해 OffersController로 요청을 전달하고, /offers로 매핑된 showOffers함수를 호출한다. showOffers는 autowired 된 offerService의 getCurrent() 함수를 호출한다.
3. offerService의 getCurrent함수는 autowried 된 offerDAO의 getOffers를 호출한다
4. offerDAO의 getOffers는 DB에 저장된 객체를 리스트로 저장하여 서비스로 반환하고 서비스는 다시 컨트롤러로 반환한다.
5. 컨트롤러에서는 모델을 이용하여 view에 있는 jsp로 결과값과 함께 반환하고, 사용자는 마지막으로 반환된 offers.jsp를 확인하게 된다.
DI는 말 그대로 의존성을 주입한다 라는 뜻이다. 쉽게 설명하면 JAVA에서 객체를 new를 이용하여 생성하는데 이렇게 사용하지 않고 외부에서 생성한 객체를 세터 또는 생성자를 통해 사용하겠다는 의미이다. 이를 의존성 주입이라 한다. 의존성 주입 방법에는 세터를 이용하거나 생성자를 이용한 방법 두가지가 있다. 예제로는 생성자를 이용한 방법으로 예제를 공부하겠다
DI 사용하지 않은 예시
그림처럼 DI를 이용하지 않고 new를 사용하여 객체를 생성하게 되면 PetOwner와 Dog간 강한 결합이 생기게 되어 도그를 수정하면 PetOwner가 수정될 수 있다.
DI
반면 다음과 같이 Bean Container를 이용하여 객체를 생성하고 의존성 주입을 시켜주면 결합이 강하지 않다는 장점이 생긴다. 이때 Bean Container는 XML로 이루어져 있다.
DI 설명
다음 그림처럼 메인함수에서는 ApplicationContext라는 BeanContainer를 생성하고 미리 작성된 xml에 따라 ApplicationContext는 Dog, Cat, PetOwner라는 객체를 생성하고 Dog, Cat을 PetOwner에 의존성 주입한다. 스프링은 new방식 대신 DI방식을 권장한다.
2. 의존성 주입 실습
#1 프로젝트 생성
File > New > Spring Legacy Project를 클릭하고 다음과 같은 Simple Spring Maven 프로젝트를 생성한다. 프로젝트명은 원하는 프로젝트명을 사용해도 무방하다. 이 실습에서는 helloDI라는 프로젝트명을 사용하겠다.
프로젝트 생성
#2 AnimalType, Cat, Dog 생성하기
src/main/java에 클래스와 인터페이스를 담을 패키지를 생성한다 패키지명은 자유로워도 좋다. 패키지를 생성하였다면 패키지 아래에 AnimalType.java라는 인터페이스를 생성한다
인터페이스 코드는 다음과 같다 인터페이스를 implements할 cat, dog클래스들이 사용할 sound라는 메소드를 정의하고 있다.
AnimalType.java
package kr.ac.hansung.helloDI;
public interface AnimalType {
public void sound();
}
다음으로 Cat.java라는 클래스를 생성한다. 게터와 세터가 만들어져 있으며 AnimalType의 sound메소드를 사용하고 있다.
Cat.java
package kr.ac.hansung.helloDI;
public class Cat implements AnimalType {
public String getMyName() {
return myName;
}
public void setMyName(String myName) {
this.myName = myName;
}
private String myName;
@Override
public void sound() {
System.out.println("Cat name = " + myName + ":" + "Meow");
}
}
마지막으로 Dog.java라는 클래스를 생성한다. 게터와 세터가 만들어져 있으며 AnimalType의 sound메소드를 사용하고 있다.
Dog.java
package kr.ac.hansung.helloDI;
public class Dog implements AnimalType {
public String getMyName() {
return myName;
}
public void setMyName(String myName) {
this.myName = myName;
}
private String myName;
@Override
public void sound() {
System.out.println("Dog name = " + myName + ":" + "Bow Wow");
}
}
#3 PetOwner클래스 생성하기
petOwner클래스는 AnimalType의 animal을 받는다 이는 cat이나 dog를 받아서 사용하겠다는 의미이고 play라는 메소드와 생성자를 정의했다.
PetOWner.java
package kr.ac.hansung.helloDI;
public class PetOwner {
private AnimalType animal;
public PetOwner(AnimalType animal) {
this.animal = animal;
}
public void play() {
animal.sound();
}
}
#4 animal.xml 생성하기
Bean Container가 생성하기 위해 참고할 xml파일을 생성한다 코드는 다음가 같다.
<bean id = "dog"> 부분이 dog라는 클래스의 객체를, <bean id = "cat"> 부분이 cat이라는 클래스의 객체를 <bean id = petOwner"> 부분이 petOwner의 객체를 생성하고 <constructor-arg ref = "dog">는 dog객체를 의존성 주입하겠다 라는 의미이며, <property name="myName" value="poddle">는 도그의 myName의 값을 poodle로 주겠다는 의미이다.
마지막으로 메인 함수인 MainApp.java를 다음과 같이 작성한다. ApplicationContext가 animal.xml을 바라보게 하고
petOwner의 person이 context의 getBean함수를 통해 객체를 가져와 사용하는 모습이다.
package kr.ac.hansung.helloDI;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("/kr/ac/hansung/helloDI/conf/animal.xml");
PetOwner person = (PetOwner) context.getBean("petOwner");
person.play();
context.close();
}
}
#5 프로젝트 Run
프로젝트를 Run 하게 되면 다음과 같은 결과를 확인할 수 있다.
결과화면
사용자는 animal.xml에서 정의한 대로 dog, cat, petOwner의 객체를 먼저 생성하고, 이후 dog를 petOwner에 주입하였으니 결과 화면에는 dog가 보이게 된다 만약 cat을 주입하고 싶다면 constructor-arg의 ref = cat으로 수정해주면 된다