Spring

서비스 객체

영카이브 2024. 2. 29. 22:55

서비스 객체 사용하기 

 

 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 정보 분리

  1. 보안 강화: Connection 정보는 주로 데이터베이스에 접속하는 데 사용된다. 이러한 정보가 코드 내에 하드코딩되어 있으면 소스 코드를 얻은 공격자가 데이터베이스에 접근할 수 있는 위험이 있다. 따라서 Connection 정보를 별도의 설정 파일에 저장하고, 이 파일을 암호화하거나 접근 권한을 제한하여 보안을 강화할 수 있다.
  2. 유지보수의 용이성: 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;
	}
	
}
 
pom.xml  JDBC라이브러리 추가
	<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