심심해서 하는 블로그 :: '분류 전체보기' 카테고리의 글 목록 (4 Page)


요새 겨울에 동남아 휴가를 가기 위해서 열심히 다이어트를 하고 있어요.

다이어트 기간을 장기간으로 생각해서 그런지 식단에서 조금 자유로운 점은 있어요.

그래도 저녁은 항상 샐러드를 먹고 마무리를 하자는 원칙은 있어서 샐러드를 만들어 먹을까 했지만, 저를 좋아하는 감자님마저 제가 요리 해준다면 안 먹는데다 채소들의 신선도도 걱정이 되서 사먹기로 결심했습니다.


알로하 포케는 IFC 몰 L1층 있어요.  유니클로 방향으로 뒤편으로 넘어가시면 스타벅스 리저브와 아오리 라멘 등 가게들 사이에 있습니다. 정확한 위치는 글의 마지막에 지도를 첨부했어요.

 

저는 퀴노아 샐러드 베이스의 스파이시 크림참치 샐러드를 주문했어요.

다른 토핑들도 넣어서 먹을 수있지만 저는 넣지 않았습니다. 사이즈도 Small 로 주문했어요. 

가격은 9500원이에요. 결코 싼 가격은 아니라고 생각할 수도 있어요. 

야채 덩어리들 주제에 9500원?? 다이어트 전에 저도 샐러드를 굳이 사먹나 생각했었으니깐요. 




샐러드 하나에 9500원이라니, 손을 바들바들 떨다가도 먹을 때는 기분이 좋답니다.

왜냐하면 그만큼 맛이 있거든요. 신선함과 맛을 둘 다 성공적으로 잡았어요.

알로하 포케의 연어나 참치 샐러드에는 날치알이 있는게 매력적이에요. 

씹을 때마다 터지는 날치알들이 식감을 살리는데 참 좋거든요.

토마토랑 야채가 들어가고 콩이 있어요. 병아리 콩이 고소하고 식감이 좋아서 만족스러웠어요.



저는 크림 참치라고 해서, 통조림 참치를 넣어 주는 줄 알았는데 참치회를 넣어줘서 깜짝 놀랐어요.

야채들은 아삭하니 신선하고 스파이시라 해서 많이 맵거나 할 줄 알았는데 전혀 맵지 않았어요.

참치 한 조각과 야채를 같이 먹으니까 아삭하고 고소하고 맛이 풍부해요.




다이어트를 고민하거나 가끔 가볍게 식사를 하고 싶으시면 먹어보는 것도 좋은 선택인거 같아요.
저도 저녁에 찾아가서 다른 메뉴도 먹어보고, 토핑도 한번 추가해서 먹어볼까 합니다.


도움이 되셨다면, 공감버튼 눌러주세요.
공감버튼은 저에게 큰 동기부여가 됩니다.


,


날이 햇빛만 없으면 선선한 바람도 불고 하는데, 햇빛이 너무 뜨거워서 고생했습니다.

보라매 공원을 걸어다니는데, 하늘이랑 구름이랑 경치는 좋은데 말인데요.

그래서 야외 데이트를 접고 스타벅스로 입성을 했어요. 낮에 돌아다니기에는 많이 더운거 같아요.



미세먼지 하나 없어서 정말 맑은 하늘, 햇살, 구름, 보라매공원 좋았지만, 너무 덥고 지치더라구요.

보통 카페가면 콜드브루만 마시는 편인데, 오늘은 땀을 많이 흘려서 그런지 여름 음료가 먹고 싶었어요.

그래서 저는 라임망고 블랜디드를, 감자님은 수박 블랜디드를 시켜서 마셔보기로 했습니다.


라임망고 블랜디드


요새 환경오염 문제로 일회용 컵을 안 줘서, 안에 당최 뭐가 들었느지 알 수가 없었어요. 

유리잔에 이런 건 줬으면 좋겠다는 생각도 들었어요. 

처음에는 라임 망고라 해서 망고 맛이 많이 나는 음료일 거라고 생각했는데, 엄청 새콤해서 놀랐어요.

맛은 새콤하면서도 라임 향도 나고, 바닥에 쌓인 망고 조각들을 먹는 게 재밌었어요. 

워낙 셔요 근데, 레모네이드보다 2배 정도 신거 같아요. 신 거를 저는 좋아해서 맛있게 먹긴 했지만, 신 걸 좋아하지 않는다면, 썩 좋은 선택지는 아닌 거 같아요. 이건 감자랑 저 둘 다 만족하면서 먹었어요.





수박 블랜디드


감자님이 시킨 수박 블랜디드. 화채 같은 느낌을 상상하고 먹었는데, 음... 음...

뭔가 오모해요. 수박을 먹을 때 이 맛을 맛본 거 같긴 한데, 수박 껍질 부분 먹을 때 이런 맛 났던거 같기도 해요.

감자님은 호박 맛 난다고 하는데, 다른 블로거들도 보니 호박 맛 난다고 투덜대네요.

미리 블로그 글을 확인하고, 메뉴를 선정할 걸 그랬어요. 가격도 보통 비싼 게 아닌데 말이죠. 

썩 추천드릴 맛은 아니에요. 수박 음료가 먹고 싶다면 주씨가서 먹는 게 한참 지혜로운 선택이에요.


신 것을 좋아한다면 라임망고 블랜디드는 추천드릴 만한데, 수박은 정말 다음 번에도 안 먹을 거 같아요.

긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기부여가 됩니다. 감사합니다.



 

,


1. PyMySQL

MySQL 데이터베이스와 연결하기 위한 오픈소스 라이브러리입니다. 해당 라이브러리를 설치하기 위해서 python -m pip install PyMySQL 명령어로 설치가능하지만, 만약 PyCharm 을 이용한다면. Setting에서 Project Interpreter 에서 아래 사진의 + 버튼을 누른뒤 PyMySQL 를 검색하여 설치할 수 있습니다.




설치가 완료가 되면 간단한 예제 코드로 Connection 테스트를 합니다.



1
2
3
4
5
6
7
8
9
10
11
12
import pymysql.cursors
 
connection = pymysql.connect(host='localhost', user='root', password='12', db='ssooni',
                             charset='utf8', autocommit=True)
 
cursor = connection.cursor()
sql = "select * from ssooni.board"
cursor.execute(sql)
 
result = cursor.fetchall()
connection.close()
print(result)
cs


- host  : MySQL가 설치되어 있는 원격지 주소를 넣어 줍니다.

- user / password :  계정명 / 비밀번호

- db : Default로 사용할 Database 이름을 넣어줍니다.

- charset : 인코딩 정보

- autocommit : Query 실행 후 자동으로 commit 명령어를 전송합니다. 

이 부분을 넣지 않으면, Insert나 Update를 실행해도 실제 DB에 반영되지 않습니다. 

실수로 데이터를 삭제 / 수정하는 일을 조금 막고자한다면, 이 옵션을 사용하지 않고, Query 실행 후 commit 할 시점에 connection.commit() 라인을 추가합니다.

  

Connection 객체로 DB 연결을 설정하고 Cursor로 데이터를 가지고 오는 Python에서 자주 볼 수 있는 형태로 데이터를 가지고 옵니다. 실행하면 아래와 같은 결과를 볼 수 있습니다.


