Spring Boot를 사용한 웹 MVC 애플리케이션 예제.

 

먼저, Spring Boot에서는 템플릿 엔진으로 Thymeleaf를 사용하고 있다.

 

본 예제는 DB에서 메뉴 정보를 조회하여 메뉴 정보를 가지고 메뉴 페이지로 이동하는 URL을 가정한 예제이다.

 

DB는 상황마다 다를 수 있으므로 DB에 관한 내용은 포함되어 있지 않다.

 

애플리케이션의 동작 순서

 

1. /menu URL을 입력하게 되면, MenuController의 goToMenu함수가 실행된다.

2. Spring Framework에 의해 생성된 MenuService의 getMenu함수가 실행된다.

3. 메뉴 정보를 조회한다.(현재는 하드코딩되어 있으며, DB 조회하는 내용으로 변경이 필요하다.)

4. 메뉴정보를 Model에 담는다.

5. Html에서 Thymeleaf을 사용하여 Object를 화면에 표시한다.

 

<!-- pom.xml의 dependencies -->
<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- spring-boot와 함께 thymeleaf도 포함 되어 있어야 한다. -->
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
</dependencies>
//DemoMvcApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoMvcApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoMvcApplication.class, args);
	}
}
//MenuController.java
package com.example.demo.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 com.example.demo.dto.MenuDTO;
import com.example.demo.service.MenuService;

@Controller
public class MenuController {

	@Autowired
	private MenuService menuService;
	
	@RequestMapping("/menu")
	public String goToMenu(Model model) {
		List<MenuDTO> menuList = menuService.getMenu();
		model.addAttribute("menuList", menuList);
		return "menu";
	}
}
//MenuService.java
package com.example.demo.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.example.demo.dto.MenuDTO;

@Service
public class MenuService {
	public List<MenuDTO> getMenu() {
		//DB에서 메뉴 데이터를 조회하는 내용으로 변경이 필요한 소스 시작
		List<MenuDTO> menuList = new ArrayList<MenuDTO>();
		MenuDTO menuDto = new MenuDTO();
		menuDto.setMenuId(1);
		menuDto.setParentMenuId(0);
		menuDto.setMenuName("Top");
		menuList.add(menuDto);
		menuDto = new MenuDTO();
		menuDto.setMenuId(2);
		menuDto.setParentMenuId(1);
		menuDto.setMenuName("First Menu");
		menuList.add(menuDto);
		menuDto = new MenuDTO();
		menuDto.setMenuId(3);
		menuDto.setParentMenuId(1);
		menuDto.setMenuName("Second Menu");
		menuList.add(menuDto);
		//DB에서 메뉴 데이터를 조회하는 내용으로 변경이 필요한 소스 끝
		return menuList;
	}
}
//MenuDTO.java
package com.example.demo.dto;

import java.util.Date;

import lombok.Data;

@Data
public class MenuDTO {
	private int menuId;
	private int parentMenuId;
	private String menuName;
	private Date regDate;
	private String regId;
	private Date modDate;
	private String modId;
}
<!-- /src/main/resources/templates/menu.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Menu</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div>Menu Page</div>
<ul th:each="menu: ${menuList}">
      <li th:text="${menu.menuName}"></li>
</ul>
</body>
</html>

 

Spring Framework에서는 AOP 사용을 위하여 XML방식도 지원을 한다.

        

AOP라는 네임스페이스 태그를 사용하면 된다.

 

XML 방식 사용 시, XML선언에 하단 내용을 추가해주여야 한다.

(기존 내용에서 xmlns:aop, xsi:schemaLocation의 aop 부분이 추가되었다.)

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

XML을 사용하여 AOP 전체 예제 내용

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

	<aop:config>
		<aop:aspect id="aspect" ref="schemaBasedCls">
		
			<aop:pointcut id="executionTest" expression="execution(* com.example.aop.PrintCls.*(..))"/>
			
			<aop:before 
				pointcut-ref="executionTest" 
				method="beforeTest"/>
			<aop:after-returning
        		pointcut-ref="executionTest"
       			returning="retVal"
        		method="executionAndReturnValTest"/>
		  	<aop:after-throwing
		        pointcut-ref="executionTest"
		        throwing="ex"
		        method="executionAndAfterThrowingValTest"/>
		  	<aop:after
        		pointcut-ref="executionTest"
        		method="executionAndAfterTest"/>
			<aop:around
        		pointcut-ref="executionTest"
        		method="executionAndAroundTest"/>
		</aop:aspect>
	</aop:config>
	
	<bean id="schemaBasedCls" class="com.example.aop.SchemaBasedCls"></bean>
	<bean id="printCls" class="com.example.aop.PrintCls"></bean>
