https://docs.spring.io/spring-security/reference/servlet/architecture.html#servlet-filterchainproxy

 

Architecture :: Spring Security

The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like authentication, authorization, exploit protection, and more. The filters are executed in a spec

docs.spring.io

일단 스프링 시큐리에 구조에 대해서 알아보려 합니다.

사실상 기존에 사용만 해봤지 실제 구조가 어떻게 굴러가는지에 대해서는 정확하게 공부하지 않았습니다.(반성중..사실 좀 바빳다는게 핑계... ㅎㅎ)

 

좋은 기회여서 일단 원서를 보면서 구조를 파악 해보려 합니다 ㅎㅎ

최대한 자세히 설명하거여서 쉬운 용어들 조차 설명을 할거라 내용이 조금 길어질수도 있습니다. 고수님들께서는 다른거 참조하셔도 됩니다. (저같은 초보를 위한 초보자들을 위해서... ㅎㅎ)

 

필터(filter)에 대해서 

스프링 시큐리티의 서블릿 제공은 서블릿 필터에서 합니다!

 

여기서 서블릿이란? (모르는 분들을 위해서)

서블릿(Servlet)이란 동적 웹 페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다. 서블릿은 웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해준다.(일단 spring mvc servlet에서는 DispatcherServlet 이어서 그건 아래에 설명하겠습니다.) 

https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EC%84%9C%EB%B8%94%EB%A6%BF

 

자바 서블릿 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 서블릿은 여기로 연결됩니다. 다른 뜻에 대해서는 서블렛 문서를 참고하십시오. JSP 파일의 수명. 자바 서블릿(Java Servlet)은 자바를 사용하여 웹페이지를 동적으

ko.wikipedia.org

 

자 그렇다면 이제 저희는 서블릿을 알고간거죠.

서블릿 필터는 일반적으로 필터의 역할을 보여주는데 도움을 준다고합니다.

아래 이미지는 single http request 다루는 일반적인 레이어입니다.

 

클라이언트에서 -> appliaciton으로 request를 보내면, URI Request 경로를 기반으로, HttpServletRequest 처리해야하는 filter 인스턴스와 서블릿을 포함하는  filterChain을 컨테이너에 생성합니다.

 

여기서 컨테이너란? (모르는 분들을 위해서)

https://ittrue.tistory.com/220

 

[Spring] 스프링 컨테이너(Spring Container)란 무엇인가?

본 내용은 온라인 강의 사이트 인프런의 김영한 님의 강의 내용이 포함되어 있습니다. '스프링 핵심 원리 - 기본편' 스프링 핵심 원리 - 기본편 - 인프런 | 강의 스프링 입문자가 예제를 만들어가

ittrue.tistory.com

스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트이다.

스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공한다.

스프링에서는 자바 객체를 빈(Bean)이라 한다.



즉, 스프링 컨테이너는 내부에 존재하는 빈의 생명주기를 관리(빈의 생성, 관리, 제거 등)하며, 생성된 빈에게 추가적인 기능을 제공하는 것이다.



스프링 컨테이너는 XML, 어노테이션 기반의 자바 설정 클래스로 만들 수 있다.

스프링 부트(Spring Boot)를 사용하기 이전에는 xml을 통해 직접적으로 설정해 주어야 했지만, 스프링 부트가 등장하면서 대부분 사용하지 않게 되었다.

출처: https://ittrue.tistory.com/220 [IT is True:티스토리]

블로그 글을 빌리자면 스프링 자바 객체를 자동적으로 생명주기를 관리해주는곳이 컨테이너입니다.

그러니까 굳이 new 객체 생성하지 않아도 된다!! 이런 느낌입니다.

말그대로 filterChain도 스프링 컨테이너에 관리되는셈이죠!

 

여기서 또하나 URI와 URL이란? (모르는 분들을 위해서..저도 가끔 헷갈림 ㅎㅎ)

 

https://grape-blog.tistory.com/10

 

URI, URL 이란?

URI란 ? URI(Uniform Resource Identifier) 인터넷 자원을 나타내는 고유 식별자 이다. URI 에 "I" 가 Identifier인 것은 인터넷에 있는 자료의 ID를 뜻하는 것이다. 즉, 다른 자료가 똑같은 이름을 가지고 있으면

grape-blog.tistory.com

출처:https://grape-blog.tistory.com/10

위에 이미지를 보시면 확실하게 아시겠죠?

쉽게 설명해서 url은 도메인만 있는거, uri는 쿼리파라미터,패스,페이지 모든걸 포함하고 있다고 생각하면 편합니다. 

 

이 filterchain은 spring mvc application에 

당연아시겠지만 spring mvc 에서의 servlet은 DispatcherServlet입니다.

 

DispatcherServlet이란?

https://mangkyu.tistory.com/18

 

[Spring] Dispatcher-Servlet(디스패처 서블릿)이란? 디스패처 서블릿의 개념과 동작 과정

이번에는 servlet의 심화 또는 대표주자인 dispatcher-servlet에 대해서 알아보도록 하겠습니다. 1. Dispatcher-Servlet(디스패처 서블릿)의 개념 [ Dispatcher-Servlet(디스패처 서블릿) 이란? ] 디스패처 서블릿의

mangkyu.tistory.com

이블로그 글을 빌리자면 

디스패처 서블릿의 dispatch는 "보내다"라는 뜻을 가지고 있습니다. 그리고 이러한 단어를 포함하는 디스패처 서블릿은 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러(Front Controller)라고 정의할 수 있습니다.
이것을 보다 자세히 설명하자면, 클라이언트로부터 어떠한 요청이 오면 Tomcat(톰캣)과 같은 서블릿 컨테이너가 요청을 받게 됩니다. 그리고 이 모든 요청을 프론트 컨트롤러인 디스패처 서블릿이 가장 먼저 받게 됩니다. 그러면 디스패처 서블릿은 공통적인 작업을 먼저 처리한 후에 해당 요청을 처리해야 하는 컨트롤러를 찾아서 작업을 위임합니다.
여기서 Front Controller(프론트 컨트롤러)라는 용어가 사용되는데, Front Controller는 주로 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해주는 컨트롤러로써, MVC 구조에서 함께 사용되는 디자인 패턴입니다.
출처: https://mangkyu.tistory.com/18 [MangKyu's Diary:티스토리]

말그대로 http 요청이 오면 가장 먼저 받는 위치에 존재하는 컨트롤러라고 생각하시면 편합니다.

 

당연히 아시겟지만 하나의 서블릿은  HttpServletRequest 및 HttpServletResponse를 처리할 수 있습니다.