실행 결과 

(1, 'ssooni', 'Welcome! Hi Je'), (13, 'aa', 'aaa'), (14, 'aa', 'aaaa'), (15, '?????', '?? ??????\n'))



2. pandas.DataFrame으로 변환

앞선 실행 결과는 Column 이 없어서 각각의 원소들이 어떤 데이터인지 알 수가 없습니다. pandas를 사용하여 Column 명을 할당하고, 데이터를 쉽게 분석하고 싶기도 합니다. 


보통 Python DB 라이브러리에서는 cursor.description 에 각각의 컬럼에 대한 정보를 따로 담습니다. 그래서 따로 함수를 짜서 cursor.description 안에서 컬럼명을 가지고 온 후 매핑을 하는 과정을 작성했습니다만, 최근에 나온 라이브러리는 그것마저 해줍니다.



1
2
3
4
5
6
7
8
9
10
11
12
import pymysql.cursors
 
connection = pymysql.connect(host='localhost', port=3306, user='root'
                             password='12', db='ssooni',
                             charset='utf8', autocommit=True, 
                             cursorclass=pymysql.cursors.DictCursor)
 
cursor = connection.cursor()
sql = "select * from ssooni.board"
cursor.execute(sql)
result = cursor.fetchall()
print(result)
cs


아까 코드와 달라진 점은 cursorclass에 DictCursor 를 추가했습니다. DB를 조회한 결과를 Column 명이 Key 인 Dictionary로 저장해 줍니다.


실행 결과

[{'bno': 1, 'userName': 'ssooni', 'contents': 'Welcome! Hi Je'}, {'bno': 13, 'userName': 'aa', 'contents': 'aaa'}, {'bno': 14, 'userName': 'aa', 'contents': 'aaaa'}, {'bno': 15, 'userName': '?????', 'contents': '?? ??????\n'}]

 

이전 결과와 달리 Column 명이 있어서 각각의 원소가 의미하는 것이 무엇인지 쉽게 이해 할 수 있습니다.

실행 결과를 Pandas DataFrame으로 바꾸는 것은 더욱 간단합니다. 


1
2
3
4
import pandas as pd
 
df = pd.DataFrame(result)
print(df)
cs


실행 결과

   bno        contents userName

0    1  Welcome! Hi Je   ssooni

1   13             aaa       aa

2   14            aaaa       aa

3   15     ?? ??????\n    ?????


이제 DataFrame으로 평균을 구하는 등 통계도 할 수 있고, 단순히 CSV 파일로 저장하여 엑셀을 정말 잘하는 팀원에게 노동을 시킬 수도 있습니다. 


도움이 되셨다면 공감 버튼 한 번만 눌러주세요. 

공감 버튼은 저에게 큰 동기부여가 됩니다.

,


가족들이 서울에 올라와서 경복궁 야간개장을 같이 관람했었는데, 우리 감자님이 상당히 기분이 안좋아 보이네요.


감 : "아.. 나도 가고 싶었는데..."

나 : "한번 찾아볼게 ㅎㅎㅎ"


매년마다 경복궁 야간 개장 예매에 실패했었는데, 혹시나 해서 찾아 봤는데 역시나!! 매진!! 

경복궁 야간개장은 9월에 성공하리라 다짐하고, 대안으로 선택한 창경궁 야간개장을 다녀왔어요


1. 예매방법

인터파크에서 창경궁 야간개장으로 검색하시면, 표를 예매할 수 있어요. 

2018년 창경궁의 야간개장 기간은 아래와 같아요. 2018년 7월은 끝나버렸으니, 아직 못가신 분들은 9월과 10월에 한 번 관람해보세요.


- 4월(궁중문화축전): 4. 28. ~ 5. 6. / 19:00 ~ 21:30(입장 마감 20:30) 

- 5월: 5. 20. ~ 6. 2. / 19:00 ~ 21:30(입장 마감 20:30) 

- 6월: 6. 17. ~ 6. 30. / 19:30 ~ 22:00(입장 마감 21:00) 

- 7월: 7. 22. ~ 8. 4. / 19:30 ~ 22:00(입장 마감 21:00) 

- 9월: 9. 16. ~ 9. 29. / 19:00 ~ 21:30(입장 마감 20:30) 

- 10월: 10. 21. ~ 11. 3. / 19:00 ~ 21:30(입장 마감 20:30)


입장료는 1000원이구요. 한복을 착용한 사람에 대해 무료로 입장 가능하지만 예매는 꼭 하셔야 해요.

현장에서 발권을 안 해줘요.






2. 가는 길

가는 길 때문에 정말 고생했어요. 창덕궁 안에 창경궁이 있으니까 광화문에서 걸어갔는데, 입구가 달라요.

낮에는 같은 입구로 들어가서 창경궁 쪽으로 갈 수 있는데, 창덕궁은 야간개장을 안해서 창덕궁 정문인 돈화문을 이용할 수 없어요. 네비게이션이나 지도로 찾으실 때 서울대학교병원이나 홍화문으로 검색을 해서 가세요. 창덕궁에서 하염없이 기다리는 두세분 본 거 같아요. 


3. 본격 관람



7시 40분쯤에 입장했는데, 아직 여름이라 조금 밝긴 했어요. 

그래서 저녁이라는 느낌보다는 새벽에 고궁을 다닌다는 느낌도 들었던거 같아요. 

경복궁보다 좋았던 점은 국악 공연을 해주는 것이였어요. 전통 악기로 재즈를 연주하는 세션이 있었는데, 전통악기에 좀 더 익숙해질 수 있었던거 같아요. 결코 전통악기들의 음색이 서양악기에 꿇리지 않다는걸 느꼈어요. 



연못을 따라 걷다보면 고궁과는 전혀 어울리지 않은 현대식 식물원이 있어요. 

우리나라 최초의 식물원인 창경궁 대온실인데요. 과거에 희귀한 열대 식물을 전시하던 곳을 지금은 각 종 천연기념물로 등록된 식물들을 전시해 놓았어요. 


7월 입장의 마지막 날에 표를 겨우 얻어서 다녀왔는데, 가을과 봄의 모습은 어떨지, 또 낮의 모습은 어떨지 궁금하네요. 

감자님도 매우 만족하고 갔답니다. 연인, 가족, 친구들끼리 이번 9월 10월에 한 번 다녀오는것은 어떠세요??


끝까지 글 읽어주셔서 감사합니다.

공감! 한 번 눌러주시면 글쓴이에게 정말 큰 동기부여가 됩니다. 감사합니다!!

,
Untitled

1. pandas 소개

데이터 분석할 때, 정말 효자 라이브러리입니다.

Python을 이용해서 데이터를 분석하는 프로젝트에서 유용하게 사용한 라이브러리입니다.

pandas는 DataFrame 이라는 자료형을 이용하여, 데이터를 저장하고 가공합니다.

In [1]:
import pandas as pd
import numpy as np

matrix = np.matrix([[1,3], [2,5]])
print("numpy.matrix : \n", matrix)

