서비스와 트랜잭션

gdhi·2023년 12월 1일
post-thumbnail

📖서비스 적용 예제 만들기


📌프로젝트 생성


👉 Mybatis 게시판 복사


👉 삭제, 다시 설치하는게 낫다(열면 자동 설치)



BorderService 인터페이스

package com.MyBatisBorder;

import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

public interface BorderService {
    public List<BorderDTO> list();
    public BorderDTO view(String id);
    public int write(BorderDTO bbsDto);
    public int delete(String id);
    public int allCount();
}



IBorderDAO 인터페이스 추가

package com.MyBatisBorder;

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface IBorderDAO {
    public List<BorderDTO> listDao();
    public BorderDTO viewDao(String id);
    public int writeDao(BorderDTO bbsDto);
    public int deleteDao(String id);
    public int allCount();
}



BorderService을 상속 받는 BorderServiceImpl 클래스

package com.MyBatisBorder;

import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class BorderServiceImpl implements BorderService{

    @Autowired
    IBorderDAO dao;

    @Override
    public List<BorderDTO> list(){
        return dao.listDao();
    };
    @Override
    public BorderDTO view(String id){
        return dao.viewDao(id);
    };
    @Override
    public int write(BorderDTO bbsDto){
        return dao.writeDao(bbsDto);
    };
    @Override
    public int delete(String id){
        return dao.deleteDao(id);
    };
    @Override
    public int allCount(){
        return dao.allCount();
    };

}



📍borderDAO.xml에 allCount 추가