하지만!! 하나 이상의 filter는 아래 다음과 같은 목적으로 이용할수 있습니다.

 

DownStream Filter 인스턴스 또는 서블릿이 호출되는 것을 방지합니다. 이 경우 필터는 일반적으로 HttpServletResponse를 작성합니다.

DownStream Filter  와 서블릿이 사용하는 HttpServletRequest나 HttpServletResponse를 수정합니다

 

DownStream Filter란?(저도 몰랐네요 ..)

말그대로 특정 필터 이후에 위치하는 필터들을 DownStream Filter라고 합니다.

 

결국 여기서 말하자고 하는것은요.

원문을보면

The power of the Filter comes from the FilterChain that is passed into it.

저도 이해한느낌이 filter의 강력한 힘은 filterchain을 통함으로라는건데 filter의 강력한점이 filterchain덕분이다 라는뜻 같네요. (혹시나 저도 이부분좀 이해가 안가서 이런느낌인가 합니다.)

 

간단히 filter chain의 코드를 보겟습니다.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

 

오늘은 여기까지하고 내일은 DelegatingFilterProxy에 대해서 알아보겠습니다.

2023.11.12 - [웹/Spring vue 웹 개발] - spring vue 댓글 완료

 

spring vue 댓글 완료

2023.11.11 - [웹/Spring vue 웹 개발] - spring vue 댓글 05 spring vue 댓글 05 2023.11.11 - [웹/Spring vue 웹 개발] - spring vue 댓글04 spring vue 댓글04 2023.11.05 - [웹/Spring vue 웹 개발] - spring vue 댓글03 spring vue 댓글03 2023.

kwaksh2319.tistory.com

 

이번에 게시판과 댓글을 추가 하면서 몇가지 어드민쪽 코드도 수정중에 있습니다. 코드는 좀더 수정한후에 블로그에 넣어도록 하겠습니다. 

영상 :

 

 

ui는 조금 나중에 수정하겠습니다. 일단 댓글 수정,삭제 그리고 게시판들을 어드민에서 관리하도록 하고요.

그리고 어드민 코드 정리한후에 상품쪽이랑 장바구니 주문쪽 다시 작성해도록 하겠습니다.

사진:

댓글 controller

import kr.co.kshproject.webDemo.Applicaiton.Comment.CommentService;
import kr.co.kshproject.webDemo.Domain.Comment.Comment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@Slf4j
@RestController
@RequestMapping("/comment")
public class CommentController {
    private final CommentService commentService;

    @Autowired
    public CommentController(CommentService commentService){
        this.commentService=commentService;
    }

    @PostMapping("/{page}/{id}")
    public ResponseEntity<Comment> save(@PathVariable int page, @PathVariable Long id, @RequestBody Comment comment) {
        return ResponseEntity.ok(commentService.save(page,id,comment));
    }

    @GetMapping
    public ResponseEntity<List<Comment>> findAll(){
        return ResponseEntity.ok( commentService.findAll());
    }

    @GetMapping("/{id}")
    public Optional<Comment> findById(@PathVariable Long id){
        return commentService.findById(id);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Comment> update(@PathVariable Long id, @RequestBody Comment comment){
        return ResponseEntity.ok(commentService.update(id,comment));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteById(@PathVariable Long id) {
        commentService.deleteById(id);
        return ResponseEntity.noContent().build();
    }

    @DeleteMapping
    public void deleteAll(){
        commentService.deleteAll();
    }
}

서비스 

package kr.co.kshproject.webDemo.Applicaiton.Comment;

import kr.co.kshproject.webDemo.Domain.Comment.Comment;
import kr.co.kshproject.webDemo.Domain.Comment.CommentRepository;
import kr.co.kshproject.webDemo.Domain.Notice.Notice;
import kr.co.kshproject.webDemo.Domain.Notice.NoticeCustomRepository;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Slf4j
@Service
@AllArgsConstructor
public class CommentServiceImpl implements CommentService{
    private static final Logger logger = LoggerFactory.getLogger(CommentServiceImpl.class);
    private final CommentRepository commentRepository;
    private final NoticeCustomRepository noticeCustomRepository;

    @Override
    public Comment save(int page, Long noticeId, Comment comment) {
        LocalDate now = LocalDate.now();
        Optional<Notice> notice =noticeCustomRepository.findWithCommentsById(page,noticeId);
        Notice tmpNotice= notice.get();

        //TODO
        comment.setUserName("admin");// 유저변경할것
        comment.setNotice(tmpNotice);
        comment.setCreatedDate(now.toString());
        return commentRepository.save(comment);
    }

    @Override
    public List<Comment> findAll() {
        return commentRepository.findAll();
    }

    @Override
    public Optional<Comment> findById(Long id) {
        return commentRepository.findById(id);
    }

    @Override
    public Comment update(Long id, Comment saveComment) {
        Optional<Comment> findComment =commentRepository.findById(id);
        if(findComment.isPresent()==false){
            return null;
        }
        Comment comment=findComment.get();
        comment.setContents(saveComment.getContents());
        return commentRepository.save(comment);
    }

    @Override
    public void deleteById(Long id) {
        commentRepository.deleteById(id);
    }

    @Override
    public void deleteAll() {
        commentRepository.deleteAll();
    }
}

레파지토리

package kr.co.kshproject.webDemo.Domain.Comment;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentRepository extends JpaRepository<Comment,Long> {
}

DTO 

package kr.co.kshproject.webDemo.Domain.Comment;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CommentDTO {
    private Long id;
    private Long noticeId; // 대신 Notice 엔티티의 ID를 저장
    private String userName;
    private String contents;
    private String createdDate;

    public CommentDTO(Comment comment) {
        this.id = comment.getId();
        this.noticeId = comment.getNotice().getId(); // Notice의 ID를 가져옵니다.
        this.userName = comment.getUserName();
        this.contents = comment.getContents();
        this.createdDate = comment.getCreatedDate();
    }
}

테이블

package kr.co.kshproject.webDemo.Domain.Comment;
import com.fasterxml.jackson.annotation.JsonBackReference;
import kr.co.kshproject.webDemo.Domain.Notice.Notice;
import lombok.*;
import javax.persistence.*;
import javax.validation.constraints.NotEmpty;

@Entity
@Table(name = "Comment")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COMMENT_SEQ")
    @SequenceGenerator(name = "COMMENT_SEQ", sequenceName = "COMMENT_SEQ", allocationSize = 1)
    private Long id;

    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="notice_id")
    @Setter
    private Notice notice;