df = pd.DataFrame([[1,3], [2,5]])
print("pandas DataFrame :\n", df)
numpy.matrix : 
 [[1 3]
 [2 5]]
pandas DataFrame :
    0  1
0  1  3
1  2  5

DataFrame은 numpy matrix에서 Matrix 행의 속성 index 와 열의 속성 column 이 적용된 형태를 합니다.

데이터베이스 테이블이나 엑셀같은 표에 데이터를 저장하는 형태와 동일합니다.

numpy.matrix를 사용하면서 불편한 점은 행렬 내부의 원소를 접근할 때 반드시 index 단위로 접근해야 했습니다.

프로그램을 혼자 짤 때는 선택한 이 원소의 의미를 이해할 수 있지만, 함께하는 프로젝트에서는 코드를 이해하는데 어렵습니다.

(거기다 난도가 있는 통계적 알고리즘 구현까지 들어가면 통계베이스 없는 저같은 개발자는 일일히 실행해야 겨우 의미를 알게 되요)

하지만 pandas는 각각의 행과 열을 지정할 수 있어서, 선택하는 데이터가 어떤 속성을 가지는지 쉽게 이해가 가능합니다.

In [2]:
matrix = np.matrix([[1,3], [2,5]])
print("A의 수학 성적 : ", matrix[0,0])

df = pd.DataFrame([[1,3], [2,5]], columns=["math", "computer"], index=["A", "B"])
print("A의 수학 성적 : ", df.loc["A", "math"])
A의 수학 성적 :  1
A의 수학 성적 :  1

또한 DataFrame 클래스안의 메소드 만으로도 프로젝트에서 사용하는 합, 평균, 분산, 표준 편차 등의 단순 통계부터

데이터를 재구성하기 위한 이동 평균, 이동 합, Resample, Groupby와 시각화까지 지원합니다.

이번 시간에는 시각화 기능을 알아 보도록 해요.




2. 시각화

DataFrame의 plot을 이용하면 다양한 형태의 그래프를 그릴 수 있습니다.

numpy Matrix를 그래프로 그린다면 matplotlib 라이브러리를 사용해서 설정을 해야한다는 것에 비해면 엄청 간단한 방법으로 그래프를 그릴 수 있어요

DataFrame.plot.bar() : 막대 그래프

DataFrame.plot.line() : 선 그래프

DataFrame.plot.scatter(x, y) : 산포도 그래프

DataFrame.plot.box() : Box 그래프

앞서 사용한 A와 B의 성적표로 막대 그래프와 선 그래프를 그려 봅시다

In [3]:
""" %matplotlib inline : 주피터 노트북 설정 때문에 사용한 거에요, """ 
%matplotlib inline 

df.plot.bar()
df.plot.line()
df.plot.box()
Out[3]:
<matplotlib.axes._subplots.AxesSubplot at 0x1e284adb9b0>

데이터 수가 많으면 숫자로 보았을 때 데이터가 어떤 분포를 가지는지 쉽게 알 수 없는데, 간단하게 그래프를 그려서 확인할 수 있어요.

산포도 그래프는 sklearn 라이브러리에 있는 샘플 데이터로 한 번 그려볼게요

sklearn 라이브러리가 없다면 아나콘다 콘솔에 pip install sklearn 명령어로 설치하시고 진행해주세요

데이터 마이닝 수업에서 단골 손님으로 언급되는 Iris 데이터를 이용해서 산포도 그래프를 그려볼게요.

아래의 함수를 실행하여, 데이터를 적재합니다.

In [4]:
from sklearn.datasets import load_iris
iris = load_iris()
data = iris['data']
feature = iris['feature_names']
label = iris['target']

print("data의 타입 : ", type(data))

# numpy.ndarray데이터도 pandas DataFramed의 데이터로 적용이 가능합니다.
iris_df = pd.DataFrame(data, columns=feature)

# label 컬럼을 추가해줍니다.
iris_df.loc[:, "label"] = label
print(iris_df.head())
data의 타입 :  <class 'numpy.ndarray'>
   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2   
1                4.9               3.0                1.4               0.2   
2                4.7               3.2                1.3               0.2   
3                4.6               3.1                1.5               0.2   
4                5.0               3.6                1.4               0.2   

   label  
0      0  
1      0  
2      0  
3      0  
4      0  

sklearn 라이브러리는 통계 알고리즘을 담고 있는 라이브러리인데요.

샘플로 데이터를 담고 있어서 데이터를 수집하는데 신경을 덜 쓸 수 있어요.

numpy.ndarraypandas.DataFrame 간의 호환성도 좋아서 여러분이 작성한 알고리즘의 결과가 numpy.ndarray 형태라 하여도,

pandas DataFrame으로 변환하여 사용할 수 있답니다.

이제 scatter 함수로 산포도를 그러볼게요. x축과 y축으로 사용할 컬럼명을 매개변수로 반드시 넣어주셔야 합니다.

In [5]:
iris_df.plot.scatter(x="sepal length (cm)", y="label")
Out[5]:
<matplotlib.axes._subplots.AxesSubplot at 0x1e2829fa5c0>

그려 놓고 보니까 각각이 무엇을 의미하는지 해석하기가 힘드네요. 서로 구분할 수 있도록 색을 넣어 줍시다.

label에 따라 색을 적용하기위해 color라는 컬럼을 새로 만들었습니다.

In [6]:
# iris 컬럼의 label은 0과 1, 2로 구성되어 있습니다. 
# label 중에 0, 1, 2 값을 같는 index를 찾아 줍니다.
zero = iris_df[iris_df["label"] == 0].index
one = iris_df[iris_df["label"] == 1].index
two = iris_df[iris_df["label"] == 2].index

# 그리고 각각의 index에 따라서 색깔 데이터를 넣어 줍니다.
iris_df.loc[zero, "color"] = "r"
iris_df.loc[one, "color"] = "g"
iris_df.loc[two, "color"] = "b"

iris_df.plot.scatter(x="sepal length (cm)", y="label", c=iris_df['color'])
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x1e286a6f828>

pandas.DataFrame으로 간단하게 데이터 가시화를 해보았습니다.

마지막으로 여러 종류의 그래프를 한 곳에 그리는 방법도 공유해드릴게요.

In [7]:
ax = iris_df["sepal length (cm)"].plot.line(color='green')
ax_2 = iris_df["petal length (cm)"].plot.bar(ax=ax, color='red')

# x축에 사용할 라벨
ax_2.set_xticks(iris_df.index[0::10])
ax_2.set_xticklabels(iris_df.index[0::10], rotation=45)

# y축에 사용할 라벨을 지정합니다.
ax_2.set_ylabel("length(cm)")

ax_2.legend()
Out[7]:
<matplotlib.legend.Legend at 0x1e284aa0f28>

plot 메소드들은 Axes라는 좌표축 객체를 반환합니다.

ax.set_xticks() : x축의 간격을 지정합니다.

ax.set_xticklabels() : x축의 간격에 라벨을 지정합니다.

ax.set_ylabel() : y축의 라벨을 지정합니다.

ax.legend() : 범례를 그래프에 넣어줍니다.

긴글 읽어 주셔서 감사합니다.