<?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.MyBatisBorder.IBorderDAO">
    <select id="listDao" resultType="com.MyBatisBorder.BorderDTO">
        select * from simple_bbs order by id desc
    </select>
    <select id="viewDao" resultType="com.MyBatisBorder.BorderDTO">
        select * from simple_bbs where id = #{id}
    </select>
    <insert id="writeDao">
        insert into simple_bbs(writer, title, content) values(#{writer}, #{title}, #{content})
    </insert>
    <delete id="deleteDao">
        delete from simple_bbs where id = #{id}
    </delete>
    <select id="allCount" resultType="_int">
        select count(*) from simple_bbs
    </select>
</mapper>



📍BorderController Service로 변경하기

package com.MyBatisBorder;

import jakarta.servlet.http.HttpServletRequest;
import org.apache.ibatis.annotations.Param;
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 org.springframework.web.bind.annotation.RequestParam;

@Controller
public class BorderController {

    //@Autowired // 자식인 객체
    //IBorderDAO dao;

    @Autowired
    BorderService borderService;

    @RequestMapping("/")
    // Service vs DAO
    public String root(){
        return "redirect:list";
    }

    @RequestMapping("/list")
    public String userListPage(Model model){
        model.addAttribute("list", borderService.list());
        int nTotalCount = borderService.allCount();
        System.out.println("Count : " + nTotalCount);

        return "list";
    }

    @RequestMapping("/view")
    public String view(@RequestParam("id") String id, Model model){
        model.addAttribute("dto", borderService.view(id));
        return "view";
    }

    @RequestMapping("/writeForm")
    public String writeForm(){
        return "writeForm"; // 화면만 나온다
    }

    @RequestMapping(value = "/write", method = RequestMethod.POST) // POST 방식으로 보낸다
    public String write(Model model, BorderDTO bbsDto){ //HttpRequest 필요없이 BBSDto에 담으면 된다
        borderService.write(bbsDto);
        return "redirect:list";


    }
   /* @RequestMapping("/write")
    public String write(Model model, HttpServletRequest request){
        dao.writeDao(request.getParameter("writer"),
                request.getParameter("title"),
                request.getParameter("content"));
        return "redirect:list";
    }*/

    @RequestMapping("/delete")
    public String delete(@RequestParam("id") String id){
        int nResult = borderService.delete(id);
        System.out.println("Delete : " + nResult);
        return "redirect:list";
    }
}



📍결과

📍어떤 구조로 실행?

@Service는 에러 발생시 앞의 명령문을 아예 실행하지 않고 트랜잭션을 이용하여 롤백을 하기위한 주석 같은 것이다. 서비스는 컨트롤러와 같은 형태의 인터페이스를 컨트롤러에서 사용하여 트랜잭션 하는 것이고 DB에 접근 할 땐 원래의 컨트롤러로 접근하는 것이다.

Spring DAO란?, DTO란?, VO란?, DTO vs VO, DTO vs Domain, DAO vs Repository









📖트랜잭션 미적용 시 에러 상황(서비스만 사용)


📌테이블 추가

create table tran1(
id varchar(20),
amount int);
create table tran2(
id varchar(20),
amount int);
create table tran3(
id varchar(20),
amount int);

insert into tran1 value('1', 100);
insert into tran2 value('1', 100);
insert into tran3 value('1', 100);
commit;

delete from tran1;
delete from tran2;
delete from tran3;
commit;

select * from tran1;
select * from tran2;
select * from tran3;

👉 실행은 잘 됐지만 말하고 싶은건 commit 하는 순간 delete 명령을 실행 👉 충돌 👉 트랜잭션을 통한 롤백을 하고 싶은 것이다.



📌프로젝트 생성

👉 이제 설정 알아서 하자. 전에 거 복붙


📍TransactionX 패키지

DTO.Transaction1/2Dto 클래스

package com.TransactionX.DTO;

import lombok.Data;

@Data
public class Transaction1Dto {
    private String consumerId;
    private int amount;
}



DAO.Transaction1/2Dao 인터페이스

package com.TransactionX.DAO;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface Transaction1Dao {
    public void pay(String consumerId, int amount);
}



Transaction1/2Dao.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.TransactionX.DAO.Transaction1Dao">

    <insert id="pay">
        insert into tran1(id, amount)
        values(#{param1}, #{param2})
    </insert>

</mapper>



SERVICE.IBuyTicketService 인터페이스

package com.TransactionX.SERVICE;

public interface IBuyTicketService {
    public int buy(String consumerId, int amount, String error);
}



SERVICE.BuyTicketService

package com.TransactionX.SERVICE;

import com.TransactionX.DAO.Transaction1Dao;
import com.TransactionX.DAO.Transaction2Dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BuyTicketService implements IBuyTicketService {
    @Autowired
    Transaction1Dao transaction1Dao; // 인터페이스 Dao1
    @Autowired
    Transaction2Dao transaction2Dao; // 인터페이스 Dao2

    @Override
    public int buy(String consumerId, int amount, String error) {

        try {
            transaction1Dao.pay(consumerId, amount);

            // 1번 의도적 에러 발생
            if (error.equals("1")) {
                int n = 10 / 0; // 0 에러
            }

            transaction2Dao.pay(consumerId, amount);

            return 1; 
            
            /*
            if(nResult == 1){
                return "buy_ticket_end";
            }
            else {
                return "buy_ticket_error";
            }
             */

        } catch (Exception e) {
            return 0;
        }
    }
}



MyController 클래스

package com.TransactionX;

import com.TransactionX.SERVICE.IBuyTicketService;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {
    @Autowired
    IBuyTicketService iBuyTicketService;

    @RequestMapping("/")
    public @ResponseBody String root(){
        return "Transaction X (1)";
    }

    @RequestMapping("/buy_ticket")
    public String buy_ticket(){
        return "buy_ticket";
    }

    @RequestMapping("/buy_ticket_card")
    public String buy_ticket_card(@RequestParam("consumerId") String consumerId,
                                  @RequestParam("amount") String amount,
                                  @RequestParam("error") String error,
                                  Model model)
    {
        int nResult = iBuyTicketService.buy(consumerId, Integer.parseInt(amount), error);

        model.addAttribute("consumerId", consumerId);
        model.addAttribute("amount", amount);

        if(nResult == 1){
            return "buy_ticket_end";
        }
        else {
            return "buy_ticket_error";
        }
    }
}



buy_ticket.jsp

<%@ 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 lang="ko">
<head>
    <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <p>카드 결제</p>

    <form action="buy_ticket_card">
        고객 아이디 : <input type="text" name="consumerId"> <br />
        티켓 구매수 : <input type="text" name="amount"> <br />
        에러 발생 여부 : <input type="text" name="error" value="0"> <br />
        <input type="submit" value="구매"> <br />
    </form>

    <hr>
    에러 발생 여부에 1을 입력하면 에러가 발생합니다.
</body>
</html>



buy_ticket_end.jsp

<%@ 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 lang="ko">
<head>
    <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8">
    <title>Document</title>
</head>
<body>
    buy_ticket_end.jsp 입니다. <br />

    ${consumerId } <br />
    ${amount } <br />

</body>
</html>



buy_ticket_error.jsp

<%@ 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 lang="ko">
<head>
    <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8">
    <title>Document</title>
</head>
<body>
    buy_ticket_error.jsp 입니다. <br />

    <h1>에러가 발생했습니다.</h1> <br />

    ${consumerId } <br />
    ${amount } <br />

</body>
</html>





📍결과



❓어떤 구조로 실행?


👉 0을 입력 했을 때 error가 발생하지 않으므로 1과 2 둘다 정상적으로 입력되지만, 1을 입력 했을 때 error 가 발생하기 전에 1에 입력이 되고 error가 발생하기 때문에 2는 실행이 되지 않는다









📖트랜잭션 매니저 사용하기


📌프로젝트 생성

복붙


📍BuyTicketService 클래스에 추가

package com.TransactionX.SERVICE;

import com.TransactionX.DAO.Transaction1Dao;
import com.TransactionX.DAO.Transaction2Dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

@Service
public class BuyTicketService implements IBuyTicketService {
    @Autowired
    Transaction1Dao transaction1Dao; // 인터페이스 Dao1
    @Autowired
    Transaction2Dao transaction2Dao; // 인터페이스 Dao2

    @Autowired
    PlatformTransactionManager transactionManager; // 👉 추가

    @Autowired
    TransactionDefinition definition; // 👉 추가

    @Override
    public int buy(String consumerId, int amount, String error) {

        TransactionStatus status = transactionManager.getTransaction(definition); // 👉 추가

        try{
            transaction1Dao.pay(consumerId, amount);

            // 1번 의도적 에러 발생
            if(error.equals("1")) {
                int n = 10 / 0; // 0 에러
            }

            transaction2Dao.pay(consumerId, amount);

            transactionManager.commit(status); // 👉 추가

            return 1;
            
            /*
            if(nResult == 1){
                return "buy_ticket_end";
            }
            else {
                return "buy_ticket_error";
            }
             */

        }catch (Exception e){
            System.out.println("롤백"); // 👉 추가
            transactionManager.rollback(status); // 👉 추가
            return 0;
        }
    }
}



📍결과










👉 롤백



❓어떤 구조로 실행?


👉 0을 입력 했을 때 error가 발생하지 않으므로 commit으로 1과 2 둘다 정상적으로 입력되지만, 1을 입력 했을 때 error 가 발생하기 전에 1에 입력이 되고 error가 발생 후 transactionManager.rollback 에 의해 2는 실행하지 않고 롤백이 되어 데이터가 아예 입력 되지 않는다.









📖트랜잭션 템플릿 사용하기


📌프로젝트 생성

복붙, 알아서 하자


📍BuyTicketService 클래스 수정

package com.TransactionX.SERVICE;

import com.TransactionX.DAO.Transaction1Dao;
import com.TransactionX.DAO.Transaction2Dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class BuyTicketService implements IBuyTicketService {
    @Autowired
    Transaction1Dao transaction1Dao; // 인터페이스 Dao1
    @Autowired
    Transaction2Dao transaction2Dao; // 인터페이스 Dao2

    /*@Autowired
    PlatformTransactionManager transactionManager;
    @Autowired
    TransactionDefinition definition;*/

    @Autowired
    TransactionTemplate transactionTemplate; // 👉 추가

    @Override
    public int buy(String consumerId, int amount, String error) {

        //TransactionStatus status = transactionManager.getTransaction(definition); // 👉 추가

        try{
            // 처음 보는 형태, 메소드 안에 메소드?? 👉 메소드가 아니고 재정의이다
            // 추상화 클래스(생성자 객체) {추상화 객체 재정의}, 객체를 만들면서 정의한다. 자바스크립트(Node.js)에서 많이 보이는 표현
            // 즉, 추상화 클래스도 단발성이지만 객체를 만들 수 있다
            transactionTemplate.execute(new TransactionCallbackWithoutResult() { // 👉 추가
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) { // 👉 자동 으로 추가 됨
                    transaction1Dao.pay(consumerId, amount);

                    // 1번 의도적 에러 발생
                    if(error.equals("1")) {
                        int n = 10 / 0; // 0 에러
                    }

                    transaction2Dao.pay(consumerId, amount);
                }
            });

            //transactionManager.commit(status);

            return 1;

        }catch (Exception e){
            System.out.println("Template ROLLBACK"); // 👉 수정
            //transactionManager.rollback(status);
            return 0;
        }
    }
}



📍결과










❓어떤 구조로 실행?


👉 트랜잭션 매니저와 같은 구조이다. 다만 매니저는 commit이 있지만 템플릿 구조엔 없다









📖⭐트랜잭션 전파 속성⭐

트랜잭션이 중첩된 상황

트랜잭션: 오늘 일기 작성하기
(1)오늘 날씨 데이터를 가져와서
(2)일기를 DB에 저장하기
(1)과 (2)를 모두 수행해야 데이터베이스의 상태가 변화하므로, (1)+(2)를 하나의 트랜잭션이라고 본다.

(1)은 트랜잭션 ⭕, (2)는 트랜잭션 ❌ 👉 (1)에의해 트랜잭션에 묶이는 상황이라면 👉 (1)과 (2)를 묶은 트랜잭션 A가 생기고 (1)의 트랜잭션은 B가 된다. 왜? 불안하니까..
(1)과 (2)에서 문제생기든 A와 B에 문제생기든 무조건 롤백!


📌프로젝트 추가/수정

이전 프로젝트에 이어서 작성


📍Transaction3Dto 클래스 생성

package com.TransactionX.DTO;

import lombok.Data;

@Data
public class Transaction3Dto {
    private String consumerId;
    private int amount;
}



📍Transaction3Dao 인터페이스 생성

package com.TransactionX.DAO;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface Transaction3Dao {
    public void pay(String consumerId, int amount);
}



📍Transaction3Dao.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.TransactionX.DAO.Transaction2Dao">

    <insert id="pay">
        insert into tran3(id, amount)
        values(#{param1}, #{param2})
    </insert>

</mapper>



📍BuyTicketService 클래스 수정

package com.TransactionX.SERVICE;

import com.TransactionX.DAO.Transaction1Dao;
import com.TransactionX.DAO.Transaction2Dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class BuyTicketService { // 👉 implements 제거
    @Autowired
    Transaction1Dao transaction1Dao; // 인터페이스 Dao1
    @Autowired
    Transaction2Dao transaction2Dao; // 인터페이스 Dao2

    @Autowired
    TransactionTemplate transactionTemplate;

    public int buy(String consumerId, int amount, String error) {

        try{
            
            transactionTemplate.execute(new TransactionCallbackWithoutResult() { 
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) { 
                    transaction1Dao.pay(consumerId, amount);

                    // 1번 의도적 에러 발생
                    if(error.equals("1")) {
                        int n = 10 / 0; // 0 에러
                    }

                    transaction2Dao.pay(consumerId, amount);
                }
            });

            //transactionManager.commit(status);

            return 1;

        }catch (Exception e){
            System.out.println("[Transaction Propagation # 2] ROLLBACK"); // 👉 수정
            return 0;
        }
    }
}



📍LogWriteService 클래스 생성

package com.TransactionX.SERVICE;

import com.TransactionX.DAO.Transaction3Dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LogWriteService {

    @Autowired
    Transaction3Dao transaction3Dao; // 인터페이스 Dao3

    public int write(String consumerId, int amount){

        try {
            transaction3Dao.pay(consumerId, amount);
            return 1;
        }catch (Exception e){
            return 0;
        }
    }
}



📍서비스를 둘 다 부르는 BuyAndLogService 클래스

package com.TransactionX.SERVICE;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class BuyAndLogService {
    
    @Autowired
    BuyTicketService buyTicketService;
    @Autowired
    LogWriteService logWriteService;
    @Autowired
    TransactionTemplate transactionTemplate;

    public int buy(String consumerId, int amount, String error) {

        try{

            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {
                    buyTicketService.buy(consumerId, amount, error);

                    // 2번 의도적 에러 발생
                    if(error.equals("2")) {
                        int n = 10 / 0; // 0 에러
                    }

                    logWriteService.write(consumerId, amount);
                }
            });

            //transactionManager.commit(status);

            return 1;

        }catch (Exception e){
            System.out.println("[Transaction Propagation # A] ROLLBACK"); // 👉 수정
            return 0;
        }
    }
}



