본문 바로가기

회고록(TIL&WIL)

TIL 2023.03.07 Spring framework 3.X (STS 3.X, AOP, Service, docker-volume)

Spring Tool Suit 3.X

최초 세팅

개발에 필요한 것은 아니나 IDE가 자바높은버젼으로 만들어 졌기 때문에 11 설치필요함
STS 4, boot 는 17이상이 필요하다.

winget install AdoptOpenJDK.OpenJDK.11

STS.ini vm은 추가 jdk,xms는 수정

-vm
C:\Program Files\AdoptOpenJDK\jdk-11.0.11.9-hotspot\bin\javaw.exe
...
-Dosgi.requiredJavaVersion=11
-Xms1024m

기존에 이클립스에서 쓰던 워크스페이스를 사용할 경우 .metadata와 server를 삭제해주는 것이 좋다.

Server생성

sts-bundle 경로에 tomcat 폴더 추가 후 해당 경로 복사

Server tomcat8.5 버젼 선택 후 경로를 붙여넣은 뒤 Download and Install

Project 생성

Spring Legacy Project

MVC project

UTF-8 셋팅

 

 

pom.xml 수정

 

web.xml 헤더부분 수정

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 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_3_1.xsd">
...

build path에서 servlet 버전 수정

pom.xml dependency 추가

<!-- spring -->
...
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-JDBC</artifactId>
  <version>${org.springframework-version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>${org.springframework-version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>${org.springframework-version}</version>
</dependency>
...
<!-- AspectJ -->
...
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>
...
<!-- Test -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
<!-- mysql -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.32</version>
</dependency>
<!-- commons-dbcp -->
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>

CRUD

VO 생성, Dao interface 정의, DaoImpl 생성

import ...;

public class DeptDaoImpl implements DeptDao {
	JdbcTemplate jdbcTemplate;
	
	public DeptDaoImpl(JdbcTemplate jdbcTemplate) {
		super();
		this.jdbcTemplate = jdbcTemplate;
	}

	@Override
	public List<DeptVo> findAll() {
		String sql = "select * from dept";
		return jdbcTemplate.query(sql, new RowMapper<DeptVo>() {
			@Override
			public DeptVo mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new DeptVo(rs.getInt("deptno"), rs.getString("dname"), rs.getString("loc"));
			}
		});
	}

	@Override
	public DeptVo findObject(int pk) {
		String sql = "select * from dept where deptno=?";
		return jdbcTemplate.queryForObject(sql, new RowMapper<DeptVo>() {
			@Override
			public DeptVo mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new DeptVo(rs.getInt("deptno"), rs.getString("dname"), rs.getString("loc"));
			}
		}, pk);
	}

	@Override
	public void insertOne(int deptno, String dname, String loc) {
		String sql = "insert into dept value (?,?,?)";
		jdbcTemplate.update(sql, deptno, dname, loc);

	}

	@Override
	public int updateOne(DeptVo bean) {
		String sql = "update dept set dname=?, loc=? where deptno=?";
		return jdbcTemplate.update(sql, bean.getDname(), bean.getLoc(), bean.getDeptno());
	}

	@Override
	public int deleteOne(int pk) {
		String sql = "delete from dept where deptno=?";
		return jdbcTemplate.update(sql, pk);
	}

}

Dao를 읽을 Config파일 생성

생성 시 next 후 aop, beans, p, tx 선택하여 생성

DBCP 를 사용하는 config 설정

<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

  <!-- DBCP -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://192.168.99.100:3306/lecture"/>
		<property name="username" value="scott"/>
		<property name="password" value="tiger"/>
	</bean>
  <!-- JDBC -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
  <!-- DAO -->
	<bean id="deptDao" class="com.bit.sts05.model.DeptDaoImpl">
		<constructor-arg ref="jdbcTemplate"></constructor-arg>
	</bean>

</beans>

Junit Test Case 생성

package com.bit.sts05.model;

import static org.junit.Assert.*;

import ...;

public class DeptDaoImplTest {
	@Test
	public void testFindAll() {
		ApplicationContext ac = null;
		ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		DeptDao deptDao = (DeptDao) ac.getBean("deptDao");
		assertNotNull(deptDao.findAll());
		assertTrue(deptDao.findAll().size()>0);
	}
...
}