도움이 되셨으면 공감 버튼 꼭 눌러주세요.


,

세상에나.. 드디어 애드고시 통과했어요 ㅠㅠ


반가워요 잘생긴 아저씨 ㅠㅠ 손가락 ㅠㅠ 팝콘님..


2016년에 이 블로그를 학교에서 공부한걸 정리하자는 목적으로 시작해서 현재까지 수차례 애드센스의 문을 두들겨 보았으나 항상 "컨텐츠 불충분"의 늪에서 벗어날 수 없었는데, 이번에 드디어 구글 애드센스 광고를 달았습니다. 저처럼 너무 광고를 개제하고 싶으나 컨텐츠 불충분의 늪에서 못 빠져나간 분들을 위해서 제가 겪어온 과정들을 소개해드리겠습니다.



1. 진짜 컨텐츠가 불충분한가?


2016년 블로그 초기에는 글은 짧고, 개발자 블로그 특징 상 소스 코드와 그림과 동영상이 주를 이루었습니다. 구글 애드센스 심사는 봇으로 이루어져서 사진과 동영상보다 글이 가지는 비중이 크다는 것 조차 모르고 신청했습니다. 결과는 


당연히 컨텐츠 불충분. 그 후 "게시물 수를 늘리면 되지 않을까?" 위해, 학교 보안 과제인 BOF 원정대의 풀이를 적는 컨텐츠를 진행했고, 18탄까지 클리어하면서 간단하게 풀이를 적은 후, 제출했지만 역시 컨텐츠 불충분 사유로 거절당했어요.



2. 양보다 질, 글자 수에 신경쓰기


그후로 좌절감을 맞보고 여러 사이트를 검색하며, 팁을 동냥했습니다. 모든 블로거들이 공통적으로 1000자 이상의 글을 쓸 것을 추천하였고, 기존의 글들을 수정하기보다 새로운 주제로 글을 작성하기 시작했어요. 기존의 글을 지우는 게 아까웠거든요. 소스 코드를 제외한 나머지 글들이 최소 700자는 넘게 적기위해 부단히 애를 썼습니다. 


그리고 문장의 모양을 이쁘게 보이기 위해 했던 개인적인 강박도 고치고자 애를 썼습니다.

예를 간단하게 들어볼게요.


에디터를 사용할 때 빨간 원처럼 글자 하나씩 남으면 상당히 언짢고 거슬려서 개행을 했었는데요. 구글 봇이 심사할 때, 혹시나 문장의 끝으로 이해를 할까봐. 잠깐의 언짢음을 참고 개행없이 글을 작성했습니다.


그리고 항상 문장의 끝을 ~입니다. ~였어요 등 완전한 문장 형태로 적고, ㅋㅋㅋ나 ㅠㅠㅠ 등 개발하면서 느낀 희노애락을 표현하는 표현을 삼가했습니다. 구글 봇이 갑 오브 갑이였기 때문에 그의 심기를 나쁘게해선 안되거든요.




그렇게 글을 작성한 뒤, 애드센스 신청 결과는?? 역시 컨텐츠 불충분!!



3. 맞춤법 띄어쓰기 검사 / 안 쓰는 카테고리 통합


대학생에서 직장인이 되면서 블로그를 관리하기가 상당히 어려울거 같아서 방치했습니다. 2018년 1월 글을 마지막으로요. 하필 배정받은 프로젝트도 시리즈로 작성하려고 했던 SpringBoot가 아닌 데이터 마이닝 프로젝트에 배정되어서 더욱 글을 쓰기가 힘들어 졌습니다.


그와중에 여자친구가 블로그를 시작했고, 책, 영화, 공연 리뷰와 데이트하면서 다닌 이곳저곳 리뷰를 했습니다. 글자 수를 신경쓰면서 최대한 길게 적어서, 애드센스도 두세차례 시도했습니다.


하지만 마찬가지 컨텐츠 불충분에 막혀 있었죠.



그리고 얼마전 애드센스를 재신청했는데, 된겁니다.. 3년을 블로그를 하면서 안되는걸 6개월 안에 해내다니 ㅠㅠ


비결을 들어 봤더니, 50개가 넘는 모든 포스팅을 맞춤법 검사와 띄어 쓰기 검사를 하여 일일히 수정했다는 겁니다.

저도 그거 듣고 모든 포스팅을 수정하진 않고 가장 최신의 13개의 글을 맞춤법 검사를 해서 글을 수정했습니다. 생각보다 많은 글이 띄어쓰기와 맞춤법이 맞지 않았다는걸 이제서야 확인을 한거죠.


그리고 사용 안하던 카테고리를 없애고 통합했습니다. 욕심이 많아서 이것저것 써보겠다고 벌려놓은 카테고리를 정리했습니다. 그리고 신청한 애드센스 결과는 앞에서 말했듯이 통과였습니다.


구글 봇이 컨텐츠의 주제를 스캔하기 때문에 맞춤법과 띄어쓰기는 필수적인 요소로 작용하는 듯 합니다. 이것저것 시도했지만 애드센스 획득이 힘들었다면, 하나의 주제에 대해 글을 길게 적고, 맞춤법과 띄어쓰기를 준수한 글을 적는다는 원칙하에 컨텐츠를 제작해보세요. 기존 블로그를 운영하는 분이라면 굳이 이전의 글을 지우지 않아도 최신의 글이 변별력이 있으면 기회를 주는 거 같아요. 


구글봇 마음을 알 수 없어서 미신처럼 떠도는 팁들 중에 하나지만, 한 번 시도해보시고, 다들 애드센스 광고 개제하기를 진심으로 바랍니다.





긴 글 읽어주셔서 감사합니다.

도움이 되셨으면 공감 버튼 눌러주세요. 공감은 가장 큰 힘이 됩니다.




,

전체 소스 코드 : https://github.com/ssooni/ssoonidev_blog

Spring Boot : ver 1.5.9

JAVA : ver 1.8 

Build Tool : MAVEN



1. 목표

MongoDB의 데이터를 접근하는 댓글 REST API를 만들어 보는 것이 목표입니다. 추가적으로 Redis Cache Layer를 두어 보는 것도 함께 실습해볼 예정입니다.


2. RestController

Spring 4.0부터 지원하는 @RestController Annotation은 @Controller와 @ResponseBody가 하나로 결합된 Annotation입니다. 따라서 이 클래스 내부의 모든 메서드들의 반환 값은 HTTP Response Body에 직접 쓰이게 됩니다. 따라서 View가 없는 Controller입니다.


대부분 Web 프로그램을 처음으로 개발한다 하면, JSP를 이용해서 화면을 만들다 보니, Web Service는 반드시 View가 있어야 한다고 생각하기 쉽습니다. 하지만 Web Service마다 목적이 있습니다. 사용자에게 View를 제공하는 경우도 있지만, 프로그램끼리 데이터를 주고받기 위해 개발하는 경우도 있습니다. RestController는 후자의 경우에 사용하는 Controller라고 할 수 있습니다. 