</beans>
package com.example.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ImportResource;

import com.example.aop.PrintCls;

@SpringBootApplication
@ImportResource("classpath:aop.xml")
public class DemoSchemabasedApplication {
	private static final Logger logger = LoggerFactory.getLogger(DemoSchemabasedApplication.class);	
	
	public static void main(String[] args) {
		ConfigurableApplicationContext run = SpringApplication.run(DemoSchemabasedApplication.class, args);
		
		PrintCls bean = run.getBean(PrintCls.class);

		logger.info("--------------    bean method start	--------------");
		bean.printExample();
		logger.info("--------------    bean method end	--------------");
		
		logger.info("--------------    bean return method start    --------------");
		bean.returnStr();
		logger.info("--------------    bean return method end    --------------");
		
		logger.info("--------------    bean throw method start    --------------");
		try {
			bean.returnThrow();
		} catch (Exception e) {
		}
		logger.info("--------------    bean throw method end    --------------");
	}
}
package com.example.aop;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintCls {

	private static final Logger logger = LoggerFactory.getLogger(PrintCls.class);
	
	public void printExample() {
		logger.info("*** printExample");
	}
	
	public String returnStr() {
		String value = "testReturning";
		logger.info("*** print returning Str" + value );
		return value;
	}
	
	public void returnThrow() throws Exception {
		String msg = "execption test";
		logger.info("*** print throw " + msg);
		throw new Exception(msg);	
	}
}
package com.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SchemaBasedCls {
	private static final Logger logger = LoggerFactory.getLogger(SchemaBasedCls.class);
	
	//--before--
	public void beforeTest() {
		logger.info("before start");
		logger.info("before end");
	}
	//--before--
	
	//--AfterReturning--
	public void executionAndReturnValTest(String retVal) {
		logger.info("execution and afterReturning Value start");
		logger.info("Value :" + retVal);
		logger.info("execution and afterReturning Value end");
	}
	//--AfterReturning--
	
	//--AfterThrowing--
	public void executionAndAfterThrowingValTest(Exception ex) {
		logger.info("execution and AfterThrowing Value start");
		logger.error(ex.getMessage());
		logger.info("execution and AfterThorwing Value end");
	}
	//--AfterThrowing--
	
	//--After--
	public void executionAndAfterTest() {
		logger.info("execution and After start");
		logger.info("execution and AFter end");
	}
	//--After--
	
	//--Around--
	public void executionAndAroundTest(ProceedingJoinPoint pjp) {
		logger.info("execution and around start");
		logger.info(pjp.getSignature().getName() + " Before Method Execution");
		try {
			pjp.proceed();
		} catch (Throwable e) {
			logger.info(pjp.getSignature().getName() + "Throw Method Execution");
		} finally {
			// Do Something useful, If you have
		}
		logger.info(pjp.getSignature().getName() + " After Method Execution");
		logger.info("execution and around end");
	}
	//--Around
}

PointCut

- execution : 지정한 메서드가 일치하는 곳에서 Aspect 적용.

- within : 특정 타입에 대해서 Aspect 적용.

- bean : 스프링 빈 이름을 사용하여 빈의 메서드에 적용.

- 연산자를 통한 PointCut ( 예시 : @PointCut("executionTest() && withinTest()") )

 

Advice

-Before : 대상 메서드가 호출되기 전에 Advice 수행

-AfterReturning : 대상 메서드가 성공적으로 완료된 후에 Adivce 수행

-AfterThrowing : 대상 메서드가 예외를 던진 후에 Advice 수행

-After : 대상 메서드의 결과에 상관없이 Advice 수행 ( finally와 같이 최종적으로 수행 )

-Around : 대상 메서드를 감싸서 대상 메서드 호출 전과 후에 Advice 수행

 

Introductions

-DeclarParents : 인터페이스로 구현된 클래스를 대상 메서드의 부모로 추가할 수 있는 기능.

 

 

<!--pom.xml-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
	<version>2.4.2</version>