AOP 사용

AOP용어

aspect
Advice(부가기능) + PointCut(advice를 어디에 적용시킬 것인지 결정)
AOP의 기본 모듈이다.
핵심기능 코드 사이에 침투된 부가기능을 독립적인 aspect로 구분해 낼수 있다.
구분된 부가기능 aspect를 런타임 시에 필요한 위치에 동적으로 참여하게 할 수 있다.
싱글톤 형태의 객체로 존재한다.
Target
핵심 기능을 담고 있는 모듈로 타겟은 부가기능을 부여할 대상이 된다.
Advice
타겟에 제공할 부가기능을 담고 있는 모듈
Join Point
어드바이스가 적용될 수 있는 위치
Pointcut
어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식이다.
포인트컷 표현식은 execution으로 시작하고 메서드의 Signature를 비교하는 방법을 주로 이용한다.
Weaving
포인트컷에 의해서 결정된 타겟의 조인 포인트에 부가기능(advice)를 삽입하는 과정을 뜻한다.
AOP가 핵심기능(타겟)의 코드에 영향을 주지 않으면서 필요한 부가기능(advice)를 추가할 수 있도록
해주는 핵심적인 처리과정이다.

Advice를 구현 클래스 정의

public class DaoAdvice {
	public void before(JoinPoint joinPoint) {
		System.out.println("before - " + joinPoint.getSignature().getName());
	}
	public void afterReturning(JoinPoint thisJoinPoint, Object rv){
		System.out.println("afterReturning...");
		System.out.println(rv);
	}
	public void afterThrowingTargetMethod(JoinPoint joinPoint, Exception ex) throws Exception{
		System.out.println(ex.toString());
	}
	public void afterTargetMethod(JoinPoint joinPoint) throws Exception{
		System.out.println(joinPoint.getThis());
	}
	public Object aroundTargetMethod(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("around before..");
		Object obj = null;
		try {
		obj = joinPoint.proceed();
		System.out.println("after returning.. return value : " + obj);
		} catch (Exception e) {
			System.out.println("after throws..");
		}
		return obj;
	}
}

applicationContext.xml 에 AOP 설정 추가

전자 정부 공식 문서 - XML AOP 지원
전자 정부 공식 문서 - @Aspectj AOP 지원

<!-- AOP aspectJ-->
<bean id="advice" class="com.bit.sts05.utils.DaoAdvice"></bean>
<aop:config>
  <!-- 뭐가 동작할때? -->
  <aop:pointcut expression="execution(* com.bit.sts05.model.DeptDao.find*(..))" id="cut1"/>
  <aop:pointcut expression="execution(* com.bit.sts05.model.DeptDao.findAll(..))" id="cut2"/>
  <!-- 어떤 관점을 기준으로? -->
  <aop:aspect ref="advice"> 
    <!-- 해당 관점의 어느 시점에서 어떤 행동을? -->
    <aop:before method="before" pointcut-ref="cut2"/>
    <aop:after-returning method="afterReturning" pointcut-ref="cut1" returning="rv"/> 
    <aop:after-throwing method="afterThrowingTargetMethod" pointcut-ref="cut1" throwing="ex"/>
    <aop:around method="aroundTargetMethod" pointcut-ref="cut2"/>
  </aop:aspect>
</aop:config>
<aop:aspectj-autoproxy/>

Service

Dao는 하나의 기능을 만들어 내는 것이고 Service에선 여러개의 기능(dao)을 하나로 묶어서 트랜잭션을 처리 할 수 있다.

DeptServiceImpl

package com.bit.sts05.service;

import java.util.List;

import com.bit.sts05.model.DeptDao;
import com.bit.sts05.model.DeptVo;

public class DeptServiceImpl implements DeptService {
	DeptDao deptDao;
	public void setDeptDao(DeptDao deptDao) {
		this.deptDao = deptDao;
	}
	
	@Override
	public List<?> getList() {
		return deptDao.findAll();
	}
	
	@Override
	public void pushList(DeptVo target) {
		deptDao.seqUpdateOne();
		deptDao.insertOne(target);
	}
	
