서비스 객체
서비스 객체 사용하기
1. IOC 컨테이너에 noticeService라는 이름의 NoticeService 클래스의 인스턴스가 등록되며, 필요한 곳에서 이 빈을 주입하여 사용할 수 있다. Spring에서 빈(Bean)을 등록한다는 것은 해당 클래스의 인스턴스를 생성하고, Spring IOC 컨테이너에 등록하여 Spring이 이를 관리하도록 만드는 것이다.
<bean id="noticeService" class="com.newlecture.web.service.NoticeService" />
2. "/notice/list" 경로로 요청이 들어왔을 때 이를 처리할 ListController 클래스를 등록하고, ListController 클래스 내의 service 필드에 "noticeService" 빈을 주입한다는 것을 의미한다. (DI)
<bean name="/notice/list" class="com.newlecture.web.controller.notice.ListController">
<!-- name="service" : ListController의 setNoticeService() -->
<!-- ref="noticeService" : noticeService을 주입 -->
<!--
public void setNoticeService(NoticeService noticeService) {
this.noticeService = noticeService;
} -->
<property name="service" ref="noticeService" />
</bean>
3. "notice.list"라는 뷰를 템플릿으로 사용하고, 해당 뷰에서 "list"라는 이름으로 공지사항 목록 데이터를 사용할 수 있도록 한다. jstl 라이브러리와 EL을 사용해서 .jsp에 설정한다.
public class ListController implements Controller {
private NoticeService noticeService;
// 의존성 주입을 위한 setter 메서드
public void setService(NoticeService noticeService) {
this.noticeService = noticeService;
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ModelAndView 객체 생성, 해당 요청에 대한 응답을 구성하기 위해 사용
ModelAndView mv = new ModelAndView("notice.list");
// NoticeService를 사용하여 공지사항 목록을 가져옴
List<Notice> list= noticeService.getList(1, "TITLE", "");
// 가져온 목록을 ModelAndView 객체에 추가
mv.addObject("list", list);
// 구성된 ModelAndView 객체 반환
return mv;
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<main class="main">
<h2 class="main title">공지사항</h2>
<div class="breadcrumb">
<h3 class="hidden">경로</h3>
<ul>
<li>home</li>
<li>고객센터</li>
<li>공지사항</li>
</ul>
</div>
<div class="search-form margin-top first align-right">
<h3 class="hidden">공지사항 검색폼</h3>
<form class="table-form">
<fieldset>
<legend class="hidden">공지사항 검색 필드</legend>
<label class="hidden">검색분류</label> <select name="f">
<option value="title">제목</option>
<option value="writerId">작성자</option>
</select> <label class="hidden">검색어</label> <input type="text" name="q"
value="" /> <input class="btn btn-search" type="submit" value="검색" />
</fieldset>
</form>
</div>
<div class="notice margin-top">
<h3 class="hidden">공지사항 목록</h3>
<table class="table">
<thead>
<tr>
<th class="w60">번호</th>
<th class="expand">제목</th>
<th class="w100">작성자</th>
<th class="w100">작성일</th>
<th class="w60">조회수</th>
</tr>
</thead>
<tbody>
<c:forEach var="n" items="${list}">
<tr>
<!-- n.id : getId() -->
<td>${n.id}</td>
<td class="title indent text-align-left"><a href="detail">${n.title }</a></td>
<td>${n.writerId}</td>
<td>${n.regDate}</td>
<td>${n.hit}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
<div class="indexer margin-top align-right">
<h3 class="hidden">현재 페이지</h3>
<div>
<span class="text-orange text-strong">1</span> / 1 pages
</div>
</div>
<div class="margin-top align-center pager">
<div>
<span class="btn btn-prev" onclick="alert('이전 페이지가 없습니다.');">이전</span>
</div>
<ul class="-list- center">
<li><a class="-text- orange bold" href="?p=1&t=&q=">1</a></li>
</ul>
<div>
<span class="btn btn-next" onclick="alert('다음 페이지가 없습니다.');">다음</span>
</div>
</div>
</main>
서비스 객체 분리하기
서비스 객체를 분리해야한다.
1. 서비스 객체를 인터페이스를 통해 정의하고, 실제 구현은 이 인터페이스를 구현하는 클래스에서 수행한다.
기능에만 집중하는 서비스 인터페이스를 도입함으로써, 예를 들어 JDBC에서 JPA로 전환하는 경우와 같이 기술적인 변경이 발생할 때 시스템의 다른 부분에 최소한의 영향을 미칠 수 있다. 이는 서비스 인터페이스를 구현하는 구현체가 변경될 때, 이를 사용하는 코드가 변경되지 않도록 하는 것을 의미한다.
2. 각 구현체는 별도의 패키지에 위치하고, 서비스 인터페이스와는 독립적으로 관리한다. 이를 통해 코드 변경 없이도 데이터 액세스 기술을 변경할 수 있으며, 시스템을 더욱 유연하고 확장 가능하게 만들 수 있습니다.
service패키지를 jdbc와 jpa로 분리, 각각 JDBCNotcieServcie클래스와 JPANotcieService클래스가 존재한다.
NotcieService 인터페이스에 정의
public interface NoticeService {
List<Notice>getList(int page, String field, String query) throws ClassNotFoundException, SQLException;
int getCount() throws ClassNotFoundException, SQLException;
int insert(Notice notice) throws SQLException, ClassNotFoundException;
int update(Notice notice) throws SQLException, ClassNotFoundException;
int delete(int id) throws ClassNotFoundException, SQLException;
}
NotcieService 인터페이스를 구현하는 JDBCNotcieServcie.java
package com.newlecture.web.service.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.newlecture.web.entity.Notice;
import com.newlecture.web.service.NoticeService;
public class JDBCNoticeService implements NoticeService{
// 알맞게 입력하기
private String url = "";
private String uid = "";
private String pwd = "";
private String driver = "";
// Service 인터페이스 내용을 구현
public List<Notice>getList(int page, String field, String query) throws ClassNotFoundException, SQLException{
int start = 1 + (page-1)*10; // 1, 11, 21, 31, ..
int end = 10*page; // 10, 20, 30, 40...
String sql = "SELECT * FROM NOTICE_VIEW WHERE "+field+" LIKE ? AND NUM BETWEEN ? AND ?";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid, pwd);
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, "%"+query+"%");
st.setInt(2, start);
st.setInt(3, end);
ResultSet rs = st.executeQuery();
List<Notice> list = new ArrayList<Notice>();
while(rs.next()){
int id = rs.getInt("ID");
String title = rs.getString("TITLE");
String writerId = rs.getString("WRITER_ID");
Date regDate = rs.getDate("REGDATE");
String content = rs.getString("CONTENT");
int hit = rs.getInt("hit");
String files = rs.getString("FILES");
Notice notice = new Notice(
id,
title,
writerId,
regDate,
content,
hit,
files
);
list.add(notice);
}
rs.close();
st.close();
con.close();
return list;
}
// Scalar
public int getCount() throws ClassNotFoundException, SQLException {
int count = 0;
String sql = "SELECT COUNT(ID) COUNT FROM NOTICE";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid, pwd);
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
if(rs.next())
count = rs.getInt("COUNT");
rs.close();
st.close();
con.close();
return count;
}
public int insert(Notice notice) throws SQLException, ClassNotFoundException {
String title = notice.getTitle();
String writerId = notice.getWriterId();
String content = notice.getContent();
String files = notice.getFiles();
String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "INSERT INTO notice ( " +
" title," +
" writer_id," +
" content," +
" files" +
") VALUES (?,?,?,?)";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid, pwd);
//Statement st = con.createStatement();
//st.ex....(sql)
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, title);
st.setString(2, writerId);
st.setString(3, content);
st.setString(4, files);
int result = st.executeUpdate();
st.close();
con.close();
return result;
}
public int update(Notice notice) throws SQLException, ClassNotFoundException {
String title = notice.getTitle();
String content = notice.getContent();
String files = notice.getFiles();
int id = notice.getId();
String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "UPDATE NOTICE " +
"SET" +
" TITLE=?," +
" CONTENT=?," +
" FILES=?" +
"WHERE ID=?";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid, pwd);
//Statement st = con.createStatement();
//st.ex....(sql)
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, title);
st.setString(2, content);
st.setString(3, files);
st.setInt(4, id);
int result = st.executeUpdate();
st.close();
con.close();
return result;
}
public int delete(int id) throws ClassNotFoundException, SQLException {
String url = "jdbc:oracle:thin:@localhost:1521/xepdb1";
String sql = "DELETE NOTICE WHERE ID=?";
Class.forName(driver);
Connection con = DriverManager.getConnection(url,uid, pwd);
//Statement st = con.createStatement();
//st.ex....(sql)
PreparedStatement st = con.prepareStatement(sql);
st.setInt(1, id);
int result = st.executeUpdate();
st.close();
con.close();
return result;
}
}
dispatcher-servlet.xml
JDBC를 쓰고싶다면 JDBCNoticeServcie로, JPA를 쓰고 싶다면 jpa.JPANoticeServcie로 바꾼다.
따라서 자바코드 수정이 없고 xml설정파일에서 수정한다.
<bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService" />
Connection 정보 분리
- 보안 강화: Connection 정보는 주로 데이터베이스에 접속하는 데 사용된다. 이러한 정보가 코드 내에 하드코딩되어 있으면 소스 코드를 얻은 공격자가 데이터베이스에 접근할 수 있는 위험이 있다. 따라서 Connection 정보를 별도의 설정 파일에 저장하고, 이 파일을 암호화하거나 접근 권한을 제한하여 보안을 강화할 수 있다.
- 유지보수의 용이성: Connection 정보를 코드에서 분리하면 나중에 데이터베이스를 변경해야 할 때 훨씬 간편하게 변경할 수 있다.
private String url =
private String uid =
private String pwd =
private String driver =
이 부분을 dispatchrer-servlet.xml파일에 분리한다.
<bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="" />
<property name="url" value="" />
<property name="username" value=""/>
<property name="password" value="" />
</bean>
JDBCNoticeService.java
package com.newlecture.web.service.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.sql.DataSource;
public class JDBCNoticeService implements NoticeService{
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
// Service 인터페이스 내용을 구현
public List<Notice>getList(int page, String field, String query) throws ClassNotFoundException, SQLException{
int start = 1 + (page-1)*10; // 1, 11, 21, 31, ..
int end = 10*page; // 10, 20, 30, 40...
String sql = "SELECT * FROM NOTICE_VIEW WHERE "+field+" LIKE ? AND NUM BETWEEN ? AND ?";
//Class.forName(driver);
//Connection con = DriverManager.getConnection(url,uid, pwd);
Connection con = dataSource.getConnection();
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, "%"+query+"%");
st.setInt(2, start);
st.setInt(3, end);
ResultSet rs = st.executeQuery();
List<Notice> list = new ArrayList<Notice>();
while(rs.next()){
int id = rs.getInt("ID");
String title = rs.getString("TITLE");
String writerId = rs.getString("WRITER_ID");
Date regDate = rs.getDate("REGDATE");
String content = rs.getString("CONTENT");
int hit = rs.getInt("hit");
String files = rs.getString("FILES");
Notice notice = new Notice(
id,
title,
writerId,
regDate,
content,
hit,
files
);
list.add(notice);
}
rs.close();
st.close();
con.close();
return list;
}
}
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>
참고) JDBC 실행 순서
참조: https://m.blog.naver.com/pjok1122/221727915740