이 전 포스팅에서 구현한 ReplyService를 이용해서 RestController를 구현해 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class ReplyController {
 
    private Logger logger = LoggerFactory.getLogger(ReplyController.class);
    
    @Autowired
    private ReplyService replyService;
    
    @GetMapping("/reply/{bno}")
    public List<ReplyDomain> findByBno(@PathVariable("bno"int bno) throws Exception{
        return replyService.findbyBno(bno);
    }
    // 이하 생략...
}
cs


코드 상에서도 일반 @Controller를 사용했을 때와 차이점을 알 수 있는데, ModelAndView 객체나 String으로 View의 경로를 지정하지 않고 데이터 그 자체를 반환하는 것을 알 수 있습니다. 이후에 실행한다면, 화면에 아래의 그림처럼 JSON 형태의 화면을 보여줄 것입니다. 물론 MongoDB에 데이터가 있는 경우에만 해당됩니다.



3. Swagger

View가 없다 보니 문제가 있습니다. 바로 시연이나 테스트에서 보여주기가 힘들다는 거죠. 데이터를 읽는 것은 그래도 저렇게 화면이라도 나오니까 망정이지만, 데이터를 입력하거나 삭제, 수정하는 경우에는 화면 없이 시연하기 어렵습니다. 그렇다고 이 시연을 위해 View를 새로 만든다는 것은 시간 낭비입니다.  하지만 Swagger를 이용하면 이런 점을 깔끔하게 해결할 수 있습니다. Swagger가 알아서 View를 만들어주기 때문이죠.





Swagger는 프로그램이 시작할 때, URL Mapping을 추적해서 위의 그림과 같은 View를 만들어 줍니다. 그리고 각각의 Tab에는 위의 그림처럼 입력이 가능한 Form과 각각의 타입 등의 정보를 제공해줍니다. 따라서 이 프로그램을 처음 보는 사람도 어떤 URL로 구성되어 있으며, 각각의 URL은 어떤 인자를 받아서 어떻게 반환하는지를 쉽게 확인이 가능합니다. 


pom.xml

1
2
3
4
5
6
7
8
9
10
11
<!-- Swagger2 -->
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.7.0</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.7.0</version>
</dependency>
cs


이제 개발에 적용하는 방법을 알아보겠습니다. 우선 swagger2와 화면을 보여줄 swagger-ui 라이브러리를 추가합니다. 그리고 Swagger를 설정하기 위한 Configuration Class를 작성합니다. 


SwaggerConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.controller"))
                .paths(PathSelectors.ant("/reply/**")) 
                .build();
    }
}
cs


line 8

컨트롤러가 있는 패키지를 지정할 수 있습니다. 만약 어떤 패키지를 특정하지 않고 모든 패키지를 추적하고 싶다면 매개변수에서 RequestHandlerSelectors.any()를 사용하면 됩니다. 


line 9

Mapping URL 중에서 어떤 URL을 사용할 것인지 정해줄 수 있습니다. 저는 /reply 밑의 모든 경로에 대하여 추적하도록 하였습니다. 마찬가지로 모든 경로를 다 추적하게 하고 싶다면 PathSelectors.any()를 매개변수로 사용하면 됩니다.


이제 Spring Boot 프로그램을 실행하여, http://localhost:8080[/root_path]/swagger-ui.html로 이동하면, 앞서 실행한 결과를 쉽게 확인할 수 있습니다. 


전체 소스코드는 GitHub를 확인해주세요.

다음 포스팅에서는 Redis로 Cache Layer를 구성하는 방법을 포스팅하겠습니다.




,

전체 소스 코드 : https://github.com/ssooni/ssoonidev_blog

Spring Boot : ver 1.5.9

JAVA : ver 1.8 

Build Tool : MAVEN



1. 목표

MongoDB의 데이터를 접근하는 댓글 REST API를 만들어 보는 것이 목표입니다. 추가적으로 Redis Cache Layer를 두어 보는 것도 함께 실습해볼 예정입니다.


2. MongoDB

MongoDB를 처음으로 사용한 건 인턴십 때였습니다. NoSQL의 존재는 이미 알고 있었지만, 이렇게 프로젝트에서 사용은 처음으로 해봤습니다. MySQL이나 오라클을 먼저 접한 저는 테이블이 없고, 난생처음 보는 쿼리에 적응하기 조금 시간이 필요했습니다.


MongoDB vs MySQL


Mongo DB

세상의 모든 데이터가 일정한 틀과 형태를 가지고 있지는 않습니다. 대화나 채팅, 음악 등이 대표적이라 할 수 있죠. 이러한 데이터는 MySQL과 같은 테이블에 저장하기 어렵습니다. 테이블을 만들 때, 하나의 공통 거릴 찾아서 하나의 속성(Column)을 만드는 일이 어렵기 때문이죠. 


Mongo DB는 Collection 안에 Document에 데이터를 저장합니다. Document는 특별한 틀을 가지고 있지 않습니다. Document의 Field 안의 데이터 형식이 서로 안 맞아도 입력이 가능하고, 각 Document들이 모두 일관된 Field를 가지지 않아도 됩니다. 이 방법은 비정형 데이터를 저장하는데 최적의 방법이라고 생각합니다. 관심이 가는 분야이고, Spring Boot 포스팅이 다 끝나는 대로 포스팅을 해보고 싶습니다.


3. Mongo + Spring Boot

전체 폴더 구조나 소스코드는 GitHub를 참고해주세요


pom.xml 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- Redis Cache Layer -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 
<!-- Swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>
<!-- Mogno DB -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
cs


spring-boot-starter-data-mongodb

Spring Boot와 Mongo를 연결하기 위해 꼭 필요한 라이브러리입니다. 설정 및 데이터 접근을 하기위한 라이브리입니다. 


spring-boot-starter-data-redis (선택)

다음 포스팅에서 다룰 예정인 Redis Cache를 구현할 때 필요한 라이브러리입니다. 몽고DB만 사용하고 말겠다 생각하는 분이라면 굳이 추가하지 않으셔도 됩니다. 


springfox-swagger2(선택)

Swagger라는 API Document를 자동으로 생성해주는 라이브러리입니다. REST API를 사용하는 경우, 대부분 View가 없어, 테스트를 하거나 시연을 하기 힘든 부분이 있었는데 그것을 해결해준 고마운 라이브러리입니다.


application.properties 작성

1
2
3
4
5
## MongoDB Config ##
spring.data.mongodb.uri=mongodb://localhost:27017
spring.data.mongodb.database=ssooni
spring.data.mongodb.username=ssooni526
spring.data.mongodb.password=ssooni
cs


MongoDB의 uri와 기본적으로 사용할 데이터베이스, 인증을 위한 username과 password 정보를 작성해줍니다. uri에  mongodb://[username]:[password]@[hostname]:[port number]/[database] 처럼 모든 정보를 넣을 수도 있습니다.


MongoConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
public class MongoConfig extends AbstractMongoConfiguration{
 
    @Value("${spring.data.mongodb.username}")
    private String userName;
    
    @Value("${spring.data.mongodb.password}")
    private String password;
    
    @Value("${spring.data.mongodb.database}")
    private String database;
    
    @Override
    protected String getDatabaseName() {
        return database;
    }
 
    @Override
    public Mongo mongo() throws Exception {
        MongoCredential credential = MongoCredential.createCredential(userName, database, password.toCharArray());
        return new MongoClient(new ServerAddress("localhost"27017), Arrays.asList(credential));
    }
    
    public @Bean MongoTemplate mongoTemplate() throws Exception{ 
        return new MongoTemplate(mongo(), database);
    }
    
}
cs


MongoDB와 연결하여 사용하기 위해 설정을 하는 클래스입니다. @Controller나 @Service Annotation처럼 @Configuration Annotation을 사용해, 이 클래스가 무엇인가를 설정하기 위한 용도로 작성된 코드임을 컨테이너에게 알려줍니다. MongoConfig 클래스는 AbstractMongoConfiguration 클래스를 상속하여 구현합니다. 

이때 반드시 getDatabaseName()(line 14)과 mongo()(line 19) 함수를 오버라이드 해야 합니다.


line 4 ~ line 11

application.properties에 정의한 값은 @Value Annotation으로 접근 가능합니다. 


line 19 

mongo() 함수는 Mongo DB에 접근할 객체를 반환하는 함수입니다. 이 때 Mongo에 접근하기 위해 인증정보가 필요한 경우에 MongoCredential 객체에 인증 정보를 담은 후 MongoClient를 생성해줍니다. 


line 24

API 개발 프로젝트에서 @Query Annotation을 사용해서 동적 쿼리를 작성한 적이 있는데,  MySQL 문과 많이 다른 데다가, SpEL은 또 처음이라 고생 많았던 기억이 있습니다. 저처럼 Mongo Query보다 SQL 계열이 익숙하다면 이 mongoTemplete를 이용하여, 최대한 비슷하게 쿼리를 작성할 수 있습니다. 진작에 알았더라면 @Query Annotation 안 썼습니다.(삽질이 소중한 이유이기도 하죠.) DAO 클래스에서 사용하기 위해서 @Bean로 등록하여 DAO 클래스에서 @Autowired로 주입하여 사용이 가능도록 합시다.


ReplyDomain.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
@Document(collection="reply")
public class ReplyDomain implements Serializable{
    
    private static final long serialVersionUID = 1L;
 
    @Id
    private String id;
    private int bno;
    private int rno;
    private String contents;
    private String userName;
}
 
cs

@Document Annotation은 Collection 내의 하나의 Document에 대응합니다. Collection 명도 넣어줘서 나중에 어떤 Collection에 접근하여 데이터를 가져오는지를 명시해 줍니다. 


@Data Annotation은 Project Lombok 라이브러리를 Maven에 등록하고 jar 파일로 STS 또는 이클립스에 적용해야 제대로 적용되는 Annotation입니다. getter와 setter, toString() 등을 자동으로 만들어주는 정말 편리한 기능입니다. 개인적으로 getter / setter 자동 완성보다 toString() 자동 완성 기능이 편리했습니다. 로그를 찍을 때, 참 편리하게 데이터를 확인할 수 있기 때문이죠. Lombok을 적용하는 방법도 포스팅으로 소개하겠습니다.


ReplyRepo.java

1
2
3
4
5
public interface ReplyRepo extends MongoRepository<ReplyDomain, Long>{
    public List<ReplyDomain> findByBno(int bno);
    public List<ReplyDomain> findAll();
}
 
cs


두 가지 방법으로 Mongo DB에 데이터를 가져오는 방법을 소개하고자 합니다. 


첫 번째는 위의 ReplyRepo.java 코드처럼 MongoRepository<DataType, SerializeType> 클래스를 상속하여 사용하는 방법입니다. findByBno처럼 사전에 정의된 Action(Find 등등)과 조건(ByBno, All 등등)을 이용하여 함수 이름을 만들어주면 알아서 해당하는 쿼리를 수행합니다. 이 방법은 조건이 매우 단순한 경우에 쉽게 데이터베이스에 접근하는 방법을 제공합니다.  


이 방법으로 정의하기 힘든 복잡한 쿼리를 사용해야 하는 경우에는 @Query Annotation을 사용하여 구현합니다. MongoDB 쿼리에 익숙하다면 이 방법으로 구현하는 것도 좋습니다.


ReplyServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Autowired
private ReplyRepo replyRepo;
    
@Autowired
private MongoTemplate mongoTemplete;
// --- 첫 번째(Repo) 방법 사용시 ----- //
@Override
public List<ReplyDomain> findbyBno(int bno) {
    logger.info("Go Mongo");
    return replyRepo.findByBno(bno);
}
 
// --- 두 번째(Templete) 방법 사용 ----- // 
@Override
public List<ReplyDomain> insert(ReplyDomain reply) {
    int bno = reply.getBno();    
    mongoTemplete.insert(reply);
    return replyRepo.findByBno(bno);
}
 
@Override
public List<ReplyDomain> update(ReplyDomain reply) {
    int bno = reply.getBno();
        
    ObjectId id = new ObjectId(reply.getId());
    Query query = new Query();
    query.addCriteria(Criteria.where("_id").is(id));
        
    Update update = new Update();
    update.set("userName", reply.getUserName());
    update.set("contents", reply.getContents());
    
    WriteResult writeResult = mongoTemplete.updateFirst(query, update, ReplyDomain.class);
    logger.info(writeResult.toString());
            
    return replyRepo.findByBno(bno);
}
cs


두 번째 방법은 앞서 설명한 MongoTemplete를 이용하는 방법입니다. 개인적으로는 두 번째 방법을 선호합니다. Query 클래스와 Criteria 클래스로 조건을 만들고 Update 객체로 수정 할 내용을 넣습니다. 그리고 MongoTemplete 객체의 메서드를 이용하여 실행합니다. 코드를 보다 보면 눈에 익숙한 where 절도 보이고 update에선 set도 보여서, SQL처럼 사용하기가 쉽습니다. 그리고 동적 쿼리가 필요한 경우에는 Java 소스코드 내에서 if문을 이용하면 구현이 쉽습니다간단한 거는 첫 번째로, 복잡한 것은 두 번째로 작성하는 것을 추천합니다. 




다음 포스팅에서는 Swagger를 이용하여 자동으로 API Document를 작성하는 방법을 포스팅하겠습니다.



,

전체 소스 코드 : https://github.com/ssooni/ssoonidev_blog

Spring Boot : ver 1.5.9

JAVA : ver 1.8 

Build Tool : MAVEN


1. 목표

사용자가 View를 통해 데이터를 조회하여, 새로운 데이터를 입력하거나 기존 데이터를 삭제, 수정이 가능한 웹페이지를 작성하는 것이 이번 포스팅의 목표입니다. 


2. controller

Controller는 사용자가 요청하는 URL에 따라, 응답을 해주는 역할을 수행합니다. 그리고 GET, POST, PUT, DELETE 등의 Method로 사용자가 어떤 종류의 요청을 하는지를 정의해  줄 수 있습니다. 따라서 "/board" 같은 주소를 사용하더라도 GET 방식이냐 POST 방식인가에 따라서 사용자에서 다른 응답을 보여줄 수 있습니다. 아래는 이번 게시판 만들기 포스팅에서 사용할 URL의 설계 방안입니다.


/board GET : 홈 화면을 보여준다.

/board POST : 새로운 게시물을 데이터베이스에 저장한다.

/board PUT  : 기존의 게시물을 수정한다.

/board/{bno} DELETE  : {bno}번 게시물을 삭제한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Controller
public class BoardController {
 
    private static Logger logger = LoggerFactory.getLogger(BoardController.class);
    
    @Autowired
    private BoardService boardService;
 
    @GetMapping("/board")
    public ModelAndView list() throws Exception{
        List<BoardDomain> boardList = boardService.findAll();
        ModelAndView nextPage = new ModelAndView("board/home");
        nextPage.addObject("boardList", boardList);
        return nextPage;
    }
    
    @PostMapping("/board")
    public void create(BoardDomain board) throws Exception{
        logger.info("POST /board : " + board.toString());
        boardService.insert(board);
    }
    
    @PutMapping("/board")
    public void modify(BoardDomain board) throws Exception{
        logger.info("PUT data : " + board.toString());
        boardService.update(board);
    }
    
    @DeleteMapping("/board/{bno}")
    public void delete(@PathVariable("bno"int bno) throws Exception{
        logger.info("DELETE bno : " + bno);
        boardService.delete(bno);
    }    
}
cs


URL 설계를 기반으로 작성한 Controller입니다. @Controller Annotation이 반드시 있어야 Spring Boot에서 웹 요청을 받아들이는 Controller로 이 클래스를 사용하고 beans로 등록합니다. 


URL Mapping

URL을 Mapping 할 때 @RequestMapping(value="/board", method=RequestMethod.GET)처럼 예전에는 조금 길게 작성하는 Annotation 코드를 @GetMapping처럼 짧게 작성할 수 있습니다. @PostMapping@PutMapping, @DeleteMapping도 마찬가지 역할을 수행합니다.


ModelAndView

10번째 줄 list() 함수는 ModelAndView 객체를 반환하는 함수입니다. ModelAndView 객체는 어떤 페이지로 이동하는 페이지 정보와 데이터를 실을 수 있는 객체입니다. 따라서 list() 함수가 수행한 뒤엔 board/list.jsp로 이동하고 이때 boardList라는 객체도 함께 실어서 이동합니다. 이 boardList는 당연히 list.jsp에서 접근 가능합니다.


@PathVariable

URL에 작성된 값을 참조하는 Annotation입니다. tistory 블로그의 URL을 보면 https://ssoonidev.tistory.com/{글번호}로 구성되어 있는데, 이 {글 번호}에 접근하기 위해 사용하는 Annotation이라고 생각하시면 됩니다.  


의존성 주입

데이터베이스에 접근하여 데이터를 처리하는 Service를 @Autowired Annotation을 이용하여 주입합니다. 흔히 Spring Framework에서 의존성 주입(DI)이라는 용어를 사용합니다. 이 개념은 포스팅 하나로 소개해도 무방할 정도로 강력한 개념이므로, 차후 포스팅으로 소개하도록 하고 지금은 간단히 "컨테이너가 미리 만들어진 객체를 가져다가 쓰겠다."라고 생각하면 좋겠습니다.

 

추가적으로 BoardMapper와 BoardService의 코드를 추가/변경하였습니다. 

GitHub의 코드를 꼭 참고하시길 바랍니다.


3. View를 만들자.

지난 포스팅에서 마지막으로 간단하게 뷰를 하나 만들어서 테스트를 수행하였습니다. 테스트 화면에서도 간단하게 Bootstrap을 적용하였는데요. 이번에는 Modal Class를 이용해서 새 글 작성과 수정, 삭제 기능을 하는 뷰를 작성해 보겠습니다. 


전체적인 화면구성

우선 전체적인 화면 구성을 봅시다. home.jsp 파일에 header.jsp 파일을 include 한 형태로 만들고자 합니다. header.jsp는 차후에 Bootstrap의 nav 클래스와 dropdown 클래스 등을 사용하여 검색 기능과 메뉴 이동 기능을 추가할 예정입니다. 시간이 여유롭다면 말이죠.



home.jsp는 게시물을 테이블 형태로 보여주는 화면입니다. 수정과 새 글쓰기 버튼을 누르면 위의 그림과 같은 Modal이 화면에 보이게 구현했습니다. 수정 버튼을 누르면 작성자 명과 작성한 콘텐츠가 텍스트 박스에 위의 오른쪽 그림같이 놓이게 됩니다. 


주요 코드만 간단하게 설명하겠습니다. 

전체 코드는 GitHub를 참고하시길 바랍니다.


home.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<body>
    <div class="container">
        <jsp:include page="../include/header.jsp" />
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th class="col-md-1">bno</th>
                    <th class="col-md-7">contents</th>
                    <th class="col-md-2">userName</th>
                    <th class="col-md-2">수정 / 삭제</th>
                </tr>
            </thead>
            <tbody>
                <c:forEach var="board" items="${boardList}">
                    <tr id="tr${board.bno}">
                        <td>${board.bno}</td>
                        <td><a href="#">${board.contents}</a></td>
                        <td>${board.userName}</td>
                        <td>
                            <div class="btn-group">
                                <button name="modify" value="${board.bno}"
                                    class="btn btn-xs btn-warning">수정</button>
                                <button name="delete" value="${board.bno}"
                                    class="btn btn-xs btn-danger">삭제</button>
                            </div>
                        </td>
                    </tr>
                </c:forEach>
            </tbody>
        </table>
        <jsp:include page="../include/modal.jsp" />
        <button id="createBtn" type="button" class="btn btn-info btn-sm"
            data-toggle="modal">새 글 쓰기</button>
    </div>
</body>
cs


Home화면 jsp 코드입니다. 주소창에 http://localhost:8080/board라고 입력했을 때 보여주는 화면입니다. 


3번째 줄  : header.jsp를 home.jsp에 포함합니다. (경로에 주의바랍니다.)


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


14번째 줄 ~ 28번째 줄 (c:foreach Line)

BoardController에서 list() 함수에서 전체 게시물 리스트를 담고 있는 "boardList"라는 List 객체를 전달해주는데,  이 객체를 ${boardList}로 받아 JSP에서 사용 가능합니다. <c:foreach>는 위의 JSP Core Tag Library를 추가하면 사용할 수 있고 for 문과 동일한 역할을 합니다. 즉, boardList 안에 있는 원소들을 board에 하나씩 할당하는 반복문을 수행합니다.  그리고 이 데이터를 테이블에 보여 주기 위해 <tr> <td>를 이용하여 행을 채워

나갑니다.


31번째 줄  : modal.jsp를 home.jsp에 포함합니다.


modal.jsp 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- Modal -->
<div class="modal fade" id="myModal" role="dialog">
    <div class="modal-dialog">
 
        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 id="modal-title" class="modal-title"></h4>
            </div>
            <div class="modal-body">
                <table class="table">
                    <tr>
                        <td>사용자명</td>
                        <td><input class="form-control" id="userName" type="text"></td>
                    </tr>
                    <tr>
                        <td>내용</td>
                        <td><textarea class="form-control" id="contents" rows="10"></textarea></td>
                    </tr>                    
                </table>
            </div>
            <div class="modal-footer">
                <button id="modalSubmit" type="button" class="btn btn-success">Submit</button>
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</div>
cs


Bootstrap Modal 입니다. 하나의 Modal을 이용해서 작성용 Modal과 수정용 Modal을 구현해서 Modal Title (9번째 줄)에 들어가는 내용이 없습니다. 여기에 들어갈 내용은 모두 modal.js에서 해결합니다.


modal.js 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
var action = '';
var url = '';
var type = '';
var bno = 0;
 
$(document).ready(function(){
 
    // 새 글 쓰기 버튼 클릭
    $("#createBtn").click(function(){
        action='create';
        type = 'POST'
        $("#modal-title").text("새 글 작성");
        $("#myModal").modal();
    });
    
    // 수정하기 버튼 클릭
    $("button[name='modify']").click(function(){
        action='modify';
        type = 'PUT';
        bno = this.value;
 
        // content 담기
        var row = $(this).parent().parent().parent();
        var tr = row.children();
        
        var userName = tr.eq(2).text();
        var contents = tr.eq(1).text();
 
        $("#modal-title").text("수정하기");
 
        $("#userName").val(userName);
        $("#contents").val(contents);
        
        $("#myModal").modal();
    });
    
    // 삭제하기 버튼 클릭
    $("button[name='delete']").click(function(){
        bno = this.value;
        $.ajax({
            url : '/board/' + bno,
            type : 'DELETE',
        });
        location.reload();
    })
    
    // Modal의 Submit 버튼 클릭
    $("#modalSubmit").click(function(){
        
        if(action == 'create'){
            bno = 0;
            url = '/board';
        }else if(action == 'modify'){
            url = '/board';
        }
 
        var data = {
            "bno" : bno,
            "userName" : $("#userName").val(),
            "contents" : $("#contents").val()
        };
        
        $.ajax({
            url : url,
            type : type,
            data : data,
success: function(data){ $("#myModal").modal('toggle'); }
complete: function(data){ location.reload(); }
        })
    });
    
 
});
 