	@Override
	public DeptVo pullList(int index) {
		// 조회수 증가 dao - update
		return deptDao.findObject(index);
	}
	
	@Override
	public boolean editList(DeptVo target) {
		if(deptDao.updateOne(target)>0) return true;
		return false;
	}
	
	@Override
	public boolean removeList(int index) {
		if(deptDao.deleteOne(index)>0) return true;
		return false;
	}
}

applicationContext.xml

<!-- Service -->
<bean id="deptService" class="com.bit.sts05.service.DeptServiceImpl">
  <property name="deptDao" ref="deptDao"></property>
</bean>
<!-- Transaction -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:method name="*" rollback-for="Exception"/>
  </tx:attributes>
</tx:advice>
<aop:config>
  <aop:pointcut expression="execution(* com.bit.sts05.service.DeptService.*(..))" id="listService"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="listService"/>
</aop:config>

Junit Test

package com.bit.sts05.service;

import ...;

public class DeptServiceImplTest {
	static ApplicationContext ac = null;
	private DeptService deptService;
	private int size;
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		ac = new GenericXmlApplicationContext("classpath:/applicationContext.xml");
	}

	@Before
	public void setUp() throws Exception {
		deptService= ac.getBean(DeptService.class);
	}

	@Test
	public void testGetList() {
		List<?> list = deptService.getList();
		assertNotNull(list);
		size = list.size();
		assertNotSame(0, size);
	}

	@Test
	public void testPushList() {
		int before = deptService.getList().size();
		DeptVo target = new DeptVo(0, "123456789012345","err");
		deptService.pushList(target);
		assertSame(before+1, deptService.getList().size());
...
}

웹 프로젝트 화 + View 추가

web.xml 수정

<!-- 기존에 작성한 xml로 지정 -->
  <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:/applicationContext.xml</param-value>
	</context-param>
...
<!-- 한글 깨짐 방지 filter -->
	<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>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

CRUD

Controller

@Controller
@RequestMapping("/dept")
public class DeptController {
	
	@Autowired
	DeptService deptService;
	
	@RequestMapping("/")
	public String list(Model model) {
		model.addAttribute("list", deptService.getList());
		return "dept/list";
	}
	
	@RequestMapping(value = "/add", method = RequestMethod.GET)
	public void addForm() {}
	
	@RequestMapping(value = "/add", method = RequestMethod.POST)
	public String add(@ModelAttribute("bean") DeptVo bean) {
		deptService.pushList(bean);
		return "redirect:./";
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.GET)
	public void detail(int deptno, Model model) {
		model.addAttribute("bean", deptService.pullList(deptno));
	}
	
	@RequestMapping(value = "/detail", method = RequestMethod.POST)
	public String remove(int deptno) {
		deptService.removeList(deptno);
		return "redirect:./";
	}
	
	@RequestMapping(value = "/edit", method = RequestMethod.GET)
	public void editForm(int deptno, Model model) {
		model.addAttribute("bean", deptService.pullList(deptno));
	}
	
	@RequestMapping(value = "/edit", method = RequestMethod.POST)
	public String editForm(@ModelAttribute("bean") DeptVo bean) {
		deptService.editList(bean);
		return "redirect:./detail?deptno="+bean.getDeptno();
	}
	
}

docker에서 volume 옵션을 이용하여 파일 복사하여 container 생성

동일한 볼륨을 사용하도록 설정하면 같은 파일을 사용할 수 있도록 할 수 있다.
mysql 데이터와 같은 것들을 볼륨설정해두면 다른 컨테이너에서도 동일한 데이터를 확인할 수 있게된다.

docker volume create vol1
docker run -it -v vol1:/temp2 61c45 /bin/bash
 
docker volume create mysqlData
docker run -it -d -p 13306:3306 -e MYSQL_ROOT_PASSWORD=mysql -e MYSQL_USER=scott -e MYSQL_PASSWORD=tiger -e MYSQL_DATABASE=lecture -v mysqlData:/var/lib/mysql mysql

앞선 컨테이너에서 사용된 데이터들이 volume에 저장되며 기존 컨테이너를 죽이고 새로운 컨테이너를 만들 때 해당 볼륨만 지정해서 넣어주면 데이터가 유지가 된다.