</dependency>
<!--service.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
     <bean id="printCls" class="com.example.aop.PrintCls"></bean>
     <bean id="aspectJTest" class="com.example.aop.AspectJTest"></bean>
</beans>
//DemoApplication.java

@SpringBootApplication
@EnableAspectJAutoProxy
@ImportResource("classpath:service.xml")
public class DemoApplication {
	private static final Logger logger = LoggerFactory.getLogger(DemoApplication.class);

	public static void main(String[] args) {
		ApplicationContext run = SpringApplication.run(DemoApplication.class, args);
		
		PrintCls bean = run.getBean(PrintCls.class);

		logger.info("--------------    bean method start	--------------");
		bean.printExample();
		logger.info("--------------    bean method end	--------------");
		
		logger.info("--------------    bean return method start    --------------");
		bean.returnStr();
		logger.info("--------------    bean return method end    --------------");
		
		logger.info("--------------    bean throw method start    --------------");
		try {
			bean.returnThrow();
		} catch (Exception e) {
		}
		logger.info("--------------    bean throw method end    --------------");
	}
}
//PrintCls.java

package com.example.aop;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintCls{
	private static final Logger logger = LoggerFactory.getLogger(PrintCls.class);
	
	public void printExample() {
		logger.info("*** printExample");
	}
	
	public String returnStr() {
		String value = "testReturning";
		logger.info("*** print returning Str" + value );
		return value;
	}
	
	public void returnThrow() throws Exception {
		String msg = "execption test";
		logger.info("*** print throw " + msg);
		throw new Exception(msg);	
	}

}
//DelcarInterface.java

package com.example.aop;

public interface DelcarInterface {
	public void declarInterfaceTest();
}
//DeclarInterfaceImpl.java

package com.example.aop;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DeclarInterfaceImpl implements DelcarInterface {
	private static final Logger logger = LoggerFactory.getLogger(DeclarInterfaceImpl.class);
	
	@Override
	public void declarInterfaceTest() {
		logger.info("/////////////// declarInterfaceTest ///////////////////");
	}
}
//AspectJTest.java

