21.12.11 fts 쿼리 튜닝

This commit is contained in:
jinia91
2021-12-11 19:38:08 +09:00
parent a845932d12
commit ca4678a35d
24 changed files with 310 additions and 116 deletions

View File

@@ -153,6 +153,11 @@ public class ArticleController {
Slice<ArticleDtoForMain> articleList = articleService.getArticlesByCategory(category, tier, pagingBoxDto.getCurPageNum())
.map(article ->
modelMapper.map(article, ArticleDtoForMain.class));
for(ArticleDtoForMain article : articleList){
article.setContent(Jsoup.parse(htmlRenderer.render(parser.parse(article.getContent()))).text());
}
//
model.addAttribute("pagingBox", pagingBoxDto);
@@ -179,6 +184,11 @@ public class ArticleController {
PagingBoxDto pagingBoxDto = PagingBoxDto.createOf(page, (int)articleList.getTotalElements());
modelsForLayout(model);
for(ArticleDtoForMain article : articleList){
article.setContent(Jsoup.parse(htmlRenderer.render(parser.parse(article.getContent()))).text());
}
//
model.addAttribute("articleList", articleList);
@@ -205,6 +215,11 @@ public class ArticleController {
PagingBoxDto pagingBoxDto = PagingBoxDto.createOf(page, (int)articleList.getTotalElements());
modelsForLayout(model);
for(ArticleDtoForMain article : articleList){
article.setContent(Jsoup.parse(htmlRenderer.render(parser.parse(article.getContent()))).text());
}
//
model.addAttribute("articleList", articleList);
@@ -255,7 +270,7 @@ public class ArticleController {
List<ArticleDtoByCategory> articleTitlesSortByCategory =
articleService
.getArticlesByCategoryForDetailView(article.getCategory())
.getArticlesByCategoryForDetailView(article.getCategory(), article)
.stream()
.map(article1 -> modelMapper.map(article1, ArticleDtoByCategory.class))
.collect(Collectors.toList());

View File

@@ -19,13 +19,23 @@ import java.util.List;
/*
- 아티클 Entity
- toc 추후 개발 예정
- 채번을 배치로 하게 하여 성능향상을 시켰고
네트워크를 두번타는 identity 대신 table 방식으로 구현된 시퀸스 방식을 채택하여 배치 인서트의 확장성을 열어둠
*/
@Entity
@Getter
@SequenceGenerator(
name = "ARTICLE_SEQ_GENERATOR",
sequenceName = "ARTICLE_SEQ",
initialValue = 1, allocationSize = 50)
/*
- fts 구현을 위한 인덱스 설정
-
*/
@Table(indexes = {
@Index(name="i_article_title", columnList = "title"),
@Index(name = "i_article_content", columnList = "content")
})
@Getter
public class Article extends BasicEntity {
@Id
@@ -33,10 +43,10 @@ public class Article extends BasicEntity {
@Column(name = "article_id")
private Long id;
@Column(nullable = false)
@Column(nullable = false, length = 50)
private String title;
@Column(nullable = false, columnDefinition = "TEXT")
@Column(nullable = false, length = 10000)
private String content;
@Column(columnDefinition = "bigint default 0",nullable = false)

View File

@@ -19,7 +19,7 @@ public interface ArticleRepository extends JpaRepository<Article, Long> {
List<Article> findTop6ByOrderByHitDesc();
/*
- 카테고리별 최대 6개 최신 게시물순으로 가져오기 페이징처리 x
- 카테고리별 최신 게시물 6개 가져오기
*/
List<Article> findTop6ByCategoryOrderByIdDesc(Category category);
@@ -41,6 +41,7 @@ public interface ArticleRepository extends JpaRepository<Article, Long> {
/*
- 카테고리별(상위 카테고리) 페이징 처리해서 최신게시물순으로 Slice 가져오기
- 토탈카운트 쿼리 x
*/
@Query("select a " +
"from Article a " +
@@ -70,19 +71,24 @@ public interface ArticleRepository extends JpaRepository<Article, Long> {
"from Article a " +
"join a.articleTagLists at " +
"join at.tags t " +
"where t.name in :tag " +
"where t.name =:tag " +
"order by a.id desc ")
Page<Article> findAllByArticleTagsOrderById(Pageable pageable, @Param("tag") String tag);
/*
- 키워드별 아티클 페이징 처리해서 조회
- 토탈 카운트 쿼리 o
- 배포시 fts로 개선해보자
*/
@Query("select a " +
"from Article a " +
"where a.title like %:keyword% " +
"or a.content like %:keyword% " +
"order by a.id desc ")
// @Query(value = "select * " +
// "from article " +
// "where match(content, :keyword) " +
// "order by article_id desc",nativeQuery = true)
Page<Article> findAllByKeywordOrderById(Pageable pageable, @Param("keyword") String keyword);
/*

View File

@@ -12,4 +12,5 @@ public interface NaArticleRepository {
@Delete("delete from article " +
"where article_id = #{articleId} ")
void deleteArticle(Long articleId);
}

View File

@@ -151,7 +151,7 @@ public class ArticleService {
/*
- 카테고리별 최신게시물 6개만 아티클 상세뷰 위해 가져오는로직
*/
public List<Article> getArticlesByCategoryForDetailView(Category category){
public List<Article> getArticlesByCategoryForDetailView(Category category, Article article){
return articleRepository.findTop6ByCategoryOrderByIdDesc(category);

View File

@@ -0,0 +1,21 @@
package myblog.blog.base;
import org.hibernate.dialect.MySQL57Dialect;
import org.hibernate.dialect.MySQL8Dialect;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.type.StandardBasicTypes;
/*
- FTS 지원을 위해 JPA에 SQL에 MYSQL 문법 추가 등록
*/
public class MysqlDialectCustom extends MySQL8Dialect {
public MysqlDialectCustom(){
super();
registerFunction(
"match", new SQLFunctionTemplate(StandardBasicTypes.DOUBLE, "match(?1) against (?2 in boolean mode)"
));
}
}

View File

@@ -26,7 +26,7 @@ public class Category extends BasicEntity {
@Column(name = "category_id")
private Long id;
@Column(nullable = false, unique = true)
@Column(nullable = false, unique = true, length = 20)
private String title;
@OneToMany(mappedBy = "category")

View File

@@ -65,7 +65,7 @@ public class CategoryService {
/*
- 카테고리와 카테고리별 아티클 수 찾기
*/
public List<CategoryNormalDto> getCategorytCountList(){
public List<CategoryNormalDto> getCategorytCountList() {
return naCategoryRepository.getCategoryCount();
}
@@ -97,7 +97,7 @@ public class CategoryService {
3-3 DB에만 존재하는 카테고리는 삭제처리
*/
@Transactional
@CacheEvict(value = {"layoutCaching","seoCaching"}, allEntries = true)
@CacheEvict(value = {"layoutCaching", "seoCaching"}, allEntries = true)
public void changeCategory(List<CategoryNormalDto> categoryList) {
// 1.카테고리 리스트 순서 작성
@@ -204,73 +204,19 @@ public class CategoryService {
}
/*
- 최초 더미 카테고리 추가 코드
- 최초 필수 더미 카테고리 추가 코드
*/
// @PostConstruct
public void insertCategory() {
Category category0 = Category.builder()
.tier(0)
.title("total")
.pSortNum(0)
.cSortNum(0)
.build();
@PostConstruct
public void insertDummyCategory() {
if(categoryRepository.findByTitle("total")==null) {
Category category0 = Category.builder()
.tier(0)
.title("total")
.pSortNum(0)
.cSortNum(0)
.build();
categoryRepository.save(category0);
Category category1 = Category.builder()
.tier(1)
.title("카테고리 부모")
.pSortNum(1)
.cSortNum(0)
.build();
categoryRepository.save(category1);
Category category2 = Category.builder()
.tier(2)
.title("카테고리 자식")
.pSortNum(1)
.cSortNum(1)
.parents(category1)
.build();
categoryRepository.save(category2);
Category category3 = Category.builder()
.tier(1)
.title("카테고리 부모2")
.pSortNum(2)
.cSortNum(0)
.build();
categoryRepository.save(category3);
Category category4 = Category.builder()
.tier(1)
.title("카테고리 부모3")
.pSortNum(3)
.cSortNum(0)
.build();
categoryRepository.save(category4);
Category category5 = Category.builder()
.tier(2)
.title("카테고리 자식2")
.pSortNum(2)
.cSortNum(1)
.parents(category3)
.build();
categoryRepository.save(category5);
Category category6 = Category.builder()
.tier(2)
.title("카테고리 자식3")
.pSortNum(2)
.cSortNum(2)
.parents(category3)
.build();
categoryRepository.save(category6);
Category category7 = Category.builder()
.tier(2)
.title("카테고리 자식4")
.pSortNum(3)
.cSortNum(1)
.parents(category4)
.build();
categoryRepository.save(category7);
}
}
}

View File

@@ -8,6 +8,9 @@ import myblog.blog.category.dto.CategoryForView;
import myblog.blog.category.service.CategoryService;
import myblog.blog.comment.dto.CommentDtoForLayout;
import myblog.blog.comment.service.CommentService;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.html.HtmlRenderer;
import org.jsoup.Jsoup;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Controller;
@@ -27,6 +30,8 @@ public class MainController {
private final ArticleService articleService;
private final CategoryService categoryService;
private final CommentService commentService;
private final HtmlRenderer htmlRenderer;
private final Parser parser;
/*
- 메인 화면 제어용 컨트롤러
@@ -41,6 +46,11 @@ public class MainController {
List<ArticleDtoForMain> popularArticles = articleService.getPopularArticles();
Slice<ArticleDtoForMain> recentArticles = articleService.getRecentArticles(0);
for(ArticleDtoForMain article : recentArticles){
article.setContent(Jsoup.parse(htmlRenderer.render(parser.parse(article.getContent()))).text());
}
//
model.addAttribute("category",categoryForView);
@@ -58,7 +68,18 @@ public class MainController {
public @ResponseBody
List<ArticleDtoForMain> mainNextPage(@PathVariable int pageNum) {
return articleService.getRecentArticles(pageNum).getContent();
List<ArticleDtoForMain> articles = articleService.getRecentArticles(pageNum).getContent();
for(ArticleDtoForMain article : articles){
String content = Jsoup.parse(htmlRenderer.render(parser.parse(article.getContent()))).text();
if(content.length()>300) {
content = content.substring(0, 300);
}
article.setContent(content);
}
return articles;
}
}

View File

@@ -27,13 +27,13 @@ public class Member extends BasicEntity {
@Column(name = "member_id")
private Long id;
@Column(nullable = false)
@Column(nullable = false, length = 50)
private String username;
@Column(nullable = false)
private String userId;
@Column(nullable = false, unique = true)
@Column(nullable = false, unique = true, length = 50)
private String email;
private String picUrl;

View File

@@ -95,7 +95,6 @@ public class Oauth2MemberService extends DefaultOAuth2UserService {
public void insertAdmin(){
Member admin = memberRepository.findByEmail(adminEmail);
if(admin == null){
admin = Member.builder()
.username(adminUsername+"#"+adminProviderId.substring(0,5))

View File

@@ -20,7 +20,7 @@ public class Tags extends BasicEntity {
@Column(name = "tags_id")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TAGS_SEQ_GENERATOR")
private Long id;
@Column(unique = true, nullable = false)
@Column(unique = true, nullable = false, length = 20)
private String name;
@OneToMany(mappedBy = "tags")
private List<ArticleTagList> articleTagLists = new ArrayList<>();

View File

@@ -19,9 +19,9 @@ server:
properties:
hibernate:
# show_sql: true
format_sql: true
format_sql:
jdbc:
batch_size: 100
batch_size:
git:

View File

@@ -695,7 +695,7 @@ ul.toolList li {
border-bottom: 1px solid #dbdbdb;
}
ul.toolList li:before {
ul.toolList .otherArticle:before {
content: "\f550";
font-family: FontAwesome;
-webkit-font-smoothing: antialiased;
@@ -705,6 +705,17 @@ ul.toolList li:before {
margin-right: 3px;
}
ul.toolList .curArticle:before {
content: "\f518";
font-family: FontAwesome;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: inline-block;
vertical-align: middle;
margin-right: 3px;
}
.badge:before {
content: "\f02c";
font-family: FontAwesome;
@@ -810,4 +821,5 @@ ul.toolList li:before {
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
}

View File

@@ -118,6 +118,15 @@ body::-webkit-scrollbar-track {
.navbar {
opacity: 0.98;
height: 50px;
}
.nav-brand{
position: absolute;
left: 50%;
margin-left: -50px;
font-size: 20px;
}
.navbar-toggler {
@@ -437,3 +446,7 @@ body::-webkit-scrollbar-track {
.categoryBox .fw-bold{
color: #1f70de !important;
}
.fa-bars{
font-size: 20px;
}

View File

@@ -57,6 +57,7 @@ function makeNextPage() {
</div>
<div class="col-9 row row-cols-1 align-self-center">
<h3 class="card-title col mb-3 text-truncate">${listElement.title}</h3>
<p class="d-none d-md-block col recent-card-text">${listElement.content}</p>
<p class="col mb-0"><small class="text-muted">${date}</small></p>
</div>
</div>

View File

@@ -63,6 +63,7 @@
</div>
<div class="col-9 row row-cols-1 align-self-center">
<h3 class="card-title col mb-3 text-truncate" th:text="${article.getTitle()}">글 제목</h3>
<p class="d-none d-md-block col recent-card-text" th:text="${article.getContent()}"></p>
<p class="col mb-0">
<small class="text-muted"
th:text="|작성일 : ${#temporals.format(article.getCreatedDate(), 'yyyy-MM-dd')}|"></small>

View File

@@ -51,7 +51,7 @@
<div class="recent-cards mt-5 ms-4 me-4">
<div class="cards-container container p-0" id="infiniteScrollBox">
<h1 class="text-center" th:text="|${param.keyword} 검색 결과|"></h1>
<h1 class="text-center" th:text="|'${param.keyword}' 검색 결과|"></h1>
<hr>
<div id="articlePage-0">
<div class="card mb-3 recent-card wow fadeInUp" th:each="article :${articleList.getContent()}">
@@ -63,6 +63,7 @@
</div>
<div class="col-9 row row-cols-1 align-self-center">
<h3 class="card-title col mb-3 text-truncate" th:text="${article.getTitle()}">글 제목</h3>
<p class="d-none d-md-block col recent-card-text" th:text="${article.getContent()}"></p>
<p class="col mb-0">
<small class="text-muted"
th:text="|작성일 : ${#temporals.format(article.getCreatedDate(), 'yyyy-MM-dd')}|"></small>
@@ -72,8 +73,6 @@
</a>
</div>
</div>
<!-- infinityScroll -->
<div id="nextPagination"></div>
</div>
</div>
@@ -85,7 +84,7 @@
<ul class="pagination">
<li class="page-item">
<a class="page-link" aria-label="First"
th:href="@{/article/list/tag/(tag=(${param.keyword}),page=(1))}"
th:href="@{/article/list/search/(keyword=(${param.keyword}),page=(1))}"
th:if="${pagingBox.getCurPageNum()}>5">
<span>«</span>
</a>
@@ -93,7 +92,7 @@
<li class="page-item">
<a class="page-link" aria-label="Previous"
th:href="@{/article/list/tag/(tag=(${param.keyword}),page=(${pagingBox.getCurPageNum()}-5))}"
th:href="@{/article/list/search/(keyword=(${param.keyword}),page=(${pagingBox.getCurPageNum()}-5))}"
th:if="${pagingBox.getCurPageNum()}>5">
<span>
< </span>
@@ -103,16 +102,16 @@
<th:block th:each="page : ${#numbers.sequence(pagingBox.getBoxStartNum(), pagingBox.getBoxEndNum())}">
<li th:if="${pagingBox.getCurPageNum()==page}" class="page-item active">
<a th:text="${page}" class="page-link"
th:href="@{/article/list/tag/(tag=(${param.keyword}),page=(${page}))}"></a>
th:href="@{/article/list/search/(keyword=(${param.keyword}),page=(${page}))}"></a>
</li>
<li th:unless="${pagingBox.getCurPageNum()==page}" class="page-item">
<a th:text="${page}" class="page-link"
th:href="@{/article/list/tag/(tag=(${param.keyword}),page=(${page}))}"></a>
th:href="@{/article/list/search/(keyword=(${param.keyword}),page=(${page}))}"></a>
</li>
</th:block>
<li class="page-item">
<a class="page-link" aria-label="Next"
th:href="@{/article/list/tag/(tag=(${param.keyword}),page=(${pagingBox.getCurPageNum()}+5))}"
th:href="@{/article/list/search/(keyword=(${param.keyword}),page=(${pagingBox.getCurPageNum()}+5))}"
th:if="${pagingBox.getCurPageNum()<=pagingBox.getPNumForNextBtn()}"
>
<span aria-hidden="true">></span>
@@ -121,7 +120,7 @@
<li class="page-item">
<a class="page-link" aria-label="Last"
th:href="@{/article/list/tag/(tag=(${param.keyword}),page=(${pagingBox.getLastPageNum()}))}"
th:href="@{/article/list/search/(keyword=(${param.keyword}),page=(${pagingBox.getLastPageNum()}))}"
th:if="${pagingBox.getCurPageNum()<=pagingBox.getPNumForNextBtn()}"
>
<span aria-hidden="true">»</span>

View File

@@ -63,6 +63,7 @@
</div>
<div class="col-9 row row-cols-1 align-self-center">
<h3 class="card-title col mb-3 text-truncate" th:text="${article.getTitle()}">글 제목</h3>
<p class="d-none d-md-block col recent-card-text" th:text="${article.getContent()}"></p>
<p class="col mb-0">
<small class="text-muted"
th:text="|작성일 : ${#temporals.format(article.getCreatedDate(), 'yyyy-MM-dd')}|"></small>

View File

@@ -120,10 +120,16 @@
th:text="|'${article.getCategory()}'|"></a>
<span>카테고리의 다른글</span></div>
<ul class="row row-cols-1 row-cols-sm-2 mt-2 ms-5 me-5 toolList">
<li th:each="list:${articlesSortBycategory}"
th:if="${list.getTitle()} != ${article.getTitle()}" class="col text-block" >
<a th:href="@{/article/view(articleId=${list.getId()})}" th:text="${list.getTitle()}">글목록</a>
</li>
<th:block th:each="list:${articlesSortBycategory}">
<li class="col text-block otherArticle" th:if="${list.getTitle()} != ${article.getTitle()}">
<a th:href="@{/article/view(articleId=${list.getId()})}" th:text="${list.getTitle()}">글목록</a>
</li>
<li class="col text-block fw-bold curArticle" th:if="${list.getTitle()} == ${article.getTitle()}">
<a th:text="${list.getTitle()}">글목록</a>
</li>
</th:block>
</ul>
<div class="mt-2 ms-5 me-5">
<a class="me-2" th:each="tag: ${article.tags}"

View File

@@ -112,6 +112,7 @@
</div>
<div class="col-9 row row-cols-1 align-self-center">
<h3 class="card-title col mb-3 text-truncate" th:text="${article.getTitle()}">글 제목</h3>
<p class="d-none d-md-block col recent-card-text" th:text="${article.getContent()}"></p>
<p class="col mb-0">
<small class="text-muted"
th:text="|작성일 : ${#temporals.format(article.getCreatedDate(), 'yyyy-MM-dd')}|"></small>

View File

@@ -15,8 +15,8 @@
aria-controls="offcanvasMenu">
<i class="fas fa-bars"></i>
</button>
<div id="nav-brand">
<a th:href="@{/}"><h4>Jinia's LOG</h4></a>
<div id="nav-brand" class="nav-brand">
<a th:href="@{/}">Jinia's LOG</a>
</div>
<div id="nav-login" sec:authorize="!isAuthenticated()">
<a th:href="@{/login}"><span class="pe-2">로그인</span></a>

View File

@@ -4,11 +4,11 @@
<div th:fragment="sideBar">
<div class="m-4 sidebar-header">
<a th:href="@{/}"><h3 class="text-black-50" style="white-space: nowrap">Jinia's LOG</h3></a>
<a href="#"><h5 class="text-black-50">ABOUT ME</h5></a>
<div class="m-4 sidebar-header" style="white-space: nowrap">
<a th:href="@{/}"><h3 class="text-black-50">Jinia's LOG</h3></a>
<a th:href="@{/aboutMe}"><h5 class="text-black-50">ABOUT ME(작성중)</h5></a>
<a sec:authorize="hasRole('ADMIN')" th:href="@{/article/write}" href="#">
<h5 class="text-black-50" style="white-space: nowrap">NEW ARTICLE</h5>
<h5 class="text-black-50">NEW ARTICLE</h5>
</a>
</div>
@@ -38,8 +38,8 @@
</div>
</li>
<!-- 카테고리 s -->
<li class="border-top my-3"></li>
<li class="mb-1 superCategory btn fs-5"><a th:href="@{/article/list(category=total,tier=0,page=1)}"
th:text="|전체글(${category.getCount()})|">토탈</a></li>
<a th:href="@{/edit/category}" sec:authorize="hasRole('ADMIN')" class="me-0 ps-0 btn"><i class="fas fa-pen"></i></a>
@@ -52,11 +52,11 @@
aria-expanded="true">
</button>
<a th:href="@{/article/list(category=${superCategory.getTitle()},tier=1,page=1)}"
class="ms-0 superCategory btn fs-6"
class="ms-0 superCategory btn fs-5"
th:text="|${superCategory.getTitle()}(${superCategory.getCount()})|" >슈퍼</a>
</div>
<div class="collapse show" th:id="|collapse-${superCategory.getId()}-sub|">
<ul class="ms-1 btn-toggle-nav list-unstyled fw-normal pb-1 small"
<ul class="ms-1 btn-toggle-nav list-unstyled fw-normal pb-1"
th:each="subCategory : ${superCategory.getCategoryTCountList()}">
<li><a th:href="@{/article/list(category=${subCategory.getTitle()},tier=2,page=1)}"
class="link-dark rounded"
@@ -64,6 +64,8 @@
</ul>
</div>
</li>
<!-- 카테고리 e -->
<!-- 최신 댓글 s -->
<li class="border-top my-3"></li>
<li class="mb-1">
<button class="btn btn-toggle align-items-center rounded collapsed fs-5" data-bs-toggle="collapse"
@@ -82,9 +84,9 @@
</ul>
</div>
</li>
<!-- 최신 댓글 e -->
<li class="border-top my-3"></li>
<li class="mb-1">
<div >
<ul class="btn-toggle-nav list-unstyled fw-normal pb-1">

View File

@@ -36,15 +36,6 @@ class ArticleRepositoryTest {
// 카테고리 삽입
// 필수 삽입 더미 카테고리
Category category0 = Category.builder()
.tier(0)
.title("total")
.pSortNum(0)
.cSortNum(0)
.build();
categoryRepository.save(category0);
// 부모 카테고리
Category category1 = Category.builder()
.tier(1)
@@ -195,6 +186,154 @@ class ArticleRepositoryTest {
}
@Test
@Rollback(value = false)
@Transactional
public void 인덱스스캔테스트() throws Exception {
// given
Category category = categoryRepository.findByTitle("카테고리 자식");
Member admin = memberRepository.findById(1L).get();
Article articleSample = Article.builder()
.category(category)
.content("## 1. 네트워크 기초\n" +
"\n" +
"### 공부 목적\n" +
"백엔드 개발자로 진로를 정하고 공부를 하면서 비전공자로서 CS의 얕은 지식에 한계를 많이 느끼고 있다. \n" +
"\n" +
"서블릿과 스프링을 통해 웹 애플리케이션을 만들면서 당장에 직면하는 http 프로토콜과 그 밑으로 숨겨져있는 데이터 송수신의 과정들, IP와 DNS 작동원리 등등\n" +
"\n" +
"아니 그런것을 떠나서 **인터넷**이라는게 뭔지조차 한두문장으로 깔끔하게 설명 못하는 내 모습이 한심하게 느껴져서 공부를 시작하고자 한다.\n" +
"\n" +
"\n" +
"### (1) 인터넷이란?\n" +
"\n" +
"인터넷을 이야기하기에 앞서 **네트워크**에 대해 먼저 정의하자.\t\n" +
"\n" +
"**네트워크**란 Net + Work의 합성어로 일하는 그물, 그물처럼 엮여서 일하는 것이란 의미로 직역해볼수 있으며, 정보통신 영역에서는 원하는 정보를 원하는 수신자에게 정확히 전송하기 위한 기반 인프라라고 설명할수 있다.\n" +
"\n" +
"여기서 정보를 송수신하는 단말(노드)은 컴퓨터, 핸드폰 등등 다양한 종류가 있지만 설명의 편의를 위해 컴퓨터로 통칭하고 이를 사용하는 네트워크, 즉 **컴퓨터 네트워크**를 앞으로 네트워크로 약칭하겠다.\n" +
"\n" +
"네트워크는 연결된 노드의 수에따라 크고 작은 네트워크로 구분될수 있는데 **인터넷**은 *TCP/IP 프로토콜*을 기반으로 전세계적으로 연결된 네트워크를 일컫는 말이다. 여기서 인터넷을 브라우저에서 구동되는 웹으로 한정지어 생각하기 쉬운데, 인터넷은 웹은 물론, 전자 메일, 동영상스트리밍, 온라인 게임 VoIP등 다양한 서비스를 포함하는 말이다.\n" +
"\n" +
"\n" +
"### (2) 랜(LAN)과 왠(WAN)\n" +
"\n" +
"인터넷의 이야기에서 네트워크로 다시 돌아와보자. \n" +
"\n" +
"네트워크는 규모에따라 분류해볼수 있는데, 대표적으로 LAN과 WAN에대해 살펴보겠다.\n" +
"![enter image description here](https://ww.namu.la/s/30118c345e47ffe378f33bb37dbdb9372e7cafd61e54c07d1b5832f52742b4b0b2807d557bd1bfc3ec3780f915773a9958845b2565add20510be3e193e53e8acb2f39f6529970f7b888ad7a5a212e19f54c1377ee9f59d70cc3f93189d1c0e71)\n" +
"LAN(Local Area Network)은 근거리 통신망의 약어로 근거리의 범위는 따로 정해진건 없지만, 일반적으로 하나의 사무실 혹은 주택내의 네트워크를 LAN이라고 부른다.\n" +
"\n" +
"근거리 통신망? 주택내의 네트워크? 뭔가 와닿지 않을수 있는데, 집에 설치된 인터넷 공유기를 떠올리면 쉽게 이해가 될것이다.\n" +
"\n" +
"**외부(ISP-인터넷서비스제공자)**에서 **인터넷회선**을 통해 **인터넷공유기(Broadband Router)**로 인터넷을 연결시키면, 이 공유기를 중심으로 단말기들과 인터넷망(사설망)이 구성되는데 이것이 바로 랜이다. \n" +
"\n" +
"위의 그림에는 컴퓨터 4대가 랜으로 묶여있지만, 컴퓨터를 비롯해서 와이파이로 연결되는 핸드폰, 게임기, 노트북 등등 이 모든 단말기들이 하나의 랜(무선랜, 유선랜)으로 불릴수 있는것이다.\n" +
"\n" +
" WAN(Wide Area Network)은 광역 통신망의 약어로, 수많은 랜을 연결하여 가장 상위에 속하는 통신망이며 ISP(KT,SK브로드밴드 등)가 인터넷 서비스를 제공하기 위해 전국에 회선을 깔아 구축한 통신망이 대표적인 예이다.\n" +
"\n" +
"\n" +
"### (3) 통신규약(Protocol)\n" +
"\n" +
"우리가 편지를 보내기 위해서는 무엇을 해야할까? 편지를 쓰고 보내는 과정을 정리해보자.\n" +
"\n" +
"1. 우선 아무 편지지나 종이쪼가리에 글을 쓸것이다. \n" +
"2. 편지 봉투에 편지를 담고, 편지봉투에 **정해진 우표**와 보내는이, 그리고 받는이를 **정해진 우편번호**로 적는다.\n" +
"3. 편지봉투를 **정해진 우체통**에 넣는다.\n" +
"\n" +
"자 여기까지만 살펴보더라도 우리는 우리가 쓴 편지(DATA)를 원하는 상대에게 보내기 위해 상당히 많은 **규칙**을 지키고 있음을 확인할 수 있다.\n" +
"\n" +
"우선 정해진 우표를 사서 붙임으로서 우편요금을 내고, 정해진 우편번호를 적음으로서 송신자와 수신자가 누군지 식별가능케 한다. 그리고 정해진 우체통에 편지를 넣음으로서 편지를 다룰 다음 사람(우편배달부)에게 전송을 완료한다.\n" +
"\n" +
"**통신 네트워크도 위와 다르지 않다.**\n" +
"\n" +
"우리가 작성하고 만든 데이터 혹은 데이터를 달라하는 요청을 누군가에게 송신하려면 수많은 네트워크 규칙을 준수해야하고, 그 규칙하에 데이터(요청)은 수신자에게 전달될 것이다.\n" +
"\n" +
"이러한 규칙을 프로토콜(Protocol)이라고 부른다.\n" +
"\n" +
"\n" +
"### (4) OSI모델과 TCP/IP 모델\n" +
"\n" +
"앞서 프로토콜에 대해 알아보았다. 그런데 만약 서울에서 사용하는 WAN과 부산의 WAN 간의 프로토콜이 다르다면? 혹은 강서구와 강동구간의 프로토콜이 다르다면? 각기 네트워크간의 프로토콜이 모두 다르다면?\n" +
"\n" +
"서로다른 프로토콜을 통역하기위해 수많은 전처리과정을 거쳐야하고 어마어마한 비효율을 낳을 것이다.\n" +
"\n" +
"이를 해결하기 위해 등장한 것이 **표준 프로토콜 규격** OSI 모델과 TCP/IP 모델이다.\n" +
"\n" +
"\n" +
"#### OSI 모델\n" +
"\n" +
"OSI 모델은 ISO(International Organization for Standardization 국제표준화기구)에서 제정한 컴퓨터 네트워크 표준 프로토콜 규격으로\n" +
"\n" +
"통신네트워크를 7계층(Layer)으로 나누어 설명하고, 각각의 프로토콜을 정의한다.\n" +
"\n" +
"![enter image description here](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTrGB2x1KXViaU-GJD61bW840ynAnUReo2TpA&usqp=CAU)\n" +
"\n" +
"|계층|이름|\n" +
"|-|-|-|\n" +
"|7계층|응용(Application)|\n" +
"|6계층|표현(Presentation)||\n" +
"|5계층|세션(Session)||\n" +
"|4계층|전송(Transport)||\n" +
"|3계층|네트워크(Network)||\n" +
"|2계층|데이터 링크(Data Link)||\n" +
"|1계층|물리(Physical)||\n" +
"\n" +
"#### TCP/IP\n" +
"\n" +
"![enter image description here](https://ww.namu.la/s/0aa9f4305d7fbcbb9bc932e86e9dc9318c5ab4c6f8adfcbf9f90e1b2a21c1d852cba07676ee2b5830979280bd20e0ff016d5981f804fa410bb1ea302a88625345256c0496e049ca2ea4840132da24f29080b75d498a813fdab24bdcb4be3422f99067b0a36f7ae1f34c57215a2deb446)\n" +
"\n" +
"현재 수많은 프로그램들이 인터넷으로 통신하는데 있어서 가장 많이 기반으로 삼는 프로토콜은 TCP와 IP이다. \n" +
"\n" +
"TCP/IP는 최초의 컴퓨터 네트워크였던 알파넷에서 사용하기 시작하였고 UNIX의 기본 프로토콜로 사용되었으며 현재 인터넷 범용 프로토콜이 되었기에, 인터넷 프로토콜 그 자체를 표현하는 용어기도 하며 크게 4계층으로 나뉜다.\n" +
"\n" +
"|계층|이름|\n" +
"|-|-|\n" +
"|4계층|응용(Application)||\n" +
"|3계층|전송(Transport)||\n" +
"|2계층|인터넷(Internet)||\n" +
"|1계층|네트워크 접속(Link)||\n" +
"\n" +
"위의 그림처럼 TCP/IP 모델은 OSI모델과 유사하게 매핑이 가능하다. 여기서 네트워크 접속계층을 OSI처럼 두개로 나누어 \n" +
"\n" +
"|계층|이름|\n" +
"|-|-|\n" +
"|5계층|응용(Application)||\n" +
"|4계층|전송(Transport)||\n" +
"|3계층|인터넷(Internet)||\n" +
"|2계층|데이터 링크(Link)||\n" +
"|1계층|물리(Physical)||\n" +
"\n" +
"처럼 5계층으로 나누기도하는데 \n" +
"\n" +
"앞으로 위의 TCP/IP 5계층 모델을 가지고 각 계층별로 자세히 살펴보며 네트워크의 전체 흐름을 공부해보겠다.")
.thumbnailUrl("더미")
.title("테스트")
.member(admin)
.toc(null)
.build();
articleRepository.save(articleSample);
for(int i =0; i<1000; i++) {
Article newArticle = Article.builder()
.category(category)
.content(articleSample.getContent())
.thumbnailUrl("더미")
.title("테스트")
.member(admin)
.toc(null)
.build();
articleRepository.save(newArticle);
}
// when
// then
}