1) DB Driver를 Java 메모리 상으로 로드
2) Driver를 통해 Java와 DB를 연결(connection)
- 이 때 url, id, pw를 넘겨줌
- url은 db 위치, id는 db 계정, pw는 비밀번호
3) 연결이 완료되면, statement나 preparedstatement로 쿼리문을 전송
4) 쿼리문이 Select라면 ResultSet으로 받고, 그 외에는 int 타입으로 받음
5) 쿼리를 다 사용했다면, ResultSet부터 역순으로 리소스를 close() 진행
Spring 설정 파일 분리
dispatcher-servlet.xml을 아래와 같이 분리한다.
- security-context.xml(보안)
- service-context.xml(서비스)
- servlet-context.xml(입,출력)
이유는 분업화에 편리하기 때문이다. 또한 분리하지 않으면 작업을 할 시 기다리는 시간이 길어진다. 이것을 동기화라고 하면 동기화가 많아지면 문제가 일어날 가능성이 높아진다.
servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 디스패처 서블릿 설정 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<!-- IndexController 빈 설정 -->
<bean name="/index" class="com.newlecture.web.controller.IndexController" />
<!-- ListController 빈 설정 -->
<bean name="/notice/list" class="com.newlecture.web.controller.notice.ListController">
</bean>
<!-- DetailController 빈 설정 -->
<bean name="/notice/detail" class="com.newlecture.web.controller.notice.DetailController" />
<!-- ViewResolver 설정 -->
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView" />
<!-- 뷰리졸버보다 우선순위를 높게하였다. -->
<property name="order" value="1" />
</bean>
<!-- TilesConfigurer 설정 -->
<bean class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions" value="/WEB-INF/tiles.xml" />
</bean>
<!-- InternalResourceViewResolver 설정 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 뷰 파일 위치의 접두사 설정 -->
<property name="prefix" value="/WEB-INF/view/"></property>
<!-- 뷰 파일 확장자 설정 -->
<property name="suffix" value=".jsp"></property>
<property name="order" value="2" />
</bean>
<!-- 리소스 핸들러 설정 -->
<mvc:resources location="/static/" mapping="/**"></mvc:resources>
</beans>
service-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:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
<context:annotation-config />
<!-- 데이터베이스 연결 빈 설정 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="" />
<property name="username" value=""/>
<property name="password" value="" />
</bean>
<!-- NoticeService 빈 설정 -->
<bean id="noticeService" class="com.newlecture.web.service.jdbc.JDBCNoticeService">
<!-- <property name="dataSource" ref="dataSource" /> -->
</bean>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0" metadata-complete="true">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/service-context.xml
/WEB-INF/spring/security-context.xml
</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
참고 자료 출처 : (47~50)
https://www.youtube.com/watch?v=hDwlM0tmgKs&list=PLq8wAnVUcTFUHYMzoV2RoFoY2HDTKru3T&index=47