티스토리 뷰
지금까지는 jar파일을 lib폴더에 직접 넣어서 DB와 연결했다면,
이번에는 Maven Respository 통해서 Spring framework에 적용할 수 있는
dependency를 추가해 DB와 연결해보자
Java ver
프로젝트 생성
Project Explorer에서 빈공간에 우클릭하면 Dynamic Web Project 만들 수 있다!
여기까진 할 수 있잖아
방금 만든 그 프로젝트를 Maven Project로 만들어주자!
혹시 다시 Maven 제거하고 싶다면, 위의 그림처럼 Disabled Maven Nature 해주믄 되니꽈
아! 메이븐 설정하고 프로젝트에 오류뜨면 그냥 alt+F5로 마무리해주십시다
Maven Repository에서 dependency 가져오기
MariaDB를 쓰고 있으니 Spring에서 mariadb를 사용할 수 있도록 해주는 dependency를 가져오자
https://mvnrepository.com/artifact/org.springframework/spring-jdbc/5.2.9.RELEASE
자바를 통해 db에 연결할 수 있는 connector인 JDBC를 Spring에서 사용할 수 있도록 Spring JDBC를 가져온다
https://mvnrepository.com/artifact/org.springframework/spring-jdbc/5.2.9.RELEASE
DB와 연결할 수 있도록 Database Connection역할을 해주는 Spring DBCP를 가져오자
https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2/2.9.0
다음은 관계형 DB와 객체지향 애플리케이션을 쉽게 매핑해주는 MyBatis를 가져오자
이전에는 jar파일로 했었지
https://mvnrepository.com/artifact/org.mybatis/mybatis/3.5.7
MyBatis를 Spring에서 사용할 수 있도록 해주는 MyBatis-Spring도 가져오자
https://mvnrepository.com/artifact/org.mybatis/mybatis-spring
가져온 dependency를 토대로 해서 Maven 기본설정 해보자!
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>Ex26MyBatisJava</groupId>
<artifactId>Ex26MyBatisJava</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<!-- 소스의 디렉토리 구조, 빌드 산출물 디렉토리 구조, 빌드시 사용할 플러긴 정보 관리 -->
<build>
<!-- sourceDirectory : 자바 소스 코드를 관리하는 디렉토리 -->
<sourceDirectory>src/main/java</sourceDirectory>
<!-- Maven제공 기능은 플러긴 기반으로 작동 따라서 설정 필요 -->
<plugins>
<!-- 컴파일 플러그인 설정 -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<!-- 16>14변경 -->
<release>14</release>
</configuration>
</plugin>
<!-- 배포 패키징 플러긴 설정 -->
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<!-- configuration 추가 -->
<configuration>
<!-- 배포할 위치 directory -->
<warSourceDirectory>src</warSourceDirectory>
</configuration>
</plugin>
</plugins>
</build>
<!-- 라이브러리 버전관리 : properties -->
<properties>
<javax.servlet-version>4.0.1</javax.servlet-version>
<javax.servlet.jsp-version>2.3.3</javax.servlet.jsp-version>
<javax.servlet.jsp.jstl-version>1.2</javax.servlet.jsp.jstl-version>
<org.springframework>5.2.2.RELEASE</org.springframework>
</properties>
<!-- 라이브러리 셋팅 : dependencies 프로그램과 의존관계인 라이브러리 관리 -->
<dependencies>
<!-- servlet-api 라이브러리 적용 위한 설정 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<!-- 버전관리는 properties에서 하기 때문에 변수처럼 사용하기 -->
<version>${javax.servlet-version}</version>
<scope>provided</scope>
</dependency>
<!-- javax.servlet.jsp-api 라이브러리 적용 위한 설정 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<!-- 버전관리는 properties에서 하기 때문에 변수처럼 사용하기 -->
<version>${javax.servlet.jsp-version}</version>
<scope>provided</scope>
</dependency>
<!-- javax.servlet/jstl 라이브러리 적용 위한 설정 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<!-- 버전관리는 properties에서 하기 때문에 변수처럼 사용하기 -->
<version>${javax.servlet.jsp.jstl-version}</version>
</dependency>
<!-- spring-webmvc 라이브러리 적용 위한 설정 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<!-- 버전관리는 properties에서 하기 때문에 변수처럼 사용하기 -->
<version>${org.springframework}</version>
</dependency>
<!-- resource 어노테이션 안먹혀서 추가함.. 요상하군... -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<!-- validation-api를 사용하기 위한 설정 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- hibernate-validator를 사용하기 위한 설정 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.1.Final</version>
</dependency>
<!-- jar파일 사용하지 않고 라이브러리로 사용하기 -->
<!-- mariadb를 연결하기 위한 라이브러리로 jar파일 대신 -->
<!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.4</version>
</dependency>
<!-- Spring JDBC -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!-- Connection역할 -->
<!-- 트랙잭션 제어 역할 -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
<!-- MyBatis를 사용하기 위한 라이브러리 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis/3.5.7 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- MyBatis-spring을 사용하기 위한 라이브러리 -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring/2.0.6 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
</dependencies>
</project>
@Resource가 안먹혀서 dependency 추가함..
여기선 Validator안쓸 것 같긴 한데, 만약 쓴다면 또 가져와야 하므로 그냥 넣어뒀다~
Spring MVC Java 기본설정
일단 Spring MVC를 어떻게 구현할지 구조부터 대충 살펴보면,
전에 JSP MVC 패턴때 BeansConfigClass 만들어서 bean을 정의해주었지
그 역할을 이젠 SpringConfigClass라는 이름으로 변경해 Java버전으로 해볼게!
import javax.servlet.Filter;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpringConfigClass extends AbstractAnnotationConfigDispatcherServletInitializer {
//AbstractAnnotationConfigDispatcherServletInitializer의 메소드 전부 override
//프로젝트에서 사용할 bean정의 클래스 지정
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
//return 추가
return new Class[] {RootAppContext.class};
}
//Spring MVC 프로젝트 설정 위한 configuration 클래스 지정
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class[] {ServletAppContext.class};
}
//DispatcherServlet에 매핑할 요청 주소 세팅
//DispatcherServlet : 클라이언트 요청을 맨 처음 받는 위치로 공통작업 후 세부 작업은 담당 컨트롤러로 전달하는 역할
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[] {"/"};
}
//얘는 안따라와서 따로 추가해줌
//request.setCharacterEncoding("UTF-8") 한 번만 설정하기
@Override
protected Filter[] getServletFilters() {
// TODO Auto-generated method stub
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
return new Filter[] {encodingFilter};
}
}
AbstractAnnotationConfigDispatcherServletInitializer를 상속받아서 그 내부 메서드를 override 받았어.
맨 마지막 filter 메서드는 안따라오더라고?
걍 내가 불렀어 filter적고 ctrl+space하믄 뜰겨
보면 먼저 bean 뭐 쓸지 getRoot~()메서드에서 정의해주고,
getServletConfigClasses()메서드에서 Configuration을 지정해준다
getServletMapping()메서드는 말그대로 매핑 역할을 하는데, 위에서 적었다시피 클라이언트 요청 받으면
그 요청에 해당하는 컨트롤러 쪽으로 보내주는 것이 바로 매핑!
맨 마지막 filter 메서드는 우리 맨~날 스크립틀릿으로 또는 컨트롤러에서 request.set~("UTF-8") 적어줬잖아?
그걸 저기다가 딱 한 번, 두 번도 아니고 한 번만 적어주면 페이지마다 적은 효과를 주는거지
이제 getRootConfigClass 메서드에서 정의될 bean 클래스 RootAppContext를 한 번 만들어보자
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import kr.co.goodee39.mapper.MapperInterface;
//Spring MVC 프로젝트에서 사용할 bean 정의하는 클래스
@Configuration
//db.properties에서 정의한 내용 가져오기
@PropertySource("/WEB-INF/properties/db.properties")
public class RootAppContext {
//db.properties에서 정의한 이름, 값 삽입해서 mariaDB와 연결
//Value어노테이션으로 해당하는 값 가져올 수 있음
@Value("${db.classname}")
private String classname;
@Value("${db.url}")
private String url;
@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
//DB 접속 정보 관리
@Bean
public BasicDataSource dataSouce() {
BasicDataSource source = new BasicDataSource();
source.setDriverClassName(classname);
source.setUrl(url);
source.setUsername(username);
source.setPassword(password);
return source;
}
//쿼리문과 DB접속 관리를 위한 객체
@Bean
public SqlSessionFactory factory(BasicDataSource source) throws Exception {
SqlSessionFactory factory = null;
try {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(source);
factory = factoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
//쿼리문 실행을 위한 객체
//Mapper를 Bean등록하기
@Bean
public MapperFactoryBean<MapperInterface> testMapper(SqlSessionFactory factory) throws Exception {
MapperFactoryBean<MapperInterface> factoryBean = new MapperFactoryBean<MapperInterface>(MapperInterface.class);
factoryBean.setSqlSessionFactory(factory);
return factoryBean;
}
//트랜잭션 설정 객체1
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
//트랜잭션 설정 객체2
@Bean
public TransactionTemplate transactionTemplate() {
return new TransactionTemplate(transactionManager());
}
}
@Configuration이 전부였던 RootAppContext에 @PropertyScource("path")를 준 이유는!
직접적으로 여기에 db연결을 위한 클래스 이름, url, username, password를 적지 않기 위함이다
후에 저 필드명에 맞춰서 properties파일을 이용해 값을 삽입해 줄 것임!
DB접속 정보 관리를 위해
DBCP2의 디팬던시를 추가했기 때문에 사용 가능한 이 BasicDataSource객체를 이용해서 JDBC 연결을 설정해주고,
쿼리문과 DB 접속 관리를 위해
Mybatis 통한 SqlSessionFactory 객체를 이용해 try ~catch문 안에서
BasicDataSource로 설정한 JDBC연결 설정을 주입해준다!
그럼 연결 쏵~
마지막으로 MapperInterface를 제너럴로 갖는 MapperFactoryBean객체 타입을 반환하는 testMapper 메서드에
SqlSessionFactory 객체를 쇽 넣어주어 쿼리문이 실행 및 결과값을 반환하도록 설정
밑에서 MapperInterface 만들거임!
아니 마지막은 이거거든?
트랜잭션 관리를 위한 bean객체를 정의해주는데,
DataSourceTransactionManager와 TransactionTemplate 객체를 받아준다!
이 두개를 가지고 트랜잭션을 제어할 수 있는데
트랜잭션이라 함은, 은행 업무를 예로 들 수 있지
내가 10만원 뽑으러 ATM기를 찾았어
내 계좌든, 카드든, 어플이든 이용해서 나를 인증하고
그 인증이 통과해야 ATM기에서 돈을 주잖아?
만약 그 인증 과정 중에서 하나라도 틀리면 어떻게 돼?
처음 화면으로 돌아가버리지.. 개빡치게
물론 보안을 위해선 그게 좋겠지... ^^
아무튼 어떤 작업이 있을 때 그 작업이 완전하게 이루어지기 위해
필요한 일련의 업무들이 있잖아?
그 때 그 업무 중에 하나라도 실패하고, 망가지면 모든 것을 수포로 되돌리는 그런 것을 트랜잭션 제어한다고 해
일부만 성공했다고 ATM기에서 돈 줘버리면 뭐야... 내 돈 돌려줘요...
properties파일에 db 연결 정보 정의
db.classname = org.mariadb.jdbc.Driver
db.url = jdbc:mariadb://127.0.0.1:3306/scott
db.username = root
db.password = 비밀이지롱
요로케 설정하면 위에서 알아서 해당하는 이름 찾아서 쏙쏙 가져간다~ 이 말썸!
이번엔 getServletConfigClass 메서드에서 Spring MVC 설정을 해줄 Configuration 클래스를 정의해보자
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//Spring MVC 프로젝트 설정 클래스
@Configuration
//@Controller 셋팅된 크래스를 Controller로 등록
@EnableWebMvc
//스캔할 패키지 저장
@ComponentScan("kr.co.goodee39.controller")
public class ServletAppContext implements WebMvcConfigurer {
//Controller의 메서드가 반환하는 jsp의 이름 앞뒤에 경로(/WEB-INF/views), 확장자(.jsp) 추가하는 설정
//configureViewResolvers
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// TODO Auto-generated method stub
WebMvcConfigurer.super.configureViewResolvers(registry);
registry.jsp("/WEB-INF/views/", ".jsp");
}
//정적 파일(img, video, audio, etc) 경로 매핑
//addResourceHandlers
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
WebMvcConfigurer.super.addResourceHandlers(registry);
registry.addResourceHandler("/**").addResourceLocations("/resources/");
}
}
좀 낯설긴한데, servlet이라는 단어는 주구장창 봐왔잖아?
한 페이지 이동 할 때마다 servlet 추가추가추가 하던 그런 날이 끝났다 이거임..
여기서 한 큐에 설정이 가능해
일단 이 클래스가 Configuration할 클래스임을 어노테이션으로 명명하고,
@EnableWebMvc를 통해 이따가 @Controller 역할을 할 클래스를 진짜 controller로서 역할 할 수 있게 만들어줘
@ComponentScan으로 경로 설정해주면 그 내부 클래스 중 @Component설정된 클래스를 찾게될건데,
사실 이번에는 @Component로 등록된 클래스는 없어ㅎㅎㅎ
그리고나서 WebMvcConfigurer 상속받아준 후에 RootConfigClass와 마찬가지로 내부 메서드를 모두 상속 받아줘
configViewResolvers(ViewResolverResistry registry)라는 메서드는 이따가 @Controller에서 어떤 페이지로 갈 수 있게끔
jsp의 이름을 반환할텐데, 그 앞전 경로를 설정해주고, jsp파일임을 명시하는 확장자 설정이 가능해
addResourceHandelers(ResourceHandlerRegistry registry) 메서드는 정적인 파일, 예를들면 이미지, 영상, 음성 등등
어딘가에 딱 넣어두고, 불러와지기만 하는 변함없는 파일이잖아?
그 경로를 설정할 수 있는데, 여기서 보면 느끼겠지만 경로가 하나뿐이잖아..
하나의 경로밖에 못써..
뭐 하나의 경로 안에 폴더를 또 만들면 쓸 수 있겠지?
아 여기서 사실 addResourceHandler("/**")는 아직 어떤 역할인건지 잘 모르겠어.. 찾아보자 더 ㅎㅎㅎㅎ
자 여기까지가 기초다 기초...
사실 아직 100% 이해된건 아니야.
이렇다니까 일단 받아들인거고, 계속 따라쳐보고, 구글링 해보면서 내것으로 만들고 또 발전도 시켜봐야지!
Controller 클래스 만들기
아까 위에서 Spring MVC를 설정하게 해주는 클래스 하나가 있었잖아?
그 클래스 내부에 @EnableWebMvc라는 어노테이션이 있었는데
이건 @Controller로 지정된 클래스가 정말 controller로서의 역할을 할 수 있게끔 해주는 어노테이션이였어.
그럼 이제 @Controller를 달고있을 클래스를 만들어보자
따지고보면 JSP에서 Servlet역할의 일부를 담당하는 것이라고도 생각할 수 있지 않나? 싶네
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
//ServletAppContext에서 설정한 configureViewResolvers()메소드 통해서
///WEB-INF/views/index.jsp로 변환됨
@RequestMapping(value="/", method=RequestMethod.GET)
public String home() {
return "index";
}
}
별건 없어
그냥 무슨 역할하는지 보자고!
위에서 계속 언급했듯 이 클래스는 controller의 역할을 할 클래스이므로 어노테이션으로 이름표 달아주고,
메소드를 하나 만드는데, 이 메소드는 무어냐
첫 페이지 역할, 그러니까 home 역할을 할 페이지를 설정하는 메서드야
그래서 이름도 home이야 후후
value는 우리 JSP MVC 패턴에서 servlet만들면 맨 상단에
@WebServlet("/블라블라")하는 공간이 있어 (ㄴㅇㄹ는 무시혀~ 걍 쓴거임)
저 블라블라는 보통 servlet이름이 들어가는데, 파일을 실행시켜서 이동한다거나 하면
url에 저기 작성된 이름이 뒤에 뜨더라고?
근데 이걸 /만 남기고 지워주면?!
그냥 RequestDispatcher rdp = request.getRequestDispatcher("");에서 설정한 jsp파일이 home으로 떠부러
servlet이라는 위치가 jsp파일 앞전에 안붙기 때문이지!
그거랑 같은 이치야 value = "/"는
아무턴, return "문자열";은 이따가 만들 home이 될 jsp파일 이름을 넣어준거야
이 controller가 실행되면 index라는 홈이름이 전달되겠지 어디로?
아까 위에서 @EnableWebMvc로 지금 이 클래스에 Controller의 자격을 준 servletAppContext 클래스로!
그리고는 configureViewResolvers(ViewResolverRegistry registry) 메소드를 만나
/WEB-INF/views/index.jsp로 변신!
그리고 index.jsp에 이미지가 있다면 addResourceHandlers(ResourceHandlerRegistry registry) 메서드 통해서
src 앞에 /resources/가 콱 붙어주겠지~
DB에 테이블 하나 만들어 놓기
컬럼 3개를 갖는 springsample 테이블 하나를 생성해두었음!
HomeController에서 설정한 첫 페이지가 되어줄 index.jsp를 만들자
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- SPRING + MYBATIS -->
<h1>SPRING + MYBATIS</h1>
<br />
<a href="input_data">Input_Data</a><br />
<a href="read_data">Read_Data</a><br />
<br />
<hr />
<h3>트랜잭션</h3>
<a href="tx_data1">트랜잭션_data1</a><br />
<a href="tx_data2">트랜잭션_data2</a><br />
</body>
</html>
input_data 페이지로 이동시 DataVO1를 커맨드 객체로 받는 inputData메서드가 실행된다!
read_data 페이지로 이동시 DataVO1를 커맨드 객체로 받는 readData메서드가 실행된다!
트랜잭션은 tx_data1, tx_data2는 오류가 발생하도록 해놓은 상태다.
쿼리 결과가 어떻게 진행되는지 보자
VO객체 만들기
public class DataVO1 {
private String column1;
private String column2;
private String column3;
public DataVO1() {
// TODO Auto-generated constructor stub
}
public String getColumn1() {
return column1;
}
public void setColumn1(String column1) {
this.column1 = column1;
}
public String getColumn2() {
return column2;
}
public void setColumn2(String column2) {
this.column2 = column2;
}
public String getColumn3() {
return column3;
}
public void setColumn3(String column3) {
this.column3 = column3;
}
}
요르케 DB에 맞춰서 컬럼따라 설정하고, getter, setter 메서드를 만들어서
다른데서 가져다가 설정하고, 사용도 할 수 있도록 한다
MapperInterface만들기
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import kr.co.goodee39.vo.DataVO1;
//Mapper내에서 Bean등록이 아니라 RootAppContext에서 등록함
public interface MapperInterface {
//input_data에서 form내 input으로 추가될 row 설정
@Insert("INSERT INTO springsample(column1, column2, column3) VALUES(#{column1}, #{column2}, #{column3})")
public void insertData(DataVO1 vo);
//추가된 내용 불러올 select문
@Select("SELECT column1, column2, column3 FROM springsample ")
public List<DataVO1> selectDataList();
}
사실상 쿼리문이라고 볼 수 있지.
그리고 DataVO1객체를 커맨드로 받아 내부 필드 값을 주입할 수 있도록 설정!
MapperInterface를 제너럴로 갖는 MapperFactoryBean객체 타입을 반환하는 testMapper 메서드 통해서
Bean을 등록했기 때문에 따로 등록 필요 없음!
@Select는 주입된 DB 뽑아보려고~
Controller 만들기
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import kr.co.goodee39.mapper.MapperInterface;
import kr.co.goodee39.vo.DataVO1;
@Controller
public class TestController {
//MapperInterface객체를 Autowired로 주입
@Autowired
MapperInterface mapper1;
//RootAppContext에서 정의한 DataSourceTransactionManager을 Autowired로 주입
//트랜잭션 제어 구VER
@Autowired
DataSourceTransactionManager transactionManager;
//RootAppContext에서 정의한 TransactionTemplate을 Autowired로 주입
//트랜잭션 제어 신VER
@Autowired
TransactionTemplate transactionTemplate;
@GetMapping("/input_data")
public String inputData(DataVO1 vo1) {
//DataVO1 커맨드 객체 받아서 form:form의 modelAttribute와 연결
return "input_data";
}
//
@PostMapping("/input_pro")
public String inputPro(DataVO1 vo1) {
//필요한 쿼리를 mapper통해 실행
//DataVO1 커맨드 객체 내 필드를 집어넣어서 실행!
mapper1.insertData(vo1);
return "input_pro";
}
@GetMapping("/read_data")
public String readData(Model model) {
/*
* 두 줄은 너무 길어~
List<DataVO1> list = mapper1.selectDataList();
model.addAttribute("list", list);
*/
model.addAttribute("list", mapper1.selectDataList());
return "read_data";
}
/*
@GetMapping("/tx_data1")
public String txData1(Model model) {
try {
DataVO1 vo1 = new DataVO1();
DataVO1 vo2 = new DataVO1();
//vo1은 길이 10에 맞는 값을 넣어서 잘 실행되고, 심지어 DB에도 반영됨
vo1.setColumn1("오늘은");
vo1.setColumn2("12월");
vo1.setColumn3("28일");
//vo2는 col2에서 길이 10 이상의 값이 있기 때문에 오류 발생
vo2.setColumn1("내일은");
vo2.setColumn2("오류를 위한 길이 10 넘치는 값 설정하기");
vo2.setColumn3("29일");
// 둘 다 정상적으로 실행되어야 vo1, vo2 반영되게 만들려는 것이 본 목적임
mapper1.insertData(vo1);
mapper1.insertData(vo2);
} catch (Exception e) {
e.printStackTrace();
}
return "tx_data1";
}
*/
//구ver 트랜잭션 제어
//둘 다 정상적으로 실행되어야 vo1, vo2 반영되게 만들려는 것이 본 목적임
//하나라도 오류시 전부 rollback
@GetMapping("/tx_data1")
public String txData1() {
//Autowired로 받아온 DataSourceTransactionManager통해 Definition과 Status 내장 객체를 가져온다
//전역으로 선언해서 사용할 수도 있지만 구VER이라 그냥 얘만 적용해쒀
TransactionDefinition def = new TransactionTemplate();
TransactionStatus status = this.transactionManager.getTransaction(def);
try {
DataVO1 vo1 = new DataVO1();
DataVO1 vo2 = new DataVO1();
vo1.setColumn1("오늘은");
vo1.setColumn2("12월");
vo1.setColumn3("28일");
//vo2는 col2에서 길이 10 이상의 값이 있기 때문에 오류 발생
vo2.setColumn1("내일은");
vo2.setColumn2("오류를 위한 길이 10 넘치는 값 설정하기");
vo2.setColumn3("29일");
//MapperInterface내의 insert어노테이션에 삽입된 쿼리 실행(vo1, vo2를 담아서!)
mapper1.insertData(vo1);
mapper1.insertData(vo2);
//둘 다 정상 실행시 commit
this.transactionManager.commit(status);
} catch (Exception e) {
e.printStackTrace();
//status에 에러발생 시 rollback : vo1도 반영되지 않음
this.transactionManager.rollback(status);
return "tx_error";
}
return "tx_data1";
}
//신 ver 트랜잭션 제어
@GetMapping("/tx_data2")
public String txData2() {
try {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
DataVO1 vo1 = new DataVO1();
DataVO1 vo2 = new DataVO1();
vo1.setColumn1("오늘은");
vo1.setColumn2("12월");
vo1.setColumn3("28일");
//vo2는 col2에서 길이 10 이상의 값이 있기 때문에 오류 발생
vo2.setColumn1("내일은");
// vo2.setColumn2("오류를 위한 길이 10 넘치는 값 설정하기");
vo2.setColumn2("12월");
vo2.setColumn3("29일");
//MapperInterface내의 insert어노테이션에 삽입된 쿼리 실행(vo1, vo2를 담아서!)
mapper1.insertData(vo1);
mapper1.insertData(vo2);
}
});
} catch (Exception e) {
e.printStackTrace();
return "tx_error";
}
return "tx_data2";
}
}
먼저 Bean등록한 MapperInferface / DataSourceTransactionManager / TransactionTemplate를
@Autowired로 주입해서 모두가 쓸 수 있도록 헌다
1) inputData() 메서드
input_data url타고 실행된 메서드 inputData에서 DataVO1를 주입받아줍니다으~
그래야 form:form에 modelAttribute로 설정해둔 dataVO1을 받아줄 수 있찌유~
2) inputPro() 메서드
이동한 input_data페이지에서는
form:form태그에서 input_pro로 설정된 action을 받아 inputPro 메서드가 실행된다
똑같이 DataVO1을 커맨드로 받아주고,
input_data페이지에서 넘겨받아 설정된 vo1의 값들을
상단에서 주입한 MapperInterface객체 타입의 mapper1통해 내부 메서드를 실행시킨다!!
성공적으로 수행되면 input_pro페이지로 이동되는데,
성공적 수행이라 함은 DB에 값이 잘 저장되었다 이말썸~
3) readData() 메서드
DB를 한 번 읽어서 출력해보려는 심산이얌
inputPro 메서드 통해서 잘 주입되었다면 좌르륵 나오겠지?
그 밑에 주석처리 포함 장황한 메서드 3개는 트랜잭션을 제어하기 위한 수단으로
@Autowired로 주입한 DataSourceTransactionManager / TransactionTemplate를 사용해
두 가지 방법으로 시도해 볼 것임
4) txData1() 메서드
DataSourceTransactionManager객체를 이용하는 방법으로,
구ver이라고 볼 수 있음
TransactionDefinition / TransactionStatus 요 두개를 받아와서
Transaction의 정의, 처리상태를 확인해 commit할 것인가 rollback할 것인가를 정해
try문 내부를 보면 예시로 DataVO1 객체 타입 받아와서 값 설정해가지고 한 번 넣어본 단이 있을거야
현재 테이블 컬럼 설정이 길이 10으로 제한되어 있는데
다른 건 다들 맞는데
vo2.setColumn2를 보면 10자는 넉근히 넘어가지
그럼 오류란 말이야
만약 트랜잭션 처리가 없었다면
vo1은 맞으니까 그냥 DB로 바로 commit 시켜버려
둘 다 맞아떨어져야 비로소 commit되는 것이 인지상정 아니겄음?
주석 처리된 txData1()메서드는 그렇게 돼
catch문에서 rollback을 설정해주믄 됩니다으~
그리고 return을 error페이지 하나 파서 거그로 보내주는겨
vo1, vo2 둘 다 잘 들어갔다면
return tx_data1으로!
얘는 오류 발생하도록 설정해뒀어
5) txData2() 메서드
TransactionTemplate 요 객체를 이용한 트랜잭션 제어로,
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {...} }
위의 구문을 이용하쥐
결과 값 도출 없이 트랜잭션 상에서 에러나 예외 발생하면 rollback시켜버린다
보기에는 길지만 뭐 override로 금방 주입할 수 있어
DataVO통해서 설정한 값을 mapper1에 대입해서 insert쿼리에 대입해 실행시켜~
만약 에러 발생하면 tx_error페이지로 이동하고, 정상적으로 수행되면 tx_data2로 이동할꾸
일단 값은 정상수행으로 넣어봤어
자바는 controller상에서 @로 주입해서 사용하니까 바로바로 try ~ catch 적용해서
예외처리페이지로의 이동까지 설정이 가능한데,
XML은 아닌 듯 허다.... 아쉽다
아님 내가 모르는 방법이 있겠쥐~
이동될 페이지 만들기!
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- SPRING + MYBATIS -->
<h1>SPRING + MYBATIS</h1>
<br />
<form:form modelAttribute="dataVO1" action="input_pro" method="post">
data1 : <form:input path="column1" type="text"/><br />
data2 : <form:input path="column2" type="text"/><br />
data3 : <form:input path="column3" type="text"/><br />
<form:button type="submit">확인</form:button><br />
</form:form>
</body>
</html>
얘가 바로 input_data페이지로 modelAtrribute를 dataVO1으로 가지고 있으며
action이 input_pro로 inputPro메서드를 실행시키게 된다
data1, 2, 3는 각각 column1, 2, 3데이터에 주입될 값이다
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- SPRING + MYBATIS -->
<h1>SPRING + MYBATIS</h1>
<br />
<h1>Input_Pro DB에 데이터 저장 완</h1>
</body>
</html>
성공적으로 mapper.insertData(vo1)수행되면 넘어갈 페이지고
DB에 반영이 되었다는 증거~
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- SPRING + MYBATIS -->
<h1>SPRING + MYBATIS</h1>
<br />
<!-- readData메서드에서 처리된 model을 통해 설정한 list라는 attribute를={list} obj이란 이름으로 받아준다 -->
<c:forEach var="obj" items="${list}">
<!-- {명명한 이름.컬럼명}으로 출력-->
<li>${obj.column1}, ${obj.column2}, ${obj.column3}</li>
</c:forEach>
</body>
</html>
read_data 성공적으로 출력시 요기 도착해서 데이터 보여줌!
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- SPRING + MYBATIS -->
<h1>SPRING + MYBATIS</h1>
<br />
<h3>트랜잭션</h3>
<h1>트랜잭션_data1</h1>
<br />
<h3>DB주입 완.</h3>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- SPRING + MYBATIS -->
<h1>SPRING + MYBATIS</h1>
<br />
<h3>트랜잭션</h3>
<h1>트랜잭션_data2</h1>
<br />
<h3>DB주입 완.</h3>
</body>
</html>
tx_data1,2 트랜잭션 에러없이 잘 처리되면 넘어갈 페이진데, 에러나게 해둬서 볼 일이 없는 페이지..
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- SPRING + MYBATIS -->
<h1>SPRING + MYBATIS</h1>
<br />
<h3>트랜잭션</h3>
<h1>트랜잭션_error</h1>
<br />
<h3>Rollback 완.</h3>
</body>
</html>
얘만 보게 될 것이다... ^^
home으로 만들었던 index페이지에서 진짜 이동 되는지 한 번 해보자!
Run on Server를 클릭해주쟈
결과
'(기초)그래서 뭘 배운거야? > SPRING' 카테고리의 다른 글
SPRING-104-Spring Web MVC : MyBatis - XML (0) | 2021.12.28 |
---|---|
SPRING-102-Spring Web MVC : Exception Handler - XML (0) | 2021.12.28 |
SPRING-101-Spring Web MVC : Exception Handler - JAVA (0) | 2021.12.28 |
- Total
- Today
- Yesterday
- ol>li
- 93점
- hr tag
- br tag
- usemap
- 비전공
- html
- boldtag
- 긴문장
- spantag
- marktag
- ul>li
- imgtag
- 단락태그
- definition List
- tablespan
- tabletag
- 복습
- ptag
- hn태그
- 2021년2회
- 줄글
- emtag
- 정보처리기사실기
- 정보처리기사
- 합격
- 정보처리기사필기
- pretag
- 2021년42회
- 정처기
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |