728x90
300x250
[Spring-Framework] 36. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java 설정)


이번 글에서 소개할 내용은 스프링 프레임워크의 트랜젝션을 조금 더 다뤄보려고 한다.


어노테이션을 사용하는 데 있어서 방법이 두 가지가 있다.


- 1. Java Config 파일 구성 방식

- 2. (context.xml) XML 파일 구성 방식


* 참고로 스프링 프레임워크는 셋팅이 시작의 반이라고 보면 될 것 같다.


[작업 환경]

IDE: Eclipse 2020-06

Framework: Spring Framework 4.2.4 RELEASE

spring-jdbc

spring-core

spring-tx

mysql-connector-java (8.0.21)

oracle jdbc (WEB-INF/lib에 넣어줘야 함.)


OpenJDK 15 (프로젝트에 적용한 자바 버전은 1.8로 셋팅하였음.)


딱딱한 글도 중요하지만, 영상으로 이게 무엇인지 소개해주겠다. (작동 원리 시연)



영상 1. Spring Framework 4.2.4 RELEASES - Transactions (시연)





1. 프로젝트 구성


프로젝트 구성에 관한 것이다.



그림 1. 프로젝트 구성도



2. POM.xml 구성하기



 <properties>
  <java-version>1.8</java-version>
  <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
 </properties>


  (중략)

  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>
  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</artifactId>
       <version>${org.springframework-version}</version>
  </dependency>
  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>
    
  (중략)

  <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.21</version>
  </dependency>




3. Build Path, Project Factes, Java compiler 설정하기


* Build Path -> JRE System Library 1.8, JUnit 5

* Java Compiler -> Compiler compliance level: 1.8

* Project Factes -> Java : 1.8


* 오라클 JDBC: WEB-INF/lib폴더에 ojdbc.jar 파일 넣어주기 




4. AccountTbl.sql


-- Transaction 실습 DB (은행 - Account)
-- Oracle 11 - 자동번호 생성 테이블 정의
-- Table 생성 (FOODMENU_TBL)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE account_tbl
(
    idx NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    balance NUMBER,
    regidate TIMESTAMP
);

-- Sequence 정의
CREATE SEQUENCE account_sequence
START WITH 1
INCREMENT BY 1;


-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER account_trigger
BEFORE INSERT
    ON account_tbl
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT account_sequence.nextval INTO :NEW.IDX FROM dual;
END;


파일명: AccountTBL.sql


[첨부(Attachments)]

AccountTbl.zip




4. db.properties (/src/main/resources)


MYSQL_DB_URL=jdbc:mysql://localhost:3306/dbanme?useUnicode=true&characterEncoding=utf8
MYSQL_DB_USERNAME=
MYSQL_DB_PASSWORD=
ORACLE_DB_URL=jdbc:oracle:thin:@127.0.0.1:1521:xe
ORACLE_DB_USERNAME=HR
ORACLE_DB_PASSWORD=1234


파일명: db.properties


[첨부(Attachments)]

db.zip



5. AccountVO.java (com.website.example.vo)


package com.website.example.vo;

import java.sql.Timestamp;;

public class AccountVO {
 
      private int idx;
      private String name;
      private int balance;
      private Timestamp regidate;
 
      public int getIdx() {
          return idx;
      }
 
      public void setIdx(int idx) {
          this.idx = idx;
      }
 
      public String getName() {
          return name;
      }
 
      public void setName(String name) {
          this.name = name;
      }
 
      public int getBalance() {
          return balance;
     }

     public void setBalance(int balance) {
          this.balance = balance;
     }


     public Timestamp getRegidate() {
         return regidate;
     }
 
     public void setRegidate(Timestamp regidate) {
         this.regidate = regidate;
     }
 
}


파일명: AccountVO.java


[첨부(Attachments)]

AccountVO.zip




6. DBConfig.java (com.website.example.common)


package com.website.example.common;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.service.AccountService;
import com.website.example.service.AccountServiceImpl;

@Configuration
@EnableTransactionManagement

public class DBConfig {

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
   
    @Bean
    public DataSource dataSource() {
     
     DataSource dataSource = new MyDataSourceFactory().getOracleDataSource();
     
     /* Apache DBCP
     
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/testDb?useUnicode=true&characterEncoding=utf8");
        dataSource.setUsername("test");
        dataSource.setPassword("test123!@#");
       
        */
        return dataSource;
    }
   
    @Bean
    public AccountDAO accountDAOImpl() {
     
     AccountDAO dao = new AccountDAOImpl(dataSource());
     return dao;
    }
   
    @Bean
    public AccountService accountServiceImpl() {
     
     AccountService service = new AccountServiceImpl(dataSource());
     return service;
    }
   
}


파일명: DBConfig.java


[첨부(Attachments)]

DBConfig.zip




7. RootConfig.java (com.website.example.common)


package com.website.example.common;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@Import({DBConfig.class})
@ComponentScan(basePackages = {"com.website.example"})
//@ComponentScan(basePackages = {"com.local.example.beans", "com.local.example.advisor"})
public class RootConfig {


}


파일명: RootConfig.java


[첨부(Attachments)]

RootConfig.zip




8. MyDataSourceFactory.java (com.website.example.common)



package com.website.example.common;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.mysql.cj.jdbc.MysqlDataSource;

import oracle.jdbc.pool.OracleDataSource;

public class MyDataSourceFactory {
 
 private InputStream is = null;
    private Properties props = null;
 
 public MyDataSourceFactory()  {
  
        String resource = "db.properties";
        this.is = getClass().getClassLoader().getResourceAsStream(resource);
        this.props = new Properties();
       
 }
 
 public DataSource getMySQLDataSource() {
  
        MysqlDataSource mysqlDS = null;
       
        try {
         
            props.load(is);
            mysqlDS = new MysqlDataSource();
            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));
            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));
            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));
           
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        return mysqlDS;
       
    }
    
    public DataSource getOracleDataSource(){
     
        OracleDataSource oracleDS = null;
       
        try {
         
            props.load(is);
            oracleDS = new OracleDataSource();
            oracleDS.setURL(props.getProperty("ORACLE_DB_URL"));
            oracleDS.setUser(props.getProperty("ORACLE_DB_USERNAME"));
            oracleDS.setPassword(props.getProperty("ORACLE_DB_PASSWORD"));
           
           
        } catch (IOException e) {
         
            e.printStackTrace();
           
        } catch (SQLException e) {
         
            e.printStackTrace();
           
        }
       
        return oracleDS;
       
    }

}


파일명: MyDataSourceFactory.java


[첨부(Attachments)]

MyDataSourceFactory.zip




9. AccountDAO.java (com.website.example.dao)


package com.website.example.dao;

import java.sql.SQLException;

import com.website.example.vo.AccountVO;


public interface AccountDAO {
 
       void createAccount(AccountVO vo) throws SQLException;
       int getBalance(String name);
       void minus(String name, int money) throws SQLException;
       void plus(String name, int money) throws SQLException;
 
}


파일명: AccountDAO.java


[첨부(Attachments)]

AccountDAO.zip

MyDataSourceFactory.zip




10. AccountRowMapper.java (com.website.example.dao)



package com.website.example.dao;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import com.website.example.vo.AccountVO;

@Service
public class AccountRowMapper implements RowMapper<AccountVO>  {


      @Override
      public AccountVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
             AccountVO vo = new AccountVO();

             vo.setIdx(rs.getInt(1));
             vo.setName(rs.getString(2));
             vo.setBalance(rs.getInt(3));
             vo.setRegidate(rs.getTimestamp(4));
  
             return vo;
      }

}


파일명: AccountRowMapper.java


[첨부(Attachments)]

AccountRowMapper.zip





11. AccountDAOImpl.java (com.website.example.dao)


package com.website.example.dao;

import java.sql.SQLException;
import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import com.website.example.vo.AccountVO;


public class AccountDAOImpl implements AccountDAO {

       // Spring Framework - JDBC
       private JdbcTemplate jdbcTemplate = null;
       private DataSource ds = null;
 
       private final String INSERT = "insert into account_tbl(name, balance, regidate) values(?, ?, ?)";
       private final String SELECT_BALANCE = "select * from account_tbl where name = ?";
       private final String UPDATE_MINUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) - ? " +
          " where name = ?";
       private final String UPDATE_PLUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) + ? " +
          " where name = ?";
 
       public AccountDAOImpl(DataSource ds) {
             this.jdbcTemplate = new JdbcTemplate(ds);
             this.ds = ds;
       }
 
       public void createAccount(AccountVO vo) throws SQLException {
   
             this.jdbcTemplate.update(INSERT, vo.getName(), vo.getBalance(), vo.getRegidate());  
       }
 
       public int getBalance(String name){
     
             Object[] args = {name};
     
             AccountVO vo = this.jdbcTemplate.queryForObject(SELECT_BALANCE, args, new AccountRowMapper());
       
             int result = vo.getBalance();
     
             return result;
       }

    
       public void minus(String name, int money) throws SQLException{
     
              this.jdbcTemplate.update(UPDATE_MINUS, name, money, name);
       
       }
   
       public void plus(String name, int money) throws SQLException{

            /*
            // 예외 발생시키기
            if(true){
                throw new RuntimeException("런타임 에러");
            }
             */
     
            this.jdbcTemplate.update(UPDATE_PLUS, name, money, name);
       }
 
}


파일명: AccountDAOImpl.java


[첨부(Attachments)]

AccountDAOImpl.zip





12. AccountService.java (com.website.example.service)



package com.website.example.service;

import java.sql.SQLException;


import com.website.example.vo.AccountVO;


public interface AccountService {

 void accountCreate(AccountVO vo) throws SQLException;
 void accountTransfer(String sender, String receiver, int money) throws SQLException;
 
}


파일명: AccountService.java


[첨부(Attachments)]

AccountService.zip




13. AccountServiceImpl.java (com.website.example.service)



package com.website.example.service;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.vo.AccountVO;

@Repository
@Transactional
public class AccountServiceImpl implements AccountService{


       private AccountDAO accountDAO;
       private DataSource ds = null;
 
       public AccountServiceImpl(DataSource ds) {
             this.accountDAO = new AccountDAOImpl(ds);
             this.ds = ds;
       }

      @Override
       public void accountCreate(AccountVO vo) throws SQLException {
             this.accountDAO.createAccount(vo);  
       }
 
      @Override
      public void accountTransfer(String sender, String receiver, int money) throws SQLException {
  
            int balance = accountDAO.getBalance(sender); // 보내는 사람 잔액 체크
      
            if(balance >= money){ // 보내는 돈이 잔액보다 많으면
     
                 accountDAO.minus(sender, money);
                 accountDAO.plus(receiver, money);
              
        } else{

                 System.out.println("돈 없음");
                 //throw new NoMoneyException();
        }
  
      }


}


파일명: AccountServiceImpl.java


[첨부(Attachments)]

AccountServiceImpl.zip





14. MainTest.java (com.website.example.unit)


package com.website.example.unit;


import java.sql.SQLException;

import java.sql.Timestamp;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.website.example.common.RootConfig;
import com.website.example.service.AccountService;
import com.website.example.vo.AccountVO;


class MainTest {

      
      @Test
      void test() throws SQLException {
  
           @SuppressWarnings("resource")
           ApplicationContext applicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
  
           AccountService service = (AccountService) applicationContext.getBean("accountServiceImpl");
  
           AccountVO vo = new AccountVO();
  
  /*
  // 1. 계정 생성
  vo.setName("홍길동");
  vo.setBalance(10000);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 10:05:20"));
  service.accountCreate(vo);
  
  // 2. 계정 생성
  vo.setName("홍길자");
  vo.setBalance(0);
  vo.setRegidate(Timestamp.valueOf("2020-01-20 22:05:20"));
  service.accountCreate(vo);
  */
  
          // 3. 거래 처리
          service.accountTransfer("홍길동", "홍길자", 500);
  
      }

}


파일명: MainTest.java


[첨부(Attachments)]

MainTest.zip




* 맺음글(Conclusion)


트랜젝션을 어노테이션 방식으로 Java Config 환경설정으로 구성하여 사용하는 방법에 대해서 소개하였다.


[Spring-Framework] 37. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java - AOP)

- https://yyman.tistory.com/1462



* 참고 자료(References)


1. Spring JDBC, https://velog.io/@koseungbin/Spring-JDBC, Accessed by 2020-10-10, Last Modified 2020-07-08.

2. Spring JDBC using Annotation based configuration, https://www.topjavatutorial.com/frameworks/spring/spring-jdbc/spring-jdbc-using-annotation-based-configuration/, Accessed by 2020-10-10, Last Modified 2016-02-14.

반응형
728x90
300x250
[Spring-Framework] 35. Spring-JDBCTemplate - 트랜젝션 (어노테이션 X)


@Transactional이라는 어노테이션 기능 방식이 아닌 직접 구현으로 트랜젝션을 Spring JDBC에서 다루는 방법을 소개하려고 한다.


아마 이전의 Spring Framework의 34번 글을 실습해보았다면, 희안하게 insert 명령은 수행되어서 SQL Developer로 SELECT문으로 출력했을 때는 화면에 보이는데, Java에서 DB 질의로 SELECT문으로 조회한다면 동작이 되는 경우도 있고, 안 된 경우도 있었을 거 같다.


이 문제는 commit()을 했느냐 안 했느냐의 차이에서 발생한다.


1. [Spring-Framework] 34. Spring JDBCTemplate - 사용하기, 2020. 10. 9. 21:01

- https://yyman.tistory.com/1459


이 글의 개선 버전이라고 보면 되겠다.


- IDE: Eclipse 2020-06

- Spring Framework 4.2.4 RELEASES



1. 프로젝트 구성


다음 그림은 프로젝트를 구성하고 있는 구성도이다.



그림 1. 프로젝트 구성도


이전 글에서는 프로젝트 구성도까진 필요할까 고민했는데, 심플하게 적어주는 것이 더 나아보여서 핵심만 적었다.

지금 글에서는 Spring JDBC에서 트랜젝션을 제대로 사용하는 방법에 대해서 소개하려고 한다.



2. Project Factes, Build Paths, Java Compiler 버전 변경해주기


* Build Paths -> JRE Library 1.8로 변경, Junit 5 추가할 것

* Java Compiler -> Compiler complicance Level: 1.8 환경으로 바꿔줄 것

* Project Factes -> Java 버전 1.8로 변경할 것


- 오라클 JDBC는 WEB-INF/lib에 넣어서 해결해주면 된다.

  (Build Paths에 등록도 해줘야 함.)




3. pom.xml 설정하기



 <properties>
  <java-version>1.8</java-version>
  <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
 </properties>
 <dependencies>


(중략)


  <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</artifactId>
       <version>${org.springframework-version}</version>
  </dependency>

(중략)



  <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.21</version>
  </dependency>





4. AccountTbl.sql


-- Transaction 실습 DB (은행 - Account)
-- Oracle 11 - 자동번호 생성 테이블 정의
-- Table 생성 (FOODMENU_TBL)
-- NEW.ID (Table의 id를 가리킴)
CREATE TABLE account_tbl
(
    idx NUMBER PRIMARY KEY,
    name VARCHAR2(30),
    balance NUMBER,
    regidate TIMESTAMP
);

-- Sequence 정의
CREATE SEQUENCE account_sequence
START WITH 1
INCREMENT BY 1;

-- Trigger 생성
-- BEFORE INSERT on '테이블명'
CREATE OR REPLACE TRIGGER account_trigger
BEFORE INSERT
    ON account_tbl
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT account_sequence.nextval INTO :NEW.IDX FROM dual;
END;


파일명: AccountTbl.sql


[첨부(Attachments)]

AccountTbl.zip



5. MyDataSourceFactory.java (com.website.example.common)


package com.website.example.common;

import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.mysql.cj.jdbc.MysqlDataSource;

import oracle.jdbc.pool.OracleDataSource;

public class MyDataSourceFactory {
 
 private InputStream is = null;
    private Properties props = null;
 
 public MyDataSourceFactory()  {
  
        String resource = "db.properties";
        this.is = getClass().getClassLoader().getResourceAsStream(resource);
        this.props = new Properties();
       
 }
 
 public DataSource getMySQLDataSource() {
  
        MysqlDataSource mysqlDS = null;
       
        try {
         
            props.load(is);
            mysqlDS = new MysqlDataSource();
            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));
            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));
            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));
           
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        return mysqlDS;
       
    }
    
    public DataSource getOracleDataSource(){
     
        OracleDataSource oracleDS = null;
       
        try {
         
            props.load(is);
            oracleDS = new OracleDataSource();
            oracleDS.setURL(props.getProperty("ORACLE_DB_URL"));
            oracleDS.setUser(props.getProperty("ORACLE_DB_USERNAME"));
            oracleDS.setPassword(props.getProperty("ORACLE_DB_PASSWORD"));
           
           
        } catch (IOException e) {
         
            e.printStackTrace();
           
        } catch (SQLException e) {
         
            e.printStackTrace();
           
        }
       
        return oracleDS;
       
    }

}


파일명: MyDataSourceFactory.java


[첨부(Attachments)]

MyDataSourceFactory.zip


AccountTbl.zip



6. AccountVO.java (com.website.example.vo)


package com.website.example.vo;

import java.sql.Timestamp;;


public class AccountVO {
 
     private int idx;
     private String name;
     private int balance;
     private Timestamp regidate;
 
     public int getIdx() {
          return idx;
     }
 
     public void setIdx(int idx) {
          this.idx = idx;
     }
 
     public String getName() {
          return name;
     }
 
     public void setName(String name) {
          this.name = name;
     }
 
     public int getBalance() {
          return balance;
     }

 

     public void setBalance(int balance) {
          this.balance = balance;
     }


     public Timestamp getRegidate() {
          return regidate;
     }
 
     public void setRegidate(Timestamp regidate) {
          this.regidate = regidate;
      }
   
}


파일명: AccountVO.java


[첨부(Attachments)]

AccountVO.zip

AccountTbl.zip



7. AccountDAO.java (com.website.example.dao)


package com.website.example.dao;


import java.sql.SQLException;

import com.website.example.vo.AccountVO;


public interface AccountDAO {
 
       void createAccount(AccountVO vo) throws SQLException;
       int getBalance(String name);
       void minus(String name, int money) throws SQLException;
       void plus(String name, int money) throws SQLException;
 
}


파일명: AccountDAO.java


[첨부(Attachments)]

AccountDAO.zip



8. AccountDAOImpl.java (com.website.example.dao)


package com.website.example.dao;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.website.example.common.MyDataSourceFactory;
import com.website.example.vo.AccountVO;

public class AccountDAOImpl implements AccountDAO {


       // Spring Framework - JDBC
       private JdbcTemplate jdbcTemplate = null;
       private DataSource ds = null;
 
       private final String INSERT = "insert into account_tbl(name, balance, regidate) values(?, ?, ?)";
       private final String SELECT_BALANCE = "select * from account_tbl where name = ?";
       private final String UPDATE_MINUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) - ? " +
          " where name = ?";
       private final String UPDATE_PLUS = "update account_tbl set balance = (select balance from account_tbl where name = ?) + ? " +
          " where name = ?";
 
 
        public AccountDAOImpl(DataSource ds) {
               this.jdbcTemplate = new JdbcTemplate(ds);
               this.ds = ds;
        }
 
         public void createAccount(AccountVO vo) throws SQLException {
  
                TransactionSynchronizationManager.initSynchronization(); // 동기화
                Connection c = DataSourceUtils.getConnection(ds); //커넥션을 생성

  
                // 데이터 저장소 바인딩(트랜젝션)
                c.setAutoCommit(false); //트랜잭션을 시작
  
                try{
   
                    this.jdbcTemplate.update(INSERT, vo.getName(), vo.getBalance(), vo.getRegidate());
                    c.commit(); // 커밋
       
               }
               catch(Exception e) {
                    c.rollback(); // 예외가 발생하면 롤백
       
               }finally {
      
                    DataSourceUtils.releaseConnection(c, ds); // 스프링 유틸리티를 이용해 DB커넥션을 닫는다.
                    TransactionSynchronizationManager.unbindResource(ds); // 동기화 작업을 종료하고
                    TransactionSynchronizationManager.clearSynchronization();     // 정리한다.
      
               }

  
 }
 
    public int getBalance(String name){
     
     Object[] args = {name};
     
     AccountVO vo = this.jdbcTemplate.queryForObject(SELECT_BALANCE, args, new AccountRowMapper());
       
        int result = vo.getBalance();
     
        return result;
    }
    
    public void minus(String name, int money) throws SQLException{

     // 예외 발생시키기
     if(true){
      throw new SQLException(); // 의도적 예외 발생
        }
     
        this.jdbcTemplate.update(UPDATE_MINUS, name, money, name);
       
    }
    
    public void plus(String name, int money) throws SQLException{
          
        this.jdbcTemplate.update(UPDATE_PLUS, name, money, name);
    }
 
}


파일명: AccountDAOImpl.java


[첨부(Attachments)]

AccountDAOImpl.zip


AccountDAO.zip



9. AccountRowMapper.java (com.website.example.dao)


package com.website.example.dao;


import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.website.example.vo.AccountVO;


public class AccountRowMapper implements RowMapper<AccountVO>  {


       @Override
        public AccountVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
                 AccountVO vo = new AccountVO();

                 vo.setIdx(rs.getInt(1));
                 vo.setName(rs.getString(2));
                 vo.setBalance(rs.getInt(3));
                 vo.setRegidate(rs.getTimestamp(4));
  
                 return vo;
        }

}


파일명: AccountRowMapper.java


[첨부(Attachments)]

AccountRowMapper.zip




10. AccountService.java (com.website.example.service)


package com.website.example.service;

import java.sql.SQLException;

import com.website.example.vo.AccountVO;


public interface AccountService {

       void accountCreate(AccountVO vo) throws SQLException;
       void accountTransfer(String sender, String receiver, int money) throws SQLException;
 
}


파일명: AccountService.java


[첨부(Attachments)]

AccountService.zip




11. AccountServiceImpl.java (com.website.example.service)


package com.website.example.service;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.website.example.dao.AccountDAO;
import com.website.example.dao.AccountDAOImpl;
import com.website.example.vo.AccountVO;



public class AccountServiceImpl implements AccountService{

      private AccountDAO accountDAO;
      private DataSource ds = null;
 
      public AccountServiceImpl(DataSource ds) {
            this.accountDAO = new AccountDAOImpl(ds);
            this.ds = ds;
      }


      @Override
      public void accountCreate(AccountVO vo) throws SQLException {
            this.accountDAO.createAccount(vo);  
      }
 

      @Override
      public void accountTransfer(String sender, String receiver, int money) throws SQLException {
  
               int balance = accountDAO.getBalance(sender); // 보내는 사람 잔액 체크
               Connection conn = null;
     
               if(balance >= money){ // 보내는 돈이 잔액보다 많으면

                    TransactionSynchronizationManager.initSynchronization(); // 트랜잭션 동기화 작업 초기화

                    conn = ds.getConnection();
                    conn.setAutoCommit(false);
         
                    try {
                       accountDAO.minus(sender, money);
                       accountDAO.plus(receiver, money);
          
                       conn.commit(); // 성공
    
                   }catch(SQLException e) {
                        conn.rollback(); // 실패
                   }
                   finally {
                        // 커넥션 종료(Spring)
                       DataSourceUtils.releaseConnection(conn, this.ds);
          
                       // 동기화 작업을 종료하고 저장소를 비운다
                       TransactionSynchronizationManager.clearSynchronization();
                   }
           
             } else{

                   System.out.println("돈 없음");
                  //throw new NoMoneyException();
             }
  
       }


}


파일명: AccountServiceImpl.java


[첨부(Attachments)]

AccountServiceImpl.zip



12. MainTest.java (com.website.example.unit)


package com.website.example.unit;

import java.sql.SQLException;
import java.sql.Timestamp;

import javax.sql.DataSource;

import org.junit.jupiter.api.Test;

import com.website.example.common.MyDataSourceFactory;
import com.website.example.service.AccountService;
import com.website.example.service.AccountServiceImpl;
import com.website.example.vo.AccountVO;


class MainTest {


     @Test
     void test() throws SQLException {

            MyDataSourceFactory sourceFactory = new MyDataSourceFactory();
            DataSource ds = sourceFactory.getOracleDataSource();
  
            AccountService service = new AccountServiceImpl(ds);
            AccountVO vo = new AccountVO();
   
            /*
            // 1. 계정 생성
           vo.setName("홍길동");
           vo.setBalance(10000);
           vo.setRegidate(Timestamp.valueOf("2020-01-20 10:05:20"));
           service.accountCreate(vo);
  
           // 2. 계정 생성
           vo.setName("홍길자");
           vo.setBalance(0);
           vo.setRegidate(Timestamp.valueOf("2020-01-20 22:05:20"));
           service.accountCreate(vo);
            */
  
           // 3. 거래 처리
           service.accountTransfer("홍길동", "홍길자", 500);
  
  
      }

}


파일명: MainTest.java


[첨부(Attachments)]

MainTest.zip

AccountServiceImpl.zip




* 맺음글(Conclusion)


스프링 JDBC에서 트랜젝션 사용하는 방법에 대해서 어노테이션을 적용하지 않는 방법에 대해서 살펴보았다.


1. [Spring-Framework] 36. Spring-JDBCTemplate - 트랜젝션 (어노테이션, Java 설정), 2020-10-10

- https://yyman.tistory.com/1461

(이해를 돕기 위하여 실제 개발환경도구에서 트랜젝션 시연 영상도 포함해서 소개하였다.)


반응형
728x90
300x250
[Spring-Framework] 34. Spring JDBCTemplate - 사용하기


사용 방법은 무척 간단하다.

pom.xml 등의 학습 지식이 없다면, 이전 글을 참고하면 좋겠다.


[작성 환경]

- IDE: Eclipse 2020-06
- Spring Framework 4.2.4 RELEASES




1. POM.xml 설정


https://mvnrepository.com/artifact/org.springframework/spring-jdbc

사이트에 접속해서 Spring-JDBC의 정보를 찾아서 pom.xml에 입력시켜 준다.

(중략)
  
  <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>


(중략)




2. 구현 부분

 

핵심만 밑줄로 표기하였다.



package com.website.example.board;

import java.sql.Date;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.website.example.common.MyDataSourceFactory;
import com.website.example.model.FoodMenuVO;
import com.website.example.model.FoodMenuViewVO;


public class BoardDAOSpring {
 
 // Spring Framework - JDBC
 private JdbcTemplate jdbcTemplate = null;
 
 private final String FOODMENU_INSERT = "insert into foodmenu_tbl(name, price, store_id, cnt, regidate) values(?, ?, ?, ?, ?)";
 private final String BOARD_UPDATE = "update board set subject=?, memo=? where id=?";
 private final String FOODMENU_DELETE = "delete foodmenu_tbl where id=?";
 private final String BOARD_GET = "select * from board where id=?";

 
 private final String BOARD_LIST = "SELECT * FROM ( " +
                "SELECT /*+ INDEX_DESC(Z OP_SAMPLE_PK) */ ROWNUM AS RNUM, Z.* FROM ( " +
                   "SELECT f1.id, f1.name, f1.price, f2.name as storename from foodmenu_tbl f1, foodstore_tbl f2 " +
                "where f1.store_id = f2.id order by f1.id desc " +
                ") Z WHERE ROWNUM <= ? " +
                ") WHERE RNUM >= ?";
 
      private final String BOARD_FULL_COUNT = "select count(*) from foodmenu_tbl f1, foodstore_tbl f2 where f1.store_id = f2.id";

      // 변형을 할 필요가 있음. (태스트용)

      public BoardDAOSpring() {

              MyDataSourceFactory sourceFactory = new MyDataSourceFactory();
              DataSource ds = sourceFactory.getOracleDataSource();

              this.jdbcTemplate = new JdbcTemplate(ds);
      }
 
      public List<FoodMenuViewVO> getList(){
 
          // 코드 간결하게 작성가능해짐.
          System.out.println("Spring JDBC - GetBoardList()");
         //return jdbcTemplate.query(BOARD_LIST, new BoardRowMapper());
  
          Object args[] = {10, 1};
  
          return jdbcTemplate.query(BOARD_LIST, args, new FoodMenuViewRowMapper());

       // return null;

     }
 
     public int getCount() {
  
          int result = 0;
          FoodMenuVO vo =  jdbcTemplate.queryForObject(BOARD_FULL_COUNT, new FoodMenuViewCntRowMapper());
          result = vo.getId();
  
          System.out.println("갯수:" + result);
  
         return result;
  
     }
 
      public void insertTest() {
  
          FoodMenuVO vo = new FoodMenuVO();

  
          // 약 10만 개
          // insert 후에 commit 할 것
          /*

          for(int j = 0; j < 2000 ; j++) {
                for ( int i = 0; i < 50; i++) {
   
                       vo.setName("하하하하1234" + i);
                       vo.setPrice(1000);
                       vo.setStore_id(1);
                       vo.setCnt(0);
                       vo.setRegidate(Date.valueOf("2020-01-03"));
                       vo.setStore_id(1);
    
                       jdbcTemplate.update(FOODMENU_INSERT, vo.getName(),
                              vo.getPrice(), vo.getStore_id(),
                              vo.getCnt(), vo.getRegidate());
    
                }
        }// end of if
  */
  
        vo.setName("야해해");
        vo.setPrice(1000);
        vo.setStore_id(1);
        vo.setCnt(0);
        vo.setRegidate(Date.valueOf("2020-01-05"));
        vo.setStore_id(1);
  
        Object[] args = {vo.getName(),
                 vo.getPrice(),
                 vo.getStore_id(),
                 vo.getCnt(),
                 vo.getRegidate()};

  
        jdbcTemplate.update(FOODMENU_INSERT, args);
  
 }
 

      // 글 삭제
      public void deleteFoodMenu(FoodMenuVO vo) {
  
             System.out.println("===> Spring JDBC로 deleteBoard() 기능 처리");
             jdbcTemplate.update( FOODMENU_DELETE, vo.getId() );
  
      }
 
 
}


파일명: BoardDAOSpring.java



3. Mapper 구현


Spring JDBC의 특징은 Mapper 영역을 구현할 수 있다는 점이다.


package com.website.example.board;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.website.example.model.FoodMenuVO;
import com.website.example.model.FoodMenuViewVO;


public class FoodMenuRowMapper implements RowMapper<FoodMenuVO>  {


        @Override
        public FoodMenuVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
              FoodMenuVO vo = new FoodMenuVO();

              vo.setId(rs.getInt(1));
              vo.setName(rs.getString(2));
              vo.setPrice(rs.getInt(3));
              vo.setStore_id(rs.getInt(4));
              vo.setCnt(rs.getInt(5));
              vo.setRegidate(rs.getDate(6));
  
              return vo;
        }

}


파일명: FoodMenuRowMapper.java


package com.website.example.board;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.website.example.model.FoodMenuVO;


public class FoodMenuViewCntRowMapper implements RowMapper<FoodMenuVO>  {


        @Override
        public FoodMenuVO mapRow(ResultSet rs, int rowNum) throws SQLException {
  
               FoodMenuVO vo = new FoodMenuVO();
               vo.setId(rs.getInt(1));
               // System.out.println(rs.getInt(1));
  
               return vo;
       }

}


파일명: FoodMenuViewCntRowMapper.java


Mapper를 두 가지 형태로 두었는데, 상황에 따라서 변형이 가능하다.



* 맺음글(Conclusion)


Spring JDBC에 대해서 살펴보았다.

짧게 적은 이유는 사용 방법이 간단해서 그렇다. Spring JDBC의 트랜젝션을 언급하기 위해서 그렇다.


1. [Spring-Framework] 35. Spring-JDBCTemplate - 트랜젝션 (어노테이션 X), 2020-10-09

- https://yyman.tistory.com/1460


반응형
728x90
300x250
[Spring Framework] 33. AOP(Aspect-Oriented-Programming) - XML 방식


XML 기반으로 작성된 AOP 구현 방법에 대해서 소개하려고 한다.

AOP의 관점에 대한 5가지 방식을 모두 적용해보았다.


[태스트 환경]

* IDE: Eclipse 2020-06

* Spring Framework 4.2.4.RELEASE
* Aspectjweaver 1.6.10
* Spring-aop 4.2.4.RELEASE
* JUnit 5




1. 프로젝트 구성도


프로젝트 구성도이다.



그림 1, 그림 2. 프로젝트 구성도




2. 프로젝트 생성


Spring Legacy Project로 프로젝트를 생성한다.

참고로 Spring MVC Project로 선택하고 생성해야 한다.



3. Build Path, Java Compiler, Project Factes 버전 맞춰주기


* Build Path: JRE 버전을 1.8로 변경해준다.
               - Add Library로 JUnit 5를 등록해준다.

* Java Compiler: Compiler compliance level - 1.8로 변경해준다.

* Project Factes: Java 버전을 1.8로 변경해준다.




4. POM.xml 설정하기


(중략)
 <properties>
  <java-version>1.8</java-version>
  <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
 </properties>

~~~

(중략)
    <!-- AspectJ -->
    <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjrt</artifactId>
     <version>${org.aspectj-version}</version>
    </dependency>
   
    <!-- AspectJWeaver -->
    <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>${org.aspectj-version}</version>
    </dependency>
   
    <!-- Spring AOP 추가(Java) --> 
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${org.springframework-version}</version>
    </dependency> 
(중략)






5. applicationContext.xml (src/main/resources/applicationContext.xml)


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:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop"
 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/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
 <!-- 방법1 - JAVA -->
 <!-- JAVA 방식(어노테이션)의 AOP - AspectJ Weaver -->
 <!--  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  어노테이션 작업시 필수 선언해야 함. -->
 
 <!--  XML 방식의 AOP - AsepectJ Weaver -->
 <bean id="resultAOP" class="com.website.example.test.ResultAOP"></bean>
 <bean id="txAdviceXML" class="com.website.example.aop.LogAdvisorXML"></bean>

 <aop:config>
  <!-- txAdviceXML 하나 영역임 -->
  <aop:aspect ref='txAdviceXML'>

   <!-- 진입영역 -->
   <!-- 1개만 가능함 -->
   <!-- <aop:pointcut id="point1" expression="execution(* com.website.example.test..*())"/> -->
   <aop:pointcut id="point1" expression="execution(* com.website.example.test.ResultAOP..*())"/>           
          
          <!-- before -->
          <aop:before method="beforeAdvice" pointcut-ref="point1"/>
          <!-- after -->
          <aop:after method="afterAdvice" pointcut-ref="point1"/>
          
          <!-- around(메서드 자체를 가로채기) -->
          <aop:around method="aroundAdvice" pointcut-ref="point1"/>
          
          <!-- afterThrowing -->
          <aop:after-throwing method="afterThrowing" pointcut-ref="point1"/>
          
          <!-- afterReturning -->
          <aop:after-returning method="afterReturning" pointcut-ref="point1"/>
          
        </aop:aspect>
       
        <!-- 2번째 선언자 -->
 </aop:config>
 
</beans>


파일명: applicationContext.xml


[첨부(Attachments)]

applicationContext.zip



6. ResultAOP.java - com.website.example.text


핵심 로직에 해당되는 부분이다. 이 부분을 비즈니스 로직이라고 표현하기도 한다.


package com.website.example.test;


public class ResultAOP {


     public void method1() {
           System.out.println("[중간]:");
  
           // afterThrowing 유발 코드
           // int d = 2/0;
  
           System.out.println("결과: 메서드");
     }
 
}


파일명: ResultAOP.java


[첨부(Attachments)]

ResultAOP.zip


applicationContext.zip



7. LogAdvisor.java - com.website.example.aop


인터페이스 정의이다.

package com.website.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public interface LogAdvisor {

        public void beforeAdvice();
        public void afterAdvice();
        public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable;
        public void afterThrowing();
        public void afterReturning();
 
}


파일명: LogAdvisor.java


[첨부(Attachments)]

LogAdvisor.zip




8. LogAdvisorXML.java - com.website.example.aop


package com.website.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;


public class LogAdvisorXML implements LogAdvisor{
  
       // around advice
       @Override
       public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
  
               System.out.println("2단계A?:");
               System.out.println("aroundMethod 호출 1");
  
               // 원래의 메소드를 호출한다.
               Object obj = pjp.proceed();

               System.out.println("2단계B?:");
               System.out.println("aroundMethod 호출 2");
  
               return obj;
       }


       // before advice
       @Override
       public void beforeAdvice() {
              System.out.println("1단계:");
              System.out.println("beforeMethod 호출");
  
       }


       // after
       @Override
       public void afterAdvice() {
              System.out.println("5단계:");
              System.out.println("afterMethod 호출");
  
       }


       // afterThrowing
      @Override
      public void afterThrowing() {
              System.out.println("4단계:");
              System.out.println("afterThrowing 호출");
      }

      // afterReturning
      @Override
      public void afterReturning() {
              System.out.println("3단계:");
              System.out.println("afterReturning 호출");
  
      }
 
}


파일명: LogAdvisorXML.java


[첨부(Attachments)]

LogAdvisorXML.zip


LogAdvisor.zip




9. TestMain.java - com.website.example.unit


package com.website.example.unit;


import org.junit.jupiter.api.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.website.example.test.ResultAOP;


public class TestMain {

      
       // 사용방법1
       @Test
       public void sample() {

                AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
   
                ResultAOP rAOP = (ResultAOP) factory.getBean("resultAOP");

                rAOP.method1();
  
                factory.close();
       }
 

       // 사용방법2
       @Test
       public void sample2() {

               ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
               ResultAOP rAOP = ctx.getBean("resultAOP", ResultAOP.class);
               //rAOP.method1();
  
               ctx.close();
       }
 
}


파일명: TestMain.java


[첨부(Attachments)]

TestMain.zip




10. 동작 결과


구현한 코드의 동작 결과이다.



그림 3. 출력 결과 1



그림 4. 출력 결과 2




11. 맺음글(Conclusion)


직관적으로 AOP를 아주 쉽게 이해할 수 있도록 작성하였다.

반응형
728x90
300x250
[Spring Framework] 32. AOP(Aspect-Oriented-Programming) - Java 방식2(Context.xml)


Spring Framework에서 사용되는 AOP 구현방식을 소개하려고 한다.

AOP를 다루고자 하는 사람들은 조금 방법이 다양하게 있으니, 원하는 방식 하나 택해서 사용하면 될듯 싶다.


[배경 입문 지식]

1. [Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (1), 2020-10-04

- https://yyman.tistory.com/1447

2. [Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (2), 2020-10-04

- https://yyman.tistory.com/1448


셋팅 방법에서 다르다.

동일하게 생각하고 작성하면, 오류를 경험할 수 있다.


AOP를 다루는 건 큰 틀에서는 같은 일인데 불구하고, Service 어노테이션 정의부터 다소 셋팅이 달라져버릴 수가 있어서 신중하게 사용하는 것을 권장한다. 셋팅을 먼저 한 경우라면, 거기에 맞춰서 흐름을 타주는 방식으로 가야 하겠다.


XML, 순수한 Java 방식, Context.xml 및 Java 방식(어노테이션) 크게 3가지 방법으로 AOP를 설정할 수 있다.

이론적으로는 같으나 실질적인 구현에서는 호환이 안 되니 주의하여 사용할 것을 권한다.


3가지 방법 중 하나를 택해서 AOP 대상을 두었으면 그 방법을 따라가는 형태로 진행하는 것을 권장한다.

(구현 관점에서보면, 표준화가 진행이 덜된 느낌이 있다.) 시간이 나면, 소개해주겠다.


[태스트 환경]

* IDE: Eclipse 2020-06 (Spring Tools 3 Addon.....)
* Spring Framework Version: 4.2.4.RELEASE

* JUnit 5(태스트 실행)




1. 프로젝트 생성


-> Spring Legacy Project -> Spring MVC Project로 진행하였음.


프로젝트 생성 및 셋팅 방법에 대해서는 "이전 글들"을 참고하면 된다. (동일하게 진행함.)




2. pom.xml


매우 중요한 부분이다. 노란색 불은 들어오는데 빌드하면 오류가 생긴 경우도 있을 수도 있다.

셋팅을 잘해야 한다.


-> Spring Tool-Suites 4.7로 작업하면 14버전을 사용해도 무방하다. 다만, 이전 버전의 Eclipse 등에서 프로젝트 구성에서 오류가 생길 수도 있다.

지금 pom 셋팅은 공통적인 작업이다.


(중략)

 <properties>
  <java-version>1.8</java-version>
  <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
 </properties>

(중략)
    
  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency> 
  
  <!-- AspectJWeaver -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.8.8</version>
  </dependency>
  
  <!-- Spring AOP 추가(Java) -->  
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>

(중략)




3. 미리 살펴보는 프로젝트 구성도


이번 작업에서는 JUnit5로 TDD 방식으로 구동한 프로젝트이다.



그림 1. 프로젝트 구성도

작업할 프로젝트 구성이다.




4. 프로젝트 환경설정(Build Path, Java Compiler, Project Factes)


Build Path의 Libraries에 들어가서 버전을 1.8로 설정한다.

JUnit 5도 추가해준다.


참고로 JUnit 5은 자바 1.8버전에서부터 지원한다.



그림 2. 프로젝트 구성도


Build Path를 설정해준다. "Add Library"를 클릭해서 JUnit 5을 추가할 수 있다.




그림 3. 자바 컴파일러 환경설정


JDK를 잘 설치하였음에도 1.8버전 등으로 변경하였을 때 이클립스에서 오류로 간주해버리는 경우가 있다.

오류가 생기는 이유는 Project Factes 옵션을 조작할 때 발생한다.


JDK Compliance의 Compiler compliance level 버전을 맞춰주면 오류가 사라지는 것을 확인할 수 있다.



그림 4. Project Factes


Java의 버전을 1.8으로 변경한다.




5. applicationJavaAOP.xml (src/main/resources/applicationJavaAOP.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:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop"
 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/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
 <aop:aspectj-autoproxy />
 <context:component-scan base-package="com.website.example" />
 <bean id="loggingAspect" class="com.website.example.aop.LogAdvisorJava" />
 
</beans>



AOP 스키마 정의는 servlet-context.xml에서 간단한 조작으로 추가하였다.


그림 5. aop 환경설정해주기 - 네임스페이스 정의


양식 틀은 /src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml에서 복사 붙여넣기하였다.


작성한 코드는 <aop>.....</bean> 이 정도이다.


* 파일명: applicationJavaAOP.xml


[첨부(Attachments)]

applicationJavaAOP.zip




6. ResultAOPJava.java (com.website.example.text)


package com.website.example.test;

import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;


@Service
public class ResultAOPJava {
 
     @Pointcut("execution(* com.website.example.test.*(..))")
      public void method1() {
              System.out.println("결과: 메서드");
      }
 
}


* 파일명: ResultAOPJava.java


[첨부(Attachments)]

ResultAOPJava.zip




7. LogAdvisor.java (com.website.example.aop)


관점에 대한 인터페이스이다.
객체의 다형성을 생각하면 좋을 것으로 보인다.


package com.website.example.aop;

import org.aspectj.lang.annotation.Aspect;


@Aspect
public interface LogAdvisor {

       public void beforeAdvice();
       public void afterAdvice();
       public void aroundAdvice();
 
}


* 파일명: LogAdvisor.java


[첨부(Attachments)]

LogAdvisor.zip

ResultAOPJava.zip




8. LogAdvisorJava.java (com.website.example.aop)


클래스명을 Impl로 해도 되었을 법한데 태스트용이니 생략하도록 하자.


package com.website.example.aop;


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Service;


// 어노테이션 방식
@Aspect
public class LogAdvisorJava implements LogAdvisor{


         @Before("execution(* com.website.example.test.ResultAOPJava.*(..))")
          public void beforeAdvice() {
                 System.out.println("전 단계");
         }

         @After("execution(* com.website.example.test.ResultAOPJava.*(..))")
         public void afterAdvice() {
                 System.out.println("후 단계");
         }


         public void aroundAdvice() {
   
          }
 
}


* 파일명: LogAdvisorJava.java


[첨부(Attachments)]

LogAdvisorJava.zip


LogAdvisor.zip

ResultAOPJava.zip



9. TestMain.java (com.website.example.test)


JUnit 태스트 도구로 실행 영역을 구현하였다.


package com.website.example.test;

import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class TestMain {


     @Test
     public void test() {


              // 방법 1
             @SuppressWarnings("resource")
             AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationJavaAOP.xml");
  
             ResultAOPJava cal = factory.getBean(ResultAOPJava.class);
             cal.method1();
  
     }
 
}


* 파일명: TestMain.java


[첨부(Attachments)]

TestMain.zip



10. 출력 결과


아래의 그림처럼 AOP가 잘 출력되는 것을 확인할 수 있다.



그림 6. AOP 화면 출력 결과


정상적으로 AOP 환경설정이 이뤄진 것을 확인할 수 있다.



* 참고자료(References)


1. Spring AOP – AspectJ Annotation Config Example - HowToDoInJava, https://howtodoinjava.com/spring-aop/spring-aop-aspectj-example-tutorial-using-annotation-config/, Accessed by 2020-10-09, Last Modified 2015-02.


-> 추천(25점): 구현에 대해서 많은 도움을 주었다.


2. 스프링 부트(Spring Boot) - AOP와 트랜잭션(Transaction) 설정하기 [개발을 시작해봐요!], https://congsong.tistory.com/25, Accessed by 2020-10-09, Last Modified 2020-04-28.

-> 추천(25점): 스프링 부트로 작성되었지만, AOP 개발 방법의 흐름을 살펴볼 수 있었음.

반응형
728x90
300x250

[Spring-Framework] 31. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (2)


1부에서는 통상적인 개발 방법으로 간단한 Calculator 클래스에 정의된 sum 함수(또는 매서드)를 출력하는 방법을 살펴보았다.

참고로 앞에 코드는 편집이 불가능한 코드라고 가정하고 접근해야 한다.


1. [Spring-Framework] 31. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (1)

https://yyman.tistory.com/1447



8. aop 패키지를 추가로 작성하여 copy & paste하기


aop 패키지를 별도로 둘 것이다.

코드를 따로 작성해야 하는가? 시점 부분만 작성하고 크게 많이 작성하진 않는다.



그림 15. Copy & Paste 하기


태스트 코드를 복사할 것이다.


(1단계) com.local.example.service에 만들어놓았던 Calculator.java를 파일 복사한다. (Ctrl + C)

(2단계) com.local.example.aop 폴더(또는 패키지)를 만든다.

(3단계) com.local.example.aop 폴데 Calculator.java를 붙여넣는다. (Ctrl + V)

(4단계) 소스 코드가 빨간 불이 들어오는 패키지 부분을 com.local.example.aop로 변경해준다.



9. 작업 방법론 - 정리해보기


직관적으로 코드 작성 전략을 소개해주려고 한다.

이런 형태로 작업을 할 것이다.



그림 16. 작업 방법


빌드하는 방법은 웹 서버에 올려서 확인하면 된다.




9. AOP 필수(코어), 핵심사항 - RootConfig.java (환경설정 파일 만들기)


패키지 경로: com.local.example.aop


반드시 만들어야 할 파일이다.

이 셋팅 파일이 없으면 동작 자체를 안 한다.



그림 17. RootConfig.java


이 코드는 AspectJ Proxy의 핵심 환경설정 파일이다.

이걸 정의하지 않으면, 동작이 안 된다.


package com.local.example.aop;


import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.EnableAspectJAutoProxy;


@Configuration

@ComponentScan(basePackages = {"com.local.example.aop"})

//@ComponentScan(basePackages = {"com.local.example.beans", "com.local.example.advisor"})

@EnableAspectJAutoProxy

public class RootConfig {


}


파일명: RootConfig.java


[첨부(Attachments)]

RootConfig.zip




10. AOP 적용, 핵심사항 - Calculator.java (살짝 수정하기)


패키지 경로: com.local.example.aop


작업 파일 원본의 권한을 가진 사람에게 하나 요청해도 무방하다. (초기 생성자 부분)

여기에서는 그런 거 신경쓰지 않고, 작업하겠다.


package com.local.example.aop;


import org.springframework.stereotype.Service;


@Service("cal")

public class Calculator {


private long x;

private long y;

private long z;

public Calculator() {

this.x = 1;

this.y = 2;

this.z = 3;

}

public Calculator(long x, long y, long z) {

this.x = x;

this.y = y;

this.z = z;

}

// 작업 원본(A 프로그래머 작성함)

public long sum() {

long result = x + y + z;

System.out.println("비즈니스 로직");

// long d = result / 0; - 일부러 만든 코드(After Throws 보여줄려고)

return result;

}

}



파일명: Calculator.java


[첨부(Attachments)]

Calculator-Aspect.zip


(참고)

주석 친 부분을 주석 풀면, After Throws를 정의한 경우라면 메시지가 출력되는 것을 볼 수 있다.


색깔 칠한 코드는 Calculator(long x, long y, long z) 코드 때문에 Calculator.class 파일로 불러오면 Error를 경험할 수 있다.

그래서 파란색깔로 칠한 코드 Calculator()를 정의해서 문제를 해결한 것이다.




11. AOP 구현(시점 구현) - 시점 예제 (Log.java)


패키지 경로: com.local.example.aop


많은 고민을 한 끝에 간단하면서 실용적인 형태로 코드를 작성하였다.

참고로 시점 정의를 안해도 빌드해보면, HomeController.java에 정의가 잘 되어있으면 돌아가긴 돌아간다.

무슨 말인지는 태스트를 해보면 알 수 있다.


package com.local.example.aop;


import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.After;

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.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Service;


// 관점, 서비스

@Aspect

@Service


// 콤포넌트 사용하지 말것

public class Log {

Logger logger =  LoggerFactory.getLogger(Log.class);

// 섞어적어놓아도 순서를 찾아서 강제 인식함.

    

// 2단계 - 전 단계 시야)

@Before("execution(* com.local.example.aop.Calculator.*(..))")

// @Before("execution(public void sum())")

// 반환값 없어도 무방

    public void logBefore() { 

logger.info("전 단계 관점 - 구현(Before)");

    }

    

// 1단계 - Before보다 전 단계 실행

    // Calculator 클래스의 모든 메서드

// 무조건 반환값이 지정되어야 함. (ProceedingJoinPoint에 대한 Object 반환값 필수 정의되어야 함)

// @Around 이 친구만 Pjp가 있어야 함.

    @Around("execution(* com.local.example.aop.Calculator.*(..))")

    /*@Around("execution(* com.example.demo.controller..*.*(..))")*/

    /*@Around("execution(* com.example.demo..*.*(..))")*/

    public Object logAround(ProceedingJoinPoint pjp) throws Throwable { 

   

logger.info("Around 단계 - 구현(Around)");

Object result = pjp.proceed();

return result;

    }

    

    // 3단계는 (중간) - 임의로 지정해줄 수 있는 것이 아님.

// 4단계: After advice

//@After("execution(* method1())")  -- method1 함수만 실행 

    // (이렇게 하면 안 됨. sum()을 콜하니깐)

    // @After("execution(* method1())")

    @After("execution(* sum())")

public void afterMethod() {

   

    logger.info( "after Method 호출" );

   

}

    

    // 5단계: 맨 마지막 단계

// after-throwing advice /// 오류가 발생할 때 호출함(무조건 보여지는 영역은 아님

    // 쉽게 비유하면, try to catch finally에서 catch 단계로 보면 됨.

    // 오류도 출력되고 after Throwing 로그 메시지도 출력된다.

    // 이 화면을 보려면, 오류나는 코드에 try to catch finally를 적용하면 안 됨.

    // 원본 비즈니스 로직 코드는 그대로 둘 것

    @AfterThrowing(

        pointcut = "execution(* com.local.example.aop.Calculator.sum(..))", 

        throwing = "ex"

    )

public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {

logger.info( "after Throwing 호출" + ex.getMessage());

}


    

}


파일명: Log.java


[첨부(Attachments)]

Log.zip





12. AOP 사용시, 핵심사항 - HomeController.java 변경하기


패키지 경로: com.local.example


HomeController.java 코드를 수정할 것이다.


package com.local.example;


import java.text.DateFormat;

import java.util.Date;

import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


import com.local.example.aop.Calculator;

import com.local.example.aop.RootConfig;


/**

 * Handles requests for the application home page.

 */

@EnableAspectJAutoProxy

@Controller

public class HomeController {

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

/**

* Simply selects the home view to render by returning its name.

*/


@RequestMapping(value = "/", method = RequestMethod.GET)

public String home(Locale locale, Model model) {

logger.info("Welcome home! The client locale is {}.", locale);

Date date = new Date();

DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);

// Java 방식 - AOP

AnnotationConfigApplicationContext context = 

new AnnotationConfigApplicationContext(RootConfig.class);

// 1. 이전 방식

// Calculator cal = new Calculator(1, 2, 3);


// 2. Aspect 적용

// 소문자로 입력(Calculator -> calculator로)

Calculator cal = (Calculator)context.getBean("cal", Calculator.class);

System.out.printf("result of sum: %d", cal.sum());

model.addAttribute("serverTime", formattedDate );

return "home";

}

}



파일명: HomeController.java


[첨부(Attachments)]

HomeController-aspect.zip




13. 동작 결과 구경하기


두 가지 결과로 작성하였다. 하나는 오류가 없는 코드이다.

하나는 오류가 있어서 After Throws가 호출되는 코드이다.



그림 17. 결과1의 오류 없는 코드(1)



그림 18. 결과1의 오류 없는 코드(2)




그림 19. 결과2의 오류 있는 코드(1)



그림 20. 결과2의 오류 있는 코드(2)





* 맺음글(Conclusion)


AOP 프로그래밍을 실질적으로 쉽게 사용하는 방법에 대해서 소개하였다.



* 참고자료(References)


1. [스프링] Error creating bean with name '***Controller': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named '***Service' is defined 에러 해결방법, https://zzznara2.tistory.com/275, Accessed by 2020-10-04, Last Modified 2015-06-18.


참고: 시중에 있는 AOP 자료를 보고 따라하다보면, 오류 먼저 구경할 것이다. AOP는 조금 어느 정도 프로그래밍 지식이 있을 때 사용하는 걸 권장하고 싶다. (쉽지 않은 부분이다.)


2. Spring : AOP with Java Config - 설정하기와 @Before advice, https://kogle.tistory.com/51, Accessed by 2020-10-04, Last Modified 2020-05-11.


추천(40점): 구축 원리에 대해서 매우 잘 나와있다. 그러나 몇 가지는 수정작업을 해줘야 한다.


3. AspectJ Weaver를 사용한 애노테이션 기반의 스프링 AOP 구현 방법, https://atoz-develop.tistory.com/entry/AspectJ-Weaver%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EA%B8%B0%EB%B0%98%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95, Accessed by 2020-10-04, Last Modified 2020-04-27.


추천(25점): 구축 원리를 잘 정리해놨다. 2번 참고자료하고 같이 보는 것을 권장한다. (예제가 살짝 아쉬움)


4. Spring AOP 스프링이 해줄건데 너가 왜 어려워 해? Spring boot에서 aop logging 사용법 제일 쉽게 알려드립니다!, https://jeong-pro.tistory.com/171, Accessed by 2020-10-04, Last Modified 2018-11-23.

반응형
728x90
300x250

[Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (1)


참고로 AOP는 Spring-Framework에만 적용되는 방법론이다.

새로 만들어진 개념은 아니고, 자바의 Proxy(패턴 아님. 탈취기법이 있음.)에서 핵심 영역의 관점으로 진화한 개념이다.


교과서나 시중 책 등에서는 이런 표현은 안 쓰긴 하지만, "탈취 방법"이라고 하는 게 적절하다고 주장하다.


해당 작업은 Spring Framework에서만 가능한 작업이다.



IDE: Spring Tool-Suite 4-4.7.2 Release(2020-08월)
Library: 

- Spring Framework 5.2.9.RELEASE (2020-09-15)

- Java Version: 14

- AspectJ: 1.9.6 (2020-07-22)

- AspectJ Weaver: 1.9.6 (2020-07-22)

- javax.servlet-api 4.0.1 (2018-04-20)


(순정 상태로 셋팅하였음.)



Project: Spring Legacy Project로 생성할 것


-> 안 보이는 사람들은 Help->Eclipse Marketplace -> STS 검색 후 "Spring Tools 3 Add-On for Spring Tools 4 3.9.14.RELEASE" 설치할 것



그림 1. STS-Addon (Eclipse Marketplace)



1. 이전의 개발 방법론(Proxy)


지금도 물론 이런 방법은 안 쓰일 수는 없다.

AOP가 먼저 나온 것이 결코 아니다. 아마 c#에도 이런 게 있는 걸로 알고 있다. (오래되서)


프로그래밍 개발을 하다보면, 로직을 구현하는데 있어서 코어 코드를 수정해서 작업을 하는 일이 많이 있다.

핵심 규칙은 x, y, z만 더해서 결과만 출력하면 되는 요구사항을 가진 문제인데 구현을 하다보면, 현실적인 문제라는 게 발생하게 된다.


그림 2. 이전의 로직 구현 방법 - 개발


그림 1과 같은 문제는 자주 생기는 문제이기도 하다.

이런 방법으로 구현하는 것이 아예 사라졌다는 게 아니다.


그림 1에서 (1단계)의 핵심 규칙을 재사용할 기회를 상실해버리는 문제가 발생하게 된다.



그림 3. 문제 인식


그림 2처럼 문제가 생겼다고 하자.

물론 머리가 조금 좋은 분들은 객체의 상속 관계로 처리해도 해결할 수 있지 않겠냐고 할 수도 있다. 

(객체지향 프로그래밍에서도 설계를 다시 해서 이런 문제를 나눠볼 수도 있겠음.)


허나 쉬운 일이 결코 아니다. 코드라는 게 이쪽에서 사용되고 있는지, 저기에서 사용되고 있는지 작성을 하게 되면 수정이 정말 어렵다는 것이다.


물론 C#을 논하는 자리가 아니어서 자바를 예로 들면, Proxy 탈취 방법이 존재한다.




그림 4. Proxy 탈취 방법 - 자바


자바에서는 


Type of Classes username new Proxy.newProxyInstance(Classes.class.getClassLoader(), new Class[] { classes.class } , 

new InvocationHandler(){

          public Object invoke(Object proxy, Method method, Object args[] throws Throwable{

                     

                  // 전단계 호출

                  Object result = method.invoke(sum, args);

                  // 후단계 호출

         }


     }


};


슈도 코드(Pseudo Code) 1. 자바의 Proxy 호출 방법


이런 방법으로 핵심 로직을 탈취하는 방법이 있다.

기본적인 원리는 이런 원리에 입각해서 만들어졌다고 보면 된다.


AOP의 용어 설명은 나중에 할 것이다. 절대로 지금 바로 알면 안 된다고 생각한다.


3단계를 기본으로 한다. "전 단계, 핵심 로직, 후 단계"

이 단계의 확장적인 개념이다.


Spring AOP에서는 5단계로 확장되었다.


전단계:    {Around 단계, Before 단계}, 

핵심로직:         핵심 로직(중간)

후단계:    {after Method 호출, AfterThrowing(선택 - 예외 처리 발생 때만)}


조금 더 이해하기 쉬워졌을 것으로 보인다.


[첨부(Attachments)]

background-aop.zip





2. 코드로 살펴보는 AOP


AOP를 이론으로만 봐서는 이해가 무척 안 될 것이다. 구현을 통해서 단계적으로 살펴보도록 하겠다.

프로젝트 생성부터 단계적으로 소개하겠다.



그림 5. 프로젝트 생성하기


File을 클릭한다.

New -> Spring Legacy Project를 클릭한다.



그림 6. 프로젝트 생성하기


Spring MVC Project를 선택한다.

Project name을 입력한다.

Next를 누른다.




그림 7. 프로젝트 생성하기


Top-level-package를 입력한다. (예: com.local.example)

Finish를 누른다.



3. POM.xml 설정하기


http://mvnrepository.com 사이트에 접속한다.

검색 키워드에 "aspectj"라고 입력한다. (세 가지 검색을 해서 버전을 획득해야 함)

검색 키워드에 "spring"라고 입력한다.

검색 키워드에 "servlet"이라고 입력한다.


두 가지의 Maven 주소를 획득해야 한다.



그림 8. MVNRepository 검색 결과의 예 (1) - AspectJ



그림 9. MVNRepository 검색 결과의 예 (1) - AspectJ


버전을 클릭하면, Maven Pom이 나온다.

이걸 복사 붙여넣기를 해도 된다.


다만 <version>만 복사해서 관리하는 방법도 있으니 자세한 건 POM.xml 소스를 참고하면 좋겠다.



그림 10. pom.xml - 변경 작업 모습(1)


<java-version>14</java-version>으로 변경한다.

<org.springframework-version>5.2.9.RELEASE</org........>로 변경한다.

<org.aspectj-version>1.9.6</org......>으로 변경한다.


<?xml version="1.0" encoding="UTF-8"?>

<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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.local</groupId>

<artifactId>example</artifactId>

<name>springAOP-javaConfig</name>

<packaging>war</packaging>

<version>1.0.0-BUILD-SNAPSHOT</version>

<properties>

<!-- 자바 버전 변경함 -->

<java-version>14</java-version>

<!-- 스프링프레임워크 버전 변경함 -->

<org.springframework-version>5.2.9.RELEASE</org.springframework-version>

<!-- AspectJ 변경함 -->

<org.aspectj-version>1.9.6</org.aspectj-version>

<org.slf4j-version>1.6.6</org.slf4j-version>

</properties>

<dependencies>

<!-- Spring -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>${org.springframework-version}</version>

<exclusions>

<!-- Exclude Commons Logging in favor of SLF4j -->

<exclusion>

<groupId>commons-logging</groupId>

<artifactId>commons-logging</artifactId>

</exclusion>

</exclusions>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>${org.springframework-version}</version>

</dependency>

<!-- AspectJ 버전 변경 -->

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjrt</artifactId>

<version>${org.aspectj-version}</version>

</dependency>

<!-- AspectJWeaver 추가 -->

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjweaver</artifactId>

<version>${org.aspectj-version}</version>

</dependency>


<!-- Logging -->

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

<version>${org.slf4j-version}</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>jcl-over-slf4j</artifactId>

<version>${org.slf4j-version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

<version>${org.slf4j-version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.15</version>

<exclusions>

<exclusion>

<groupId>javax.mail</groupId>

<artifactId>mail</artifactId>

</exclusion>

<exclusion>

<groupId>javax.jms</groupId>

<artifactId>jms</artifactId>

</exclusion>

<exclusion>

<groupId>com.sun.jdmk</groupId>

<artifactId>jmxtools</artifactId>

</exclusion>

<exclusion>

<groupId>com.sun.jmx</groupId>

<artifactId>jmxri</artifactId>

</exclusion>

</exclusions>

<scope>runtime</scope>

</dependency>


<!-- @Inject -->

<dependency>

<groupId>javax.inject</groupId>

<artifactId>javax.inject</artifactId>

<version>1</version>

</dependency>

      <!-- Servlet -->

            <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> 

            <dependency> 

                  <groupId>javax.servlet</groupId>

                  <artifactId>javax.servlet-api</artifactId>

                  <version>4.0.1</version>

                  <scope>provided</scope> 

            </dependency>

<dependency>

<groupId>javax.servlet.jsp</groupId>

<artifactId>jsp-api</artifactId>

<version>2.1</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>jstl</artifactId>

<version>1.2</version>

</dependency>

<!-- Test -->

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.7</version>

<scope>test</scope>

</dependency>        

</dependencies>

    <build>

        <plugins>

            <plugin>

                <artifactId>maven-eclipse-plugin</artifactId>

                <version>2.9</version>

                <configuration>

                    <additionalProjectnatures>

                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>

                    </additionalProjectnatures>

                    <additionalBuildcommands>

                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>

                    </additionalBuildcommands>

                    <downloadSources>true</downloadSources>

                    <downloadJavadocs>true</downloadJavadocs>

                </configuration>

            </plugin>

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>2.5.1</version>

                <configuration>

                    <source>1.6</source>

                    <target>1.6</target>

                    <compilerArgument>-Xlint:all</compilerArgument>

                    <showWarnings>true</showWarnings>

                    <showDeprecation>true</showDeprecation>

                </configuration>

            </plugin>

            <plugin>

                <groupId>org.codehaus.mojo</groupId>

                <artifactId>exec-maven-plugin</artifactId>

                <version>1.2.1</version>

                <configuration>

                    <mainClass>org.test.int1.Main</mainClass>

                </configuration>

            </plugin>

        </plugins>

    </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom.zip





4. Project의 Properties - Build Path, Project Factes 설정하기 (Java Version 변경)


프로젝트의 자바 버전을 변경할 것이다.



그림 11. 프로젝트의 마우스 오른쪽 버튼 메뉴 모습


프로젝트를 선택한 후 마우스 오른쪽 버튼을 누른다.

Properties를 클릭한다.




그림 12. Java Build Path


Java Build Path를 클릭한다.

JRE System Library를 [JavaSE-14]로 변경한다.




그림 13. Project Factes


Project Factes를 클릭한다.

Java의 버전을 14로 바꿔준다.




5. web.xml - 서블릿 스팩 (2.5에서 4.0으로 변경)


서블릿 스팩을 변경할 것이다.

web.xml에서 변경해주면 된다.



그림 14. web.xml (servlet 4.0 스팩으로 변경)



<?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">


<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/spring/root-context.xml</param-value>

</context-param>

<!-- Creates the Spring Container shared by all Servlets and Filters -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<!-- Processes application requests -->

<servlet>

<servlet-name>appServlet</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>appServlet</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>


</web-app>



파일명: web.xml


[첨부(Attachments)]

web.zip



6. service - Calculator.java


복잡한 설명을 적기보다는 작업화면하고 코드를 소개하겠다.



그림 15. Calculator.java


package com.local.example.service;


public class Calculator {


private long x;

private long y;

private long z;

public Calculator(long x, long y, long z) {

this.x = x;

this.y = y;

this.z = z;

}

// 작업 원본(A 프로그래머 작성함)

public long sum() {

long result = x + y + z;

return result;

}


}


파일명: Calculator.java


[첨부(Attachments)]

 Calculator-original.zip


참고로 지금 코드로는 AspectJ가 동작되지 않는 코드이다.
이유는 초기 매개변수가 없는 생성자가 없어서 그렇다.

(다음 페이지에서 소개하겠음.)



7. Controller - HomeController.java


아마 순정 코드에 Calculator의 sum()를 호출한 것을 보면, 다음과 같이 되어있다.


package com.local.example;


import java.text.DateFormat;

import java.util.Date;

import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


import com.local.example.aop.Calculator;

import com.local.example.aop.RootConfig;


/**

 * Handles requests for the application home page.

 */

public class HomeController {

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

/**

* Simply selects the home view to render by returning its name.

*/


@RequestMapping(value = "/", method = RequestMethod.GET)

public String home(Locale locale, Model model) {

logger.info("Welcome home! The client locale is {}.", locale);

Date date = new Date();

DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);


Calculator cal = new Calculator(1, 2, 3);

System.out.printf("result of sum: %d", cal.sum());

model.addAttribute("serverTime", formattedDate );

return "home";

}

}



파일명: HomeController.java


이렇게 하면, 잘 동작할 것이다.

결과가 "result of sum: 6"이 출력될 것이다.



* 2부에서 AOP 사용 코드로 전환하는 방법에 대해서 소개하겠다.


1. [Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (2), 2020-10-04

https://yyman.tistory.com/1448


반응형
728x90
300x250

[Spring-F.] 29. MyBatis 3.5, Spring Framework 5.4, Oracle 19g - 방법5(SqlMap Spring전용)


소스 코드 위주로 소개하겠다.


[실험 결과]

1. [Spring-Framework] Spring Framework 5.4, MyBatis 3.5 연동 방법 - 실험 결과, 2020-10-02

   - https://yyman.tistory.com/1437


[이전 게시글]

1. [Spring-F.] 28. MyBatis 3.5, Spring Framework 5.4, Oracle 19g - 방법4(SqlMap Spring전용)

   - https://yyman.tistory.com/1441



41. 결과


XML 방식의 XML Mapping으로 구성하여 동작한 결과이다.



그림 25. 결과 5번



그림 26. 프로젝트 구성도



42. web.xml - 기본값


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_3_1.xsd"

version="3.1">


<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/spring/root-context.xml

</param-value>

</context-param>

<!-- Creates the Spring Container shared by all Servlets and Filters -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


<!-- Processes application requests -->

<servlet>

<servlet-name>appServlet</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>

/WEB-INF/spring/appServlet/servlet-context.xml

</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>appServlet</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>


</web-app>





43. com.example.spbatis.db - SqlMapSessionFactory.java


package com.example.spbatis.db;



import java.io.IOException;

import java.io.InputStream;

import java.sql.SQLException;


import javax.sql.DataSource;


import org.apache.ibatis.io.Resources;

import org.apache.ibatis.mapping.Environment;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import org.apache.ibatis.transaction.TransactionFactory;

import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;


import com.example.spbatis.model.CompUsers;


import oracle.jdbc.pool.OracleDataSource;


public class SqlMapSessionFactory {


private static SqlMapSessionFactory factory = new SqlMapSessionFactory();

private final static String driverName = "oracle.jdbc.driver.OracleDriver";

private final static String dbUrl = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";

private final static String userName = "사용자계정명";

private final static String userPassword = "비밀번호";

public static SqlSessionFactory ssf;


public static SqlMapSessionFactory getInstance() {

return factory;

}

private SqlMapSessionFactory () {    }

static {

/* 방법1

DataSource dataSource = getOracleDataSource();

TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);

Configuration configuration = new Configuration(environment);

configuration.addMapper(CompUsersMapper.class);

ssf = new SqlSessionFactoryBuilder().build(configuration);

*/

}

    

    public static SqlSessionFactory getSqlSessionFactory(){

   


/* 방법2: 공식 메뉴얼 참고 */

DataSource dataSource = getOracleDataSource();


SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); // Spring Framework만 지원

factoryBean.setDataSource(dataSource);

try {

//쿼리가 정의된 xml 파일들의 위치 지정

        factoryBean.setMapperLocations(

                new PathMatchingResourcePatternResolver().getResources("com/example/spbatis/mapper/*.xml")

        );

    

ssf = factoryBean.getObject();

// 클래스 등록

// ssf.getConfiguration().addMapper(CompUsersMapper.class);

} catch (Exception e) {

e.printStackTrace();

}

   

    // 방법 1, 방법 2 공통

        return ssf;

    }

    

    /*

* Description: 순정 오라클 데이터소스

*/

    private static DataSource getOracleDataSource(){


    OracleDataSource oracleDS = null;


    try {

            oracleDS = new OracleDataSource();

            oracleDS.setURL(dbUrl);

            oracleDS.setUser(userName);

            oracleDS.setPassword(userPassword);


        } catch (SQLException e) {

            e.printStackTrace();

        }


        return oracleDS;


    }

    

}



파일명: SqlMapSessionFactory.java


[첨부(Attachments)]

SqlMapSessionFactory.zip




44. com.example.spbatis.mapper - CompUsersMapper.xml


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper

  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.example.spbatis.mapper">


<select id="allCompUsers" resultType="com.example.spbatis.model.CompUsers">

select * from comp_users

</select>

<!-- 

<select id="allAddress" resultType="com.edu.db.AddressDto">

select * from addressbook

</select>

<select id="selectAddress" parameterType="Integer" resultType="com.edu.db.AddressDto">

select NUM, NAME, ADDRESS, BIRTHDATE

from addressbook

  where num=#{num}

</select>

<insert id="insertAddress" parameterType="com.edu.db.AddressDto">

insert into

addressbook(NAME, ADDRESS, BIRTHDATE)

values

(#{name},#{address},#{birthdate})

</insert>

<delete id="deleteAddress" parameterType="Integer">

DELETE FROM AddressBook

WHERE NUM = #{num}

</delete>

<update id="updateAddress" parameterType="com.edu.db.AddressDto" >

update addressbook

set birthdate = #{birthdate}, name = #{name}, address =#{address}

where num = #{num}

</update>

 -->

</mapper>


파일명: CompUsersMapper.xml


[첨부(Attachments)]

CompUsersMapper.zip





45. com.example.spbatis.dao - CompUsersDao.java


package com.example.spbatis.dao;


import java.util.List;


import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;


import com.example.spbatis.db.SqlMapSessionFactory;

import com.example.spbatis.model.CompUsers;



public class CompUsersDao {


    SqlSessionFactory factory = SqlMapSessionFactory.getSqlSessionFactory();

    

    public CompUsersDao() {

        

    }

    

    // SQL 세션 열기

    public List<CompUsers> selectAddress() {


    List<CompUsers> user = null;

try (SqlSession session = factory.openSession()) {

 

user = session.selectList("com.example.spbatis.mapper.allCompUsers");

session.close();

// System.out.println("계정명:" + user.getUsername());


}

return user;


    }

    

}



파일명: CompUsersDao.java


[첨부(Attachments)]

CompUsersDao.zip





46. com.example.spbatis.service - CompUsersService.java


package com.example.spbatis.service;


import java.util.List;


import com.example.spbatis.dao.CompUsersDao;

import com.example.spbatis.model.CompUsers;


public class CompUsersService {


private CompUsersDao dao = null;

public CompUsersService() {

dao = new CompUsersDao();

}

public List<CompUsers> getAllCompUsers() {

return dao.selectAddress();

}

 

}



파일명: CompUsersService.java


[첨부(Attachments)]

CompUsersService.zip




47. com.example.spbatis - HomeController.java


package com.example.spbatis;


import java.text.DateFormat;

import java.util.Date;

import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

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 org.springframework.web.bind.annotation.RequestMethod;


import com.example.spbatis.service.CompUsersService;


/**

 * Handles requests for the application home page.

 */

@Controller

public class HomeController {

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

private CompUsersService compUserService;

@RequestMapping(value = "/", method = RequestMethod.GET)

public String home(Locale locale, Model model) {

logger.info("Welcome home5 - Java(XML Mappers)! The client locale is {}.", locale);

Date date = new Date();

DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);

compUserService = new CompUsersService();

model.addAttribute("serverTime", formattedDate );

model.addAttribute("list", compUserService.getAllCompUsers()) ;

return "home";

}

}



파일명: HomeController.java


[첨부(Attachments)]

HomeController.zip





48. view - home.jsp


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>

<%@ page session="false" %>


<html>

<head>

<title>Home</title>

</head>

<body>

<h1>

Hello world!  

</h1>


<P>  The time on the server is ${serverTime}. </P>

<h3>SqlMapSessionFactory 방식(Java - XML Mapper - Spring 전용 버전)</h3>

<c:forEach items="${list }" var="val" >

    ${val.username} / 

    ${val.password} /

    ${val.enabled }<br>

</c:forEach>

<c:if test="${empty list }">

    ${"데이터가 존재하지않아요."}

</c:if>


</body>

</html>



파일명: home.jsp


[첨부(Attachments)]

home.zip



* 맺음글(Conclusion)


조금 작성하는데 힘이 들었지만. MyBatis 하나 셋팅에는 다양한 방법이 지원된다는 것을 알 수 있었다.



* 참고 자료(References)


1. mybatis-spring - 마이바티스 스프링 연동모듈 | 시작하기, https://mybatis.org/spring/ko/getting-started.html, Accessed by 2020-10-02, Last Modified 2020-06-05.

   [비고] - 공식 사이트이다. (이 메뉴얼만 가지고는 다 안 될 수도 있다.)


2. 스프링프레임워크 Java config로 MyBatis 사용하기, https://offbyone.tistory.com/381, Accessed by 2020-10-02, Last Modified 2019-03-17.


3. [mybatis] 다중 Database Setting, https://wfreud.tistory.com/310, Accessed by 2020-10-02, Last Modified 2019-03-21.


4. maven프로젝트 src/main/resource경로의 파일 읽기, https://blog.naver.com/down83/50092131189, Accessed by 2020-10-02, Last Modified 2010-07-13.


5. MyBatis List와 Map으로 데이터 불러오기, https://smujihoon.tistory.com/112, Accessed by 2020-10-02, Last Modified 2019-02-11.


5. [Mybatis] root-context.xml 자료(Oracle), https://datamod.tistory.com/81, Accessed by 2020-10-02, Last Modified 2018-05-20.


6. 7. 공용 클래스 작성 - commonDAo, https://tapasnote.tistory.com/21?category=697717, Accessed by 2020-10-02, Last Modified 2017-04-12.


7. DatsSource, 커넥션 풀을 이용한 DB 연결, https://m.blog.naver.com/PostView.nhn?blogId=2hyoin&logNo=220647895962, Accessed by 2020-10-02, Last Modified 2016-03-07.


8. MyBatis tutorial - Introductory MyBatis tutorial, http://zetcode.com/db/mybatis/, Accessed by 2020-10-02, Last Modified 2020-07-06.

   [영어 사이트]


반응형

+ Recent posts