본문 바로가기

회고록(TIL&WIL)

TIL 2023.03.08 Spring framework 3.X(docker_volume, lombok, @Aspectj, Transaction, mybatis)

docker - mysql

volume을 이용하여 데이터 유지하기 때문에 서버가 꺼지면 컨테이너도 지워지도록 설정(--rm)해도 괜찮음.
앞으로 mysql를 켤 때 아래의 코드로 실행하면 기존의 데이터도 유지되는 mysql를 컨테이너로 띄울 수 있다.

docker volume create dummySql

docker run -it --rm -d -p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=mysql \
-e MYSQL_DATABASE=lecture \
-e MYSQL_USER=scott -e MYSQL_PASSWORD=tiger \
--name mysql8 -v dummySql:/var/lib/mysql mysql

Spring framework legacy project 기본 세팅

pom.xml - 자바버전 1.8로 수정, plugin 부분도 수정

dependency 추가

  • servlet, jsp 최신버전으로
  • spring aop, test, jdbc 추가(버전은 동일하도록)
  • mysql connect-j 추가
  • mybatis-spring, mybatis 추가
  • common dbcp 추가

web.xml - 스키마 서버와 동일하게 변경, utf-8 encoding 필터 추가

root-context.xml에서 사용할 namespace 추가 (p, tx, aop)

네임스페이스를 추가함으로써 기존의 DBCP, JDBC 작성 시 properties를 아래처럼 작성 할 수 있다.

<!-- DBCP -->
<bean id="driver" class="com.mysql.cj.jdbc.Driver"></bean>
<bean id="dataSource" 
  class="org.springframework.jdbc.datasource.SimpleDriverDataSource"
  p:driver-ref="driver"
  p:url="jdbc:mysql://192.168.99.100:3306/lecture"
  p:username="scott" p:password="tiger"/>
<!-- JDBC -->
<bean id="jdbcTemplate" 
  class="org.springframework.jdbc.core.JdbcTemplate"
  p:dataSource-ref="dataSource"/>
<bean id="deptService" 
    class="com.bit.sts06.service.DeptServiceImpl" 
    p:deptDao-ref="deptDao"/>
<bean id="deptDao" 
    class="com.bit.sts06.model.DeptDaoJdbcImpl"
    p:jdbcTemplate-ref="jdbcTemplate"/>

lombok

Vo를 작성할 때 어노테이션을 이용하여 보다 편하게 작성할 수 있도록 해주는 라이브러리

최초 설치 1회 다운로드

다운로드 받은 파일을 한글경로가 아닌 곳으로 이동 시킨 후 설치 시작
보통은 자동으로 STS.exe를 잡아주나 잡아주지 않을 경우 직접 잡아주면 끝

이후 프로젝트에 사용 시 dependency 추가

<!-- lombok -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.26</version>
</dependency>

Vo 작성

//@Getter
//@Setter
//@ToString
//@AllArgsConstructor
//@NoArgsConstructor
//@EqualsAndHashCode

//위의 어노테이션들을 한방에 하는 것
(생성자의 경우 명시하지 않을 경우 생성자는 기본생성자. 둘다 사용할 경우 둘다 명시해야함, 하나만 명시하면 하나만됨)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeptVo {
	private int deptno;
	private String dname, loc;
}

컴포넌트

Spring framework로 프로젝트를 생성했을 때 serlvet-context.xml에 기본적으로 추가되는 component-scan에 의해서 설정한 패키지아래의 클래스들을 스캔하는데
@Component이 달려있는 클래스들을 스캔하여 객체 생성 및 실행하는데 스프링에서 클래스들의 역할에 맞는 실행을 할 수 있도록 어노테이션을 제공한다. @Component를 상속 받은 하위 어노테이션들 @Controller -> @Repository -> @Service 순으로 객체를 생성 및 실행한다.
추가로 conponent-scan시 스캔하고 싶지않은 클래스, 어노테이션을 지정하거나 반대의 경우도 추가로 설정할 수 있다.
아래의 예제는 두개의 context.xml 에서 scan을 하다보니 객체가 2개씩 생기기 때문에 일부러 제외시켜준 것.

<!-- servlet-context.xml -->
<context:component-scan base-package="com.bit.sts06" >
  <context:exclude-filter type="regex" expression="com.bit.sts06.service.*"/>
  <context:exclude-filter type="regex" expression="com.bit.sts06.model.*"/>
</context:component-scan>

<!-- root-context.xml -->
<context:component-scan base-package="com.bit.sts06">
	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	<context:exclude-filter type="regex" expression="com.bit.sts06.HomeController"/>
	<context:exclude-filter type="regex" expression="com.bit.sts06.DeptController"/>
</context:component-scan>
@Controller
@RequestMapping("/dept")
public class DeptController {
	@Autowired
	DeptService deptService;
  ...
}
------------------
@Repository
public class DeptDaoJdbcImpl implements DeptDao {
	@Autowired
	JdbcTemplate jdbcTemplate;
  ...
}
------------------
@Service
public class DeptServiceImpl implements DeptService {
	@Autowired
	DeptDao deptDao;
  ...
}