 cs


각각의 버튼의 클릭 이벤트를 처리하는 함수들로 구성되어 있습니다. Modal을 보여주는 부분은 $("#myModal").modal() 부분입니다. jQuery에서 # selector는 element의 ID를 찾아 추적합니다. 앞선 modal.jsp에서 modal의 id를 myModal 임을 확인 할 수 있습니다.


또한 눈에 띄는 것은 Ajax 구문입니다.  데이터가 입력 / 수정하면 기존 방식에서는 화면 전체가 reload 됩니다. 변화하는 부분은 테이블 하나인데 전체 페이지가 변경되는 쓸모없는 자원이 낭비합니다. 이 낭비를 줄이기 위해 나온 것이 Ajax입니다 Ajax는 서버에 요청을 자바스크립트에서 하기 때문에 화면 전체가 아닌 일부분만 갱신이 가능합니다.


물론 지금 구현한 함수는 완벽한 Ajax 구문이 아닙니다. 왜냐하면 화면을 전체 Reload 하기 때문에 굳이 Ajax가 아니더라도 해결할 수 있는 부분입니다. 맛보기로 이런 게 있구나 보여주는 것이죠. 이것이 완벽한 Ajax를 구현한 것은 아니라는 점 생각해주시길 바랍니다.


-- 내용 추가 18.08.16 --

borker님의 실행 결과 리플래시가 안되는 문제가 있다해서 확인하였고, MySQL의 insert 요청을 보낸 동시에

화면이 reload해서 새로고침이 안됩니다. success로 Modal 을 닫고, complete로 reload하는 함수를 추가하여 

반영하였습니다. 피드백 해주셔서 감사합니다.


이제 어느 정도 게시판 다운 모습을 하기 시작합니다. 

추가적인 기능을 넣어보는 것은 조금씩 조금씩 애정을 갖고 만들어 나가보겠습니다.

혹시 먼저 만들어 보았으면 하는 기능이 있다면 댓글로 남겨주세요


다음 포스팅은 Mongo DB를 이용해서 REST API를 만드는 것을 해보도록 하겠습니다.

전체 소스 코드는 GitHub를 참조해주시길 바랍니다.



,

1. Chocolaty Package Manager

Mac Book의 brew, RedHat 계열 리눅스의 yum, Python의 pip, Debian 계열 리눅스의 apt-get, NodeJS의 npm 등 Package Manager는 개발자들이 Commend Line 상으로 쉽게 Package를 설치하고 Upgrade 할 수 있는 편리한 Tool이다. 


Windows에서 일반적으로 MySQL을 설치하는 순서

1. MySQL 홈페이지로 간다. (구글 - MySQL 설치 검색 - 홈페이지 접속) 

2. MySQL 인스톨러를 다운로드 받은 뒤 실행한다.

3. 모니터를 가만히 쳐다본다.


RedHat 계열 리눅스에서 MySQL을 설치한다면

1. sudo yum install -y mysql

2. 끝



Command Line이 익숙하지 않는다면 직접 홈페이지를 찾아가서 설치하는 것도 좋지만, 리눅스나 Mac OS를 사용하다가 Windows에 환경설정을 하고자 하면, 귀찮다. 매우 귀찮다. 마침 새로운 개발 노트북에 개발 환경을 설정하는 과정에서 혹시 Windows에도 Package Manager가 있을까 했는데, Chocolaty라는 Package Manager가 있었다.


설치 과정  

https://chocolatey.org/install 로 접속하게 되면 PowerShell 또는 cmd로 설치가 가능한 명령어를 제공한다.



Windows PowerShell을 이용해서 밑의 코드를 복사-붙여넣기 후 실행을 하면 혼자서 열심히 동작을 수행한다. 완료가 된 후에 choco라고 명령어를 타이핑했을 때, 아래 그림처럼 버전 정보를 제공하면, 성공적으로 설치가 완료된 것이다. 




패키지 검색 / 설치 / 갱신 / 삭제  

설치가 완료되었으면 한 번 실행을 해보자. Sublime Text를 다운로드하기 위해 우선 Sublime Text가 Choco에 등록되어 있는지 확인하는 절차를 먼저 가진다.  cmd나 PowerShell 실행할 때 관리자 권한으로 실행을 해야 패키지를 정상적으로 설치 할 수 있다. 

choco search sublime



와.. 많기도 하다.  우리는 그 중에서 SublimeText3를 설치해 보자. 

choco install -y sublimetext3


설치가 끝났다. 설치 경로는 C:\Program Files\Sublime Text 3에 설치되어있다. 간혹 C:\tools나 C:\ProgramData 경로에 설치되는 패키지도 있다. Sublime Text 3을 저는 잘 사용하지 않기 때문에, 다시 삭제하도록 하겠다.


choco uninstall sublimetext3 


update나 upgrade를 이용하면 버전을 최신 버전으로도 관리가 가능하다. Windows에서도 이런 툴을 사용할 수 있어서, 나중에 포맷이나 새로운 컴퓨터를 또 얻는다면, Command만 파일에 잘 저장하여, 실행하면 간단하게 개발 환경을 구성할 수 있을 것으로 기대한다.


 



,