package com.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class AspectJTest {

	private static final Logger logger = LoggerFactory.getLogger(AspectJTest.class);

	//--PointCut--
	@Pointcut("execution(* com.example.aop.PrintCls.*(..))")
	public void executionTest() {
		logger.info("executeTest start");
		logger.info("executeTest end");
	}
	
	@Pointcut("within(com.example.aop.PrintCls)")
	public void withinTest() {
		logger.info("withinTest start");
		logger.info("withinTest end");
	}
	//--PointCut--
	
	//--Before--
	@Before("execution(* com.example.aop.PrintCls.*(..))")
	public void beforeTest() {
		logger.info("before start");
		logger.info("before end");
	}
	
	@Before("executionTest()")
	public void executionAndBefore() {
		logger.info("execution and Before start");
		logger.info("execution and Before end");
	}
	
	@Before("withinTest()")
	public void withinAndBefore() {
		logger.info("within and Before start");
		logger.info("within and Before end");
	}
	//--Before--
	
	
	//--AfterReturning--
	@AfterReturning("execution(* com.example.aop.PrintCls.*(..))")
	public void afterReturningTest() {
		logger.info("afterReturningTest start");
		logger.info("afterReturningTest end");
	}
	
	@AfterReturning("executionTest()")
	public void executionAndAfterReturning() {
		logger.info("execution and afterReturning start");
		logger.info("execution and afterReturning end");
	}
	
	@AfterReturning("withinTest()")
	public void withinAndAfterReturningTest() {
		logger.info("within and afterReturning start");
		logger.info("within and afterReturning end");
	}

	@AfterReturning(
			pointcut="executionTest()",
			returning="retVal")
	public void executionAndReturnValTest(String retVal) {
		logger.info("execution and afterReturning Value start");
		logger.info("Value :" + retVal);
		logger.info("execution and afterReturning Value end");
	}
	
	@AfterReturning(
			pointcut="withinTest()",
			returning="retVal")
	public void withinAndReturnValTest(String retVal) {
		logger.info("within and afterReturning Value start");
		logger.info("Value :" + retVal);
		logger.info("within and afterReturning Value end");
	}
	//--AfterReturning--
	
	//--AfterThrowing--
	 @AfterThrowing("executionTest()")
	 public void executionAndAfterThrowingTest() {
		 logger.info("execution and AfterThrowing start");
		 logger.info("execution and AfterThrowing end");
	 }
	 
	 @AfterThrowing("withinTest()")
	 public void withinAndAfterThrowingTest() {
		 logger.info("within and AfterThrowing start");
		 logger.info("within and AfterThrowing end");
	 }
	 
	 @AfterThrowing(
		        pointcut="executionTest()",
		        throwing="ex")
	 public void executionAndAfterThrowingValTest(Exception ex) {
		 logger.info("execution and AfterThrowing Value start");
		 logger.error(ex.getMessage());
		 logger.info("execution and AfterThorwing Value end");
	 }
	 
	 
	 @AfterThrowing(
		        pointcut="executionTest()",
		        throwing="ex")
	 public void withinAndAfterThrowingValTest(Exception ex) {
		 logger.info("execution and AfterThrowing Value start");
		 logger.error(ex.getMessage());
		 logger.info("execution and AfterThorwing Value end");
	 }
	 //--AfterThrowing--
	 
	 
	 //--After--
	 @After("executionTest()")
	 public void executionAndAfterTest() {
		 logger.info("execution and After start");
		 logger.info("execution and AFter end");
	 }
	 
	 @After("withinTest()")
	 public void withinAndAfterTest() {
		 logger.info("within and after start");
		 logger.info("within and after end");
	 }
	 //--After
	 
	 //--Around
	 @Around("executionTest()")
	 public void executionAndAroundTest(ProceedingJoinPoint  pjp){
		 logger.info("execution and around start");
		 logger.info(pjp.getSignature().getName() + " Before Method Execution");
		 try {
			 pjp.proceed();
		 } catch (Throwable e) {
			 logger.info(pjp.getSignature().getName() + "Throw Method Execution");
		 } finally {
			 // Do Something useful, If you have
		 }
		 logger.info(pjp.getSignature().getName() + " After Method Execution");
		 logger.info("execution and around end");
	 }
	 
	 @Around("withinTest()")
	 public void withinAndAroundTest(ProceedingJoinPoint  pjp) {
		 logger.info("within and around start");
		 logger.info(pjp.getSignature().getName() + " Before Method Execution");
		 try {
			 pjp.proceed();
		 } catch(Throwable e) {
			 logger.info(pjp.getSignature().getName() + "Throw Method Execution");
		 } finally {
			 // Do Something useful, If you have
		 }
		logger.info(pjp.getSignature().getName() + " After Method Execution");

		 logger.info("within and around end");
	 }
	 //--Around--
	 
	 
	 
	 //--Introductions ( DeclarParents )--
	 @DeclareParents(value="com.example.aop.PrintCls", defaultImpl=DeclarInterfaceImpl.class)
	 public static DelcarInterface mixin;

	 @Before("executionTest() && this(dInterface)")
	 public void declarTest(DelcarInterface dInterface) {
		 dInterface.declarInterfaceTest();
	 }
	 //--Introductions ( DeclarParents )--
}

AOP

- 기본적인 개념은 공통 관심 사항을 구현한 코드를 핵심로직을 구현한 코드 안에 삽입한다는 것.

   

AOP 용어

- Aspect : 공통 코드 ( ex : 트랙잭션, 로깅 )

- Advice : 공통코드를 적용하는 시점. ( 특정 메소드 실행전, 특정 메소드 실행후 등등 )

- Joinpoint : Advice가 적용 가능한 지점. 

- Pointcut : JointPoint중에서 Aspect가 적용되는 위치.

- Introduction : 함수나 멤버변수를 추가적으로 선언하는 기능

- Weaving : 핵심로직에서 Aspect가 동작이 되는 것.

    Weaving 방식

    1. 컴파일 시

    2. 클래스 로딩시

    3. 런타임 시

 

Spring에서 제공되는 AOP.

- @AspectJ ( 어노테이션 방식 )

- Schema-based AOP ( XML 방식 )



시스템의 요구사항이나 설계에 따라 상황에 맞는 AOP를 사용하면 되지만, 

Schema-based AOP가 가진 단점을 이해해야 한다.

 

1. XML방식이기 때문에 완전한 캡슐화 되지는 않는다.

- 설정파일에 XML과 빈(Bean)에 대한 선언이 분할되어 사용되기 때문이다.