AOP - @Aspect

기존 AOP를 작성하기 위해선 메서드를 작성하고 Xml에서 해당 메서드들을 직접 매핑을 해줬어야했는데 해당 작업을 @Aspect 어노테이션으로 생략할 수 있다.
xml에서는 아래의 코드 한줄만 추가 해준 후 AOP메서드를 작성한 클래스에 @Component와 @Aspect을 달아주면 자동으로 연결이 된다.

<!-- aop -->
<aop:aspectj-autoproxy/>
@Component
@Aspect
public class DaoAop {
	
	@Around(value = "execution(* com.bit.sts06.model.DeptDao.*(..))")
	public Object aroud(ProceedingJoinPoint joinPoint) {
		Object obj = null;
		try {
			System.out.println("before... "+ Arrays.toString(joinPoint.getArgs()));
			obj = joinPoint.proceed();
			System.out.println(obj);
		} catch (Throwable e) {
			System.out.println(">>>>>>"+e.toString());
		}
		System.out.println("after");
		return obj;
	}

	@Before(value = "execution(* com.bit.sts06.model.DeptDao.*(..))")
	public void before(JoinPoint joinPoint) {
		System.out.println("before dao... : " + Arrays.toString(joinPoint.getArgs()));
	}
	
	@AfterReturning(
			value = "execution(* com.bit.sts06.model.DeptDao.*(..))",
			returning = "rv"
			)
	public void afterSuccess(JoinPoint joinPoint, Object rv) {
		System.out.println("after dao... data : " + rv);
	}
	
	@AfterThrowing(
			value = "execution(* com.bit.sts06.model.DeptDao.*(..))",
			throwing = "err"
			)
	public void afterError(JoinPoint joinPoint, Exception err) {
		System.out.println(err.toString());
	}
	
	@After(value = "execution(* com.bit.sts06.model.DeptDao.*(..))")
	public void after(JoinPoint joinPoint) {
		System.out.println("after");
	}
}

Transaction

xml에 아래 코드를 작성한뒤 Transaction이 필요한 메서드에 @Transactional 을 달아주면 트랜잭션이 적용된다.

<!-- transaction -->
<bean id="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
	p:dataSource-ref="dataSource"/>
<!-- 객체 이름을 transactionManager로하면 생략이 가능하다 -->
<!-- <tx:annotation-driven transaction-manager="transactionManager"/> -->
<tx:annotation-driven />
@Transactional
@Override
public List<DeptVo> selectAll() {
	return deptDao.findAll();
}

@Transactional
@Override
public void insertOne(DeptVo bean) {
	deptDao.insertOne(bean);
}

mybatis

dependency 추가

<!-- mybatis-spring -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

root-context.xml 에 코드 추가

<!-- mybatis -->
<bean id="sqlSessionFactory"
	class="org.mybatis.spring.SqlSessionFactoryBean"
	p:configLocation="classpath:/mybatis-config.xml"
	p:dataSource-ref="dataSource"/>

<bean id="sqlSession" 
	class="org.mybatis.spring.SqlSessionTemplate">
	<constructor-arg ref="sqlSessionFactory"></constructor-arg>
</bean>

resources 폴더에 mybatis-config.xml 추가

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <mappers>
    <mapper resource="mapper/dept-mapper.xml"/>
  </mappers>
</configuration>

mapper/dept-mapper.xml 추가

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dept">
  <select id="selectAll" resultType="com.bit.sts06.model.DeptVo">
  	select * from dept order by deptno desc
  </select>
  <select id="selectOne" parameterType="int" resultType="com.bit.sts06.model.DeptVo">
  	select * from dept where deptno=#{val}
  </select>
  <insert id="insertOne" parameterType="com.bit.sts06.model.DeptVo">
  	insert into dept value (#{deptno},#{dname},#{loc})
  </insert>
  <update id="updateOne" parameterType="com.bit.sts06.model.DeptVo">
  	update dept set dname=#{dname}, loc=#{loc} where deptno=#{deptno}
  </update>
  <delete id="deleteOne" parameterType="int">
  	delete from dept where deptno=#{val}
  </delete>
</mapper>

DaoImpl 변경

import ...;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class DeptDaoMyImpl implements DeptDao {
	@Autowired
	SqlSession sqlSession;

	@Override
	public List<DeptVo> findAll() {
		// 파라미터로 namespace.id
		return sqlSession.selectList("dept.selectAll");
	}

	@Override
	public DeptVo findObject(int key) {
		return sqlSession.selectOne("dept.selectOne", key);
	}

	@Override
	public void insertOne(DeptVo bean) {
		sqlSession.insert("dept.insertOne", bean);
	}

	@Override
	public int updateOne(DeptVo bean) {
		return sqlSession.update("dept.updateOne", bean);
	}

	@Override
	public int deleteOne(int key) {
		return sqlSession.delete("dept.deleteOne", key);
	}
}