    @NotEmpty
    @Setter
    private String userName;

    @NotEmpty
    @Setter
    private String contents;

    @NotEmpty
    @Setter
    @Column(name = "created_date")
    private String createdDate;
}

 

' > Spring vue 웹 개발' 카테고리의 다른 글

spring vue jpa repository 테스트코드  (1) 2023.11.26
spring vue entity 변경  (3) 2023.11.25
spring vue 댓글 완료  (0) 2023.11.12
spring vue 댓글 05  (1) 2023.11.11
spring vue 댓글04  (1) 2023.11.11

2023.11.11 - [웹/Spring vue 웹 개발] - spring vue 댓글 05

 

spring vue 댓글 05

2023.11.11 - [웹/Spring vue 웹 개발] - spring vue 댓글04 spring vue 댓글04 2023.11.05 - [웹/Spring vue 웹 개발] - spring vue 댓글03 spring vue 댓글03 2023.10.14 - [웹/Spring vue 웹 개발] - spring vue 댓글 02 spring vue 댓글 02이전

kwaksh2319.tistory.com

일단 게시판도 조금 수정해야하는 부분도 있어서 완료했고요. 댓글들도 전부 달리도록 하였습니다.

이제 세션추가랑 유저구분할수 있도록 할거고요. 음 먼저 관리자 페이지는 제가 지금 다시 개발중이라 일단 개발되는데로 운영서버에 올릴 예정입니다. 아직 운영서버에는 적용이 안되었습니다.

영상:

이미지:

 

코드:

게시판 컨트롤러

@Slf4j
@RestController
@RequestMapping("/notice")
public class NoticeController {
   private final NoticeService noticeService;

    @Autowired
    public NoticeController(NoticeService noticeService){
        this.noticeService=noticeService;
   }

   @PostMapping
   public ResponseEntity<Notice> save(@RequestBody Notice notice){
        return ResponseEntity.ok(noticeService.save(notice));
   }

    @GetMapping
    public ResponseEntity< Map<String,List> > findAll(){
        return ResponseEntity.ok( noticeService.findAllWithComments(1));
    }

   @GetMapping("/{page}")
    public ResponseEntity< Map<String,List> > findAll(@PathVariable int page){
        return ResponseEntity.ok( noticeService.findAllWithComments(page));
   }

   @GetMapping("/{page}/{id}")
   public ResponseEntity<Notice> findWithCommentsById(@PathVariable int page,@PathVariable Long id){
        Optional<Notice> notice=noticeService.findWithCommentsById(page,id);
        return ResponseEntity.ok( notice.get() );
   }

   @PutMapping("/{id}")
   public ResponseEntity<Notice> update(@PathVariable Long id, @RequestBody Notice notice){
       return ResponseEntity.ok(noticeService.update(id,notice));
   }

   @DeleteMapping
   public void deleteAll(){
        noticeService.deleteAll();
   }
}

게시판 서비스 

package kr.co.kshproject.webDemo.Applicaiton.Notice;

import kr.co.kshproject.webDemo.Domain.Notice.Notice;
import kr.co.kshproject.webDemo.Domain.Notice.NoticeCustomRepository;
import kr.co.kshproject.webDemo.Domain.Notice.NoticeRepository;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Slf4j
@Service
public class NoticeServiceImpl implements NoticeService {
    private static final Logger logger = LoggerFactory.getLogger(NoticeServiceImpl.class);

    private final NoticeRepository noticeRepository;

    private final NoticeCustomRepository noticeCustomRepository;

    @Autowired
    public NoticeServiceImpl(NoticeRepository noticeRepository,NoticeCustomRepository noticeCustomRepository){
        this.noticeRepository=noticeRepository;
        this.noticeCustomRepository=noticeCustomRepository;
    }

    @Override
    public Notice save(Notice notice) {
        LocalDate now = LocalDate.now();
        notice.setCreatedDate(now.toString());
        notice.setUsername("admin");
        return noticeRepository.save(notice);
    }
    
    @Override
    public List<Notice> findAll() {
        return noticeRepository.findAll();
    }

    @Override
    public Optional<Notice> findById(Long id) {
        return noticeRepository.findById(id);
    }

    @Override
    public Map<String,List> findAllWithComments(int page) {
        //admin get pagesize;
        int pageSize=10;
        return noticeCustomRepository.findAllWithComments(page,pageSize);
    }

    @Override
    public Optional<Notice> findWithCommentsById(int page,Long id) {
        Optional<Notice> result= noticeCustomRepository.findWithCommentsById(page,id);
        return result;
    }

    @Override
    public Notice update(Long id,Notice saveNotice) {
        Optional<Notice> findNotice = noticeRepository.findById(id);

        if(findNotice.isPresent()==false){
            return null;
        }

        Notice notice=findNotice.get();
        notice.setTitle(saveNotice.getTitle());
        notice.setContents(saveNotice.getContents());
        return noticeRepository.save(notice);
    }
    @Override
    public void deleteAll() {
        noticeRepository.deleteAll();
    }
}

게시판 레파지토리

import org.springframework.stereotype.Repository;

import javax.persistence.criteria.CriteriaBuilder;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Repository
public interface NoticeCustomRepository {
    public Map<String, List> findAllWithComments(int page, int pageSize);

     Long findAllCountNotice(CriteriaBuilder cb);
    Optional<Notice> findWithCommentsById(int page,Long id) ;
}

게시판 레파지토리

import lombok.extern.slf4j.Slf4j;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class NoticeRepositoryImpl implements NoticeCustomRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Map<String,List> findAllWithComments(int page, int pageSize) {
        List<Long> totalSize =new LinkedList<>();
        Map<String,List> noticeDTOList=new ConcurrentHashMap<>();

        //Criteria API 사용
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //NoticeDTO <= Notice 커멘트 사용하지 않기 위함
        CriteriaQuery<NoticeDTO> cq = cb.createQuery(NoticeDTO.class);

        //from 절
        Root<Notice> notice = cq.from(Notice.class);
        //SELECT id,username,title,contents,email,createdDate FROM Notice LIMIT [pageSize] OFFSET (page-1) * [pageSize] ;
        cq.select(cb.construct(
                NoticeDTO.class,
                notice.get("id"),
                notice.get("username"),
                notice.get("title"),
                notice.get("contents"),
                notice.get("email"),
                notice.get("createdDate")
        ));
        //cq 쿼리 실행
        TypedQuery<NoticeDTO> query = entityManager.createQuery(cq);
        //게시판 총사이즈
        Long tSize=findAllCountNotice(cb);
        tSize=tSize/pageSize+1;
        totalSize.add(tSize);

        //cq 쿼리 실행 후 페이지 갯수만큼 불러오기
        query.setFirstResult( (page-1) *pageSize);
        query.setMaxResults(pageSize);

        //맵리스트 저장
        noticeDTOList.put("lists",query.getResultList());
        noticeDTOList.put("totalSize",totalSize);

        //결과값 리턴
        return noticeDTOList;
    }

    @Override
    public Long findAllCountNotice(CriteriaBuilder cb) {
        //게시판 총 카운트
        CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
        Root<Notice> countRoot = countQuery.from(Notice.class);
        countQuery.select(cb.count(countRoot));
        Long totalRecords = entityManager.createQuery(countQuery).getSingleResult();
        return totalRecords;
    }


    @Override
    public Optional<Notice> findWithCommentsById(int page,Long id)  {

        //Criteria API 사용
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //Notice 사용 이유는 댓글들을 불러오기때문
        CriteriaQuery<Notice> cq = cb.createQuery(Notice.class);

        /*select * from Notice LEFT JOIN Comments where id='id'*/
        //from Notice
        Root<Notice> notice = cq.from(Notice.class);
        //left join
        notice.fetch("comments", JoinType.LEFT);
        cq.where(cb.equal(notice.get("id"), id));
        //cq 쿼리 실행
        TypedQuery<Notice> query = entityManager.createQuery(cq);
        List<Notice> result = query.getResultList();
        //삼항 연산자 result 비엇을시 empty() 존재지 resutl 리턴
        return result.isEmpty() ? Optional.empty(): Optional.of(result.get(0));
    }
}

게시판 dto

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class NoticeDTO {
    private Long id;
    private String username;
    private String title;
    private String contents;
    private String email;
    private String createdDate;

    // Constructor that takes an entity Notice
    public NoticeDTO(Notice notice) {
        this.id = notice.getId();
        this.username = notice.getUsername();
        this.title = notice.getTitle();
        this.contents = notice.getContents();
        this.email = notice.getEmail();
        this.createdDate = notice.getCreatedDate();
    }
}

게시판 테이블

import com.fasterxml.jackson.annotation.JsonManagedReference;
import kr.co.kshproject.webDemo.Domain.Comment.Comment;
import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "Notice")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Notice {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NOTICE_SEQ")
    @SequenceGenerator(name = "NOTICE_SEQ", sequenceName = "NOTICE_SEQ", allocationSize = 1)
    @Column(name = "ID")
    private Long id; //key

    @NotEmpty
    @Setter
    private String username;

    @NotEmpty
    @Setter
    private String title;

    @NotEmpty
    @Setter
    private String contents;

    @NotEmpty
    @Setter
    private String email;

    @NotEmpty
    @Setter
    @Column(name = "created_date")
    private String createdDate;

    @JsonManagedReference
    @OneToMany(mappedBy = "notice", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Comment> comments = new ArrayList<>();
}

댓글 컨트롤러

import kr.co.kshproject.webDemo.Applicaiton.Comment.CommentService;
import kr.co.kshproject.webDemo.Domain.Comment.Comment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@Slf4j
@RestController
@RequestMapping("/comment")
public class CommentController {
    private final CommentService commentService;

    @Autowired
    public CommentController(CommentService commentService){
        this.commentService=commentService;
    }

    @PostMapping("/{page}/{id}")
    public ResponseEntity<Comment> save(@PathVariable int page, @PathVariable Long id, @RequestBody Comment comment) {
        return ResponseEntity.ok(commentService.save(page,id,comment));
    }

    @GetMapping
    public ResponseEntity<List<Comment>> findAll(){
        return ResponseEntity.ok( commentService.findAll());
    }

    @GetMapping("/{id}")
    public Optional<Comment> findById(@PathVariable Long id){
        return commentService.findById(id);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Comment> update(@PathVariable Long id, @RequestBody Comment comment){
        return ResponseEntity.ok(commentService.update(id,comment));
    }

    @DeleteMapping
    public void deleteAll(){
        commentService.deleteAll();
    }
}

 

댓글 서비스

package kr.co.kshproject.webDemo.Applicaiton.Comment;

import kr.co.kshproject.webDemo.Domain.Comment.Comment;

import java.util.List;
import java.util.Optional;

public interface CommentService {
    Comment save(int page,Long noticeId,Comment comment);
    List<Comment> findAll();
    Optional<Comment> findById(Long id);
    Comment update(Long id,Comment saveComment);
    void deleteAll();
}
package kr.co.kshproject.webDemo.Applicaiton.Notice;

import kr.co.kshproject.webDemo.Domain.Notice.Notice;
import kr.co.kshproject.webDemo.Domain.Notice.NoticeCustomRepository;
import kr.co.kshproject.webDemo.Domain.Notice.NoticeRepository;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Slf4j
@Service
public class NoticeServiceImpl implements NoticeService {
    private static final Logger logger = LoggerFactory.getLogger(NoticeServiceImpl.class);

    private final NoticeRepository noticeRepository;

    private final NoticeCustomRepository noticeCustomRepository;

    @Autowired
    public NoticeServiceImpl(NoticeRepository noticeRepository,NoticeCustomRepository noticeCustomRepository){
        this.noticeRepository=noticeRepository;
        this.noticeCustomRepository=noticeCustomRepository;
    }

    @Override
    public Notice save(Notice notice) {
        LocalDate now = LocalDate.now();
        notice.setCreatedDate(now.toString());
        notice.setUsername("admin");
        return noticeRepository.save(notice);
    }

    @Override
    public List<Notice> findAll() {
        return noticeRepository.findAll();
    }

    @Override
    public Optional<Notice> findById(Long id) {
        return noticeRepository.findById(id);
    }

    @Override
    public Map<String,List> findAllWithComments(int page) {
        //admin get pagesize;
        int pageSize=10;
        return noticeCustomRepository.findAllWithComments(page,pageSize);
    }

    @Override
    public Optional<Notice> findWithCommentsById(int page,Long id) {
        Optional<Notice> result= noticeCustomRepository.findWithCommentsById(page,id);
        return result;
    }

    @Override
    public Notice update(Long id,Notice saveNotice) {
        Optional<Notice> findNotice = noticeRepository.findById(id);

        if(findNotice.isPresent()==false){
            return null;
        }

        Notice notice=findNotice.get();
        notice.setTitle(saveNotice.getTitle());
        notice.setContents(saveNotice.getContents());
        return noticeRepository.save(notice);
    }
    @Override
    public void deleteAll() {
        noticeRepository.deleteAll();
    }
}

댓글 레파지토리

package kr.co.kshproject.webDemo.Domain.Comment;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentRepository extends JpaRepository<Comment,Long> {
}

댓글 테이블

import com.fasterxml.jackson.annotation.JsonBackReference;
import kr.co.kshproject.webDemo.Domain.Notice.Notice;
import lombok.*;
import javax.persistence.*;
import javax.validation.constraints.NotEmpty;

@Entity
@Table(name = "Comment")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COMMENT_SEQ")
    @SequenceGenerator(name = "COMMENT_SEQ", sequenceName = "COMMENT_SEQ", allocationSize = 1)
    private Long id;

    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="notice_id")
    @Setter
    private Notice notice;

    @NotEmpty
    @Setter
    private String userName;

    @NotEmpty
    @Setter
    private String contents;

    @NotEmpty
    @Setter
    @Column(name = "created_date")
    private String createdDate;
}

' > Spring vue 웹 개발' 카테고리의 다른 글

spring vue entity 변경  (3) 2023.11.25
spring vue 어드민 리팩토링01  (0) 2023.11.18
spring vue 댓글 05  (1) 2023.11.11
spring vue 댓글04  (1) 2023.11.11
spring vue 댓글03  (0) 2023.11.05

2023.11.11 - [웹/Spring vue 웹 개발] - spring vue 댓글04

 

spring vue 댓글04

2023.11.05 - [웹/Spring vue 웹 개발] - spring vue 댓글03 spring vue 댓글03 2023.10.14 - [웹/Spring vue 웹 개발] - spring vue 댓글 02 spring vue 댓글 02이전에 이어서 service랑 controller를 작성을 전부 완료하였고 테스트

kwaksh2319.tistory.com

기존에 좀 많이 힘들었던 부분이 제법있었습니다. 

먼저 

hibernate.HibernateException: collection was evicted

이 문제가 발생했었는데요. 

이 코드에서의 문제가 발생했었습니다. 

@Override
    public Optional<Notice> findWithCommentsById(Long id)  {
        //Criteria API 사용
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //Notice 사용 이유는 댓글들을 불러오기때문
        CriteriaQuery<Notice> cq = cb.createQuery(Notice.class);

        /*select * from Notice LEFT JOIN Comments where id='id'*/
        //from Notice
        Root<Notice> notice = cq.from(Notice.class);
        //left join
        notice.fetch("comments", JoinType.LEFT);
        cq.where(cb.equal(notice.get("id"), id));
        //cq 쿼리 실행
        TypedQuery<Notice> query = entityManager.createQuery(cq);
        List<Notice> result = query.getResultList();
        //삼항 연산자 result 비엇을시 empty() 존재지 resutl 리턴
        return result.isEmpty() ? Optional.empty(): Optional.of(result.get(0));
    }
TypedQuery<Notice> query = entityManager.createQuery(cq);

 이 쿼리 실행문에서 발생했었는데 알고보니 제가 이전에

@JsonManagedReference
@OneToMany(mappedBy = "notice", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Comment> comments = new HashSet<>();

해쉬셋으로 댓글을 가져왔었습니다.

이것저것 찾아보다가 

https://stackoverflow.com/questions/65058723/hibernateexception-in-hashcode-or-in-equals

 

HibernateException in hashCode or in equals

I am using spring and hibernate. When fetching without having implemented equals and hashCode, everyting works fine. When I add the two methods and execute the query a Lazyinitializationexception is

stackoverflow.com

글을 빌리자면 

  • 컬렉션 로딩: Hibernate는 Company 엔티티의 garages와 같은 내부 컬렉션을 로딩해야 할 필요가 있습니다.
  • 해시 코드 호출 시점: Company의 hashCode 메소드가 호출되는 시점에서 Hibernate는 아직 내부 컬렉션을 로딩하지 않았습니다. 이는 HashSet 내부에서 Hibernate의 내부 코드에 의해 발생합니다.
  • 내부 혼란: 컬렉션이 로딩되어야 한다는 요청은 있었지만, 실제로 아직 로딩되지 않았기 때문에 Hibernate는 혼란스러워합니다.

이런 문제가 발생한다고 하여서 

@JsonManagedReference
@OneToMany(mappedBy = "notice", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();

리스트로 바꿔서 해결했습니다.

ㅠㅠㅠㅠㅠ 

그리고 나서 또 문제가 발생했습니다. 

HttpMessageNotWritableException

위에처럼 messageNotWritabelException이 발생햇는데 

다행히도 이문제는

@JsonManagedReference
@JsonBackReference

으로 해결할수 있었습니다. 

무슨 문제이길래 이 어노테이션으로 해결한걸까요? 다음과 같습니다.

@JsonManagedReference와 @JsonBackReference

이 두 어노테이션은 순환 참조 문제를 해결하는 데 사용됩니다. 순환 참조는 JSON 직렬화 과정에서 무한 루프를 발생시킬 수 있으며, 이를 방지하기 위해 이 어노테이션이 필요합니다.

  • @JsonManagedReference: '부모' 측에 사용되며, 직렬화 과정에서 이 어노테이션이 적용된 필드를 포함시킵니다.
  • @JsonBackReference: '자식' 측에 사용되며, 직렬화 과정에서 이 어노테이션이 적용된 필드를 제외합니다. 이 어노테이션은 역직렬화 과정에서는 유효합니다.

결론적으론 JSON 직렬화 과정에서 무한 루프를 방지하기 위함인데  JSON 직렬화 과정에서 무한 루프는 왜 일어날까요?

 

JSON 직렬화 과정에서 무한 루프가 발생하는 주된 이유는 "순환 참조" 때문입니다. 순환 참조는 객체가 직접적이거나 간접적으로 자기 자신을 참조할 때 발생합니다. 예를 들어, 두 객체가 서로를 참조하거나, 객체가 자신을 포함하는 컬렉션을 참조하는 경우가 있습니다.  

 

순환참조로 인한 무한루프가 발생할수 있다고해서 이를 방지하기 위함이라고 합니다. 음 쉽게 설명해서 양방향으로 서로 참조하는걸 단방향으로 바꿔주는 셈이죠 (제가 이해한건 이거인데 틀리면 지적부탁드립니다.)

 

그래서 댓글을 넣고 게시판을 확인해봤습니다. 아직 vue는 개발이 덜 되어서 postman으로 확인해봤습니다. 

결과 화면 :

잘 나왔죠! 내일은 vue쪽도 만들어서 댓글도 보이도록 해보겠습니다. 시간이 나오려나 모르겠네요 ㅎㅎ ㅠ 

' > Spring vue 웹 개발' 카테고리의 다른 글

spring vue 어드민 리팩토링01  (0) 2023.11.18
spring vue 댓글 완료  (0) 2023.11.12
spring vue 댓글04  (1) 2023.11.11
spring vue 댓글03  (0) 2023.11.05
spring vue 댓글 02  (0) 2023.10.14

2023.11.05 - [웹/Spring vue 웹 개발] - spring vue 댓글03

 

spring vue 댓글03

2023.10.14 - [웹/Spring vue 웹 개발] - spring vue 댓글 02 spring vue 댓글 02이전에 이어서 service랑 controller를 작성을 전부 완료하였고 테스트도 완료하였습니다. 내일은 프론트단을 완료하고 현재 pc내에서

kwaksh2319.tistory.com

먼저 오늘 좀 여러가지 문제점발견하면서 해결을 했습니다.

먼저 기존에 말했던 @Transational방식이 아니라 DTO를 이용하여 댓글을 post 했습니다. 

NoticeDTO

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class NoticeDTO {
    private Long id;
    private String username;
    private String title;
    private String contents;
    private String email;
    private String createdDate;

    // Constructor that takes an entity Notice
    public NoticeDTO(Notice notice) {
        this.id = notice.getId();
        this.username = notice.getUsername();
        this.title = notice.getTitle();
        this.contents = notice.getContents();
        this.email = notice.getEmail();
        this.createdDate = notice.getCreatedDate();
    }
}

 


NoticeRepositoryImpl 

package kr.co.kshproject.webDemo.Domain.Notice;

import lombok.extern.slf4j.Slf4j;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;
import java.util.List;
import java.util.Optional;

@Slf4j
public class NoticeRepositoryImpl implements NoticeCustomRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<NoticeDTO> findAllWithComments() {
        //Criteria API 사용
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //NoticeDTO <= Notice 커멘트 사용하지 않기 위함
        CriteriaQuery<NoticeDTO> cq = cb.createQuery(NoticeDTO.class);

        //from 절
        Root<Notice> notice = cq.from(Notice.class);
        //선택 및 조건 설정 =>select id,username,title,contents,email,createdDate from notice;
        cq.select(cb.construct(
                NoticeDTO.class,
                notice.get("id"),
                notice.get("username"),
                notice.get("title"),
                notice.get("contents"),
                notice.get("email"),
                notice.get("createdDate")
        ));

        //cq 쿼리 실행
        TypedQuery<NoticeDTO> query = entityManager.createQuery(cq);
        //결과값 리턴
        return query.getResultList();
    }


    @Override
    public Optional<Notice> findWithCommentsById(Long id)  {
        //Criteria API 사용
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        //Notice 사용 이유는 댓글들을 불러오기때문
        CriteriaQuery<Notice> cq = cb.createQuery(Notice.class);

        /*select * from Notice LEFT JOIN Comments where id='id'*/
        //from Notice
        Root<Notice> notice = cq.from(Notice.class);
        //left join
        notice.fetch("comments", JoinType.LEFT);
        cq.where(cb.equal(notice.get("id"), id));
        //cq 쿼리 실행
        TypedQuery<Notice> query = entityManager.createQuery(cq);
        List<Notice> result = query.getResultList();
        //삼항 연산자 result 비엇을시 empty() 존재지 resutl 리턴
        return result.isEmpty() ? Optional.empty(): Optional.of(result.get(0));
    }
}

여기서  아래 findAllwithCommets는 게시글 전부 불러오는거고요. 제가 선택한 방법은 Criterai api를 사용해서 쿼리문을 불어왔습니다 left join에 대해서 이해하셔야 아래 구문이 이해가 가십니다.  

@Override
public List<NoticeDTO> findAllWithComments() {
    //Criteria API 사용
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    //NoticeDTO <= Notice 커멘트 사용하지 않기 위함
    CriteriaQuery<NoticeDTO> cq = cb.createQuery(NoticeDTO.class);

    //from 절
    Root<Notice> notice = cq.from(Notice.class);
    //선택 및 조건 설정 =>select id,username,title,contents,email,createdDate from notice;
    cq.select(cb.construct(
            NoticeDTO.class,
            notice.get("id"),
            notice.get("username"),
            notice.get("title"),
            notice.get("contents"),
            notice.get("email"),
            notice.get("createdDate")
    ));

    //cq 쿼리 실행
    TypedQuery<NoticeDTO> query = entityManager.createQuery(cq);
    //결과값 리턴
    return query.getResultList();
}

이런식으로 transactal query를 불러오지않고 불러오도록 하였습니다. 

결과 화면 : 

' > Spring vue 웹 개발' 카테고리의 다른 글

spring vue 댓글 완료  (0) 2023.11.12
spring vue 댓글 05  (1) 2023.11.11
spring vue 댓글03  (0) 2023.11.05
spring vue 댓글 02  (0) 2023.10.14
spring vue 댓글 01  (1) 2023.10.09

2023.10.14 - [웹/Spring vue 웹 개발] - spring vue 댓글 02

spring vue 댓글 02

이전에 이어서 service랑 controller를 작성을 전부 완료하였고 테스트도 완료하였습니다. 내일은 프론트단을 완료하고 현재 pc내에서 웹 작동해볼 예정이고 운영웹에는 테스트 조금더 한후에 적용

kwaksh2319.tistory.com

10월 중순부터 11월 아마 중순까지는 주말마다  바쁘기도 했고 주말마다 좀 바쁠 예정입니다. 아마 12월 넘어가야 여유가 생길듯합니다. 일단 그래도 틈틈히 시간나면 개발하겠습니다. 요즘 알고리즘이 여러가지 복습을 하다보니 좀 늦어지네요.
 
일단 게시판불러오면서 댓글들도 불러오는게 문제여서 몇가지를 수정했습니다.
 

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: 
Could not write JSON: failed to lazily initialize a collection of role:
kr.co.kshproject.webDemo.Domain.Notice.Notice.comments,
could not initialize proxy - no Session; nested exception is com.fasterxml.
jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: 
kr.co.kshproject.webDemo.Domain.Notice.Notice.comments, 
could not initialize proxy 
- no Session (through reference chain: java.util.ArrayList[0]-
>kr.co.kshproject.webDemo.Domain.Notice.Notice["comments"])]

 
원인은 아래와 LazyInitializationException 오류 메시지는 Hibernate 세션이 닫혀있는 상태여서 그렇습니다.  lazy loading 으로 인한 문제라고합니다.
 Hibernate 세션이란?
https://www.tutorialspoint.com/hibernate/hibernate_sessions.htm

Hibernate - Sessions

Hibernate Sessions - A Session is used to get a physical connection with a database. The Session object is lightweight and designed to be instantiated each time an interaction is needed with the database. Persistent objects are saved and retrieved through

www.tutorialspoint.com

데이터베이스와 물리적으로 연결하는 데 사용됩니다. Session 개체는 가볍고 데이터베이스와의 상호 작용이 필요할 때마다 인스턴스화되도록 설계되었습니다. 영구 객체는 Session 객체를 통해 저장되고 검색이라고 하는데. 제가 이해한건 그냥 로그인이나 원격 같은데 접속할떄 그 세션과 동일하게 db 세션객체에 접근해서 crud를 해줄수 있는게 아닐까 생각합니다.
 제가 생각한게 맞은게아니라면 잘못된거면 지적 부탁드리겠습니다.
 
lazy loading 이란?
즉, 사용자가 보지 않는 것들을 당장 로딩하지 않는다.
그러다가 나중에 사용자가 필요로 하는 시점에 로딩하는 것입니다.
https://ko.wikipedia.org/wiki/%EC%A7%80%EC%97%B0%EB%90%9C_%EB%A1%9C%EB%94%A9

FetchType.LAZY
@OneToMany(mappedBy = "notice", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Comment> comments = new ArrayList<>();

그래서 일단 transcationl(readyOnly=true) 접근하여 해결하였습니다.
여러가지 방법이 있는데 
다음에는 DTO방식으로 접근하는걸로 수정해보도록 하겠습니다. 
그리고 다른방식으로는 osiv도 있는데요 이것도 잠재적 위험성을 가지고 있다고합니다.
https://medium.com/frientrip/spring-boot%EC%9D%98-open-in-view-%EA%B7%B8-%EC%9C%84%ED%97%98%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-83483a03e5dc

Spring Boot의 open-in-view, 그 위험성에 대하여.

실제 서버 장애 해결과정을 중심으로

medium.com

 

spring.jpa.open-in-view = false

링크글을 빌리면 저도 비슷하게 db connection deadlock을 걸려본적이있는데 당시에는 제가 enitiyManger.close를 하지않아 발생한거였습니다. 반대로 이글을 빌리자면 opiv 을 true로 변경시 문제가 발생할수 있다고합니다. 와 무섭네요...
아무튼 lazy-loading을 해결하기위해 여러가지 방법이 있긴하지만 그중에서 가장 좋은 방향으로 선택하는게 좋겠네요 
  
서비스 

@Transactional(readOnly = true)
@Override
public List<Notice> findAllWithComments() {
      return noticeRepository.findAllWithComments();
}

컨트롤러 

@GetMapping
 public ResponseEntity<List<Notice>> findAll(){
      return ResponseEntity.ok( noticeService.findAllWithComments());
}

이렇게 하니 문제없이 잘 되었습니다. 
성공화면 : 

 

' > Spring vue 웹 개발' 카테고리의 다른 글

spring vue 댓글 05  (1) 2023.11.11
spring vue 댓글04  (1) 2023.11.11
spring vue 댓글 02  (0) 2023.10.14
spring vue 댓글 01  (1) 2023.10.09
spring vue 게시판 리팩토링 완료 (프론트단 및 백단 연결 변경 완료)  (0) 2023.10.08

일단 이건 작성하려고 했는데 어쩌다보니 계속 뒤로 밀리게되어 이제 작성하게 되었습니다. 제가 초창기 신입일때  회사 홈페이지를 프론트쪽을 수정 후 재시작할일 있어서 재시작하였는데 사수도 없고 인수인계도 잘 되어 있지 않는 상태에서 자신감 넘치게 재시작을 눌렀습니다. ㅎㅎ ㅠ 
 
그때 당시에 문제가 발생한게 갑작스레 메일들이 나갔는데... ㅠ 당시 엄청 호되게 혼났엇습니다. 잘모르면 일단 확인을 철저히 하고 했어야했는데 뭐 핑계라면 인수인계 부족.... ㅎ 아무튼 확인 안한 제잘못이 큽니다.. 사실 post -fix 메일에 대한 존재 조차 몰랐기 때문에 무지로 인한 문제라고 생각했습니다. 만약에 제가 전체적으로 웹서비스에 대한 이해도가 높고 서버에 대한 이해도가 높았다면 다양한 시각으로 체크 후 재시작을 했을거라고 생각합니다.  
 
그렇다면 post-fix 메일이 뭘까요? 일단 인프라쪽은 정확하게 모르는 부분이 상당히 많아서 .... ㅠ 예전에 java.mail은 구현은 해봤지만 실제적으로 smtp에 대한 이해도는 상당히 낮습니다... 그렇다면 postfix email은?
https://www.lesstif.com/1stb/postfix-email-18219586.html

Postfix Email 서버

# 메일 서버의 도메인 이름을 설정한다. fully-qualified domain name 이어야 한다. myhostname = mail.example.com # 도메인 이름을 설정한다. mydomain 을 특별히 설정하지 않을 경우 myhostname 에 설정된 값에서 첫

www.lesstif.com

링크의 말을 빌리면 
sendmail 같은 SMTP(Simple Mail Transfer Protocl) 를 구현한 소프트웨어를 MTA(Mail Transfer Agent) 라고 부르며 MS의 아웃룩이나 모질라의 썬더버드, 콘솔에서 구동되는 mutt 등의 사용자 프로그램은 MUA(Mail User Agent) 라고 분류한다. 
sendmail 은 전통적으로 많이 사용되던 MTA 였고 RHEL 5 까지는 기본 메일 서버 데몬이었으나 RHEL 6 부터는 postfix 로 교체되었다. 물론 원하는 사용자는 sendmail 을 MTA 로 사용하는 것도 가능하다.
 
말그대로 리눅스에서 제공하는 메일서비스인듯합니다. 
그렇다면 smtp란 뭘까요?
 
https://ko.wikipedia.org/wiki/%EA%B0%84%EC%9D%B4_%EC%9A%B0%ED%8E%B8_%EC%A0%84%EC%86%A1_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C

간이 우편 전송 프로토콜 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 간이 전자 우편 전송 프로토콜(Simple Mail Transfer Protocol, SMTP)은 인터넷에서 이메일을 보내기 위해 이용되는 프로토콜이다. 사용하는 TCP 포트번호는 25번이다. 상

ko.wikipedia.org

간이 전자 우편 전송 프로토콜(Simple Mail Transfer Protocol, SMTP)은 인터넷에서 이메일을 보내기 위해 이용되는 프로토콜이다. 사용하는 TCP 포트번호는 25번이다. 상대 서버를 지시하기 위해서 DNS의 MX레코드가 사용된다. RFC2821에 따라 규정되어 있다. 메일 서버간의 송수신뿐만 아니라, 메일 클라이언트에서 메일 서버로 메일을 보낼 때에도 사용되는 경우가 많다.
 
말그대로 메일끼리 서로 주고 받는거라고 생각하면 편합니다.
smtp 사용법은 인터넷에 많으니 참고하시면 되고요.
사용할때 사전지식을 일단 잘 알고넘어가는게 더 중요해서 
 
SMTP는 Simple Mail Transfer Protocol의 약자입니다. 인터넷을 통해 이메일 메시지를 보내고 받는 데 사용되는 통신 프로토콜입니다. 메일 서버 및 기타 메시지 전송 에이전트(MTA)는 SMTP를 사용하여 메일 메시지를 보내고, 받고, 중계합니다.
 
SMTP 포트란?
https://www.cloudflare.com/ko-kr/learning/email-security/smtp-port-25-587/
 
SMTP와 같은 대부분의 네트워킹 프로토콜은 특정 포트로 이동하도록 설계되어 있습니다. 네트워킹에서 포트는 컴퓨터 내의 가상 위치입니다.
 
SMTP 포트번호는?
https://inpa.tistory.com/entry/LINUX-%F0%9F%93%9A-%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%A6%AC%EB%88%85%EC%8A%A4-%ED%8F%AC%ED%8A%B8-%EC%A0%95%EB%A6%AC

🐧 리눅스 주요 포트 번호 종류

리눅스 포트 자주 쓰이는 포트 종류를 표로 나열해 보았다. 보라색으로 강조된 부분만 봐도 충분하다. 이밖의 자세한 포트 종류들은 리눅스 디렉토리 /etc/services 에서 확인할 수 있다. 윈도우 포

inpa.tistory.com

리눅스는 25번인데요. 
 
음 제가 이전에 API 개발하면서 예외발생시 오류건을 메일로 전송해주는걸 구현한적이 있습니다.
그떄도 아마 25번을 이용했었습니다. 윈도우 서버 OS였습니다. 
이상 smtp에 대해서 알아봤고요.
 
아래는 간단한 smtp를 이용한  자바 코드가 있습니다.

import java.util.*;

import javax.mail.*;
import javax.mail.internet.*;

import org.springframework.stereotype.Service;

@Service("MailService")
public class MailService {

	/** 메일 HOST **/
	private static final String HOST = IP주소;// smtp.naver.com
	/** 메일 PORT **/
	private static final String PORT = 포트번호;// 25, 587
	/** 메일 ID **/
	private static final String MAIL_ID = "송신자 이메일";
	/** 메일 PW **/
	private static final String MAIL_PW = "송신자 비밀번호";

	/*수신자 이메일*/
	private String email_to_1 = "메일아이디1";
	private String email_to_2 = "메일아이디2";
	private String email_to_3 = "메일아이디3";

	public MailService() {

	}

	public MailService(String email_to_1, String email_to_2, String email_to_3) {
		this.email_to_1 = email_to_1;
		this.email_to_2 = email_to_2;
		this.email_to_3 = email_to_3;
	}

	private void sendMail(String subject, String body, boolean bMail) {

		try {
			if(bMail==false){
            //메일 보낼지 말지 
				return;
			}

			InternetAddress[] receiverList = new InternetAddress[2];
			receiverList[0] = new InternetAddress(email_to_1);
			receiverList[1] = new InternetAddress(email_to_2);

			// SMTP 발송 Properties 설정
			Properties props = new Properties();
			props.put("mail.transport.protocol", "smtp");
			props.put("mail.smtp.host", MailService.HOST);
			props.put("mail.smtp.port", MailService.PORT);
			props.put("mail.smtp.starttls.enable", "true");
			props.put("mail.smtp.ssl.trust", MailService.HOST);
			props.put("mail.smtp.auth", "true");

			// SMTP Session 생성
			Session mailSession = Session.getDefaultInstance(props,
					new javax.mail.Authenticator() {
						@Override
						protected javax.mail.PasswordAuthentication getPasswordAuthentication() {
							return new javax.mail.PasswordAuthentication(
									MailService.MAIL_ID, MailService.MAIL_PW);
						}
					});

			// Mail 조립
			Message mimeMessage = new MimeMessage(mailSession);
			mimeMessage.setFrom(new InternetAddress(MailService.MAIL_ID,"제목"));
			mimeMessage.setHeader(MailService.MAIL_ID, "mymessage");
			mimeMessage.setRecipients(Message.RecipientType.TO, receiverList);
			// 메일 제목
			mimeMessage.setSubject(MimeUtility.encodeText(subject, "utf-8", "B"));
			// 메일 본문 (.setText를 사용하면 단순 텍스트 전달 가능)
			mimeMessage.setContent(body, "text/html; charset=UTF-8");
			// Mail 발송
			Transport.send(mimeMessage);
		} catch (Exception e) {
			log.debug("email 전송 오류");
		}
	}
    
	/*API JSON DATA ERROR 메일 내용*/
	public void sendNotiMail(String subject, String message, String url,
			String jsonData, String inputdata, String jsonErr, String empty,boolean bMail) {
		String body = "<b>Err Message</b>:"
				+ "<br>"
				+ message
				+ "<br><br>"
				+ "<b>APIURL</b>:"
				+ "<br>"
				+ url.replace("&reg", "&amp;reg")
				+ "<br><br>"
				+ "<b>json parse err msg</b>:"
				+ "<br>"
				+ jsonErr
				+ "<br><br>"
				+ "<b>parse json data</b>:"
				+ jsonData.replaceAll("\\{", "<br>{&nbsp;&nbsp;&nbsp;&nbsp;")
						  .replaceAll(",", "<br>&nbsp;&nbsp;&nbsp;&nbsp;")
				+ "<br><br>"
				+ "<b>input data:</b>"
				+ inputdata.replaceAll(",", ",<br>&nbsp;&nbsp;&nbsp;&nbsp;")
						.replaceAll("\\{", "<br>{&nbsp;&nbsp;&nbsp;&nbsp;");
		sendMail(subject, body,bMail);
	  }
      ......
    }

 

https://www.youtube.com/watch?v=hjDSKhyYK14

설명을 너무 잘하셔서 일단 올려봅니다 ㅎ 

+ Recent posts