2. XML에 선언 된 포인트 컷을 결합 할 수 없습니다. 

@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

DI ( Dependency Injection )은 객체 간의 결합을 느슨하게 만들어주는 개념이다.



1. 객체간의 강한 결합 vs 느슨한 결합

2. 왜 느슨한 결합이 좋은 것 인가?

3. Spring에서 DI를 어떻게 사용하면 되는 것 인가?

 

1. 객체간의 강한 결합 vs 느슨한 결합

public interface BookDAO{}
public class MagazineDAO implements BookDAO{ String monthly = ""; }
public class NovelDAO implements BookDAO{}


public interface Book{}
public class MagazineServiceImpl implements Book{
    private BookDAO bookDAO;
    public MagazineServiceImpl() {
        //강한 결합
        //MagazineService 객체가 생성되는 시점에 
        //MagazineDAO를 생성하여 사용하게 된다.
        bookDAO = new MagazineDAO();
    }
    
}

public class NovelServiceImpl implements Book{
    private BookDAO bookDAO;
    public NovelServiceImpl(BookDAO bookDAO) {
        //느슨한 결합
        //NovelService 객체가 생성되는 시점에
        //BookDAO가 자동으로 주입되어 사용하게 된다.
        this.bookDAO = bookDAO;
    }
}

 

2. 왜 강한 결합을 사용하면 안 좋은 것인가?

강한 결합을 사용하는 것이 반드시 나쁜 것이 아니다.

작은 프로젝트에서 강한 결합을 사용하도 무방하다.

그러나, 대형 프로젝트에서는 많은 클래스 객체들이 생성되고, 관리되어야 하기 때문에 느슨한 결합을 사용하는 게 유리하다.

 

추가적으로, 코드의 재사용성이나 단위테스트에도 느슨한 결합이 강한 결합보다 유리하다. 

 

3. Spring에서 DI를 어떻게 사용하면 되는 것인가?

Spring에서는 대표적으로 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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- NovelService 객체 정의 -->
    <bean id="NovelService" class="com.example.cls.NovelServiceImpl">  
        <!-- bookDAO라는 이름으로 NovelService Injection을 정의 -->
		<property name="bookDAO" ref="bookDAO"/>
    </bean>

    <!-- bookDAO 객체 정의 -->
    <bean id="bookDAO" class="com.example.cls.NovelDAO">
    </bean>
</beans>

 


먼저, AOP나 CGLIB 내용은 고려하지 않고 기본 @Transactional을 사용 할 경우를 다룬 내용입니다.


우선, Spring boot에서 @Transactional 위한 기본 설정 사항


@Configuration

@EnableTransactionManagement

public class DataSourceConfig{

..

    @Bean

    public DataSourceTransactionManager txManager() {

        return new DataSourceTransactionManager(getDataSource());

    }

...

}


 위와 같이 설정을 하고 사용하게 되면 @Transactional 이용하여 트랜잭션을 사용 할 있다.

 


유의점

1. Dynamics proxy 사용하기 때문에 인터페이스 구현이 필요하다.


따라서, 아래와 같은 구조를 가져야 한다.

public interface AService {

}


public class AServiceImp implements AService {

@Transactional

public void biz() {

}

}



2. 내부 호출, 재귀 호출 로서 사용하면 안된다.


예를 들면

public class AServiceImp implements AService {


@Transactional

public void biz() {

//insert 호출

mapper.insertBiz();

//내부호출

bizUpdate();

}


@Transactional

public void bizUpdate() {

mapper.updateBiz();

mapper.deleteBiz();

}

}


위와 같이 @Transactional을 사용한 함수여도 내부적으로 호출을 사용하게 되면

트랙잭션이 동작하지 않게 된다.


따라서, 2가지 유의점을 고려하여 @Transactional을 사용하여야 한다.



장시간 동안 DB Connection에 요청이 없을 경우, connection이 종료되는 현상


로그상으로는 autoReconnection=true를 이용하라고 하지만, 실제로는 추천되는 방법이 아니다.


따라서 아래와 같은 설정을 통하여 connection이 종료되는 현상을 방지한다.



application.properties

spring.datasource.test-on-borrow=true

#mysql

spring.datasource.validation-query=SELECT 1

#oracle

spring.datasource.validation-query=select 1 from dual