📍buy_ticket.jsp 추가

에러 발생 여부에 2을 입력하면 에러가 발생합니다.


📍MyController 클래스 수정

package com.TransactionX;

import com.TransactionX.SERVICE.BuyAndLogService;
import com.TransactionX.SERVICE.IBuyTicketService;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {
    //@Autowired
    //IBuyTicketService iBuyTicketService;
    
    @Autowired
    BuyAndLogService buyAndLogService;

    @RequestMapping("/")
    public @ResponseBody String root(){
        return "Transaction Propagation (4)";
    }

    @RequestMapping("/buy_ticket")
    public String buy_ticket(){
        return "buy_ticket";
    }

    @RequestMapping("/buy_ticket_card")
    public String buy_ticket_card(@RequestParam("consumerId") String consumerId,
                                  @RequestParam("amount") String amount,
                                  @RequestParam("error") String error,
                                  Model model)
    {
        // int nResult = iBuyTicketService.buy(consumerId, Integer.parseInt(amount), error);
        int nResult = buyAndLogService.buy(consumerId, Integer.parseInt(amount), error);

        model.addAttribute("consumerId", consumerId);
        model.addAttribute("amount", amount);

        if(nResult == 1){
            return "buy_ticket_end";
        }
        else {
            return "buy_ticket_error";
        }
    }
}



전파 속성 설정

REQUIRED

  • Default 속성
  • 기본적으로 부모 트랜잭션이 있으면 부모 트랜잭션에 종속
  • 부모 트랜잭션이 없을 경우에는 새로운 트랜잭션 생성

REQUIRES_NEW

  • 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션 생성
  • 부모 트랜잭션은 현재 트랜잭션이 종료될 때 까지 대기상태로 존재
  • 자기 트랜잭션에서 예외가 발생해 Rollback이 진행돼도 부모 트랜잭션에 Rollback이 전파되지 않는다.
  • 부모 트랜잭션에서 예외가 발생해 Rollback이 진행돼도 자기 트랜잭션에 Rollback이 전파되지 않는다.

SUPPORTS

  • 부모 트랜잭션이 있을 때만 해당 부모 트랜잭션에 종속
  • 부모 트랜잭션이 없으면 트랜잭션이 적용되지 않는다.

NOT_SUPPORTED

  • 부모 트랜잭션이 있으면 부모 트랜잭션을 대기시키고 트랜잭션 없이 실행
  • 부모 트랜잭션이 없으면 트랜잭션 없이 실행된다.

MANDATORY

  • 부모 트랜잭션이 있을 때만 해당 부모 트랜잭션에 종속
  • 부모 트랜잭션이 없으면 예외가 발생한다.

NEVER

  • 트랜잭션이 적용되면 안되는 경우에 사용
  • 부모 트랜잭션이 있으면 예외가 발생한다.
  • 부모 트랜잭션이 없으면 트랜잭션 없이 실행된다.

NESTED

  • 부모 트랜잭션이 있으면 새로운 트랜잭션 생성
    REQUIRED_NEW와는 다르다.
  • 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만 자신의 커밋과 롤백은 부모 트랜잭션에게 영향을 주지 않는다.
  • 자식 트랜잭션이 실패하면 부모 트랜잭션은 Rollback되지 않는다.
  • 부모 트랜잭션이 실패하면 자식 트랜잭션은 Rollback 된다.

📍BuyTicketService 클래스 추가(속성 변경)

@Autowired
    TransactionTemplate transactionTemplate;

    @Transactional(propagation = Propagation.REQUIRED) // 👉 추가
    public int buy(String consumerId, int amount, String error) {

        try{

            transactionTemplate.execute(new TransactionCallbackWithoutResult() {



📌결과(전파 속성 바꾸면서 테스트)


📍REQUIRED


정상 입력




👉 셋다 들어감



1 에러 입력




👉 롤백 2개 출력, 데이터는 당연히 들어가지 않았다



2 에러 입력




👉 BuyAndLogService 에서 에러발생, 롤백 1개 출력



📍REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)


정상입력





1 에러 입력




👉 롤백 2개 출력



2 에러 입력




👉 BuyAndLogService 에러, 롤백 1개 출력

👉 Dao1, Dao2

👉 Dao3









📖시큐리티 기초

Spring 시큐리티 이걸 왜 만들었을까? 👉 개발자 편하라고.. 개발에 집중하라고.. 암호화(암호화 키로 평문을 암호화 한다), 복호화(암호화된 값을 키로 평문으로 바꾼다), 해쉬(비밀번호 1234 👉 23578aergerg2352uj3iruoe 로 변형) 등을 쉽게 할 수 있다.
스프링시큐리티의 기본 개념과 구조


📌프로젝트 생성


👉 Spring Security 의존성 추가, 나머지 설정 알아서

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-security'
	testImplementation 'org.springframework.security:spring-security-test'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0'
	compileOnly 'jakarta.servlet:jakarta.servlet-api:6.0.0'
	implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl:3.0.1'
	implementation 'org.apache.tomcat:tomcat-jasper:10.1.16'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc:3.2.0'
	implementation 'mysql:mysql-connector-java:8.0.33'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
	testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3'
	implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
}


📍Security 패키지

WebSecurityConfig 클래스 생성

❗ 책의 내용으로 작성하면 안된다. 2022년에 바뀜

package com.Security.auth;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/").permitAll()
                        .requestMatchers("/css/**", "/js/**", "/img/**").permitAll()
                        .requestMatchers("/guest/**").permitAll()
                        .requestMatchers("/member/**").hasAnyRole("USER", "ADMIN")
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        .anyRequest().authenticated()
                )
                .formLogin(formLogin -> formLogin.permitAll())
                .logout(logout -> logout.permitAll());
        return http.build();
    }


//    @Autowired
//    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication()
//                .withUser("user").password(passwordEncoder().encode("1234")).
//                roles("USER")
//                .and()
//                .withUser("admin").password(passwordEncoder().encode("1234")).
//                roles("ADMIN");
//        // ROLE_ADMIN 에서 ROLE_는 자동으로 붙는다
//    }
//
//    // passwordEncoder() 추가
//    @Bean
//    public PasswordEncoder passwordEncoder(){
//        return new BCryptPasswordEncoder();
//    }
//
//}
}



MyController 클래스 생성

package com.Security;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {

    @RequestMapping("/")
    public @ResponseBody String root(){
        return "Security (1)";
    }

    @RequestMapping("/guest/welcome")
    public String welcome1(){
        return "guest/welcome1";
    }
    @RequestMapping("/member/welcome")
    public String welcome2(){
        return "member/welcome2";
    }
    @RequestMapping("/admin/welcome")
    public String welcome3(){
        return "admin/welcome3";
    }
}



welcome1(guest), 2(member), 3(admin)

<%@ 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 lang="ko">
<head>
    <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8">
    <title>Document</title>
</head>
<body>
    Welcome : Guest
</body>
</html>



0개의 댓글