Ecoer Logo
VOTING POWER100.00%
DOWNVOTE POWER100.00%
RESOURCE CREDITS100.00%
REPUTATION PROGRESS15.23%
Net Worth
1.015USD
STEEM
0.000STEEM
SBD
1.932SBD
Effective Power
5.008SP
├── Own SP
1.502SP
└── Incoming Deleg
+3.505SP

Detailed Balance

STEEM
balance
0.000STEEM
market_balance
0.000STEEM
savings_balance
0.000STEEM
reward_steem_balance
0.000STEEM
STEEM POWER
Own SP
1.502SP
Delegated Out
0.000SP
Delegation In
3.505SP
Effective Power
5.008SP
Reward SP (pending)
0.000SP
SBD
sbd_balance
1.932SBD
sbd_conversions
0.000SBD
sbd_market_balance
0.000SBD
savings_sbd_balance
0.000SBD
reward_sbd_balance
0.000SBD
{
  "balance": "0.000 STEEM",
  "savings_balance": "0.000 STEEM",
  "reward_steem_balance": "0.000 STEEM",
  "vesting_shares": "2443.299333 VESTS",
  "delegated_vesting_shares": "0.000000 VESTS",
  "received_vesting_shares": "5700.360473 VESTS",
  "sbd_balance": "1.932 SBD",
  "savings_sbd_balance": "0.000 SBD",
  "reward_sbd_balance": "0.000 SBD",
  "conversions": []
}

Account Info

nameigna84
id767825
rank1,408,970
reputation17343846052
created2018-02-19T08:21:12
recovery_accountsteem
proxyNone
post_count20
comment_count0
lifetime_vote_count0
witnesses_voted_for0
last_post2018-03-09T06:37:39
last_root_post2018-03-09T06:37:39
last_vote_time2018-03-09T09:59:00
proxied_vsf_votes0, 0, 0, 0
can_vote1
voting_power0
delayed_votes0
balance0.000 STEEM
savings_balance0.000 STEEM
sbd_balance1.932 SBD
savings_sbd_balance0.000 SBD
vesting_shares2443.299333 VESTS
delegated_vesting_shares0.000000 VESTS
received_vesting_shares5700.360473 VESTS
reward_vesting_balance0.000000 VESTS
vesting_balance0.000 STEEM
vesting_withdraw_rate0.000000 VESTS
next_vesting_withdrawal1969-12-31T23:59:59
withdrawn0
to_withdraw0
withdraw_routes0
savings_withdraw_requests0
last_account_recovery1970-01-01T00:00:00
reset_accountnull
last_owner_update1970-01-01T00:00:00
last_account_update2018-02-19T08:26:45
minedNo
sbd_seconds602,254,782
sbd_last_interest_payment2018-03-02T04:12:18
savings_sbd_last_interest_payment1970-01-01T00:00:00
{
  "active": {
    "account_auths": [],
    "key_auths": [
      [
        "STM8XW5Je1BKj3VZP8QVjrLQH97YbT9eX1gnUbknvD9ZMy1Dvuhbr",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "balance": "0.000 STEEM",
  "can_vote": true,
  "comment_count": 0,
  "created": "2018-02-19T08:21:12",
  "curation_rewards": 0,
  "delegated_vesting_shares": "0.000000 VESTS",
  "downvote_manabar": {
    "current_mana": 2035914951,
    "last_update_time": 1779067314
  },
  "guest_bloggers": [],
  "id": 767825,
  "json_metadata": "{\"profile\":{\"profile_image\":\"http://dn-s-story.kakao.co.kr/dn/exUqf/hyuEIvJ7Rf/nKYptaTKKkylcaeO8pOzu1/img_s.jpg?width=442&height=448&convert=150x150\",\"cover_image\":\"http://dn-l1-story.kakao.co.kr/dn/bxqhtN/hyuH5QUyJd/CkOxx1k7sIM58UdRbOVUM0/img_l.jpg?width=1920&height=1080\",\"name\":\"야훔\",\"location\":\"Seoul, South Korea.\",\"website\":\"http://blog.thereis.xyz\"}}",
  "last_account_recovery": "1970-01-01T00:00:00",
  "last_account_update": "2018-02-19T08:26:45",
  "last_owner_update": "1970-01-01T00:00:00",
  "last_post": "2018-03-09T06:37:39",
  "last_root_post": "2018-03-09T06:37:39",
  "last_vote_time": "2018-03-09T09:59:00",
  "lifetime_vote_count": 0,
  "market_history": [],
  "memo_key": "STM7yrL4A3xo3vg7hh9haaiMNwq4J26V5d145c9Ch6kEtVKaBeeXo",
  "mined": false,
  "name": "igna84",
  "next_vesting_withdrawal": "1969-12-31T23:59:59",
  "other_history": [],
  "owner": {
    "account_auths": [],
    "key_auths": [
      [
        "STM7YVYhm6w2bFvyL1JwemGy6rdF2PpZfrBq5y2MT9jy6FYeAqdhQ",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "pending_claimed_accounts": 0,
  "post_bandwidth": 0,
  "post_count": 20,
  "post_history": [],
  "posting": {
    "account_auths": [],
    "key_auths": [
      [
        "STM7iyGSUiJWeSWget67QR5eR6SNLA9YzPakbz6ssMY9XvLYNpDDF",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "posting_json_metadata": "{\"profile\":{\"profile_image\":\"http://dn-s-story.kakao.co.kr/dn/exUqf/hyuEIvJ7Rf/nKYptaTKKkylcaeO8pOzu1/img_s.jpg?width=442&height=448&convert=150x150\",\"cover_image\":\"http://dn-l1-story.kakao.co.kr/dn/bxqhtN/hyuH5QUyJd/CkOxx1k7sIM58UdRbOVUM0/img_l.jpg?width=1920&height=1080\",\"name\":\"야훔\",\"location\":\"Seoul, South Korea.\",\"website\":\"http://blog.thereis.xyz\"}}",
  "posting_rewards": 1387,
  "proxied_vsf_votes": [
    0,
    0,
    0,
    0
  ],
  "proxy": "",
  "received_vesting_shares": "5700.360473 VESTS",
  "recovery_account": "steem",
  "reputation": "17343846052",
  "reset_account": "null",
  "reward_sbd_balance": "0.000 SBD",
  "reward_steem_balance": "0.000 STEEM",
  "reward_vesting_balance": "0.000000 VESTS",
  "reward_vesting_steem": "0.000 STEEM",
  "savings_balance": "0.000 STEEM",
  "savings_sbd_balance": "0.000 SBD",
  "savings_sbd_last_interest_payment": "1970-01-01T00:00:00",
  "savings_sbd_seconds": "0",
  "savings_sbd_seconds_last_update": "1970-01-01T00:00:00",
  "savings_withdraw_requests": 0,
  "sbd_balance": "1.932 SBD",
  "sbd_last_interest_payment": "2018-03-02T04:12:18",
  "sbd_seconds": "602254782",
  "sbd_seconds_last_update": "2018-03-14T07:34:12",
  "tags_usage": [],
  "to_withdraw": 0,
  "transfer_history": [],
  "vesting_balance": "0.000 STEEM",
  "vesting_shares": "2443.299333 VESTS",
  "vesting_withdraw_rate": "0.000000 VESTS",
  "vote_history": [],
  "voting_manabar": {
    "current_mana": "8143659806",
    "last_update_time": 1779067314
  },
  "voting_power": 0,
  "withdraw_routes": 0,
  "withdrawn": 0,
  "witness_votes": [],
  "witnesses_voted_for": 0,
  "rank": 1408970
}

Withdraw Routes

IncomingOutgoing
Empty
Empty
{
  "incoming": [],
  "outgoing": []
}
From Date
To Date
steemdelegated 3.505 SP to @igna84
2026/05/18 01:21:54
delegateeigna84
delegatorsteem
vesting shares5700.360473 VESTS
Transaction InfoBlock #106144775/Trx 917e93b25e084cf550d84d25be4873ae4fd9135d
View Raw JSON Data
{
  "block": 106144775,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "5700.360473 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-05-18T01:21:54",
  "trx_id": "917e93b25e084cf550d84d25be4873ae4fd9135d",
  "trx_in_block": 1,
  "virtual_op": 0
}
steemdelegated 1.837 SP to @igna84
2026/05/12 08:21:09
delegateeigna84
delegatorsteem
vesting shares2988.150068 VESTS
Transaction InfoBlock #105981113/Trx 28f093ddd857067b4c9a397dab69a90a9b08fe7f
View Raw JSON Data
{
  "block": 105981113,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "2988.150068 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-05-12T08:21:09",
  "trx_id": "28f093ddd857067b4c9a397dab69a90a9b08fe7f",
  "trx_in_block": 0,
  "virtual_op": 0
}
steemdelegated 3.513 SP to @igna84
2026/04/26 00:40:57
delegateeigna84
delegatorsteem
vesting shares5712.876229 VESTS
Transaction InfoBlock #105512395/Trx 8a7f4a3c7f388f275b963ae919dafcb738a08642
View Raw JSON Data
{
  "block": 105512395,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "5712.876229 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-04-26T00:40:57",
  "trx_id": "8a7f4a3c7f388f275b963ae919dafcb738a08642",
  "trx_in_block": 0,
  "virtual_op": 0
}
steemdelegated 1.863 SP to @igna84
2026/01/23 10:47:33
delegateeigna84
delegatorsteem
vesting shares3029.696887 VESTS
Transaction InfoBlock #102855396/Trx d1a0af3492c40501d2f06308769cd7bf1e259c11
View Raw JSON Data
{
  "block": 102855396,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "3029.696887 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2026-01-23T10:47:33",
  "trx_id": "d1a0af3492c40501d2f06308769cd7bf1e259c11",
  "trx_in_block": 1,
  "virtual_op": 0
}
steemdelegated 1.964 SP to @igna84
2024/12/17 06:04:45
delegateeigna84
delegatorsteem
vesting shares3193.916084 VESTS
Transaction InfoBlock #91301755/Trx d4f18c948922ba59fe8351bc56435c9f620a3292
View Raw JSON Data
{
  "block": 91301755,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "3193.916084 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2024-12-17T06:04:45",
  "trx_id": "d4f18c948922ba59fe8351bc56435c9f620a3292",
  "trx_in_block": 1,
  "virtual_op": 0
}
steemdelegated 2.068 SP to @igna84
2023/11/13 21:47:00
delegateeigna84
delegatorsteem
vesting shares3363.049616 VESTS
Transaction InfoBlock #79855945/Trx b676228d15db4f9a7ac2b72ff009eafce4d7cf4b
View Raw JSON Data
{
  "block": 79855945,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "3363.049616 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2023-11-13T21:47:00",
  "trx_id": "b676228d15db4f9a7ac2b72ff009eafce4d7cf4b",
  "trx_in_block": 5,
  "virtual_op": 0
}
steemdelegated 3.874 SP to @igna84
2023/09/21 23:08:39
delegateeigna84
delegatorsteem
vesting shares6300.328402 VESTS
Transaction InfoBlock #78349402/Trx 59c7213f01f10cf6b14610eb1f6a6457b58276cb
View Raw JSON Data
{
  "block": 78349402,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "6300.328402 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2023-09-21T23:08:39",
  "trx_id": "59c7213f01f10cf6b14610eb1f6a6457b58276cb",
  "trx_in_block": 1,
  "virtual_op": 0
}
steemdelegated 4.010 SP to @igna84
2022/11/03 12:45:51
delegateeigna84
delegatorsteem
vesting shares6522.009840 VESTS
Transaction InfoBlock #69114528/Trx 03fe525dae82cfa5a2057f818c43ee2fea82de3b
View Raw JSON Data
{
  "block": 69114528,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "6522.009840 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2022-11-03T12:45:51",
  "trx_id": "03fe525dae82cfa5a2057f818c43ee2fea82de3b",
  "trx_in_block": 1,
  "virtual_op": 0
}
steemdelegated 4.146 SP to @igna84
2022/01/17 11:56:24
delegateeigna84
delegatorsteem
vesting shares6742.543071 VESTS
Transaction InfoBlock #60810589/Trx 61bfff14c7ed03100f45af7e9f3d946b2538c023
View Raw JSON Data
{
  "block": 60810589,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "6742.543071 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2022-01-17T11:56:24",
  "trx_id": "61bfff14c7ed03100f45af7e9f3d946b2538c023",
  "trx_in_block": 8,
  "virtual_op": 0
}
steemdelegated 4.259 SP to @igna84
2021/06/14 01:48:57
delegateeigna84
delegatorsteem
vesting shares6926.311729 VESTS
Transaction InfoBlock #54608918/Trx 7dc139c17a0038388889a693278588e1ac5e78bb
View Raw JSON Data
{
  "block": 54608918,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "6926.311729 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2021-06-14T01:48:57",
  "trx_id": "7dc139c17a0038388889a693278588e1ac5e78bb",
  "trx_in_block": 3,
  "virtual_op": 0
}
steemdelegated 4.374 SP to @igna84
2020/12/11 12:06:21
delegateeigna84
delegatorsteem
vesting shares7113.733703 VESTS
Transaction InfoBlock #49356333/Trx 476f5c904677fb6ee3583ab09f586aa1d326b190
View Raw JSON Data
{
  "block": 49356333,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "7113.733703 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2020-12-11T12:06:21",
  "trx_id": "476f5c904677fb6ee3583ab09f586aa1d326b190",
  "trx_in_block": 2,
  "virtual_op": 0
}
steemdelegated 1.176 SP to @igna84
2020/12/06 05:43:18
delegateeigna84
delegatorsteem
vesting shares1912.543513 VESTS
Transaction InfoBlock #49207893/Trx 86a01278527c62595edcf7c5afaa7aaec98f3bdb
View Raw JSON Data
{
  "block": 49207893,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "1912.543513 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2020-12-06T05:43:18",
  "trx_id": "86a01278527c62595edcf7c5afaa7aaec98f3bdb",
  "trx_in_block": 10,
  "virtual_op": 0
}
steemdelegated 4.378 SP to @igna84
2020/12/05 15:44:09
delegateeigna84
delegatorsteem
vesting shares7119.941557 VESTS
Transaction InfoBlock #49191426/Trx 328ef5aa266813c65d2ee76fdb33454b2855c39f
View Raw JSON Data
{
  "block": 49191426,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "7119.941557 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2020-12-05T15:44:09",
  "trx_id": "328ef5aa266813c65d2ee76fdb33454b2855c39f",
  "trx_in_block": 3,
  "virtual_op": 0
}
steemdelegated 1.181 SP to @igna84
2020/11/02 17:42:33
delegateeigna84
delegatorsteem
vesting shares1920.017158 VESTS
Transaction InfoBlock #48260240/Trx d15ad703c95fbb86ef2e571195e56d4cafc29823
View Raw JSON Data
{
  "block": 48260240,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "1920.017158 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2020-11-02T17:42:33",
  "trx_id": "d15ad703c95fbb86ef2e571195e56d4cafc29823",
  "trx_in_block": 5,
  "virtual_op": 0
}
steemdelegated 4.503 SP to @igna84
2020/05/09 06:41:33
delegateeigna84
delegatorsteem
vesting shares7322.746916 VESTS
Transaction InfoBlock #43218154/Trx f4ac577a2a5f686cc50203a54274621fe47fac1d
View Raw JSON Data
{
  "block": 43218154,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "7322.746916 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2020-05-09T06:41:33",
  "trx_id": "f4ac577a2a5f686cc50203a54274621fe47fac1d",
  "trx_in_block": 15,
  "virtual_op": 0
}
steemdelegated 1.201 SP to @igna84
2020/05/08 10:26:33
delegateeigna84
delegatorsteem
vesting shares1953.311140 VESTS
Transaction InfoBlock #43194423/Trx 3280a1d01daf62fee30a31562650810cc6a7346e
View Raw JSON Data
{
  "block": 43194423,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "1953.311140 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2020-05-08T10:26:33",
  "trx_id": "3280a1d01daf62fee30a31562650810cc6a7346e",
  "trx_in_block": 14,
  "virtual_op": 0
}
2020/02/19 08:44:42
authorsteemitboard
bodyCongratulations @igna84! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@igna84/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table> <sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@igna84) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=igna84)_</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/steemitboard/@steemitboard/valentine-s-day-challenge-give-a-badge-to-your-beloved"><img src="https://steemitimages.com/64x128/http://i.cubeupload.com/LvDzr5.png"></a></td><td><a href="https://steemit.com/steemitboard/@steemitboard/valentine-s-day-challenge-give-a-badge-to-your-beloved">Valentine's day challenge - Give a badge to your beloved!</a></td></tr></table> ###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes!
json metadata{"image":["https://steemitboard.com/img/notify.png"]}
parent authorigna84
parent permlinkcvnbi-usb
permlinksteemitboard-notify-igna84-20200219t084441000z
title
Transaction InfoBlock #40951169/Trx b1802540277173a81fe1da069b291cd9beb93f69
View Raw JSON Data
{
  "block": 40951169,
  "op": [
    "comment",
    {
      "author": "steemitboard",
      "body": "Congratulations @igna84! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@igna84/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table>\n\n<sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@igna84) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=igna84)_</sub>\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/steemitboard/@steemitboard/valentine-s-day-challenge-give-a-badge-to-your-beloved\"><img src=\"https://steemitimages.com/64x128/http://i.cubeupload.com/LvDzr5.png\"></a></td><td><a href=\"https://steemit.com/steemitboard/@steemitboard/valentine-s-day-challenge-give-a-badge-to-your-beloved\">Valentine's day challenge - Give a badge to your beloved!</a></td></tr></table>\n\n###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes!",
      "json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
      "parent_author": "igna84",
      "parent_permlink": "cvnbi-usb",
      "permlink": "steemitboard-notify-igna84-20200219t084441000z",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2020-02-19T08:44:42",
  "trx_id": "b1802540277173a81fe1da069b291cd9beb93f69",
  "trx_in_block": 4,
  "virtual_op": 0
}
steemdelegated 4.622 SP to @igna84
2019/06/06 02:57:12
delegateeigna84
delegatorsteem
vesting shares7517.067799 VESTS
Transaction InfoBlock #33550562/Trx 4c6959e94e0e6704060679d5fd9a18cc38295234
View Raw JSON Data
{
  "block": 33550562,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "7517.067799 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2019-06-06T02:57:12",
  "trx_id": "4c6959e94e0e6704060679d5fd9a18cc38295234",
  "trx_in_block": 5,
  "virtual_op": 0
}
2019/02/19 08:50:03
authorsteemitboard
bodyCongratulations @igna84! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@igna84/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table> <sub>_[Click here to view your Board](https://steemitboard.com/@igna84)_</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/valentine/@steemitboard/valentine-challenge-love-is-in-the-air"><img src="https://steemitimages.com/64x128/http://i.cubeupload.com/LvDzr5.png"></a></td><td><a href="https://steemit.com/valentine/@steemitboard/valentine-challenge-love-is-in-the-air">Valentine challenge - Love is in the air!</a></td></tr></table> > Support [SteemitBoard's project](https://steemit.com/@steemitboard)! **[Vote for its witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1)** and **get one more award**!
json metadata{"image":["https://steemitboard.com/img/notify.png"]}
parent authorigna84
parent permlinkcvnbi-usb
permlinksteemitboard-notify-igna84-20190219t085002000z
title
Transaction InfoBlock #30479662/Trx 650d02e1b702cebe1777a21fe051e5eea52d26b2
View Raw JSON Data
{
  "block": 30479662,
  "op": [
    "comment",
    {
      "author": "steemitboard",
      "body": "Congratulations @igna84! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@igna84/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table>\n\n<sub>_[Click here to view your Board](https://steemitboard.com/@igna84)_</sub>\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/valentine/@steemitboard/valentine-challenge-love-is-in-the-air\"><img src=\"https://steemitimages.com/64x128/http://i.cubeupload.com/LvDzr5.png\"></a></td><td><a href=\"https://steemit.com/valentine/@steemitboard/valentine-challenge-love-is-in-the-air\">Valentine challenge - Love is in the air!</a></td></tr></table>\n\n> Support [SteemitBoard's project](https://steemit.com/@steemitboard)! **[Vote for its witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1)** and **get one more award**!",
      "json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
      "parent_author": "igna84",
      "parent_permlink": "cvnbi-usb",
      "permlink": "steemitboard-notify-igna84-20190219t085002000z",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2019-02-19T08:50:03",
  "trx_id": "650d02e1b702cebe1777a21fe051e5eea52d26b2",
  "trx_in_block": 6,
  "virtual_op": 0
}
steemdelegated 4.745 SP to @igna84
2018/06/13 09:56:45
delegateeigna84
delegatorsteem
vesting shares7716.276271 VESTS
Transaction InfoBlock #23282710/Trx e1481a307024b62d01973429b194ebb269fde7e8
View Raw JSON Data
{
  "block": 23282710,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "7716.276271 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-06-13T09:56:45",
  "trx_id": "e1481a307024b62d01973429b194ebb269fde7e8",
  "trx_in_block": 8,
  "virtual_op": 0
}
steemdelegated 17.329 SP to @igna84
2018/03/14 09:02:42
delegateeigna84
delegatorsteem
vesting shares28180.756395 VESTS
Transaction InfoBlock #20663871/Trx 687629de6bf198211bfda7e5011d69c477e0d22a
View Raw JSON Data
{
  "block": 20663871,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "28180.756395 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-14T09:02:42",
  "trx_id": "687629de6bf198211bfda7e5011d69c477e0d22a",
  "trx_in_block": 37,
  "virtual_op": 0
}
igna84claimed reward balance: 0.954 SBD, 0.491 SP
2018/03/14 07:34:12
accountigna84
reward sbd0.954 SBD
reward steem0.000 STEEM
reward vests798.326890 VESTS
Transaction InfoBlock #20662101/Trx a7bbec52304b92f6547cdda5f24e2cabe9a23d26
View Raw JSON Data
{
  "block": 20662101,
  "op": [
    "claim_reward_balance",
    {
      "account": "igna84",
      "reward_sbd": "0.954 SBD",
      "reward_steem": "0.000 STEEM",
      "reward_vests": "798.326890 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-14T07:34:12",
  "trx_id": "a7bbec52304b92f6547cdda5f24e2cabe9a23d26",
  "trx_in_block": 7,
  "virtual_op": 0
}
igna84received 0.290 SBD, 0.152 SP author reward for @igna84 / table-colspan-rowspan
2018/03/14 01:32:39
authorigna84
permlinktable-colspan-rowspan
sbd payout0.290 SBD
steem payout0.000 STEEM
vesting payout247.039408 VESTS
Transaction InfoBlock #20654872/Virtual Operation #44
View Raw JSON Data
{
  "block": 20654872,
  "op": [
    "author_reward",
    {
      "author": "igna84",
      "permlink": "table-colspan-rowspan",
      "sbd_payout": "0.290 SBD",
      "steem_payout": "0.000 STEEM",
      "vesting_payout": "247.039408 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-14T01:32:39",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": 44
}
igna84received 0.291 SBD, 0.148 SP author reward for @igna84 / spring-security-preauthorize-postauthorize
2018/03/13 00:54:45
authorigna84
permlinkspring-security-preauthorize-postauthorize
sbd payout0.291 SBD
steem payout0.000 STEEM
vesting payout240.927478 VESTS
Transaction InfoBlock #20625328/Virtual Operation #18
View Raw JSON Data
{
  "block": 20625328,
  "op": [
    "author_reward",
    {
      "author": "igna84",
      "permlink": "spring-security-preauthorize-postauthorize",
      "sbd_payout": "0.291 SBD",
      "steem_payout": "0.000 STEEM",
      "vesting_payout": "240.927478 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-13T00:54:45",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": 18
}
igna84received 0.373 SBD, 0.191 SP author reward for @igna84 / spring-boot-jpa-hibernate-2
2018/03/12 06:11:48
authorigna84
permlinkspring-boot-jpa-hibernate-2
sbd payout0.373 SBD
steem payout0.000 STEEM
vesting payout310.360004 VESTS
Transaction InfoBlock #20602887/Virtual Operation #26
View Raw JSON Data
{
  "block": 20602887,
  "op": [
    "author_reward",
    {
      "author": "igna84",
      "permlink": "spring-boot-jpa-hibernate-2",
      "sbd_payout": "0.373 SBD",
      "steem_payout": "0.000 STEEM",
      "vesting_payout": "310.360004 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-12T06:11:48",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": 26
}
codingmanupvoted (100.00%) @igna84 / cvnbi-usb
2018/03/09 09:59:09
authorigna84
permlinkcvnbi-usb
votercodingman
weight10000 (100.00%)
Transaction InfoBlock #20521200/Trx 55169b2657c1648e092f055e3b0f2050f09b6502
View Raw JSON Data
{
  "block": 20521200,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "cvnbi-usb",
      "voter": "codingman",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T09:59:09",
  "trx_id": "55169b2657c1648e092f055e3b0f2050f09b6502",
  "trx_in_block": 7,
  "virtual_op": 0
}
igna84upvoted (100.00%) @mmcartoon-kr / 4jdhw5
2018/03/09 09:59:00
authormmcartoon-kr
permlink4jdhw5
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20521197/Trx d5ea2eaf1b773e4dedc788b6789750445ab14abd
View Raw JSON Data
{
  "block": 20521197,
  "op": [
    "vote",
    {
      "author": "mmcartoon-kr",
      "permlink": "4jdhw5",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T09:59:00",
  "trx_id": "d5ea2eaf1b773e4dedc788b6789750445ab14abd",
  "trx_in_block": 28,
  "virtual_op": 0
}
2018/03/09 09:58:21
idfollow
json["follow",{"follower":"igna84","following":"mmcartoon-kr","what":["blog"]}]
required auths[]
required posting auths["igna84"]
Transaction InfoBlock #20521184/Trx ae659108125b585b7d35e99c536804e3156cba3c
View Raw JSON Data
{
  "block": 20521184,
  "op": [
    "custom_json",
    {
      "id": "follow",
      "json": "[\"follow\",{\"follower\":\"igna84\",\"following\":\"mmcartoon-kr\",\"what\":[\"blog\"]}]",
      "required_auths": [],
      "required_posting_auths": [
        "igna84"
      ]
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T09:58:21",
  "trx_id": "ae659108125b585b7d35e99c536804e3156cba3c",
  "trx_in_block": 20,
  "virtual_op": 0
}
igna84upvoted (100.00%) @mmcartoon-kr / 20180307
2018/03/09 09:56:30
authormmcartoon-kr
permlink20180307
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20521147/Trx 59677a477cb8997b477b91921ce2135cdf57fb2c
View Raw JSON Data
{
  "block": 20521147,
  "op": [
    "vote",
    {
      "author": "mmcartoon-kr",
      "permlink": "20180307",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T09:56:30",
  "trx_id": "59677a477cb8997b477b91921ce2135cdf57fb2c",
  "trx_in_block": 32,
  "virtual_op": 0
}
igna84upvoted (100.00%) @codingman / 5txnpg-keypad
2018/03/09 06:44:33
authorcodingman
permlink5txnpg-keypad
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20517309/Trx 6ffff6fa816d3d6c6de4521737ddc30f27f69590
View Raw JSON Data
{
  "block": 20517309,
  "op": [
    "vote",
    {
      "author": "codingman",
      "permlink": "5txnpg-keypad",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T06:44:33",
  "trx_id": "6ffff6fa816d3d6c6de4521737ddc30f27f69590",
  "trx_in_block": 3,
  "virtual_op": 0
}
igna84upvoted (100.00%) @asbear / 6sdqrm
2018/03/09 06:42:36
authorasbear
permlink6sdqrm
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20517270/Trx ff5de9f2cc5a514869a49a63a965665901321d50
View Raw JSON Data
{
  "block": 20517270,
  "op": [
    "vote",
    {
      "author": "asbear",
      "permlink": "6sdqrm",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T06:42:36",
  "trx_id": "ff5de9f2cc5a514869a49a63a965665901321d50",
  "trx_in_block": 10,
  "virtual_op": 0
}
igna84upvoted (100.00%) @igna84 / cvnbi-usb
2018/03/09 06:37:39
authorigna84
permlinkcvnbi-usb
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20517171/Trx a74f51a0add443a1537574fe9c8d99ad1edc6ea4
View Raw JSON Data
{
  "block": 20517171,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "cvnbi-usb",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T06:37:39",
  "trx_id": "a74f51a0add443a1537574fe9c8d99ad1edc6ea4",
  "trx_in_block": 48,
  "virtual_op": 0
}
igna84published a new post: cvnbi-usb
2018/03/09 06:37:39
authorigna84
body안녕! 오늘은 USB를 갖고 윈도우즈 설치를 할 수 있는 부팅 USB를 만드는 시간을 갖도록 하겠다. 물론 USB 부팅을 위한 BIOS 설정은 알아서 하길 바란다. 나는 그저 Windows 10을 ISO로 다운 받아 부팅 USB를 만드는 것까지만 알려줄거다. 왜 USB로 부팅이 안되는지 물어봐도 답장 안해줄거다. 일단 [https://www.microsoft.com/ko-kr/software-download/windows10](https://www.microsoft.com/ko-kr/software-download/windows10) 로 가서 Windows 10 ISO를 USB로 옮기는 방법들에 대해서 한번 훑어보고 오자. ![다운로드.png](https://steemitimages.com/DQmR4mf5W68L3aBZMzWStLnM1Yh9X7LCKG6ztXtte4RK8f9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png) 이제 대략 8G쯤되는 USB를 준비하기바란다. 왜냐하면 용량이 작으면 Windows 10을 담을 수가 없으니까 그런거다.(순정 Windows10은 대략 4.3G 정도의 용량을 갖는다.) 준비가 됐으면 ISO 파일을 구해야하는데 어디서 구하는지 모른다면 아래의 절차를 따라오면 된다. 막 어디서 무슨 인증키 없어도 된다는 Windows 10 ISO 파일을 토렌트 같은 어둠의 경로에서 다운로드 받다가 바이러스 걸려서 여태까지 모아놓은 이미지, 사진들 다 날려먹고 후회하지 말고 마이크로 소프트에서 제공하는 경로를 따라 ISO 파일을 다운로드 하기를 바란다. 일단 Chrome 브라우저를 준비해야한다. 예전에는 링크를 걍 열어줬었는데 마이크로 소프트가 트래픽 때문에 고생하는지 일단 PC로 페이지를 접근하면 WIndows 10 ISO 파일을 다운로드 할 수 있는 링크를 제공하지 않는다. 예전엔 그냥 공개해줬던거 같은데... 사실 Chrome 브라우저말고 Safari 브라우저를 이용해도 괜찮다. 그런데 포스팅은 Chrome 브라우저 기준으로 할거니까 Safari 브라우저 이용자들은 다른데 가서 방법을 알아올 것. 일단 위에서 알려준 Windows 10 다운로드 링크를 크롬으로 따라 간 뒤에 F12 키를 누르거나 화면에다가 마우스 오른쪽 클릭을 한다. ![다운로드 (1).png](https://steemitimages.com/DQmUG72FyE2ydEcSYbK97BGJRiu8m7Xitympzz7vUa1ovm1/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png) 검사를 클릭하면 요상한 창이 하나 뜰거다. 처음 띄워본 친구들은 하단에 막 요상하고 이상한 화면이 등장할텐데 이걸두고 "개발자 도구"라고 부른다. ![다운로드 (2).png](https://steemitimages.com/DQmZfhksZLS9461VYT9p5yNKDjSaNLBbYCQhLUW4xJK4wtQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png) 이 창에서 Elements 왼쪽에 있는 작은 아이콘 두개 중 오른쪽 걸 클릭한다. ![다운로드 (3).png](https://steemitimages.com/DQmQ5FN5ifNRMjxvrkLjcpfr8N3YDeZyBUnQxwCtrXo3TzN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png) 이 버튼이 뭐냐하면 웹 페이지를 모바일 버전으로 보고 싶을 때 누르는 버튼이다. 이걸 누르고 F5 키를 누르자. ![다운로드 (4).png](https://steemitimages.com/DQmPzUipNco18u95XibzxBCXcmUiHFe1WFFx8fNFEXQhugW/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png) 그러면 아까 보던 화면이랑 다른 화면이 등장하는 것을 확인할 수 있다. 혹시 화면이 너무 작게 나오면 상단의 숫자들을 제어해서 화면을 크게 만들던지 아니면 Responsive 를 클릭해 iPad 모드로 보는것도 가능하다. ![다운로드 (5).png](https://steemitimages.com/DQmV2e1kgFnP42UmmaFvvAd6ACpBPYyki59CZUAQofcpADQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png) 이렇게 보면 화면이 읽을만하게 커졌을 텐데 페이지 중간쯤에 보면 버전 선택이라고 보일거다. 거기서 Windows 10 선택. ![다운로드 (6).png](https://steemitimages.com/DQmNpenRQ1guz8rCCJJxBooNEUH6w21dckMQKqUgRpSFStX/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png) 확인 버튼을 누르자. 그러면 아래쪽에 제품 언어 선택란이 나오는데 원하는 언어를 설치한다. 난 1개 국어 마스터니까 한국어로 설정했다. ![다운로드 (7).png](https://steemitimages.com/DQmYZC25b2angiAe23Kihq6CfGiERrzPDV9GaCbdqtyLLgE/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png) 한국어가 잘 안보이시는 분들은 위에서 모바일 버전으로 페이지를 볼 수 있다고 하면서 알려준 옵션들을 조절해서 화면 크기를 축소해 보시기를 바랍니다. 암튼 한국어를 선택하는 것까지 성공을 했다면 "확인" 클릭 ![다운로드 (8).png](https://steemitimages.com/DQmcCw15A9poJmGUpSYN3RxjFZvWXVEqLgATjrie3DwtEPY/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png) 그러면 이렇게 다운로드를 할 수 있는 24시간동안 유효한 링크를 제공 받고 여기서 64비트 다운로드를 클릭하면 64비트 Windows 10 ISO 파일을 다운로드 할 수 있다. 대략 4.3G 쯤 되니까 10MB/s 정도의 속도로 다운로드 받으면 6분? 8분? 이정도면 다운로드를 완료할 수 있다. ![다운로드 (9).png](https://steemitimages.com/DQmepKkhE7kbN18aHPxURRSozyReKiMCsoAi1BZV7f4pMYF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png) 다운로드가 진행되는 동안 우리는 USB에 윈도우를 넣어줄 유틸리티를 찾아야한다. 대표적으로 Rufus 라는 프로그램을 사용한다. Rufus를 다운로드 하는건 아래의 링크를 따라가서 다운로드 받으면 된다. ([https://rufus.akeo.ie/](https://rufus.akeo.ie/)) ![다운로드 (10).png](https://steemitimages.com/DQmQFyi9mJcqmqhamYAvXPGXkqZiSXfh18cpBtyiaNRoq1N/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(10).png) 나는 예전에 포터블 버전으로 받아놓은게 있어서 그걸 이용하도록 하겠다. 이제 Windows 10도 다운로드 다 받았고 Rufus도 준비가 완료되었다면 Rufus를 실행하자. ![다운로드 (11).png](https://steemitimages.com/DQmf3LDAmwgEjcAwVgkGGaXVjWUG6UrSxQ6Y5n9mmS8wGCg/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(11).png) ![다운로드 (12).png](https://steemitimages.com/DQmPn7YErk4uzToEzzPzZH2Zn3mSEWwDnBnsFyUjx5WtAhN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(12).png) ![다운로드 (13).png](https://steemitimages.com/DQmNbHDVzv9tk36tWrh4ychdBRoyJva5LqnKDVV81N9WdiM/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(13).png) ![다운로드 (14).png](https://steemitimages.com/DQmVNvBfYUsYAVH3jBwG1XBDkUUsc5xGxFraW9KLeEzcfP8/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(14).png) 여기까지 했으면 "확인"을 클릭하자. 그러면 부팅이 가능한 WIndows10 USB를 만들어준다. 부팅 USB만들기 참 쉬운듯? ![다운로드.gif](https://steemitimages.com/DQmepA4vbafUCtfwkR2QPRqRz4AXiX5tgaeQXRKtRNozuw4/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.gif)
json metadata{"tags":["kr-dev"],"image":["https://steemitimages.com/DQmR4mf5W68L3aBZMzWStLnM1Yh9X7LCKG6ztXtte4RK8f9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png","https://steemitimages.com/DQmUG72FyE2ydEcSYbK97BGJRiu8m7Xitympzz7vUa1ovm1/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png","https://steemitimages.com/DQmZfhksZLS9461VYT9p5yNKDjSaNLBbYCQhLUW4xJK4wtQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png","https://steemitimages.com/DQmQ5FN5ifNRMjxvrkLjcpfr8N3YDeZyBUnQxwCtrXo3TzN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png","https://steemitimages.com/DQmPzUipNco18u95XibzxBCXcmUiHFe1WFFx8fNFEXQhugW/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png","https://steemitimages.com/DQmV2e1kgFnP42UmmaFvvAd6ACpBPYyki59CZUAQofcpADQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png","https://steemitimages.com/DQmNpenRQ1guz8rCCJJxBooNEUH6w21dckMQKqUgRpSFStX/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png","https://steemitimages.com/DQmYZC25b2angiAe23Kihq6CfGiERrzPDV9GaCbdqtyLLgE/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png","https://steemitimages.com/DQmcCw15A9poJmGUpSYN3RxjFZvWXVEqLgATjrie3DwtEPY/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png","https://steemitimages.com/DQmepKkhE7kbN18aHPxURRSozyReKiMCsoAi1BZV7f4pMYF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png","https://steemitimages.com/DQmQFyi9mJcqmqhamYAvXPGXkqZiSXfh18cpBtyiaNRoq1N/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(10).png","https://steemitimages.com/DQmf3LDAmwgEjcAwVgkGGaXVjWUG6UrSxQ6Y5n9mmS8wGCg/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(11).png","https://steemitimages.com/DQmPn7YErk4uzToEzzPzZH2Zn3mSEWwDnBnsFyUjx5WtAhN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(12).png","https://steemitimages.com/DQmNbHDVzv9tk36tWrh4ychdBRoyJva5LqnKDVV81N9WdiM/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(13).png","https://steemitimages.com/DQmVNvBfYUsYAVH3jBwG1XBDkUUsc5xGxFraW9KLeEzcfP8/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(14).png","https://steemitimages.com/DQmepA4vbafUCtfwkR2QPRqRz4AXiX5tgaeQXRKtRNozuw4/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.gif"],"links":["https://www.microsoft.com/ko-kr/software-download/windows10","https://rufus.akeo.ie/"],"app":"steemit/0.1","format":"markdown"}
parent author
parent permlinkkr-dev
permlinkcvnbi-usb
title윈도우 설치 USB 만들기
Transaction InfoBlock #20517171/Trx a74f51a0add443a1537574fe9c8d99ad1edc6ea4
View Raw JSON Data
{
  "block": 20517171,
  "op": [
    "comment",
    {
      "author": "igna84",
      "body": "안녕!\n\n오늘은 USB를 갖고 윈도우즈 설치를 할 수 있는 부팅 USB를 만드는 시간을 갖도록 하겠다.\n\n물론 USB 부팅을 위한 BIOS 설정은 알아서 하길 바란다. 나는 그저 Windows 10을 ISO로 다운 받아 부팅 USB를 만드는 것까지만 알려줄거다. 왜 USB로 부팅이 안되는지 물어봐도 답장 안해줄거다.\n\n\n\n일단 [https://www.microsoft.com/ko-kr/software-download/windows10](https://www.microsoft.com/ko-kr/software-download/windows10) 로 가서 Windows 10 ISO를 USB로 옮기는 방법들에 대해서 한번 훑어보고 오자.\n\n![다운로드.png](https://steemitimages.com/DQmR4mf5W68L3aBZMzWStLnM1Yh9X7LCKG6ztXtte4RK8f9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png)\n\n이제 대략 8G쯤되는 USB를 준비하기바란다. 왜냐하면 용량이 작으면 Windows 10을 담을 수가 없으니까 그런거다.(순정 Windows10은 대략 4.3G 정도의 용량을 갖는다.)\n\n\n\n준비가 됐으면 ISO 파일을 구해야하는데 어디서 구하는지 모른다면 아래의 절차를 따라오면 된다.\n\n막 어디서 무슨 인증키 없어도 된다는 Windows 10 ISO 파일을 토렌트 같은 어둠의 경로에서 다운로드 받다가 바이러스 걸려서 여태까지 모아놓은 이미지, 사진들 다 날려먹고 후회하지 말고 마이크로 소프트에서 제공하는 경로를 따라 ISO 파일을 다운로드 하기를 바란다.\n\n\n\n일단 Chrome 브라우저를 준비해야한다. 예전에는 링크를 걍 열어줬었는데 마이크로 소프트가 트래픽 때문에 고생하는지 일단 PC로 페이지를 접근하면 WIndows 10 ISO 파일을 다운로드 할 수 있는 링크를 제공하지 않는다. 예전엔 그냥 공개해줬던거 같은데...\n\n\n\n사실 Chrome 브라우저말고 Safari 브라우저를 이용해도 괜찮다. 그런데 포스팅은 Chrome 브라우저 기준으로 할거니까 Safari 브라우저 이용자들은 다른데 가서 방법을 알아올 것.\n\n\n\n일단 위에서 알려준 Windows 10 다운로드 링크를 크롬으로 따라 간 뒤에 F12 키를 누르거나 화면에다가 마우스 오른쪽 클릭을 한다.\n\n![다운로드 (1).png](https://steemitimages.com/DQmUG72FyE2ydEcSYbK97BGJRiu8m7Xitympzz7vUa1ovm1/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png)\n\n검사를 클릭하면 요상한 창이 하나 뜰거다. 처음 띄워본 친구들은 하단에 막 요상하고 이상한 화면이 등장할텐데 이걸두고 \"개발자 도구\"라고 부른다.\n\n![다운로드 (2).png](https://steemitimages.com/DQmZfhksZLS9461VYT9p5yNKDjSaNLBbYCQhLUW4xJK4wtQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png)\n\n이 창에서 Elements 왼쪽에 있는 작은 아이콘 두개 중 오른쪽 걸 클릭한다.\n\n![다운로드 (3).png](https://steemitimages.com/DQmQ5FN5ifNRMjxvrkLjcpfr8N3YDeZyBUnQxwCtrXo3TzN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png)\n\n이 버튼이 뭐냐하면 웹 페이지를 모바일 버전으로 보고 싶을 때 누르는 버튼이다. 이걸 누르고 F5 키를 누르자.\n\n![다운로드 (4).png](https://steemitimages.com/DQmPzUipNco18u95XibzxBCXcmUiHFe1WFFx8fNFEXQhugW/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png)\n\n그러면 아까 보던 화면이랑 다른 화면이 등장하는 것을 확인할 수 있다. 혹시 화면이 너무 작게 나오면 상단의 숫자들을 제어해서 화면을 크게 만들던지 아니면 Responsive 를 클릭해 iPad 모드로 보는것도 가능하다.\n\n![다운로드 (5).png](https://steemitimages.com/DQmV2e1kgFnP42UmmaFvvAd6ACpBPYyki59CZUAQofcpADQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png)\n\n이렇게 보면 화면이 읽을만하게 커졌을 텐데 페이지 중간쯤에 보면 버전 선택이라고 보일거다. 거기서 Windows 10 선택.\n\n![다운로드 (6).png](https://steemitimages.com/DQmNpenRQ1guz8rCCJJxBooNEUH6w21dckMQKqUgRpSFStX/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png)\n\n확인 버튼을 누르자. 그러면 아래쪽에 제품 언어 선택란이 나오는데 원하는 언어를 설치한다. 난 1개 국어 마스터니까 한국어로 설정했다.\n\n![다운로드 (7).png](https://steemitimages.com/DQmYZC25b2angiAe23Kihq6CfGiERrzPDV9GaCbdqtyLLgE/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png)\n\n한국어가 잘 안보이시는 분들은 위에서 모바일 버전으로 페이지를 볼 수 있다고 하면서 알려준 옵션들을 조절해서 화면 크기를 축소해 보시기를 바랍니다. 암튼 한국어를 선택하는 것까지 성공을 했다면 \"확인\" 클릭\n\n![다운로드 (8).png](https://steemitimages.com/DQmcCw15A9poJmGUpSYN3RxjFZvWXVEqLgATjrie3DwtEPY/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png)\n\n그러면 이렇게 다운로드를 할 수 있는 24시간동안 유효한 링크를 제공 받고 여기서 64비트 다운로드를 클릭하면 64비트 Windows 10 ISO 파일을 다운로드 할 수 있다.\n\n\n\n대략 4.3G 쯤 되니까 10MB/s 정도의 속도로 다운로드 받으면 6분? 8분? 이정도면 다운로드를 완료할 수 있다.\n\n![다운로드 (9).png](https://steemitimages.com/DQmepKkhE7kbN18aHPxURRSozyReKiMCsoAi1BZV7f4pMYF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png)\n\n다운로드가 진행되는 동안 우리는 USB에 윈도우를 넣어줄 유틸리티를 찾아야한다. 대표적으로 Rufus 라는 프로그램을 사용한다.\n\nRufus를 다운로드 하는건 아래의 링크를 따라가서 다운로드 받으면 된다.\n\n([https://rufus.akeo.ie/](https://rufus.akeo.ie/))\n\n![다운로드 (10).png](https://steemitimages.com/DQmQFyi9mJcqmqhamYAvXPGXkqZiSXfh18cpBtyiaNRoq1N/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(10).png)\n\n나는 예전에 포터블 버전으로 받아놓은게 있어서 그걸 이용하도록 하겠다.\n\n\n\n이제 Windows 10도 다운로드 다 받았고 Rufus도 준비가 완료되었다면 Rufus를 실행하자.\n\n![다운로드 (11).png](https://steemitimages.com/DQmf3LDAmwgEjcAwVgkGGaXVjWUG6UrSxQ6Y5n9mmS8wGCg/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(11).png)\n\n![다운로드 (12).png](https://steemitimages.com/DQmPn7YErk4uzToEzzPzZH2Zn3mSEWwDnBnsFyUjx5WtAhN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(12).png)\n\n![다운로드 (13).png](https://steemitimages.com/DQmNbHDVzv9tk36tWrh4ychdBRoyJva5LqnKDVV81N9WdiM/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(13).png)\n\n![다운로드 (14).png](https://steemitimages.com/DQmVNvBfYUsYAVH3jBwG1XBDkUUsc5xGxFraW9KLeEzcfP8/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(14).png)\n\n여기까지 했으면 \"확인\"을 클릭하자. 그러면 부팅이 가능한 WIndows10 USB를 만들어준다.\n\n\n\n부팅 USB만들기 참 쉬운듯?\n\n![다운로드.gif](https://steemitimages.com/DQmepA4vbafUCtfwkR2QPRqRz4AXiX5tgaeQXRKtRNozuw4/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.gif)",
      "json_metadata": "{\"tags\":[\"kr-dev\"],\"image\":[\"https://steemitimages.com/DQmR4mf5W68L3aBZMzWStLnM1Yh9X7LCKG6ztXtte4RK8f9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png\",\"https://steemitimages.com/DQmUG72FyE2ydEcSYbK97BGJRiu8m7Xitympzz7vUa1ovm1/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png\",\"https://steemitimages.com/DQmZfhksZLS9461VYT9p5yNKDjSaNLBbYCQhLUW4xJK4wtQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png\",\"https://steemitimages.com/DQmQ5FN5ifNRMjxvrkLjcpfr8N3YDeZyBUnQxwCtrXo3TzN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png\",\"https://steemitimages.com/DQmPzUipNco18u95XibzxBCXcmUiHFe1WFFx8fNFEXQhugW/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png\",\"https://steemitimages.com/DQmV2e1kgFnP42UmmaFvvAd6ACpBPYyki59CZUAQofcpADQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png\",\"https://steemitimages.com/DQmNpenRQ1guz8rCCJJxBooNEUH6w21dckMQKqUgRpSFStX/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png\",\"https://steemitimages.com/DQmYZC25b2angiAe23Kihq6CfGiERrzPDV9GaCbdqtyLLgE/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png\",\"https://steemitimages.com/DQmcCw15A9poJmGUpSYN3RxjFZvWXVEqLgATjrie3DwtEPY/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png\",\"https://steemitimages.com/DQmepKkhE7kbN18aHPxURRSozyReKiMCsoAi1BZV7f4pMYF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png\",\"https://steemitimages.com/DQmQFyi9mJcqmqhamYAvXPGXkqZiSXfh18cpBtyiaNRoq1N/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(10).png\",\"https://steemitimages.com/DQmf3LDAmwgEjcAwVgkGGaXVjWUG6UrSxQ6Y5n9mmS8wGCg/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(11).png\",\"https://steemitimages.com/DQmPn7YErk4uzToEzzPzZH2Zn3mSEWwDnBnsFyUjx5WtAhN/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(12).png\",\"https://steemitimages.com/DQmNbHDVzv9tk36tWrh4ychdBRoyJva5LqnKDVV81N9WdiM/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(13).png\",\"https://steemitimages.com/DQmVNvBfYUsYAVH3jBwG1XBDkUUsc5xGxFraW9KLeEzcfP8/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(14).png\",\"https://steemitimages.com/DQmepA4vbafUCtfwkR2QPRqRz4AXiX5tgaeQXRKtRNozuw4/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.gif\"],\"links\":[\"https://www.microsoft.com/ko-kr/software-download/windows10\",\"https://rufus.akeo.ie/\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
      "parent_author": "",
      "parent_permlink": "kr-dev",
      "permlink": "cvnbi-usb",
      "title": "윈도우 설치 USB 만들기"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T06:37:39",
  "trx_id": "a74f51a0add443a1537574fe9c8d99ad1edc6ea4",
  "trx_in_block": 48,
  "virtual_op": 0
}
steemdelegated 17.825 SP to @igna84
2018/03/09 06:36:00
delegateeigna84
delegatorsteem
vesting shares28987.345518 VESTS
Transaction InfoBlock #20517138/Trx c46714eec6d522a8b032ca408015718872ca1acd
View Raw JSON Data
{
  "block": 20517138,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "28987.345518 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T06:36:00",
  "trx_id": "c46714eec6d522a8b032ca408015718872ca1acd",
  "trx_in_block": 58,
  "virtual_op": 0
}
igna84claimed reward balance: 0.354 SBD, 0.148 SP
2018/03/09 06:30:15
accountigna84
reward sbd0.354 SBD
reward steem0.000 STEEM
reward vests240.976374 VESTS
Transaction InfoBlock #20517023/Trx 14d59d433c3b90d05240c031399169c22aab5f10
View Raw JSON Data
{
  "block": 20517023,
  "op": [
    "claim_reward_balance",
    {
      "account": "igna84",
      "reward_sbd": "0.354 SBD",
      "reward_steem": "0.000 STEEM",
      "reward_vests": "240.976374 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T06:30:15",
  "trx_id": "14d59d433c3b90d05240c031399169c22aab5f10",
  "trx_in_block": 41,
  "virtual_op": 0
}
igna84upvoted (100.00%) @codingman / tmp36
2018/03/09 06:28:54
authorcodingman
permlinktmp36
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20516996/Trx 5261de44c8354509afb8a97b59ffc76d58c18146
View Raw JSON Data
{
  "block": 20516996,
  "op": [
    "vote",
    {
      "author": "codingman",
      "permlink": "tmp36",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T06:28:54",
  "trx_id": "5261de44c8354509afb8a97b59ffc76d58c18146",
  "trx_in_block": 22,
  "virtual_op": 0
}
igna84received 0.354 SBD, 0.148 SP author reward for @igna84 / spring-security-secured-preauthorize-postauthorize-annotation
2018/03/09 04:19:39
authorigna84
permlinkspring-security-secured-preauthorize-postauthorize-annotation
sbd payout0.354 SBD
steem payout0.000 STEEM
vesting payout240.976374 VESTS
Transaction InfoBlock #20514410/Virtual Operation #8
View Raw JSON Data
{
  "block": 20514410,
  "op": [
    "author_reward",
    {
      "author": "igna84",
      "permlink": "spring-security-secured-preauthorize-postauthorize-annotation",
      "sbd_payout": "0.354 SBD",
      "steem_payout": "0.000 STEEM",
      "vesting_payout": "240.976374 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-09T04:19:39",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": 8
}
2018/03/07 11:02:51
authorigna84
permlinktable-colspan-rowspan
votercodingman
weight10000 (100.00%)
Transaction InfoBlock #20464938/Trx 560eba7617e91e08d3dee50e7a666d3c6d676873
View Raw JSON Data
{
  "block": 20464938,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "table-colspan-rowspan",
      "voter": "codingman",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T11:02:51",
  "trx_id": "560eba7617e91e08d3dee50e7a666d3c6d676873",
  "trx_in_block": 4,
  "virtual_op": 0
}
2018/03/07 09:45:06
authorkangsukin
body첫사진 선택을 잘한 것 같네요 ㅎㅎ 팔로우 할게요^^
json metadata{"tags":["kr"],"app":"steemit/0.1"}
parent authorigna84
parent permlinkre-kangsukin-6qwbxw-20180307t065601671z
permlinkre-igna84-re-kangsukin-6qwbxw-20180307t094504819z
title
Transaction InfoBlock #20463389/Trx 3baf7ad558cf77d4fab44663604c47469154a1e6
View Raw JSON Data
{
  "block": 20463389,
  "op": [
    "comment",
    {
      "author": "kangsukin",
      "body": "첫사진 선택을 잘한 것 같네요 ㅎㅎ\n팔로우 할게요^^",
      "json_metadata": "{\"tags\":[\"kr\"],\"app\":\"steemit/0.1\"}",
      "parent_author": "igna84",
      "parent_permlink": "re-kangsukin-6qwbxw-20180307t065601671z",
      "permlink": "re-igna84-re-kangsukin-6qwbxw-20180307t094504819z",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T09:45:06",
  "trx_id": "3baf7ad558cf77d4fab44663604c47469154a1e6",
  "trx_in_block": 9,
  "virtual_op": 0
}
2018/03/07 08:42:24
authorigna84
permlinkspring-boot-jpa-hibernate-2
voterkdj
weight2000 (20.00%)
Transaction InfoBlock #20462139/Trx 316afed1e507ac2310e0842afa8692bc736c8cb6
View Raw JSON Data
{
  "block": 20462139,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-boot-jpa-hibernate-2",
      "voter": "kdj",
      "weight": 2000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T08:42:24",
  "trx_id": "316afed1e507ac2310e0842afa8692bc736c8cb6",
  "trx_in_block": 14,
  "virtual_op": 0
}
2018/03/07 08:41:57
authorigna84
permlinkspring-security-preauthorize-postauthorize
voterkdj
weight2000 (20.00%)
Transaction InfoBlock #20462130/Trx d8c53bb838605efc8a03d4cde878c68d22f24e18
View Raw JSON Data
{
  "block": 20462130,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-security-preauthorize-postauthorize",
      "voter": "kdj",
      "weight": 2000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T08:41:57",
  "trx_id": "d8c53bb838605efc8a03d4cde878c68d22f24e18",
  "trx_in_block": 5,
  "virtual_op": 0
}
2018/03/07 08:40:45
authorigna84
permlinktable-colspan-rowspan
voterkdj
weight2000 (20.00%)
Transaction InfoBlock #20462106/Trx a993819039c8104162298893aff9c3d8e789cdac
View Raw JSON Data
{
  "block": 20462106,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "table-colspan-rowspan",
      "voter": "kdj",
      "weight": 2000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T08:40:45",
  "trx_id": "a993819039c8104162298893aff9c3d8e789cdac",
  "trx_in_block": 4,
  "virtual_op": 0
}
steemdelegated 17.975 SP to @igna84
2018/03/07 08:06:42
delegateeigna84
delegatorsteem
vesting shares29231.407857 VESTS
Transaction InfoBlock #20461429/Trx 8739e8007c428facbaea2911c216b4bf25391352
View Raw JSON Data
{
  "block": 20461429,
  "op": [
    "delegate_vesting_shares",
    {
      "delegatee": "igna84",
      "delegator": "steem",
      "vesting_shares": "29231.407857 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T08:06:42",
  "trx_id": "8739e8007c428facbaea2911c216b4bf25391352",
  "trx_in_block": 14,
  "virtual_op": 0
}
2018/03/07 06:56:03
authorigna84
body첫 사진을 보고 뭐에 홀린듯이 들어와 보팅하고 갑니다. 침이 고이네요! ㅎㅎ
json metadata{"tags":["kr"],"app":"steemit/0.1"}
parent authorkangsukin
parent permlink6qwbxw
permlinkre-kangsukin-6qwbxw-20180307t065601671z
title
Transaction InfoBlock #20460021/Trx bccad627202d9345b9ddb1a3a8a84492b1d0f28b
View Raw JSON Data
{
  "block": 20460021,
  "op": [
    "comment",
    {
      "author": "igna84",
      "body": "첫 사진을 보고 뭐에 홀린듯이 들어와 보팅하고 갑니다.\n침이 고이네요! ㅎㅎ",
      "json_metadata": "{\"tags\":[\"kr\"],\"app\":\"steemit/0.1\"}",
      "parent_author": "kangsukin",
      "parent_permlink": "6qwbxw",
      "permlink": "re-kangsukin-6qwbxw-20180307t065601671z",
      "title": ""
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T06:56:03",
  "trx_id": "bccad627202d9345b9ddb1a3a8a84492b1d0f28b",
  "trx_in_block": 17,
  "virtual_op": 0
}
igna84upvoted (100.00%) @kangsukin / 6qwbxw
2018/03/07 06:55:09
authorkangsukin
permlink6qwbxw
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20460003/Trx 72cad755eccc9cce9bb40049074cde526ed57535
View Raw JSON Data
{
  "block": 20460003,
  "op": [
    "vote",
    {
      "author": "kangsukin",
      "permlink": "6qwbxw",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T06:55:09",
  "trx_id": "72cad755eccc9cce9bb40049074cde526ed57535",
  "trx_in_block": 14,
  "virtual_op": 0
}
igna84upvoted (100.00%) @codingman / keypad
2018/03/07 06:51:51
authorcodingman
permlinkkeypad
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20459937/Trx 508c63b7d05bb49c368ad77132898151b5b90bfc
View Raw JSON Data
{
  "block": 20459937,
  "op": [
    "vote",
    {
      "author": "codingman",
      "permlink": "keypad",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T06:51:51",
  "trx_id": "508c63b7d05bb49c368ad77132898151b5b90bfc",
  "trx_in_block": 35,
  "virtual_op": 0
}
igna84claimed reward balance: 0.410 SBD, 0.151 SP
2018/03/07 06:47:27
accountigna84
reward sbd0.410 SBD
reward steem0.000 STEEM
reward vests245.086526 VESTS
Transaction InfoBlock #20459850/Trx b2addce7d4470af42a695af6e5442522f6221df2
View Raw JSON Data
{
  "block": 20459850,
  "op": [
    "claim_reward_balance",
    {
      "account": "igna84",
      "reward_sbd": "0.410 SBD",
      "reward_steem": "0.000 STEEM",
      "reward_vests": "245.086526 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T06:47:27",
  "trx_id": "b2addce7d4470af42a695af6e5442522f6221df2",
  "trx_in_block": 18,
  "virtual_op": 0
}
igna84received 0.410 SBD, 0.151 SP author reward for @igna84 / spring-boot-mongodb-redis
2018/03/07 04:17:21
authorigna84
permlinkspring-boot-mongodb-redis
sbd payout0.410 SBD
steem payout0.000 STEEM
vesting payout245.086526 VESTS
Transaction InfoBlock #20456860/Virtual Operation #16
View Raw JSON Data
{
  "block": 20456860,
  "op": [
    "author_reward",
    {
      "author": "igna84",
      "permlink": "spring-boot-mongodb-redis",
      "sbd_payout": "0.410 SBD",
      "steem_payout": "0.000 STEEM",
      "vesting_payout": "245.086526 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T04:17:21",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": 16
}
igna84upvoted (100.00%) @asbear / forte
2018/03/07 01:43:21
authorasbear
permlinkforte
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20453794/Trx 904cfff033e156e6507f0381aac5d671c6fd09f1
View Raw JSON Data
{
  "block": 20453794,
  "op": [
    "vote",
    {
      "author": "asbear",
      "permlink": "forte",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T01:43:21",
  "trx_id": "904cfff033e156e6507f0381aac5d671c6fd09f1",
  "trx_in_block": 5,
  "virtual_op": 0
}
igna84followed @asbear
2018/03/07 01:41:54
idfollow
json["follow",{"follower":"igna84","following":"asbear","what":["blog"]}]
required auths[]
required posting auths["igna84"]
Transaction InfoBlock #20453765/Trx 04288c193439039174bdcb8b9683fc4492e81caf
View Raw JSON Data
{
  "block": 20453765,
  "op": [
    "custom_json",
    {
      "id": "follow",
      "json": "[\"follow\",{\"follower\":\"igna84\",\"following\":\"asbear\",\"what\":[\"blog\"]}]",
      "required_auths": [],
      "required_posting_auths": [
        "igna84"
      ]
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T01:41:54",
  "trx_id": "04288c193439039174bdcb8b9683fc4492e81caf",
  "trx_in_block": 25,
  "virtual_op": 0
}
igna84upvoted (100.00%) @asbear / 4sjejt
2018/03/07 01:40:42
authorasbear
permlink4sjejt
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20453741/Trx aa88ef5e22eb8e669575429c391d8ebce1e34d96
View Raw JSON Data
{
  "block": 20453741,
  "op": [
    "vote",
    {
      "author": "asbear",
      "permlink": "4sjejt",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T01:40:42",
  "trx_id": "aa88ef5e22eb8e669575429c391d8ebce1e34d96",
  "trx_in_block": 33,
  "virtual_op": 0
}
2018/03/07 01:32:39
authorigna84
permlinktable-colspan-rowspan
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20453580/Trx e73447d50f76ba7b6d5d4379ef366856353200e6
View Raw JSON Data
{
  "block": 20453580,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "table-colspan-rowspan",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T01:32:39",
  "trx_id": "e73447d50f76ba7b6d5d4379ef366856353200e6",
  "trx_in_block": 23,
  "virtual_op": 0
}
igna84published a new post: table-colspan-rowspan
2018/03/07 01:32:39
authorigna84
bodyTable 태그를 쓰다보면 만나는 레이아웃이 하나 있는데 ![다운로드.png](https://steemitimages.com/DQmRMLnpT4b9BP2Jf7WKbbjVXy5UDxoRsqTCrTYnn3Nk1AQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png) 이렇게 뭔가 엑셀에서 사용되는 병합셀이다. 도대체 이걸 어떻게 만들지 "html table 셀 병합"이라고 검색을 해보면 colspan 이나 rowspan을 사용하라고 하는데 뭔소린지 잘 모르겠다면 아래를 참고해주길 바란다. ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style type="text/css"> table { border-collapse: collapse; border-spacing: 0; } .tbl { width: 100%; } .tbl th { font-size: 12px; background-color: #CCC; border: 1px solid #666; text-align: center; vertical-align: middle; padding: 5px; } </style> </head> <body> <table class="tbl"> <thead> <tr> <th rowspan="2"><input type="checkbox"></th> <th rowspan="2">발급번호</th> <th rowspan="2">쿠폰명</th> <th colspan="2">유효기간</th> <th rowspan="2">쿠폰종류</th> <th rowspan="2">쿠폰타입</th> <th rowspan="2">중복발급</th> <th rowspan="2">발급개수</th> <th rowspan="2">상태</th> <th rowspan="2">등록일</th> </tr> <tr> <th>시작일</th> <th>종료일</th> </tr> </thead> </table> </body> </html> ``` 일단 완성된 코드를 보자. 우리의 참고 디자인은 thead 부분의 레이아웃이라 thead에서 샘플을 제작했다. th코드에 rowspan="2" 또는 colspan="2"라고 쓴 것을 확인할 수 있다. 이 속성이 어떤 역할을 하느냐 이해하기 전에 row와 column의 차이를 이해해야하는데 row는 우리 말로 "줄"을 의미한다. column은 "칸"을 의미한다. ![다운로드.jpg](https://steemitimages.com/DQmbCvqZq4QBb2qAfZXvECxppGELLjsGAL7BcWTc7Pn54BB/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg) 이렇게 rowspan="2"를 적용시키면 ![다운로드 (1).png](https://steemitimages.com/DQmdp9bL8PWn7UfPfGrFMoUuKLRk2GAmMnQ9t97SSGCR8PG/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png) 2개의 tr을 이용해서 두줄로 만든 테이블이지만 rowspan="2" 덕택에 위아래 "줄"이 사라지고 병합되어있는걸 확인할 수 있다. 그렇다면 숫자는 무슨 의미냐 하면 "자신을 포함한" 병합할 셀의 갯수를 의미한다. ![다운로드 (2).png](https://steemitimages.com/DQmbWRt14gUwBUeuw9MPdvi924Pumi416PYLnqxjucAbh7r/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png) 그리고 colspan="2"을 적용시키면 ![다운로드 (3).png](https://steemitimages.com/DQmVaAdmaczm6bchss1eXdSJG9Y4pKMhXZ3Q2YP2cPLbazt/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png) 이렇게 두개의 "칸"을 병합시킨다. colspan의 col은 column의 앞 세글자를 따온 것이다. 그래서 colspan="2"에서 2의 의미는 "자신을 포함한" 병합될 셀의 갯수를 의미한다. ![다운로드 (4).png](https://steemitimages.com/DQmec6eSFYmTbHkzoHtUnqNGGP47ysir2sumBsCrEuUEXza/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png) 그리고 다음 tr엔 이렇게 유효기간 아래 들어갈 시작일과 종료일을 입력한다. ![다운로드 (5).png](https://steemitimages.com/DQmZTsZ4TFKLoYJGktYrN7AT4zY9MxyCZFYirMWFEiK1gjF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png) 그러면 이렇게 시작일과 종료일이 쏙 들어가게된다. 아주 간단 샘플을 예로 colspan과 rowspan을 설명했는데 정리하자면 아래와 같다. * rowspan은 "줄"을 병합한다. * colspan은 "칸"을 병합한다. * 적용하는 수치값은 "자신을 포함한" 병합할 셀의 갯수를 의미한다. * 병합하는 셀의 기준은 "자신"을 중심으로 오른쪽으로, 아래로 병합된다. * 이렇게 병합하면 본래 해당 위치에 들어가야할 셀의 위치가 "밀려"서 다음 셀이 위치해야할 곳으로 이동해버린다. * colspan과 rowspan을 이용하면 전체 셀의 갯수를 셈할 때 햇깔리기 쉬운데 이것을 방지하는려면 * 전체 줄의 갯수와 칸의 갯수를 셈한 뒤 칸의 갯수는 colspan 속성이 없는 것은 셀의 갯수를, colspan속성이 있으면 속성값을 더해 비교하면 정신건강에 도움이 된다. * rowspan의 경우엔 tr의 갯수와 rowspan의 속성값을 비교하면 정신건강에 도움이 된다.(예를 들어 샘플의 전체 칸의 갯수는 11개고 줄의 갯수는 2개다) * 처음 하면 꽤나 햇깔리기 좋기때문에 연습을 하면서 감을 익히는게 최선이다.(머리가 좋으면 금방 원리를 이해할 수 있다. 머리가 좋지않다면 연습만이 살길이다. 참고로 나는 능숙하게 쓰기까지 한 3개월쯤 걸린것 같다......)
json metadata{"tags":["kr-dev","dev","html","table","span"],"image":["https://steemitimages.com/DQmRMLnpT4b9BP2Jf7WKbbjVXy5UDxoRsqTCrTYnn3Nk1AQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png","https://steemitimages.com/DQmbCvqZq4QBb2qAfZXvECxppGELLjsGAL7BcWTc7Pn54BB/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg","https://steemitimages.com/DQmdp9bL8PWn7UfPfGrFMoUuKLRk2GAmMnQ9t97SSGCR8PG/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png","https://steemitimages.com/DQmbWRt14gUwBUeuw9MPdvi924Pumi416PYLnqxjucAbh7r/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png","https://steemitimages.com/DQmVaAdmaczm6bchss1eXdSJG9Y4pKMhXZ3Q2YP2cPLbazt/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png","https://steemitimages.com/DQmec6eSFYmTbHkzoHtUnqNGGP47ysir2sumBsCrEuUEXza/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png","https://steemitimages.com/DQmZTsZ4TFKLoYJGktYrN7AT4zY9MxyCZFYirMWFEiK1gjF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png"],"app":"steemit/0.1","format":"markdown"}
parent author
parent permlinkkr-dev
permlinktable-colspan-rowspan
titleTable colspan, rowspan
Transaction InfoBlock #20453580/Trx e73447d50f76ba7b6d5d4379ef366856353200e6
View Raw JSON Data
{
  "block": 20453580,
  "op": [
    "comment",
    {
      "author": "igna84",
      "body": "Table 태그를 쓰다보면 만나는 레이아웃이 하나 있는데\n![다운로드.png](https://steemitimages.com/DQmRMLnpT4b9BP2Jf7WKbbjVXy5UDxoRsqTCrTYnn3Nk1AQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png)\n\n이렇게 뭔가 엑셀에서 사용되는 병합셀이다.\n\n도대체 이걸 어떻게 만들지 \"html table 셀 병합\"이라고 검색을 해보면 colspan 이나 rowspan을 사용하라고 하는데\n\n뭔소린지 잘 모르겠다면 아래를 참고해주길 바란다.\n\n```\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>Document</title>\n    <style type=\"text/css\">\n    table {\n        border-collapse: collapse;\n        border-spacing: 0;\n    }\n \n    .tbl {\n        width: 100%;\n    }\n \n    .tbl th {\n        font-size: 12px;\n        background-color: #CCC;\n        border: 1px solid #666;\n        text-align: center;\n        vertical-align: middle;\n        padding: 5px;\n    }\n    </style>\n</head>\n<body>\n    <table class=\"tbl\">\n        <thead>\n            <tr>\n                <th rowspan=\"2\"><input type=\"checkbox\"></th>\n                <th rowspan=\"2\">발급번호</th>\n                <th rowspan=\"2\">쿠폰명</th>\n                <th colspan=\"2\">유효기간</th>\n                <th rowspan=\"2\">쿠폰종류</th>\n                <th rowspan=\"2\">쿠폰타입</th>\n                <th rowspan=\"2\">중복발급</th>\n                <th rowspan=\"2\">발급개수</th>\n                <th rowspan=\"2\">상태</th>\n                <th rowspan=\"2\">등록일</th>\n            </tr>\n            <tr>\n                <th>시작일</th>\n                <th>종료일</th>\n            </tr>\n        </thead>\n    </table>\n</body>\n</html>\n```\n일단 완성된 코드를 보자.\n\n우리의 참고 디자인은 thead 부분의 레이아웃이라 thead에서 샘플을 제작했다.\n\nth코드에 rowspan=\"2\" 또는 colspan=\"2\"라고 쓴 것을 확인할 수 있다.\n\n이 속성이 어떤 역할을 하느냐 이해하기 전에 row와 column의 차이를 이해해야하는데\n\n\n\nrow는 우리 말로 \"줄\"을 의미한다.\n\ncolumn은 \"칸\"을 의미한다.\n\n![다운로드.jpg](https://steemitimages.com/DQmbCvqZq4QBb2qAfZXvECxppGELLjsGAL7BcWTc7Pn54BB/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg)\n\n이렇게 rowspan=\"2\"를 적용시키면\n\n![다운로드 (1).png](https://steemitimages.com/DQmdp9bL8PWn7UfPfGrFMoUuKLRk2GAmMnQ9t97SSGCR8PG/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png)\n\n2개의 tr을 이용해서 두줄로 만든 테이블이지만 rowspan=\"2\" 덕택에 위아래 \"줄\"이 사라지고\n\n병합되어있는걸 확인할 수 있다.\n\n그렇다면 숫자는 무슨 의미냐 하면\n\n\"자신을 포함한\" 병합할 셀의 갯수를 의미한다.\n\n![다운로드 (2).png](https://steemitimages.com/DQmbWRt14gUwBUeuw9MPdvi924Pumi416PYLnqxjucAbh7r/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png)\n\n그리고 colspan=\"2\"을 적용시키면\n\n![다운로드 (3).png](https://steemitimages.com/DQmVaAdmaczm6bchss1eXdSJG9Y4pKMhXZ3Q2YP2cPLbazt/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png)\n\n이렇게 두개의 \"칸\"을 병합시킨다.\n\ncolspan의 col은 column의 앞 세글자를 따온 것이다.\n\n그래서 colspan=\"2\"에서 2의 의미는\n\n\n\n\"자신을 포함한\" 병합될 셀의 갯수를 의미한다.\n\n![다운로드 (4).png](https://steemitimages.com/DQmec6eSFYmTbHkzoHtUnqNGGP47ysir2sumBsCrEuUEXza/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png)\n\n그리고 다음 tr엔 이렇게 유효기간 아래 들어갈 시작일과 종료일을 입력한다.\n\n![다운로드 (5).png](https://steemitimages.com/DQmZTsZ4TFKLoYJGktYrN7AT4zY9MxyCZFYirMWFEiK1gjF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png)\n\n그러면 이렇게 시작일과 종료일이 쏙 들어가게된다.\n\n아주 간단 샘플을 예로 colspan과 rowspan을 설명했는데 정리하자면 아래와 같다.\n\n\n\n* rowspan은 \"줄\"을 병합한다.\n* colspan은 \"칸\"을 병합한다.\n* 적용하는 수치값은 \"자신을 포함한\" 병합할 셀의 갯수를 의미한다.\n* 병합하는 셀의 기준은 \"자신\"을 중심으로 오른쪽으로, 아래로 병합된다.\n* 이렇게 병합하면 본래 해당 위치에 들어가야할 셀의 위치가 \"밀려\"서 다음 셀이 위치해야할 곳으로 이동해버린다.\n* colspan과 rowspan을 이용하면 전체 셀의 갯수를 셈할 때 햇깔리기 쉬운데 이것을 방지하는려면 \n* 전체 줄의 갯수와 칸의 갯수를 셈한 뒤 칸의 갯수는 colspan 속성이 없는 것은 셀의 갯수를, colspan속성이 있으면 속성값을 더해 비교하면 정신건강에 도움이 된다.\n* rowspan의 경우엔 tr의 갯수와 rowspan의 속성값을 비교하면 정신건강에 도움이 된다.(예를 들어 샘플의 전체 칸의 갯수는 11개고 줄의 갯수는 2개다)\n* 처음 하면 꽤나 햇깔리기 좋기때문에 연습을 하면서 감을 익히는게 최선이다.(머리가 좋으면 금방 원리를 이해할 수 있다. 머리가 좋지않다면 연습만이 살길이다. 참고로 나는 능숙하게 쓰기까지 한 3개월쯤 걸린것 같다......)",
      "json_metadata": "{\"tags\":[\"kr-dev\",\"dev\",\"html\",\"table\",\"span\"],\"image\":[\"https://steemitimages.com/DQmRMLnpT4b9BP2Jf7WKbbjVXy5UDxoRsqTCrTYnn3Nk1AQ/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png\",\"https://steemitimages.com/DQmbCvqZq4QBb2qAfZXvECxppGELLjsGAL7BcWTc7Pn54BB/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg\",\"https://steemitimages.com/DQmdp9bL8PWn7UfPfGrFMoUuKLRk2GAmMnQ9t97SSGCR8PG/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png\",\"https://steemitimages.com/DQmbWRt14gUwBUeuw9MPdvi924Pumi416PYLnqxjucAbh7r/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(2).png\",\"https://steemitimages.com/DQmVaAdmaczm6bchss1eXdSJG9Y4pKMhXZ3Q2YP2cPLbazt/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png\",\"https://steemitimages.com/DQmec6eSFYmTbHkzoHtUnqNGGP47ysir2sumBsCrEuUEXza/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png\",\"https://steemitimages.com/DQmZTsZ4TFKLoYJGktYrN7AT4zY9MxyCZFYirMWFEiK1gjF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
      "parent_author": "",
      "parent_permlink": "kr-dev",
      "permlink": "table-colspan-rowspan",
      "title": "Table colspan, rowspan"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-07T01:32:39",
  "trx_id": "e73447d50f76ba7b6d5d4379ef366856353200e6",
  "trx_in_block": 23,
  "virtual_op": 0
}
2018/03/06 10:36:39
authorigna84
permlinkspring-security-preauthorize-postauthorize
votercodingman
weight10000 (100.00%)
Transaction InfoBlock #20435690/Trx e7d297bd95b1eb70fb5cc0e86346e4b79db63f40
View Raw JSON Data
{
  "block": 20435690,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-security-preauthorize-postauthorize",
      "voter": "codingman",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T10:36:39",
  "trx_id": "e7d297bd95b1eb70fb5cc0e86346e4b79db63f40",
  "trx_in_block": 36,
  "virtual_op": 0
}
2018/03/06 07:28:33
idfollow
json["follow",{"follower":"igna84","following":"twinbraid","what":["blog"]}]
required auths[]
required posting auths["igna84"]
Transaction InfoBlock #20431931/Trx 8bdbf6c234b95460e155b80640756f85a9927f99
View Raw JSON Data
{
  "block": 20431931,
  "op": [
    "custom_json",
    {
      "id": "follow",
      "json": "[\"follow\",{\"follower\":\"igna84\",\"following\":\"twinbraid\",\"what\":[\"blog\"]}]",
      "required_auths": [],
      "required_posting_auths": [
        "igna84"
      ]
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T07:28:33",
  "trx_id": "8bdbf6c234b95460e155b80640756f85a9927f99",
  "trx_in_block": 47,
  "virtual_op": 0
}
igna84upvoted (100.00%) @twinbraid / 3b9m5n-wallet
2018/03/06 07:28:21
authortwinbraid
permlink3b9m5n-wallet
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20431927/Trx 1629da5d776ecd78b01eb773e25b08f93398175d
View Raw JSON Data
{
  "block": 20431927,
  "op": [
    "vote",
    {
      "author": "twinbraid",
      "permlink": "3b9m5n-wallet",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T07:28:21",
  "trx_id": "1629da5d776ecd78b01eb773e25b08f93398175d",
  "trx_in_block": 16,
  "virtual_op": 0
}
igna84upvoted (100.00%) @jacobyu / 2
2018/03/06 02:17:24
authorjacobyu
permlink2
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20425713/Trx 8323f5ae742c107c6b73f36316c4bffb7264f1e2
View Raw JSON Data
{
  "block": 20425713,
  "op": [
    "vote",
    {
      "author": "jacobyu",
      "permlink": "2",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T02:17:24",
  "trx_id": "8323f5ae742c107c6b73f36316c4bffb7264f1e2",
  "trx_in_block": 20,
  "virtual_op": 0
}
2018/03/06 00:54:45
authorigna84
permlinkspring-security-preauthorize-postauthorize
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20424062/Trx ff5ff5da8ddbc48bb31c4753bf6a6cd37a0162a6
View Raw JSON Data
{
  "block": 20424062,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-security-preauthorize-postauthorize",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T00:54:45",
  "trx_id": "ff5ff5da8ddbc48bb31c4753bf6a6cd37a0162a6",
  "trx_in_block": 63,
  "virtual_op": 0
}
2018/03/06 00:54:45
authorigna84
body요즘은 처음에 시작했던 시리즈는 만들지 않고 짧은 코딩들이나 팁을 계속 올리고 있는데 이것은 시리즈를 만들기 위해 잠깐 숨고르기를 하고 있는 중이기 때문이라고 믿어주길 바란다는 서문을 미리 깔고.... 요즘 지난번 포스팅에서 언급했듯 프로젝트를 진행하면서 Spring boot로 Restful APIs 서버를 개발하고 있는 중인데 이 Restful APIs의 가장 치명적인 단점은 보안모델을 만들기가 매우 까다롭다는 것이다. 아무렇게나 대충 만들면 스니핑 툴을 이용한 Javascript 변조를 이용해 해킹이 가능할 수도 있고 쿠키를 이용하면 그건 그것대로 문제가 된다. 툴키디급 해커들의 좋은 놀이터를 만들어주게 된다는 거다. 그래서 조금(?) 공부를 한 친구들은 OAuth2나 Spring Security를 이용한 인증처리를 하면 되는 것 아니냐 하면 그건 반만 생각한 대답일거다. 인증이라는 것은 크게 두가지로 생각해볼 수 있는데 상술(上述)한 로그인 인증과 다른 하나는 API에 대한 접근 "권한" 처리다. * 로그인 인증 * 권한처리 이 두가지가 보안처리의 핵심적인 기능이다. 로그인이야 너무 당연하고 보안관련해서 가장먼저 배우는 것이니 이부분은 나중에 OAuth2를 포스팅하면서 같이 이야기하도록 하고 오늘은 두번째 "권한처리"에 대한 이야기를 먼저 해보자. API를 만들다보면 어떤 조건에 부합하는 사용자만 접근을 할 수 있도록 제한을 둬야만 할 때가 있다. 예를들어, 관리자만 접근할 수 있는 API, 본인만 접근 할 수 있는 API 등등 뭔가 까다롭고 복잡한 조건을 걸어서 접근을 제한하고 싶은 API가 있다면 그걸 어떻게 처리할 것이냐가 오늘 포스팅의 주제다. 일단 Spring Security는 ROLE 기반의 권한처리가 기본이다. 아래의 코드 처럼. ``` @Secured("ROLE_ADMIN") @RequestMapping( value = "/my/api/address", method = RequestMethod.GET ) public String somthingMethod( HttpServletRequest request, HttpServletResponse response ){ return "Hello, World!"; } ``` @Secured 어노테이션을 이용하면 사용자의 권한정보에 따라 자동으로 해당 메서드의 접근을 제한할 수 있게 된다. 생각외로 매우 간편한 방식이다. 그런데 이렇게 Spring Security를 셋팅하고 사용하면서 나는 생각했다. >요 메서드는 "사용자 본인"과 관리자만 접근할 수 있는 메서드로 지정하고 싶구나 이러한 요구사항은 웹사이트를 개발할 때 빈번하게 발생하는 요구사항이다. 가장 많이 발생하는 부분이 바로 개인정보영역이다. SM(Server(or Service) Management) 업무를 하는 개발자의 경우 가장 많이 받는 연락이 "제가 암호를 까먹어서"라는 연락이다. 뭐, 바로 암호를 알려주면 좋겠지만 일반적으로 암호같은 것은 해시(Hash)화 해서 저장해 놓기때문에 관리자라 해도 암호를 알려줄 수 없는 경우가 많다. 그래서 대신 암호를 초기화 하거나 초기화 할 수 있도록 메일을 보내주는데 이때 관리자는 개인정보 영역을 접근할 권한을 들고 있어야하는 경우가 생긴다. (물론 관리자 대부분은 (법적으로 책임을 지는)보안서약을 하고 업무를 하고 있기때문에 개인정보를 유출 시킬 수 없다.) 위와 같은 경우 개인정보에 접근이 가능한 사용자는 "사용자 본인"을 포함한 "관리자 권한"을 들고 있는 사용자여야 한다. 그럼 위에서 이야기한 @Secured 어노테이션의 권한설정을 관리자로 지정하더라도 문제는 "본인"만 접근이 가능해야한다는 조건엔 부합되지 않는 다는 문제가 생긴다. 그래서 일반 사용자용, 관리자용 함수를 두개 만들어서 처리하는 방법도 있을 수 있지만 아름다운(?) 코딩을 지향하는 우리가 같은 역할을 하는 함수를 권한문제 때문에 두개나 만드는건 좋은 방법이 아니라는 것을 본능처럼 알고 있을 것이라고 믿어 의심치 않는다. 게다가 코드 상에서 본인임을 인증하기위한 로직을 짜기 위해서 SecurityContextHolder.getContext().getAuthentication().getPrincipal() 에 접근해서 현재 사용자의 정보를 들고와 확인하는 코드를 만들어내는건 효율하고는 상관이 없어지게 된다. 매번 클라이언트의 데이터 변조를 통한 해킹을 방지하려고 저 코드를 사용하기엔 너무 비효율적이다. 물론 결과만을 놓고 보면 충분히 생각해낼 수 있는 방법이라고 생각한다. 나도 예전에 Security를 처음 접했을 땐 과정은 모르겠고 일단 만들자 해서 문제들을 해결(?) 했었다. (우리는 흔한 말로 문제를 시멘트로 발라버린다고 표현했었는데..) 하지만 좋은 방법이 아니다. 코드는 아름다워야한다. 간결하고 단순해야하며 나 이외의 다른 사람이 봤을 때 부끄러운 코드를 작성해서는 안된다. 그래서 아래의 해결방법을 찾았다. 일단, 우리는 위에서 이야기했던 요구사항에 대응하기 위해서 몇가지 표현식들을 알아둘 필요가 있다. ([https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#el-common-built-in](https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#el-common-built-in)) * hasRole([role]) : 현재 사용자의 권한이 파라미터의 권한과 동일한 경우 true * hasAnyRole([role1,role2]) : 현재 사용자의 권한디 파라미터의 권한 중 일치하는 것이 있는 경우 true * principal : 사용자를 증명하는 주요객체(User)를 직접 접근할 수 있다. * authentication : SecurityContext에 있는 authentication 객체에 접근 할 수 있다. * permitAll : 모든 접근 허용 * denyAll : 모든 접근 비허용 * isAnonymous() : 현재 사용자가 익명(비로그인)인 상태인 경우 true * isRememberMe() : 현재 사용자가 RememberMe 사용자라면 true * isAuthenticated() : 현재 사용자가 익명이 아니라면 (로그인 상태라면) true * isFullyAuthenticated() : 현재 사용자가 익명이거나 RememberMe 사용자가 아니라면 true 이 표현식들을 어떻게 사용하느냐하면 아래와같이 사용할 수 있다. ``` @PostAuthorize("isAuthenticated() and (( returnObject.name == principal.name ) or hasRole('ROLE_ADMIN'))") @RequestMapping( value = "/{seq}", method = RequestMethod.GET ) public User getuser( @PathVariable("seq") long seq ){ return userService.findOne(seq); } ``` @PostAuthorize 어노테이션은 함수를 실행하고 클라이언트한테 응답을 하기 직전에 권한을 검사하는 어노테이션이다. 표현식에는 포함되지 않았지만 returnObject란 이 함수가 반환하는 데이터 오브젝트를 의미한다. 그래서 위의 @PostAuthorize 어노테이션의 의미를 우리말로 번역하면 이렇게 되는 거다. > 잠깐, 클라이언트한테 응답하기 전에 검문이 있겠습니다. 로그인상태 입니까? > 그리고 반환되는 사용자의 이름과 현재 사용자의 이름이 일치합니까? 또는 현재 사용자가 관리자 권한을 들고있습니까? > 이 조건을 만족하는 사용자의 경우에만 응답할 수 있습니다. 아니라면 403 에러로 응답해드립니다. 이렇게 이야기 할 수 있다. 그리고 @PostAuthorize와는 다르게 @PreAuthorize 라는 어노테이션이 있는데 이건 요청이 들어와 함수를 실행하기 전에 권한을 검사하는 어노테이션이다. 아래와 같이 사용할 수 있다. ``` @PreAuthorize("isAuthenticated() and (( #user.name == principal.name ) or hasRole('ROLE_ADMIN'))") @RequestMapping( value = "", method = RequestMethod.PUT) public ResponseEntity<Message> updateUser( User user ){ userService.updateUser( user ); return new ResponseEntity<Message>( new Message(), HttpStatus.OK ); } ``` @PreAuthorize에도 @PostAuthorize의 returnObject 처럼 "파라미터"에 접근할 수 있는 접두문자가 있는데 그게 바로 "#"이다. 그래서 표현식에서 #user 라고 하면 파라미터로 전달되는 user 객체에 접근할 수 있다. 의미는 위에서 이야기 한 것과 비슷한 의미다. 다른 점이 있다면 함수를 실행하기 전이냐 아니면 사용자에게 응답하기 전이냐의 시점차이가 있을 뿐이다. @Secured 어노테이션 외에 @PreAuthorize와 @PostAuthorize 어노테이션을 이용하면 조금 더 복잡하고 어려운 권한 설정을 손쉽게 해결할 수 있다. 오늘은 여기까지!
json metadata{"tags":["kr-dev","java","spring","springboot","springsecurity"],"users":["secured","postauthorize","preauthorize"],"links":["https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#el-common-built-in"],"app":"steemit/0.1","format":"markdown"}
parent author
parent permlinkkr-dev
permlinkspring-security-preauthorize-postauthorize
titleSpring Security @PreAuthorize, @PostAuthorize 를 사용하는 신박한 전처리 후처리 기법
Transaction InfoBlock #20424062/Trx ff5ff5da8ddbc48bb31c4753bf6a6cd37a0162a6
View Raw JSON Data
{
  "block": 20424062,
  "op": [
    "comment",
    {
      "author": "igna84",
      "body": "요즘은 처음에 시작했던 시리즈는 만들지 않고 짧은 코딩들이나 팁을 계속 올리고 있는데 이것은 시리즈를 만들기 위해 잠깐 숨고르기를 하고 있는 중이기 때문이라고 믿어주길 바란다는 서문을 미리 깔고....\n\n\n\n요즘 지난번 포스팅에서 언급했듯 프로젝트를 진행하면서 Spring boot로 Restful APIs 서버를 개발하고 있는 중인데\n\n이 Restful APIs의 가장 치명적인 단점은 보안모델을 만들기가 매우 까다롭다는 것이다. 아무렇게나 대충 만들면 스니핑 툴을 이용한 Javascript 변조를 이용해 해킹이 가능할 수도 있고 쿠키를 이용하면 그건 그것대로 문제가 된다. 툴키디급 해커들의 좋은 놀이터를 만들어주게 된다는 거다. 그래서 조금(?) 공부를 한 친구들은 OAuth2나 Spring Security를 이용한 인증처리를 하면 되는 것 아니냐 하면 그건 반만 생각한 대답일거다.\n\n\n\n인증이라는 것은 크게 두가지로 생각해볼 수 있는데 상술(上述)한 로그인 인증과 다른 하나는 API에 대한 접근 \"권한\" 처리다.\n\n* 로그인 인증\n* 권한처리\n\n이 두가지가 보안처리의 핵심적인 기능이다.\n\n로그인이야 너무 당연하고 보안관련해서 가장먼저 배우는 것이니 이부분은 나중에 OAuth2를 포스팅하면서 같이 이야기하도록 하고 오늘은 두번째 \"권한처리\"에 대한 이야기를 먼저 해보자.\n\nAPI를 만들다보면 어떤 조건에 부합하는 사용자만 접근을 할 수 있도록 제한을 둬야만 할 때가 있다. 예를들어, 관리자만 접근할 수 있는 API, 본인만 접근 할 수 있는 API 등등 뭔가 까다롭고 복잡한 조건을 걸어서 접근을 제한하고 싶은 API가 있다면 그걸 어떻게 처리할 것이냐가 오늘 포스팅의 주제다.\n\n일단 Spring Security는 ROLE 기반의 권한처리가 기본이다. 아래의 코드 처럼.\n\n```\n@Secured(\"ROLE_ADMIN\")\n@RequestMapping( value = \"/my/api/address\", method = RequestMethod.GET )\npublic String somthingMethod( HttpServletRequest request, HttpServletResponse response ){\n    return \"Hello, World!\";\n}\n```\n\n@Secured 어노테이션을 이용하면 사용자의 권한정보에 따라 자동으로 해당 메서드의 접근을 제한할 수 있게 된다. 생각외로 매우 간편한 방식이다. 그런데 이렇게 Spring Security를 셋팅하고 사용하면서 나는 생각했다.\n\n>요 메서드는 \"사용자 본인\"과  관리자만 접근할 수 있는 메서드로 지정하고 싶구나\n\n이러한 요구사항은 웹사이트를 개발할 때 빈번하게 발생하는 요구사항이다. 가장 많이 발생하는 부분이 바로 개인정보영역이다.\n\nSM(Server(or Service) Management) 업무를 하는 개발자의 경우 가장 많이 받는 연락이 \"제가 암호를 까먹어서\"라는 연락이다. 뭐, 바로 암호를 알려주면 좋겠지만 일반적으로 암호같은 것은 해시(Hash)화 해서 저장해 놓기때문에 관리자라 해도 암호를 알려줄 수 없는 경우가 많다. 그래서 대신 암호를 초기화 하거나 초기화 할 수 있도록 메일을 보내주는데 이때 관리자는 개인정보 영역을 접근할 권한을 들고 있어야하는 경우가 생긴다. \n\n(물론 관리자 대부분은 (법적으로 책임을 지는)보안서약을 하고 업무를 하고 있기때문에 개인정보를 유출 시킬 수 없다.)\n\n\n\n위와 같은 경우 개인정보에 접근이 가능한 사용자는 \"사용자 본인\"을 포함한 \"관리자 권한\"을 들고 있는 사용자여야 한다.\n\n그럼 위에서 이야기한 @Secured 어노테이션의 권한설정을 관리자로 지정하더라도 문제는 \"본인\"만 접근이 가능해야한다는 조건엔 부합되지 않는 다는 문제가 생긴다.\n\n\n\n그래서 일반 사용자용, 관리자용 함수를 두개 만들어서 처리하는 방법도 있을 수 있지만 아름다운(?) 코딩을 지향하는 우리가 같은 역할을 하는 함수를 권한문제 때문에 두개나 만드는건 좋은 방법이 아니라는 것을 본능처럼 알고 있을 것이라고 믿어 의심치 않는다. 게다가 코드 상에서 본인임을 인증하기위한 로직을 짜기 위해서 SecurityContextHolder.getContext().getAuthentication().getPrincipal() 에 접근해서 현재 사용자의 정보를 들고와 확인하는 코드를 만들어내는건 효율하고는 상관이 없어지게 된다. 매번 클라이언트의 데이터 변조를 통한 해킹을 방지하려고 저 코드를 사용하기엔 너무 비효율적이다.\n\n물론 결과만을 놓고 보면 충분히 생각해낼 수 있는 방법이라고 생각한다. 나도 예전에 Security를 처음 접했을 땐 과정은 모르겠고 일단 만들자 해서 문제들을 해결(?) 했었다.\n\n(우리는 흔한 말로 문제를 시멘트로 발라버린다고 표현했었는데..)\n\n하지만 좋은 방법이 아니다. 코드는 아름다워야한다. 간결하고 단순해야하며 나 이외의 다른 사람이 봤을 때 부끄러운 코드를 작성해서는 안된다.\n\n그래서 아래의 해결방법을 찾았다.\n\n\n\n일단, 우리는 위에서 이야기했던 요구사항에 대응하기 위해서 몇가지 표현식들을 알아둘 필요가 있다.\n\n([https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#el-common-built-in](https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#el-common-built-in))\n\n* hasRole([role]) : 현재 사용자의 권한이 파라미터의 권한과 동일한 경우 true\n* hasAnyRole([role1,role2]) : 현재 사용자의 권한디 파라미터의 권한 중 일치하는 것이 있는 경우 true\n* principal : 사용자를  증명하는 주요객체(User)를 직접 접근할 수 있다.\n* authentication : SecurityContext에 있는 authentication 객체에 접근 할 수 있다.\n* permitAll : 모든 접근 허용\n* denyAll : 모든 접근 비허용\n* isAnonymous() : 현재 사용자가 익명(비로그인)인 상태인 경우 true\n* isRememberMe() : 현재 사용자가 RememberMe 사용자라면 true\n* isAuthenticated() : 현재 사용자가 익명이 아니라면 (로그인 상태라면) true\n* isFullyAuthenticated() : 현재 사용자가 익명이거나 RememberMe 사용자가 아니라면 true\n\n이 표현식들을 어떻게 사용하느냐하면 아래와같이 사용할 수 있다.\n\n```\n@PostAuthorize(\"isAuthenticated() and (( returnObject.name == principal.name ) or hasRole('ROLE_ADMIN'))\")\n@RequestMapping( value = \"/{seq}\", method = RequestMethod.GET )\npublic User getuser( @PathVariable(\"seq\") long seq ){\n    return userService.findOne(seq);\n}\n```\n\n@PostAuthorize 어노테이션은 함수를 실행하고 클라이언트한테 응답을 하기 직전에 권한을 검사하는 어노테이션이다. 표현식에는 포함되지 않았지만 returnObject란 이 함수가 반환하는 데이터 오브젝트를 의미한다.\n\n그래서 위의 @PostAuthorize 어노테이션의 의미를 우리말로 번역하면 이렇게 되는 거다.\n\n> 잠깐, 클라이언트한테 응답하기 전에 검문이 있겠습니다. 로그인상태 입니까?\n> 그리고 반환되는 사용자의 이름과 현재 사용자의 이름이 일치합니까? 또는 현재 사용자가 관리자 권한을 들고있습니까?\n> 이 조건을 만족하는 사용자의 경우에만 응답할 수 있습니다. 아니라면 403 에러로 응답해드립니다.\n\n이렇게 이야기 할 수 있다. 그리고 @PostAuthorize와는 다르게 @PreAuthorize 라는 어노테이션이 있는데 이건 요청이 들어와 함수를 실행하기 전에 권한을 검사하는 어노테이션이다. 아래와 같이 사용할 수 있다.\n\n```\n@PreAuthorize(\"isAuthenticated() and (( #user.name == principal.name ) or hasRole('ROLE_ADMIN'))\")\n@RequestMapping( value = \"\", method = RequestMethod.PUT)\npublic ResponseEntity<Message> updateUser( User user ){\n    userService.updateUser( user );\n    return new ResponseEntity<Message>( new Message(), HttpStatus.OK );\n}\n```\n@PreAuthorize에도 @PostAuthorize의 returnObject 처럼 \"파라미터\"에 접근할 수 있는 접두문자가 있는데 그게 바로 \"#\"이다. 그래서 표현식에서 #user 라고 하면 파라미터로 전달되는 user 객체에 접근할 수 있다. 의미는 위에서 이야기 한 것과 비슷한 의미다. 다른 점이 있다면 함수를 실행하기 전이냐 아니면 사용자에게 응답하기 전이냐의 시점차이가 있을 뿐이다.\n\n@Secured 어노테이션 외에 @PreAuthorize와 @PostAuthorize 어노테이션을 이용하면 조금 더 복잡하고 어려운 권한 설정을 손쉽게 해결할 수 있다.\n\n오늘은 여기까지!",
      "json_metadata": "{\"tags\":[\"kr-dev\",\"java\",\"spring\",\"springboot\",\"springsecurity\"],\"users\":[\"secured\",\"postauthorize\",\"preauthorize\"],\"links\":[\"https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#el-common-built-in\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
      "parent_author": "",
      "parent_permlink": "kr-dev",
      "permlink": "spring-security-preauthorize-postauthorize",
      "title": "Spring Security @PreAuthorize, @PostAuthorize 를 사용하는 신박한 전처리 후처리 기법"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T00:54:45",
  "trx_id": "ff5ff5da8ddbc48bb31c4753bf6a6cd37a0162a6",
  "trx_in_block": 63,
  "virtual_op": 0
}
igna84upvoted (100.00%) @codingman / 4apv1f-3d
2018/03/06 00:46:30
authorcodingman
permlink4apv1f-3d
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20423897/Trx 7969eb70d203336f24041e77afd1a47beffda71e
View Raw JSON Data
{
  "block": 20423897,
  "op": [
    "vote",
    {
      "author": "codingman",
      "permlink": "4apv1f-3d",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T00:46:30",
  "trx_id": "7969eb70d203336f24041e77afd1a47beffda71e",
  "trx_in_block": 13,
  "virtual_op": 0
}
igna84upvoted (100.00%) @jacobyu / 4loogd
2018/03/06 00:45:24
authorjacobyu
permlink4loogd
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20423875/Trx bfcb3db7c0f98dd869451a4630e13b486bf35d39
View Raw JSON Data
{
  "block": 20423875,
  "op": [
    "vote",
    {
      "author": "jacobyu",
      "permlink": "4loogd",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-06T00:45:24",
  "trx_id": "bfcb3db7c0f98dd869451a4630e13b486bf35d39",
  "trx_in_block": 2,
  "virtual_op": 0
}
igna84upvoted (100.00%) @puberty / 85-2-sns-enlte
2018/03/05 12:09:12
authorpuberty
permlink85-2-sns-enlte
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20408756/Trx 7fcd4c03b12c1372484fb39710ffef570048d06a
View Raw JSON Data
{
  "block": 20408756,
  "op": [
    "vote",
    {
      "author": "puberty",
      "permlink": "85-2-sns-enlte",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T12:09:12",
  "trx_id": "7fcd4c03b12c1372484fb39710ffef570048d06a",
  "trx_in_block": 35,
  "virtual_op": 0
}
2018/03/05 11:19:21
authorigna84
permlinkspring-boot-jpa-hibernate-2
votercodingman
weight10000 (100.00%)
Transaction InfoBlock #20407760/Trx 82e8d3005c0f849d9681ec9bc598d8b912442019
View Raw JSON Data
{
  "block": 20407760,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-boot-jpa-hibernate-2",
      "voter": "codingman",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T11:19:21",
  "trx_id": "82e8d3005c0f849d9681ec9bc598d8b912442019",
  "trx_in_block": 8,
  "virtual_op": 0
}
igna84upvoted (100.00%) @codingman / ugkrj-interrupt
2018/03/05 11:00:24
authorcodingman
permlinkugkrj-interrupt
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20407381/Trx 97ce088d7399ee1da1e35073c7242c6e13cad75c
View Raw JSON Data
{
  "block": 20407381,
  "op": [
    "vote",
    {
      "author": "codingman",
      "permlink": "ugkrj-interrupt",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T11:00:24",
  "trx_id": "97ce088d7399ee1da1e35073c7242c6e13cad75c",
  "trx_in_block": 52,
  "virtual_op": 0
}
igna84claimed reward balance: 0.094 SBD, 0.036 SP
2018/03/05 07:54:21
accountigna84
reward sbd0.094 SBD
reward steem0.000 STEEM
reward vests59.235300 VESTS
Transaction InfoBlock #20403660/Trx 20791ab57ba85c0a040c30e1d483ebe56eac48b9
View Raw JSON Data
{
  "block": 20403660,
  "op": [
    "claim_reward_balance",
    {
      "account": "igna84",
      "reward_sbd": "0.094 SBD",
      "reward_steem": "0.000 STEEM",
      "reward_vests": "59.235300 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T07:54:21",
  "trx_id": "20791ab57ba85c0a040c30e1d483ebe56eac48b9",
  "trx_in_block": 68,
  "virtual_op": 0
}
2018/03/05 06:41:48
authorigna84
permlinkspring-boot-jpa-hibernate-2
voterhr1
weight2 (0.02%)
Transaction InfoBlock #20402209/Trx 2b456a318fb75a1753728b3941c028747f07a829
View Raw JSON Data
{
  "block": 20402209,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-boot-jpa-hibernate-2",
      "voter": "hr1",
      "weight": 2
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T06:41:48",
  "trx_id": "2b456a318fb75a1753728b3941c028747f07a829",
  "trx_in_block": 34,
  "virtual_op": 0
}
2018/03/05 06:11:48
authorigna84
permlinkspring-boot-jpa-hibernate-2
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20401610/Trx b1d1fa62e59ed7f225496fc15f7bc792b8c4441e
View Raw JSON Data
{
  "block": 20401610,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-boot-jpa-hibernate-2",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T06:11:48",
  "trx_id": "b1d1fa62e59ed7f225496fc15f7bc792b8c4441e",
  "trx_in_block": 29,
  "virtual_op": 0
}
igna84published a new post: spring-boot-jpa-hibernate-2
2018/03/05 06:11:48
authorigna84
body자, 지난시간 JPA를 위해 DB도 만들고 셋팅도 하고 Table도 만들고 이것저것 했다. 이제 Repository와 Service 를 만들어서 user 테이블의 정보를 가져오는 API를 만드는 것이 이번 포스팅의 목적이다. 자, 우리가 순차적으로 작업을 진행할 필요가 있는데 무엇을 먼저 작업하면 좋을지 하는 거다. 우리는 user테이블에서 데이터를 가져와서 API를 이용해 클라이언트에게 전달할 건데 가장 필요한게 무엇일까? 대부분의 개발자들이 먼저 하는 작업이 무엇이냐하면 Controller먼저 만든다. 일단 요청을 해야 뭔가를 얻어올 수 있다고 생각해서다. 하지만 뭔가를 얻어온다는 말에 속에 간과하고 있는게 한가지 있다. 그것이 바로 "뭔가" 라고 하는 단어다. "뭔가"를 얻어오려면 그 "뭔가"를 담을 수 있는 통이 필요한데 그것이 있어야 비로소 DB에서 데이터를 가져오고, 그 데이터를 가공할 수도 있다. 한마디로 "여자친구에게 바라는 점이 무엇인지 묻기전에 여자친구가 있는지 부터 물어보는게 예의"인것 처럼 말이다. ![다운로드.jpg](https://steemitimages.com/DQmZU64tXsD9DgyqLD3rXAhzynUHLn2XvrK1sKjMVg929qH/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg) 이런 이유로 일단 엔티티(Entity)라는걸 만들어 볼거다. 이 엔티티라는게 무어냐 물어본다면 사전적인 의미로는 "실체"라고도 하고 "객체"라고도 하는데 개발에서도 이 의미와 별반 다르지 않은 "어떤 데이터의 실질적인 정의" 라고 생각하면 되겠다. ![다운로드 (1).jpg](https://steemitimages.com/DQmSBxaX4XRJdmnpdD4F3UrbLM2dCs8nMYZpyn8EjgYHrW9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).jpg) 그래서 지금부터 Entity클래스를 만들건데 Lombok 라이브러리를 아주 적극 활용하겠다. 아, 뭔가 클래스를 새로만들땐 Package부터 만들어주는게 인지상정이다. 이 Entity 클래스는 "com.example.sample.data.entity"에 User라는 클래스명으로 만들도록 하겠다. ``` package com.example.sample.data.entity; import java.sql.Timestamp; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import lombok.Getter; import lombok.Setter; @Entity @Table(name = "user") @Setter @Getter public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private @Column(name = "uid") Long uid; private @Column(name = "email") String email; private @Column(name = "password") String password; private @Column(name = "nick") String nick; @CreationTimestamp private @Column(name = "cdate") Timestamp cdate; @UpdateTimestamp private @Column(name = "udate") Timestamp udate; } ``` 이렇게 만들면 Entity는 완성이 되었다. 음, 여기서 가장 중요한 부분은 @Column 어노테이션인데 이 Column 어노테이션에서는 DB의 실제 컬럼명을 입력해주면 된다. 그리고 @Id 어노테이션을 PK(Prime Key: 기본키)를 의미하며 GerneratedValue 어노테이션을 통해서 이 PK값을 어떻게 입력할 것인지 정의할 수 있다. * GenerationType.AUTO : DB에 맞게 자동 선택 * GenerationType.IDENTITY : DB의 Identity 컬럼을 이용 * GenerationType.SEQUENCE : DB의 시퀀스 컬럼을 이용 * GenerationType.TABLE : 유일성이 보장된 데이터베이스 테이블을 이용 위의 내용으로 정의하는데 조금 더 자세한 이야기는 링크를 따라가 공부해보도록 하자. ([http://jsaver.tistory.com/entry/Id%EC%99%80-GeneratedValue-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98](http://jsaver.tistory.com/entry/Id%EC%99%80-GeneratedValue-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98)) 그리고 별도로 @CreationTimestamp의 경우엔 데이터를 Insert해줄때 자동으로 시간을 입력해주고, @UpdateTimestamp는 Update해줄때 자동으로 시간을 입력해준다. 어노테이션의 의미를 대충 정리했고 Entity도 만들었다면 이제 Controller부터 만들어보도록하자. 이 Controller를 만든다는 것은 Restful API를 만든다는 이야기고 이 Restful API는 기본적인 기능규칙에 의해서 만들어진다고 생각하면 처음 공부할 때 편하다. 대부분(?)의 웹서비스는 CRUD를 기초로 만들어져있다.(C - Create, R - Read, U - Update, D - Delete를 의미한다.) UI의 구성도 API 구조도 이 CRUD를 기초로 만들면 일단 큰 골격에서 크게 벗어나지 않는다. 이런 내용을 Restful APIs에서는 Request method로 구분해서 처리를 하는데 아래와 같이 정리한다. * GET : 혹은 항목을 조회할 때 (Read) * POST : 등록할 때 (Create) * PUT, PATCH : 수정할 때 (Update) * DELETE : 삭제할 때(Delete) 이 항목을 어디서 정의하느냐 하면 우리가 만들 Controller에서 RequestMapping 어노테이션에서 정의한다. 이제부터 나는 위의 내용을 토대로 UserController라는 클래스를 com.example.sample.controller 패키지에 만들도록 하겠다. UserController.java ``` package com.example.sample.controller; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.example.sample.model.ApiResponseMessage; import com.example.sample.data.entity.User; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @RestController @RequestMapping(value = "/user") @Api(value = "UserController", description = "사용자 관련 API",basePath = "/user") public class UserController { @RequestMapping(value = "", method = RequestMethod.GET) @ApiOperation(value = "사용자 목록 조회", notes = "사용자 목록을 조회하는 API.") public List<User> getUserList(){ return null; } @RequestMapping(value = "/{uid}", method = RequestMethod.GET) @ApiOperation(value = "사용자 정보 조회", notes = "사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.") public User getUser( @PathVariable("uid") Long uid ){ return null; } @RequestMapping(value = "", method = RequestMethod.POST) @ApiOperation(value = "사용자 정보 등록", notes = "사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.") public ResponseEntity<ApiResponseMessage> insertUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); return response; } @RequestMapping(value = "", method = RequestMethod.PUT) @ApiOperation(value = "사용자 정보 수정", notes = "사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.<br>이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.") public ResponseEntity<ApiResponseMessage> updateUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); return response; } @RequestMapping(value = "/{uid}", method = RequestMethod.DELETE) @ApiOperation(value = "사용자 정보 삭제", notes = "사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.") public ResponseEntity<ApiResponseMessage> deleteUser( @PathVariable("uid") Long uid ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); return response; } } ``` 처음에 개발에 들어갈때는 일단 뭔가 기능을 구현하는 것보다 전체적으로 구조를 잡아 나가는 것이 중요하다. 이 과정을 통해 개발이 진행되면 전체적을 기능의 목록들이 나열되게 되고 위와 같이 아무 기능은 없으나 앞으로 개발해야하는 기능의 목록들을 나열하면 프로젝트의 큰 틀이 눈에 들어오게된다. 일단 위와 같이 Controller 클래스를 짰다면 이제 Service 인터페이스 클래스를 만들어보도록 하자. 인터페이스 클래스가 뭔지 모르는 사람은 검색을 좀 해서 개념을 잡고 돌아오거나 혹은 나중에(언제가 될지 모르겠지만) 진행할 인터페이스와 추상화 클래스에 대한 설명으로 포스팅하는 것을 기다리도록. 이번 "프로젝트 시작하기" 시리즈의 목적은 "내가 뭘 좀 모르지만 일단 프로젝트를 진행해야겠는데 뭘 어떻게하는지 알려주는" 것에 있다. 그러므로 자세하게 설명하고 싶은 마음은 있으나 자세하게 설명했다가는 이 포스팅을 끝내지 못할 수도 있으니 일단 너그러운 마음으로 넘어가도록 하자. 나는 com.example.sample.service 패키지에 UserService 인터페이스 클래스를 만들거다. UserService.java ``` package com.example.sample.service; import java.util.List; import com.example.sample.data.entity.User; public interface UserService { /** * 사용자 목록 조회 * @return */ public List<User> selectUserList(); /** * 사용자 조회 * @param uid * @return */ public User selectUser(Long uid); /** * 사용자 등록 * @param user */ public void insertUser(User user); /** * 사용자 정보 수정 * @param user */ public void updateUser(User user); /** * 사용자 삭제 * @param uid */ public void deleteUser(Long uid); } ``` 인터페이스 클래스를 만들었다면 이제 구현체도 만들어야지. com.example.sample.service.impl 패키지에 UserServiceImpl 이라는 클래스를 만들어 방금 만든 UserService 인터페이스 클래스의 구현체를 만들건데 아래의 이미지를 따라서 만들면 작업하기가 편하다. ![다운로드.png](https://steemitimages.com/DQmZFT5qydK6uaYoo7Hm8yCER3wbNaAVJxHonYG2ZPpRcPR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png) 클래스의 이름을 적고 Interfaces 항목 오른쪽에 있는 Add라는 버튼을 클릭한다. ![다운로드 (1).png](https://steemitimages.com/DQmdxHUFLzRK5RRMXcMPA1i6AxJ2msppFs8kjWRgQfEzkS6/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png) 여기에서 우리가 방금 만든 UserService를 검색하면 Matching Items에 항목이 나타날텐데 이 때 Package정보를 잘 보고 선택한 다음 OK버튼을 클릭한다. ![다운로드.png](https://steemitimages.com/DQmZFT5qydK6uaYoo7Hm8yCER3wbNaAVJxHonYG2ZPpRcPR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png) 위의 상태가 되었으면 Finish버튼을 클릭한다. 그러고 나면 알아서 인터페이스 클래스의 내용을 토대로 메서드들을 만들어준다. ![다운로드 (3).png](https://steemitimages.com/DQmXFGiSzfhbLBZsdfafA8Szf3Ke9ffSgtG7AniiLf2VVUr/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png) 그럼 일단 UserService에 대한 아주 기본적인 내용이 만들어졌으니 UserController 클래스에 약간(?)의 수정을 하도록 하자. ``` package com.example.sample.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.example.sample.model.ApiResponseMessage; import com.example.sample.data.entity.User; import com.example.sample.service.UserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @RestController @RequestMapping(value = "/user") @Api(value = "UserController", description = "사용자 관련 API",basePath = "/user") public class UserController { @Autowired UserService userService; @RequestMapping(value = "", method = RequestMethod.GET) @ApiOperation(value = "사용자 목록 조회", notes = "사용자 목록을 조회하는 API.") public List<User> getUserList(){ return userService.selectUserList(); } @RequestMapping(value = "/{uid}", method = RequestMethod.GET) @ApiOperation(value = "사용자 정보 조회", notes = "사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.") public User getUser( @PathVariable("uid") Long uid ){ return userService.selectUser(uid); } @RequestMapping(value = "", method = RequestMethod.POST) @ApiOperation(value = "사용자 정보 등록", notes = "사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.") public ResponseEntity<apiresponsemessage> insertUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.insertUser(user); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 등록에 실패하였습니다.", "ERROR00001", "Fail to registration for user information."); response = new ResponseEntity<apiresponsemessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } @RequestMapping(value = "", method = RequestMethod.PUT) @ApiOperation(value = "사용자 정보 수정", notes = "사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.<br>이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.") public ResponseEntity<ApiResponseMessage> updateUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.updateUser(user); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 수정에 실패하였습니다.", "ERROR00002", "Fail to update for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } @RequestMapping(value = "/{uid}", method = RequestMethod.DELETE) @ApiOperation(value = "사용자 정보 삭제", notes = "사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.") public ResponseEntity<ApiResponsemessage> deleteUser( @PathVariable("uid") Long uid ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.deleteUser(uid); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 삭제에 실패하였습니다.", "ERROR00003", "Fail to remove for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } } ``` 뭐가 많이 변했는데 데이터를 등록하고 수정하고 삭제하는 부분의 코드가 조금(?) 많이 늘었고 Autowired라고 해서 UserService라는 인터페이스 클래스를 자동으로 주입하게끔 해놨다. UserServiceImpl 클래스도 아니고 왜 인터페이스 클래스를 가져다 놓은걸까. 일단 조금 이따가 UserServiceImpl 클래스에 @Service라고 하는 어노테이션을 박아넣을텐데 이 Service 어노테이션은 자신이 어떤 인터페이스클래스를 구현했는지 판단하여 자동으로 @Autowired의 인터페이스 객체에 구현체를 넣어 사용할 수 있게 해준다. 뭔 소린지 모르겠다고? 당연하지, 이걸 이해하기 위해서는 @Autowired와 @Service, @Repository 와 Interface, Abstract의 차이점과 도데체 Interface가 뭐고 그와 같이 등장하는 Abstract(추상화) 클래스들은 무엇이며 이걸 이용해 할 수 있는 일은 무엇인지, 왜 이용해야하는지 등등등등 알아야할게 너무 많거든. 어떤 한 개념을 이해하기 위해 수반되는 배경지식의 양이 기하급수적으로 늘어나는데 그걸 이 포스팅 한장으로 배우기엔 설명하는 나도, 배우는 당신도 힘들다. 솔직히 Interface 클래스 하나만 배운다고 해도 아마 하나의 카테고리로 시리즈 포스팅을 해야할 걸. 그러니 잘 모르더라도 따라가도록 하자. 배경지식들은 차차 배운다고 생각하고. 지금 중요한건 사이트를 만드는거지 왜 Interface 클래스를 가져와 쓰는게 아니니까. 일단 이렇게 만들었으면 Repository 인터페이스 클래스를 만들건데 여기서 아주 아주 아주 JPA-Hibernate의 큰 장점이 등장하게 된다. 일단 만들고 나서 설명해주겠다. * Package: com.example.sample.data.repository * Class : UserRepository * Class Type : Interface ``` package com.example.sample.data.repository; import org.springframework.data.repository.CrudRepository; import com.example.sample.data.entity.User; public interface UserRepository extends CrudRepository<User, Long>{ } ``` 끝이다. 더이상 뭘 하지 않아도 된다. 어찌된 영문이냐고? 일단 우리는 최초에 "spring-boot-starter-data-jpa" 라이브러리를 pom.xml에 추가한걸 기억해야한다. 그래서 우리가 JPA의 기능을 이용할 수 있는 거니까. 그리고 JPA의 기능을 오늘 배우는게 가장 중요하다. 기 기능들을 잘 이용하면 생산성이 비약적으로 높아지기 때문이다. 생산성이 높아진다는 이야기는 곧 야근을 덜 할 수 있다는 이야기다. 일단 JPA의 기능은 3가지로 요약할 수 있다. * 메소드의 이름으로 쿼리 생성 * 메소드의 이름으로 JPA NamedQuery 호출 * @Query 어노테이션을 사용해서 Repository Interface에 쿼리를 직접 정의 이렇게 3가지를 이야기 한다. 그 중에서도 첫번째 기능이 중요하다. 두번째 기능이나 세번째 기능은 뭐 인터페이스 클래스에다가 쿼리를 직접 입력할 수 있다는건데 이건 생산성하고 별로 상관없는 이야기니까 패스. 메소드의 이름으로 쿼리를 생성한다는 이야기가 무엇이냐면 이런거다. JPA가 미리 정의해 놓은 메소드 명명 규칙이 있는데 그 규칙을 따라 인터페이스 클래스에 메소드를 선언하면 서버를 기동하거나 컴파일 단계에서 Spring Framework에서 자동으로 구현체를 만들어 준다. 이게 얼마나 중요한 내용이냐면 예전에 Mybatis를 사용했을 때나 혹은 이런 ORM 관련 라이브러리가 없었을 적에는 코드에 직접 SQL 문을 작성해야했다. ``` SELECT * FROM user WHERE email LIKE '%test%' ``` 이런 단순한 쿼리문 부터 아주 복잡한 쿼리문까지 만들었어야 했다. 그리고 메서드를 만들고 그걸 동작하게하고.. 뭐 암튼 할일이 엄청 많았는데 JPA를 이용하면 인터페이스 클래스에 아래와 같이 코딩하면 된다. ``` package com.example.sample.data.repository; import java.util.List; import org.springframework.data.repository.CrudRepository; import com.example.sample.data.entity.User; public interface UserRepository extends CrudRepository<User, Long>{ List<User> findByEmail(String email); } ``` 위와같이 코딩을 하면 그냥 email 컬럼을 검색해서 목록으로 결과물을 뱉어낸다. 쿼리는 어떻게 하냐고? 짤 필요없다. 그냥 JPA가 알아서 만들어준다. 언제 만들어주느냐면 서버가 기동될때, 코드가 컴파일 될 때 자동으로 SQL 문을 짜고 DB와 연동해 데이터 가져올 구현체를 생성해서 어플리케이션에 등록해준다. 앞으로 Query몰라도 어플리케이션을 만들 수 있다. 물론 복잡하고 Join도 해야하고 막 그런건 어떻게 하느냐고 물어보시는 분들 계신데 그건 나중에 QueryDSL라는걸 공부할 때 마저 하는걸로 하고, 오늘은 이 메소드 네이밍 규칙을 알아가도록 하자.([https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation)) 간혹 이런분들이 있다. "저는 조건이 좀 까다로와서요" 라면서 조건들을 나열하는 분들. 그런 분들을 위해 조건들을 아래와 같이 나열하는 것도 가능하다. ``` package com.example.sample.data.repository; import java.sql.Timestamp; import java.util.List; import org.springframework.data.repository.CrudRepository; import com.example.sample.data.entity.User; public interface UserRepository extends CrudRepository<User, Long>{ List<User> findByEmailLikeOrickLikeAndCdateAfter(String email, String nick, Timestamp cdate); } ``` 아, 그리고 이러한 규칙을 통해 만들어진 CrudRepository 인터페이스 클래스를 이용하면 기본적인 메소드들을 제공해서 개발자로 하여금 아무것도 코딩하지 않더라도 기능을 구현해준다. 정말이다. 아무것도 안해도 된다. 지원하는 기능은 아래와 같다. * count() : 개수 확인 * delete() : 1건 또는 여러 건 삭제 * deleteAll() : 모두 삭제 * exists() : ID 있는지 확인 * findAll() : 전체 또는 ID목록으로 조회 * findOne() : ID로 조회 * save() : 1건 또는 여러 건 저장, 존재하는 데이터는 수정 막 중구난방으로 설명이 진행되서 정신이 없을텐데 Repository에 대해 이제부터 정리를 좀 하겠다. 1. 데이터의 흐름 데이터의 흐름은 사용자의 요청 -> 컨트롤러(Controller) -> 서비스(Service) -> 리포지토리(Repository) -> DB 방향으로 Request(요청) 절차가 수행되고 이와 반대의 흐름으로 Response(응답) 절차를 수행하게 된다. 2. Controller 사용자의 요청과 응답을 직접적으로 처리하는 단계로 요청 내용을 정리하고 그에 맞는 응답을 할 수 있도록 서비스 메소드를 호출한다. 3. Service 컨트롤러에서 요청하는 데이터를 Repository의 메소드를 호출해 정제하고 정리해서 컨트롤러에 전달한다. 이때 Transaction을 정의하고 DB 커넥션 풀을 적절하게 관리할 수 있도록 설정한다. Transaction을 적절하게 설정하게 되면 데이터 처리중 문제가 발생했을 때 RollBack을 할 수 있다. 2. Repository 우리는 JPA 라이브러리를 이용하고 있어서 인터페이스클래스안에 네이밍을 맞춰서 메소드를 선언해놓으면 Spring boot가 기동할때 자동으로 구현체를 만들어서 붙여준다. 구현체를 만드느라 시간 낭비할 필요없다. 클라이언트의 요구사항이 매우 난해하여 굳이 뭔가를 해야할 때는 나중에 QueryDSL이라는 것을 통해 포스팅을 하겠다. 이제 좀 정리가 되는가? 그러면 UserController, UserService, UserServiceImpl, UserRepository 코드의 완성형을 아래에 쭉 나열해놓겠다. * Package: com.example.sample.controller * Class : UserController * Class Type : Class ``` package com.example.sample.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.example.sample.data.entity.User; import com.example.sample.model.ApiResponseMessage; import com.example.sample.service.UserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @RestController @RequestMapping(value = "/user") @Api(value = "UserController", description = "사용자 관련 API",basePath = "/user") public class UserController { @Autowired UserService userService; @RequestMapping(value = "", method = RequestMethod.GET) @ApiOperation(value = "사용자 목록 조회", notes = "사용자 목록을 조회하는 API.") public List<User> getUserList(){ return userService.selectUserList(); } @RequestMapping(value = "/{uid}", method = RequestMethod.GET) @ApiOperation(value = "사용자 정보 조회", notes = "사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.") public User getUser( @PathVariable("uid") Long uid ){ return userService.selectUser(uid); } @RequestMapping(value = "", method = RequestMethod.POST) @ApiOperation(value = "사용자 정보 등록", notes = "사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.") public ResponseEntity<ApiResponseMessage> insertUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.insertUser(user); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 등록에 실패하였습니다.", "ERROR00001", "Fail to registration for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } @RequestMapping(value = "", method = RequestMethod.PUT) @ApiOperation(value = "사용자 정보 수정", notes = "사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.<br>이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.") public ResponseEntity<ApiResponseMessage> updateUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.updateUser(user); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 수정에 실패하였습니다.", "ERROR00002", "Fail to update for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } @RequestMapping(value = "/{uid}", method = RequestMethod.DELETE) @ApiOperation(value = "사용자 정보 삭제", notes = "사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.") public ResponseEntity<ApiResponseMessage> deleteUser( @PathVariable("uid") Long uid ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.deleteUser(uid); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 삭제에 실패하였습니다.", "ERROR00003", "Fail to remove for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } } ``` * Package: com.example.sample.service * Class : UserService * Class Type : Interface ``` package com.example.sample.service; import java.util.List; import com.example.sample.data.entity.User; public interface UserService { /** * 사용자 목록 조회 * @return */ public List<User> selectUserList(); /** * 사용자 조회 * @param uid * @return */ public User selectUser(Long uid); /** * 사용자 등록 * @param user */ public void insertUser(User user); /** * 사용자 정보 수정 * @param user */ public void updateUser(User user); /** * 사용자 삭제 * @param uid */ public void deleteUser(Long uid); } ``` * Package: com.example.sample.data.service.impl * Class : UserServiceImpl * Class Type : Class ``` package com.example.sample.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.example.sample.data.entity.User; import com.example.sample.data.repository.UserRepository; import com.example.sample.service.UserService; import com.google.common.collect.ImmutableList; @Service public class UserServiceImpl implements UserService { @Autowired UserRepository userRepository; @Override @Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public List<User> selectUserList() { return ImmutableList.copyOf(userRepository.findAll()); } @Override @Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public User selectUser(Long uid) { return userRepository.findOne(uid); } @Override @Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public void insertUser(User user) { userRepository.save(user); } @Override @Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public void updateUser(User user) { userRepository.save(user); } @Override @Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public void deleteUser(Long uid) { userRepository.delete(uid); } } ``` * Package: com.example.sample.data.data.repository * Class : UserRepository * Class Type : Interface ``` package com.example.sample.data.repository; import org.springframework.data.repository.CrudRepository; import com.example.sample.data.entity.User; public interface UserRepository extends CrudRepository<User, Long>{} ``` 이렇게 위와같이 코드를 작성하고 어플리케이션을 기동하고 난 뒤에 localhost:8080/swagger-ui.html 로 가보자. 아무 화면도 안나오는 사람들이 간혹 있는데 크롬브라우저 기준으로 컨트롤 + 쉬프트 + R 키를 누르면(맥OS에선 커멘드 + 쉬프트 + R) 이런 화면이 나올거다. ![다운로드 (4).png](https://steemitimages.com/DQmXFVTrcLuakdZBFzLTrW1qyTpQGu3UwM857GPeDKj8qr3/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png) 새로 추가된 UserController 가 보이는가? 그러면 저 타이틀을 클릭해보자. ![다운로드 (5).png](https://steemitimages.com/DQmaPRs99UjhLcCHyMc8rbtCJUMPjeWpursKv8DsT6JxitF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png) 이렇게 API들 목록이 나열된다. 아직 우리가 POST랑 Update랑 준비된게 없으니 일단 처음 "[GET] /user" API를 클릭해보자. 그리고 조금 아래부분에 Try it out!이라는 버튼을 누른다. 그러면 갑자기 STS의 콘솔창에서 자동으로 쿼리를 만들어 던진다. ![다운로드 (6).png](https://steemitimages.com/DQmScKrqJCu3ZrYxDdnxbqDjnXAPkhzDnRZHtZft7woDAaj/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png) 그리고 가져온 데이터는 브라우저에 표시된다. ![다운로드 (7).png](https://steemitimages.com/DQmbUJJuRc83JhBpoieugwWRbKMSNiKMATJLegYCD23G4mR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png) 우리가 DB에 넣어줬던 그 데이터다. 그러면 [GET] /user/{uid} 를 호출해볼까 ![다운로드 (8).png](https://steemitimages.com/DQmSwbByMsCHfN7kQ6jYxQvN6SKFBduZWzFtWMenyMVbav5/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png) uid 값을 1로 만들어서 던지면 uid가 1로 등록되었던 데이터가 Response Body에 응답되었다는것을 확인할 수 있다. 이때 자동으로 생성된 SQL을 보면 ![다운로드 (9).png](https://steemitimages.com/DQmaGaynSZKr2xD7dmcwktYCjW637hN46eFzKdX2zx2GF8C/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png) 이렇게 자동으로 쿼리문도 만들어지게 된다. 이로써 아주 기초적인 Restful APIs 프로젝트를 시작했다. 이제 우리는 웹 서비스의 기초 모듈들을 개발하면서 발생할 수 있는 이슈들에 대해서 차차 공부해가도록 하자. 이상 여기까지.
json metadata{"tags":["kr-dev","java","spring","springboot","hibernate"],"users":["column","updatetimestamp","service","autowired","repository","query"],"image":["https://steemitimages.com/DQmZU64tXsD9DgyqLD3rXAhzynUHLn2XvrK1sKjMVg929qH/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg","https://steemitimages.com/DQmSBxaX4XRJdmnpdD4F3UrbLM2dCs8nMYZpyn8EjgYHrW9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).jpg","https://steemitimages.com/DQmZFT5qydK6uaYoo7Hm8yCER3wbNaAVJxHonYG2ZPpRcPR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png","https://steemitimages.com/DQmdxHUFLzRK5RRMXcMPA1i6AxJ2msppFs8kjWRgQfEzkS6/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png","https://steemitimages.com/DQmXFGiSzfhbLBZsdfafA8Szf3Ke9ffSgtG7AniiLf2VVUr/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png","https://steemitimages.com/DQmXFVTrcLuakdZBFzLTrW1qyTpQGu3UwM857GPeDKj8qr3/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png","https://steemitimages.com/DQmaPRs99UjhLcCHyMc8rbtCJUMPjeWpursKv8DsT6JxitF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png","https://steemitimages.com/DQmScKrqJCu3ZrYxDdnxbqDjnXAPkhzDnRZHtZft7woDAaj/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png","https://steemitimages.com/DQmbUJJuRc83JhBpoieugwWRbKMSNiKMATJLegYCD23G4mR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png","https://steemitimages.com/DQmSwbByMsCHfN7kQ6jYxQvN6SKFBduZWzFtWMenyMVbav5/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png","https://steemitimages.com/DQmaGaynSZKr2xD7dmcwktYCjW637hN46eFzKdX2zx2GF8C/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png"],"links":["http://jsaver.tistory.com/entry/Id%EC%99%80-GeneratedValue-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98","https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation"],"app":"steemit/0.1","format":"markdown"}
parent author
parent permlinkkr-dev
permlinkspring-boot-jpa-hibernate-2
titleSpring boot로 JPA-Hibernate 셋팅(2편)
Transaction InfoBlock #20401610/Trx b1d1fa62e59ed7f225496fc15f7bc792b8c4441e
View Raw JSON Data
{
  "block": 20401610,
  "op": [
    "comment",
    {
      "author": "igna84",
      "body": "자, 지난시간 JPA를 위해 DB도 만들고 셋팅도 하고 Table도 만들고 이것저것 했다.\n\n이제 Repository와 Service 를 만들어서 user 테이블의 정보를 가져오는 API를 만드는 것이 이번 포스팅의 목적이다.\n\n\n\n자, 우리가 순차적으로 작업을 진행할 필요가 있는데 무엇을 먼저 작업하면 좋을지 하는 거다.\n\n우리는 user테이블에서 데이터를 가져와서 API를 이용해 클라이언트에게 전달할 건데 가장 필요한게 무엇일까?\n\n\n\n대부분의 개발자들이 먼저 하는 작업이 무엇이냐하면 Controller먼저 만든다. 일단 요청을 해야 뭔가를 얻어올 수 있다고 생각해서다. 하지만 뭔가를 얻어온다는 말에 속에 간과하고 있는게 한가지 있다. 그것이 바로 \"뭔가\" 라고 하는 단어다.\n\n\"뭔가\"를 얻어오려면 그 \"뭔가\"를 담을 수 있는 통이 필요한데 그것이 있어야 비로소 DB에서 데이터를 가져오고, 그 데이터를 가공할 수도 있다. 한마디로 \"여자친구에게 바라는 점이 무엇인지 묻기전에 여자친구가 있는지 부터 물어보는게 예의\"인것 처럼 말이다.\n\n![다운로드.jpg](https://steemitimages.com/DQmZU64tXsD9DgyqLD3rXAhzynUHLn2XvrK1sKjMVg929qH/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg)\n\n이런 이유로 일단 엔티티(Entity)라는걸 만들어 볼거다. 이 엔티티라는게 무어냐 물어본다면 사전적인 의미로는 \"실체\"라고도 하고 \"객체\"라고도 하는데 개발에서도 이 의미와 별반 다르지 않은 \"어떤 데이터의 실질적인 정의\" 라고 생각하면 되겠다.\n\n![다운로드 (1).jpg](https://steemitimages.com/DQmSBxaX4XRJdmnpdD4F3UrbLM2dCs8nMYZpyn8EjgYHrW9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).jpg)\n\n그래서 지금부터 Entity클래스를 만들건데 Lombok 라이브러리를 아주 적극 활용하겠다.\n\n\n\n아, 뭔가 클래스를 새로만들땐 Package부터 만들어주는게 인지상정이다. 이 Entity 클래스는 \"com.example.sample.data.entity\"에 User라는 클래스명으로 만들도록 하겠다.\n\n```\npackage com.example.sample.data.entity;\n \nimport java.sql.Timestamp;\n \nimport javax.persistence.Column;\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.GenerationType;\nimport javax.persistence.Id;\nimport javax.persistence.Table;\n \nimport org.hibernate.annotations.CreationTimestamp;\nimport org.hibernate.annotations.UpdateTimestamp;\n \nimport lombok.Getter;\nimport lombok.Setter;\n \n@Entity\n@Table(name = \"user\")\n@Setter\n@Getter\npublic class User {\n \n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private @Column(name = \"uid\") Long uid;\n    private @Column(name = \"email\") String email;\n    private @Column(name = \"password\") String password;\n    private @Column(name = \"nick\") String nick;\n     \n    @CreationTimestamp\n    private @Column(name = \"cdate\") Timestamp cdate;\n     \n    @UpdateTimestamp\n    private @Column(name = \"udate\") Timestamp udate;\n}\n```\n이렇게 만들면 Entity는 완성이 되었다. 음, 여기서 가장 중요한 부분은 @Column 어노테이션인데 이 Column 어노테이션에서는 DB의 실제 컬럼명을 입력해주면 된다. 그리고 @Id 어노테이션을 PK(Prime Key: 기본키)를 의미하며 GerneratedValue 어노테이션을 통해서 이 PK값을 어떻게 입력할 것인지 정의할 수 있다.\n\n* GenerationType.AUTO : DB에 맞게 자동 선택 \n* GenerationType.IDENTITY : DB의 Identity 컬럼을 이용 \n* GenerationType.SEQUENCE : DB의 시퀀스 컬럼을 이용 \n* GenerationType.TABLE : 유일성이 보장된 데이터베이스 테이블을 이용 \n\n위의 내용으로 정의하는데 조금 더 자세한 이야기는 링크를 따라가 공부해보도록 하자.\n\n([http://jsaver.tistory.com/entry/Id%EC%99%80-GeneratedValue-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98](http://jsaver.tistory.com/entry/Id%EC%99%80-GeneratedValue-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98))\n\n\n그리고 별도로 @CreationTimestamp의 경우엔 데이터를 Insert해줄때 자동으로 시간을 입력해주고, @UpdateTimestamp는 Update해줄때 자동으로 시간을 입력해준다.\n\n어노테이션의 의미를 대충 정리했고 Entity도 만들었다면 이제 Controller부터 만들어보도록하자. 이 Controller를 만든다는 것은 Restful API를 만든다는 이야기고 이 Restful API는 기본적인 기능규칙에 의해서 만들어진다고 생각하면 처음 공부할 때 편하다.\n\n대부분(?)의 웹서비스는 CRUD를 기초로 만들어져있다.(C - Create, R - Read, U - Update, D - Delete를 의미한다.) UI의 구성도 API 구조도 이 CRUD를 기초로 만들면 일단 큰 골격에서 크게 벗어나지 않는다.\n\n이런 내용을 Restful APIs에서는 Request method로 구분해서 처리를 하는데 아래와 같이 정리한다.\n\n* GET : 혹은 항목을 조회할 때 (Read)\n* POST : 등록할 때 (Create)\n* PUT, PATCH : 수정할 때 (Update)\n* DELETE : 삭제할 때(Delete)\n\n이 항목을 어디서 정의하느냐 하면 우리가 만들 Controller에서 RequestMapping 어노테이션에서 정의한다.\n\n이제부터 나는 위의 내용을 토대로 UserController라는 클래스를 com.example.sample.controller 패키지에 만들도록 하겠다.\n\n\n\nUserController.java\n```\npackage com.example.sample.controller;\n \nimport java.util.List;\n \nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n \nimport com.example.sample.model.ApiResponseMessage;\nimport com.example.sample.data.entity.User;\n \nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\n \n@RestController\n@RequestMapping(value = \"/user\")\n@Api(value = \"UserController\", description = \"사용자 관련 API\",basePath = \"/user\")\npublic class UserController {\n \n \n    @RequestMapping(value = \"\", method = RequestMethod.GET)\n    @ApiOperation(value = \"사용자 목록 조회\", notes = \"사용자 목록을 조회하는 API.\")\n    public List<User> getUserList(){\n        return null;\n    }\n \n    @RequestMapping(value = \"/{uid}\", method = RequestMethod.GET)\n    @ApiOperation(value = \"사용자 정보 조회\", notes = \"사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.\")\n    public User getUser( @PathVariable(\"uid\") Long uid ){\n        return null;\n    }\n \n    @RequestMapping(value = \"\", method = RequestMethod.POST)\n    @ApiOperation(value = \"사용자 정보 등록\", notes = \"사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.\")\n    public ResponseEntity<ApiResponseMessage> insertUser( User user ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        return response;\n    }\n     \n    @RequestMapping(value = \"\", method = RequestMethod.PUT)\n    @ApiOperation(value = \"사용자 정보 수정\", notes = \"사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.<br>이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.\")\n    public ResponseEntity<ApiResponseMessage> updateUser( User user ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        return response;\n    }\n \n    @RequestMapping(value = \"/{uid}\", method = RequestMethod.DELETE)\n    @ApiOperation(value = \"사용자 정보 삭제\", notes = \"사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.\")\n    public ResponseEntity<ApiResponseMessage> deleteUser( @PathVariable(\"uid\") Long uid ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        return response;\n    }\n}\n```\n처음에 개발에 들어갈때는 일단 뭔가 기능을 구현하는 것보다 전체적으로 구조를 잡아 나가는 것이 중요하다. 이 과정을 통해 개발이 진행되면 전체적을 기능의 목록들이 나열되게 되고 위와 같이 아무 기능은 없으나 앞으로 개발해야하는 기능의 목록들을 나열하면 프로젝트의 큰 틀이 눈에 들어오게된다.\n\n\n\n일단 위와 같이 Controller 클래스를 짰다면 이제 Service 인터페이스 클래스를 만들어보도록 하자. \n\n인터페이스 클래스가 뭔지 모르는 사람은 검색을 좀 해서 개념을 잡고 돌아오거나 혹은 나중에(언제가 될지 모르겠지만) 진행할 인터페이스와 추상화 클래스에 대한 설명으로 포스팅하는 것을 기다리도록. 이번 \"프로젝트 시작하기\" 시리즈의 목적은 \"내가 뭘 좀 모르지만 일단 프로젝트를 진행해야겠는데 뭘 어떻게하는지 알려주는\" 것에 있다. 그러므로 자세하게 설명하고 싶은 마음은 있으나 자세하게 설명했다가는 이 포스팅을 끝내지 못할 수도 있으니 일단 너그러운 마음으로 넘어가도록 하자.\n\n\n\n나는 com.example.sample.service 패키지에 UserService 인터페이스 클래스를 만들거다. \n\n\n\nUserService.java\n```\npackage com.example.sample.service;\n \nimport java.util.List;\n \nimport com.example.sample.data.entity.User;\n \npublic interface UserService {\n    /**\n     * 사용자 목록 조회\n     * @return\n     */\n    public List<User> selectUserList();\n     \n    /**\n     * 사용자 조회\n     * @param uid\n     * @return\n     */\n    public User selectUser(Long uid);\n     \n    /**\n     * 사용자 등록\n     * @param user\n     */\n    public void insertUser(User user);\n     \n    /**\n     * 사용자 정보 수정\n     * @param user\n     */\n    public void updateUser(User user);\n     \n    /**\n     * 사용자 삭제\n     * @param uid\n     */\n    public void deleteUser(Long uid);\n}\n```\n인터페이스 클래스를 만들었다면 이제 구현체도 만들어야지.\n\ncom.example.sample.service.impl 패키지에 UserServiceImpl 이라는 클래스를 만들어 방금 만든 UserService 인터페이스 클래스의 구현체를 만들건데 아래의 이미지를 따라서 만들면 작업하기가 편하다.\n\n![다운로드.png](https://steemitimages.com/DQmZFT5qydK6uaYoo7Hm8yCER3wbNaAVJxHonYG2ZPpRcPR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png)\n\n클래스의 이름을 적고 Interfaces 항목 오른쪽에 있는 Add라는 버튼을 클릭한다.\n\n![다운로드 (1).png](https://steemitimages.com/DQmdxHUFLzRK5RRMXcMPA1i6AxJ2msppFs8kjWRgQfEzkS6/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png)\n\n여기에서 우리가 방금 만든 UserService를 검색하면 Matching Items에 항목이 나타날텐데 이 때 Package정보를 잘 보고 선택한 다음 OK버튼을 클릭한다.\n\n![다운로드.png](https://steemitimages.com/DQmZFT5qydK6uaYoo7Hm8yCER3wbNaAVJxHonYG2ZPpRcPR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png)\n\n위의 상태가 되었으면 Finish버튼을 클릭한다. 그러고 나면 알아서 인터페이스 클래스의 내용을 토대로 메서드들을 만들어준다.\n\n![다운로드 (3).png](https://steemitimages.com/DQmXFGiSzfhbLBZsdfafA8Szf3Ke9ffSgtG7AniiLf2VVUr/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png)\n\n그럼 일단 UserService에 대한 아주 기본적인 내용이 만들어졌으니 UserController 클래스에 약간(?)의 수정을 하도록 하자.\n\n```\npackage com.example.sample.controller;\n \nimport java.util.List;\n \nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n \nimport com.example.sample.model.ApiResponseMessage;\nimport com.example.sample.data.entity.User;\nimport com.example.sample.service.UserService;\n \nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\n \n@RestController\n@RequestMapping(value = \"/user\")\n@Api(value = \"UserController\", description = \"사용자 관련 API\",basePath = \"/user\")\npublic class UserController {\n \n    @Autowired\n    UserService userService;\n     \n \n    @RequestMapping(value = \"\", method = RequestMethod.GET)\n    @ApiOperation(value = \"사용자 목록 조회\", notes = \"사용자 목록을 조회하는 API.\")\n    public List<User> getUserList(){\n        return userService.selectUserList();\n    }\n \n    @RequestMapping(value = \"/{uid}\", method = RequestMethod.GET)\n    @ApiOperation(value = \"사용자 정보 조회\", notes = \"사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.\")\n    public User getUser( @PathVariable(\"uid\") Long uid ){\n        return userService.selectUser(uid);\n    }\n \n    @RequestMapping(value = \"\", method = RequestMethod.POST)\n    @ApiOperation(value = \"사용자 정보 등록\", notes = \"사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.\")\n    public ResponseEntity<apiresponsemessage> insertUser( User user ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        try {\n            userService.insertUser(user);\n        } catch(Exception ex){\n            message = new ApiResponseMessage(\"Failed\", \"사용자 등록에 실패하였습니다.\", \"ERROR00001\", \"Fail to registration for user information.\");\n            response = new ResponseEntity<apiresponsemessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);\n        }\n         \n        return response;\n    }\n     \n    @RequestMapping(value = \"\", method = RequestMethod.PUT)\n    @ApiOperation(value = \"사용자 정보 수정\", notes = \"사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.<br>이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.\")\n    public ResponseEntity<ApiResponseMessage> updateUser( User user ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        try {\n            userService.updateUser(user);\n        } catch(Exception ex){\n            message = new ApiResponseMessage(\"Failed\", \"사용자 정보 수정에 실패하였습니다.\", \"ERROR00002\", \"Fail to update for user information.\");\n            response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);\n        }\n         \n        return response;\n    }\n \n    @RequestMapping(value = \"/{uid}\", method = RequestMethod.DELETE)\n    @ApiOperation(value = \"사용자 정보 삭제\", notes = \"사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.\")\n    public ResponseEntity<ApiResponsemessage> deleteUser( @PathVariable(\"uid\") Long uid ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        try {\n            userService.deleteUser(uid);\n        } catch(Exception ex){\n            message = new ApiResponseMessage(\"Failed\", \"사용자 정보 삭제에 실패하였습니다.\", \"ERROR00003\", \"Fail to remove for user information.\");\n            response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);\n        }\n         \n        return response;\n    }\n}\n```\n뭐가 많이 변했는데 데이터를 등록하고 수정하고 삭제하는 부분의 코드가 조금(?) 많이 늘었고 Autowired라고 해서 UserService라는 인터페이스 클래스를 자동으로 주입하게끔 해놨다.\n\nUserServiceImpl 클래스도 아니고 왜 인터페이스 클래스를 가져다 놓은걸까.\n\n\n\n일단 조금 이따가 UserServiceImpl 클래스에 @Service라고 하는 어노테이션을 박아넣을텐데 이 Service 어노테이션은 자신이 어떤 인터페이스클래스를 구현했는지 판단하여 자동으로 @Autowired의 인터페이스 객체에 구현체를 넣어 사용할 수 있게 해준다. 뭔 소린지 모르겠다고? 당연하지, 이걸 이해하기 위해서는 @Autowired와  @Service, @Repository 와 Interface, Abstract의 차이점과 도데체 Interface가 뭐고 그와 같이 등장하는 Abstract(추상화) 클래스들은 무엇이며 이걸 이용해 할 수 있는 일은 무엇인지, 왜 이용해야하는지 등등등등 알아야할게 너무 많거든.\n\n어떤 한 개념을 이해하기 위해 수반되는 배경지식의 양이 기하급수적으로 늘어나는데 그걸 이 포스팅 한장으로 배우기엔 설명하는 나도, 배우는 당신도 힘들다. 솔직히 Interface 클래스 하나만 배운다고 해도 아마 하나의 카테고리로 시리즈 포스팅을 해야할 걸.\n\n그러니 잘 모르더라도 따라가도록 하자. 배경지식들은 차차 배운다고 생각하고. 지금 중요한건 사이트를 만드는거지 왜 Interface 클래스를 가져와 쓰는게 아니니까.\n\n\n\n일단 이렇게 만들었으면 Repository 인터페이스 클래스를 만들건데 여기서 아주 아주 아주 JPA-Hibernate의 큰 장점이 등장하게 된다.\n\n일단 만들고 나서 설명해주겠다.\n\n\n\n* Package: com.example.sample.data.repository\n* Class : UserRepository\n* Class Type : Interface\n```\npackage com.example.sample.data.repository;\n \nimport org.springframework.data.repository.CrudRepository;\n \nimport com.example.sample.data.entity.User;\n \npublic interface UserRepository extends CrudRepository<User, Long>{\n}\n```\n끝이다.\n\n더이상 뭘 하지 않아도 된다.\n\n어찌된 영문이냐고?\n\n\n\n일단 우리는 최초에 \"spring-boot-starter-data-jpa\" 라이브러리를 pom.xml에 추가한걸 기억해야한다. 그래서 우리가 JPA의 기능을 이용할 수 있는 거니까. 그리고 JPA의 기능을 오늘 배우는게 가장 중요하다. 기 기능들을 잘 이용하면 생산성이 비약적으로 높아지기 때문이다. 생산성이 높아진다는 이야기는 곧 야근을 덜 할 수 있다는 이야기다.\n\n\n\n일단 JPA의 기능은 3가지로 요약할 수 있다.\n\n* 메소드의 이름으로 쿼리 생성\n* 메소드의 이름으로 JPA NamedQuery 호출\n* @Query 어노테이션을 사용해서 Repository Interface에 쿼리를 직접 정의\n\n\n이렇게 3가지를 이야기 한다. 그 중에서도 첫번째 기능이 중요하다. 두번째 기능이나 세번째 기능은 뭐 인터페이스 클래스에다가 쿼리를 직접 입력할 수 있다는건데 이건 생산성하고 별로 상관없는 이야기니까 패스.\n\n메소드의 이름으로 쿼리를 생성한다는 이야기가 무엇이냐면 이런거다. JPA가 미리 정의해 놓은 메소드 명명 규칙이 있는데 그 규칙을 따라 인터페이스 클래스에 메소드를 선언하면 서버를 기동하거나 컴파일 단계에서 Spring Framework에서 자동으로 구현체를 만들어 준다.\n\n이게 얼마나 중요한 내용이냐면 예전에 Mybatis를 사용했을 때나 혹은 이런 ORM 관련 라이브러리가 없었을 적에는 코드에 직접 SQL 문을 작성해야했다.\n\n```\nSELECT\n    *\nFROM\n    user\nWHERE\n    email LIKE '%test%'\n```\n이런 단순한 쿼리문 부터 아주 복잡한 쿼리문까지 만들었어야 했다.  그리고 메서드를 만들고 그걸 동작하게하고.. 뭐 암튼 할일이 엄청 많았는데 JPA를 이용하면 인터페이스 클래스에 아래와 같이 코딩하면 된다.\n\n```\npackage com.example.sample.data.repository;\n \nimport java.util.List;\n \nimport org.springframework.data.repository.CrudRepository;\n \nimport com.example.sample.data.entity.User;\n \npublic interface UserRepository extends CrudRepository<User, Long>{\n    List<User> findByEmail(String email);\n \n}\n```\n\n위와같이 코딩을 하면 그냥 email 컬럼을 검색해서 목록으로 결과물을 뱉어낸다. 쿼리는 어떻게 하냐고? 짤 필요없다. 그냥 JPA가 알아서 만들어준다. 언제 만들어주느냐면 서버가 기동될때, 코드가 컴파일 될 때 자동으로 SQL 문을 짜고 DB와 연동해 데이터 가져올 구현체를 생성해서 어플리케이션에 등록해준다.\n\n앞으로 Query몰라도 어플리케이션을 만들 수 있다. 물론 복잡하고 Join도 해야하고 막 그런건 어떻게 하느냐고 물어보시는 분들 계신데 그건 나중에 QueryDSL라는걸 공부할 때 마저 하는걸로 하고, 오늘은 이 메소드 네이밍 규칙을 알아가도록 하자.([https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation))\n\n간혹 이런분들이 있다. \"저는 조건이 좀 까다로와서요\" 라면서 조건들을 나열하는 분들. 그런 분들을 위해 조건들을 아래와 같이 나열하는 것도 가능하다.\n\n```\npackage com.example.sample.data.repository;\n \nimport java.sql.Timestamp;\nimport java.util.List;\n \nimport org.springframework.data.repository.CrudRepository;\n \nimport com.example.sample.data.entity.User;\n \npublic interface UserRepository extends CrudRepository<User, Long>{\n    List<User> findByEmailLikeOrickLikeAndCdateAfter(String email, String nick, Timestamp cdate);\n}\n```\n\n아, 그리고 이러한 규칙을 통해 만들어진 CrudRepository 인터페이스 클래스를 이용하면 기본적인 메소드들을 제공해서 개발자로 하여금 아무것도 코딩하지 않더라도 기능을 구현해준다. 정말이다. 아무것도 안해도 된다. 지원하는 기능은 아래와 같다.\n\n* count() : 개수 확인\n* delete() : 1건 또는 여러 건 삭제\n* deleteAll() : 모두 삭제\n* exists() : ID 있는지 확인\n* findAll() : 전체 또는 ID목록으로 조회\n* findOne() : ID로 조회\n* save() : 1건 또는 여러 건 저장, 존재하는 데이터는 수정\n\n막 중구난방으로 설명이 진행되서 정신이 없을텐데 Repository에 대해 이제부터 정리를 좀 하겠다.\n\n\n\n1. 데이터의 흐름\n\n데이터의 흐름은 사용자의 요청 -> 컨트롤러(Controller) -> 서비스(Service) -> 리포지토리(Repository) -> DB 방향으로 Request(요청) 절차가 수행되고 이와 반대의 흐름으로 Response(응답) 절차를 수행하게 된다.\n\n\n\n2. Controller\n\n사용자의 요청과 응답을 직접적으로 처리하는 단계로 요청 내용을 정리하고 그에 맞는 응답을 할 수 있도록 서비스 메소드를 호출한다.\n\n\n\n3. Service\n\n컨트롤러에서 요청하는 데이터를 Repository의 메소드를 호출해 정제하고 정리해서 컨트롤러에 전달한다.\n\n이때 Transaction을 정의하고 DB 커넥션 풀을 적절하게 관리할 수 있도록 설정한다. Transaction을 적절하게 설정하게 되면 데이터 처리중 문제가 발생했을 때 RollBack을 할 수 있다.\n\n\n\n2. Repository\n\n우리는 JPA 라이브러리를 이용하고 있어서 인터페이스클래스안에 네이밍을 맞춰서 메소드를 선언해놓으면 Spring boot가 기동할때 자동으로 구현체를 만들어서 붙여준다. 구현체를 만드느라 시간 낭비할 필요없다. 클라이언트의 요구사항이 매우 난해하여 굳이 뭔가를 해야할 때는 나중에 QueryDSL이라는 것을 통해 포스팅을 하겠다.\n\n이제 좀 정리가 되는가? 그러면 UserController, UserService, UserServiceImpl, UserRepository 코드의 완성형을 아래에 쭉 나열해놓겠다.\n\n\n* Package: com.example.sample.controller\n* Class : UserController\n* Class Type : Class\n```\npackage com.example.sample.controller;\n \nimport java.util.List;\n \nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n \nimport com.example.sample.data.entity.User;\nimport com.example.sample.model.ApiResponseMessage;\nimport com.example.sample.service.UserService;\n \nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\n \n@RestController\n@RequestMapping(value = \"/user\")\n@Api(value = \"UserController\", description = \"사용자 관련 API\",basePath = \"/user\")\npublic class UserController {\n \n    @Autowired\n    UserService userService;\n     \n \n    @RequestMapping(value = \"\", method = RequestMethod.GET)\n    @ApiOperation(value = \"사용자 목록 조회\", notes = \"사용자 목록을 조회하는 API.\")\n    public List<User> getUserList(){\n        return userService.selectUserList();\n    }\n \n    @RequestMapping(value = \"/{uid}\", method = RequestMethod.GET)\n    @ApiOperation(value = \"사용자 정보 조회\", notes = \"사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.\")\n    public User getUser( @PathVariable(\"uid\") Long uid ){\n        return userService.selectUser(uid);\n    }\n \n    @RequestMapping(value = \"\", method = RequestMethod.POST)\n    @ApiOperation(value = \"사용자 정보 등록\", notes = \"사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.\")\n    public ResponseEntity<ApiResponseMessage> insertUser( User user ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        try {\n            userService.insertUser(user);\n        } catch(Exception ex){\n            message = new ApiResponseMessage(\"Failed\", \"사용자 등록에 실패하였습니다.\", \"ERROR00001\", \"Fail to registration for user information.\");\n            response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);\n        }\n         \n        return response;\n    }\n     \n    @RequestMapping(value = \"\", method = RequestMethod.PUT)\n    @ApiOperation(value = \"사용자 정보 수정\", notes = \"사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.<br>이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.\")\n    public ResponseEntity<ApiResponseMessage> updateUser( User user ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        try {\n            userService.updateUser(user);\n        } catch(Exception ex){\n            message = new ApiResponseMessage(\"Failed\", \"사용자 정보 수정에 실패하였습니다.\", \"ERROR00002\", \"Fail to update for user information.\");\n            response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);\n        }\n         \n        return response;\n    }\n \n    @RequestMapping(value = \"/{uid}\", method = RequestMethod.DELETE)\n    @ApiOperation(value = \"사용자 정보 삭제\", notes = \"사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.\")\n    public ResponseEntity<ApiResponseMessage> deleteUser( @PathVariable(\"uid\") Long uid ){\n        ApiResponseMessage message = new ApiResponseMessage(\"Success\", \"등록되었습니다.\", \"\", \"\");\n        ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);\n         \n        try {\n            userService.deleteUser(uid);\n        } catch(Exception ex){\n            message = new ApiResponseMessage(\"Failed\", \"사용자 정보 삭제에 실패하였습니다.\", \"ERROR00003\", \"Fail to remove for user information.\");\n            response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);\n        }\n         \n        return response;\n    }\n}\n```\n\n* Package: com.example.sample.service\n* Class : UserService\n* Class Type : Interface\n```\npackage com.example.sample.service;\n \nimport java.util.List;\n \nimport com.example.sample.data.entity.User;\n \npublic interface UserService {\n    /**\n     * 사용자 목록 조회\n     * @return\n     */\n    public List<User> selectUserList();\n     \n    /**\n     * 사용자 조회\n     * @param uid\n     * @return\n     */\n    public User selectUser(Long uid);\n     \n    /**\n     * 사용자 등록\n     * @param user\n     */\n    public void insertUser(User user);\n     \n    /**\n     * 사용자 정보 수정\n     * @param user\n     */\n    public void updateUser(User user);\n     \n    /**\n     * 사용자 삭제\n     * @param uid\n     */\n    public void deleteUser(Long uid);\n}\n```\n\n* Package: com.example.sample.data.service.impl\n* Class : UserServiceImpl\n* Class Type : Class\n```\npackage com.example.sample.service.impl;\n \nimport java.util.List;\n \nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Isolation;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\n \nimport com.example.sample.data.entity.User;\nimport com.example.sample.data.repository.UserRepository;\nimport com.example.sample.service.UserService;\nimport com.google.common.collect.ImmutableList;\n \n@Service\npublic class UserServiceImpl implements UserService {\n \n    @Autowired\n    UserRepository userRepository;\n     \n    @Override\n    @Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)\n    public List<User> selectUserList() {\n        return ImmutableList.copyOf(userRepository.findAll());\n    }\n \n    @Override\n    @Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)\n    public User selectUser(Long uid) {\n        return userRepository.findOne(uid);\n    }\n \n    @Override\n    @Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)\n    public void insertUser(User user) {\n        userRepository.save(user);\n    }\n \n    @Override\n    @Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)\n    public void updateUser(User user) {\n        userRepository.save(user);\n    }\n \n    @Override\n    @Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)\n    public void deleteUser(Long uid) {\n        userRepository.delete(uid);\n \n    }\n}\n```\n\n* Package: com.example.sample.data.data.repository\n* Class : UserRepository\n* Class Type : Interface\n```\npackage com.example.sample.data.repository;\n \nimport org.springframework.data.repository.CrudRepository;\n \nimport com.example.sample.data.entity.User;\n \npublic interface UserRepository extends CrudRepository<User, Long>{}\n```\n\n이렇게 위와같이 코드를 작성하고 어플리케이션을 기동하고 난 뒤에 localhost:8080/swagger-ui.html 로 가보자. 아무 화면도 안나오는 사람들이 간혹 있는데 크롬브라우저 기준으로 컨트롤 + 쉬프트 + R 키를 누르면(맥OS에선 커멘드 + 쉬프트 + R) 이런 화면이 나올거다.\n\n![다운로드 (4).png](https://steemitimages.com/DQmXFVTrcLuakdZBFzLTrW1qyTpQGu3UwM857GPeDKj8qr3/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png)\n\n새로 추가된 UserController 가 보이는가? 그러면 저 타이틀을 클릭해보자.\n\n![다운로드 (5).png](https://steemitimages.com/DQmaPRs99UjhLcCHyMc8rbtCJUMPjeWpursKv8DsT6JxitF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png)\n\n이렇게 API들 목록이 나열된다.\n\n\n\n아직 우리가 POST랑 Update랑 준비된게 없으니 일단 처음 \"[GET] /user\" API를 클릭해보자.\n\n그리고 조금 아래부분에 Try it out!이라는 버튼을 누른다.\n\n\n\n그러면 갑자기 STS의 콘솔창에서 자동으로 쿼리를 만들어 던진다.\n\n![다운로드 (6).png](https://steemitimages.com/DQmScKrqJCu3ZrYxDdnxbqDjnXAPkhzDnRZHtZft7woDAaj/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png)\n\n그리고 가져온 데이터는 브라우저에 표시된다.\n\n![다운로드 (7).png](https://steemitimages.com/DQmbUJJuRc83JhBpoieugwWRbKMSNiKMATJLegYCD23G4mR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png)\n\n우리가 DB에 넣어줬던 그 데이터다.\n\n\n\n그러면 [GET] /user/{uid} 를 호출해볼까\n\n![다운로드 (8).png](https://steemitimages.com/DQmSwbByMsCHfN7kQ6jYxQvN6SKFBduZWzFtWMenyMVbav5/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png)\n\nuid 값을 1로 만들어서 던지면 uid가 1로 등록되었던 데이터가 Response Body에 응답되었다는것을 확인할 수 있다.\n\n이때 자동으로 생성된 SQL을 보면\n\n![다운로드 (9).png](https://steemitimages.com/DQmaGaynSZKr2xD7dmcwktYCjW637hN46eFzKdX2zx2GF8C/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png)\n\n이렇게 자동으로 쿼리문도 만들어지게 된다.\n\n이로써 아주 기초적인 Restful APIs 프로젝트를 시작했다. 이제 우리는 웹 서비스의 기초 모듈들을 개발하면서 발생할 수 있는 이슈들에 대해서 차차 공부해가도록 하자.\n\n이상 여기까지.",
      "json_metadata": "{\"tags\":[\"kr-dev\",\"java\",\"spring\",\"springboot\",\"hibernate\"],\"users\":[\"column\",\"updatetimestamp\",\"service\",\"autowired\",\"repository\",\"query\"],\"image\":[\"https://steemitimages.com/DQmZU64tXsD9DgyqLD3rXAhzynUHLn2XvrK1sKjMVg929qH/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.jpg\",\"https://steemitimages.com/DQmSBxaX4XRJdmnpdD4F3UrbLM2dCs8nMYZpyn8EjgYHrW9/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).jpg\",\"https://steemitimages.com/DQmZFT5qydK6uaYoo7Hm8yCER3wbNaAVJxHonYG2ZPpRcPR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C.png\",\"https://steemitimages.com/DQmdxHUFLzRK5RRMXcMPA1i6AxJ2msppFs8kjWRgQfEzkS6/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(1).png\",\"https://steemitimages.com/DQmXFGiSzfhbLBZsdfafA8Szf3Ke9ffSgtG7AniiLf2VVUr/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(3).png\",\"https://steemitimages.com/DQmXFVTrcLuakdZBFzLTrW1qyTpQGu3UwM857GPeDKj8qr3/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(4).png\",\"https://steemitimages.com/DQmaPRs99UjhLcCHyMc8rbtCJUMPjeWpursKv8DsT6JxitF/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(5).png\",\"https://steemitimages.com/DQmScKrqJCu3ZrYxDdnxbqDjnXAPkhzDnRZHtZft7woDAaj/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(6).png\",\"https://steemitimages.com/DQmbUJJuRc83JhBpoieugwWRbKMSNiKMATJLegYCD23G4mR/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(7).png\",\"https://steemitimages.com/DQmSwbByMsCHfN7kQ6jYxQvN6SKFBduZWzFtWMenyMVbav5/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(8).png\",\"https://steemitimages.com/DQmaGaynSZKr2xD7dmcwktYCjW637hN46eFzKdX2zx2GF8C/%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C%20(9).png\"],\"links\":[\"http://jsaver.tistory.com/entry/Id%EC%99%80-GeneratedValue-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98\",\"https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
      "parent_author": "",
      "parent_permlink": "kr-dev",
      "permlink": "spring-boot-jpa-hibernate-2",
      "title": "Spring boot로 JPA-Hibernate 셋팅(2편)"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T06:11:48",
  "trx_id": "b1d1fa62e59ed7f225496fc15f7bc792b8c4441e",
  "trx_in_block": 29,
  "virtual_op": 0
}
igna84received 0.094 SBD, 0.036 SP author reward for @igna84 / spring-boot-web-swagger
2018/03/05 05:35:42
authorigna84
permlinkspring-boot-web-swagger
sbd payout0.094 SBD
steem payout0.000 STEEM
vesting payout59.235300 VESTS
Transaction InfoBlock #20400887/Virtual Operation #4
View Raw JSON Data
{
  "block": 20400887,
  "op": [
    "author_reward",
    {
      "author": "igna84",
      "permlink": "spring-boot-web-swagger",
      "sbd_payout": "0.094 SBD",
      "steem_payout": "0.000 STEEM",
      "vesting_payout": "59.235300 VESTS"
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-05T05:35:42",
  "trx_id": "0000000000000000000000000000000000000000",
  "trx_in_block": 4294967295,
  "virtual_op": 4
}
igna84upvoted (100.00%) @virus707 / 2vrk18-4
2018/03/02 06:50:36
authorvirus707
permlink2vrk18-4
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20316010/Trx ea4c2e7797f4affba0c39344ca1aaa84e69dae26
View Raw JSON Data
{
  "block": 20316010,
  "op": [
    "vote",
    {
      "author": "virus707",
      "permlink": "2vrk18-4",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-02T06:50:36",
  "trx_id": "ea4c2e7797f4affba0c39344ca1aaa84e69dae26",
  "trx_in_block": 40,
  "virtual_op": 0
}
igna84upvoted (100.00%) @lynxit / 1
2018/03/02 06:48:48
authorlynxit
permlink1
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20315974/Trx 865a83d27b4f8a0be7f16d260cfcdff3858932b0
View Raw JSON Data
{
  "block": 20315974,
  "op": [
    "vote",
    {
      "author": "lynxit",
      "permlink": "1",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-02T06:48:48",
  "trx_id": "865a83d27b4f8a0be7f16d260cfcdff3858932b0",
  "trx_in_block": 34,
  "virtual_op": 0
}
igna84followed @kdj
2018/03/02 06:47:15
idfollow
json["follow",{"follower":"igna84","following":"kdj","what":["blog"]}]
required auths[]
required posting auths["igna84"]
Transaction InfoBlock #20315943/Trx e62ce10701b748027223b0791fd2c23b88da42ed
View Raw JSON Data
{
  "block": 20315943,
  "op": [
    "custom_json",
    {
      "id": "follow",
      "json": "[\"follow\",{\"follower\":\"igna84\",\"following\":\"kdj\",\"what\":[\"blog\"]}]",
      "required_auths": [],
      "required_posting_auths": [
        "igna84"
      ]
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-02T06:47:15",
  "trx_id": "e62ce10701b748027223b0791fd2c23b88da42ed",
  "trx_in_block": 7,
  "virtual_op": 0
}
2018/03/02 05:25:39
authorigna84
permlinkspring-boot-mongodb-redis
voterkdj
weight2000 (20.00%)
Transaction InfoBlock #20314311/Trx 7107c8f50a11f2eb1e9bb29aef1a41e2f1ed8185
View Raw JSON Data
{
  "block": 20314311,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-boot-mongodb-redis",
      "voter": "kdj",
      "weight": 2000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-02T05:25:39",
  "trx_id": "7107c8f50a11f2eb1e9bb29aef1a41e2f1ed8185",
  "trx_in_block": 19,
  "virtual_op": 0
}
2018/03/02 05:20:15
authorigna84
permlinkspring-security-secured-preauthorize-postauthorize-annotation
voterkdj
weight2000 (20.00%)
Transaction InfoBlock #20314203/Trx 1b64c94fbdc26534f0b14065acd2f673f61bae91
View Raw JSON Data
{
  "block": 20314203,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-security-secured-preauthorize-postauthorize-annotation",
      "voter": "kdj",
      "weight": 2000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-02T05:20:15",
  "trx_id": "1b64c94fbdc26534f0b14065acd2f673f61bae91",
  "trx_in_block": 21,
  "virtual_op": 0
}
2018/03/02 04:19:39
authorigna84
permlinkspring-security-secured-preauthorize-postauthorize-annotation
voterigna84
weight10000 (100.00%)
Transaction InfoBlock #20312991/Trx 3b2198a98bdff27b36b349d637f3b7d6d4265889
View Raw JSON Data
{
  "block": 20312991,
  "op": [
    "vote",
    {
      "author": "igna84",
      "permlink": "spring-security-secured-preauthorize-postauthorize-annotation",
      "voter": "igna84",
      "weight": 10000
    }
  ],
  "op_in_trx": 0,
  "timestamp": "2018-03-02T04:19:39",
  "trx_id": "3b2198a98bdff27b36b349d637f3b7d6d4265889",
  "trx_in_block": 8,
  "virtual_op": 0
}

Account Metadata

POSTING JSON METADATA
profile{"profile_image":"http://dn-s-story.kakao.co.kr/dn/exUqf/hyuEIvJ7Rf/nKYptaTKKkylcaeO8pOzu1/img_s.jpg?width=442&height=448&convert=150x150","cover_image":"http://dn-l1-story.kakao.co.kr/dn/bxqhtN/hyuH5QUyJd/CkOxx1k7sIM58UdRbOVUM0/img_l.jpg?width=1920&height=1080","name":"야훔","location":"Seoul, South Korea.","website":"http://blog.thereis.xyz"}
JSON METADATA
profile{"profile_image":"http://dn-s-story.kakao.co.kr/dn/exUqf/hyuEIvJ7Rf/nKYptaTKKkylcaeO8pOzu1/img_s.jpg?width=442&height=448&convert=150x150","cover_image":"http://dn-l1-story.kakao.co.kr/dn/bxqhtN/hyuH5QUyJd/CkOxx1k7sIM58UdRbOVUM0/img_l.jpg?width=1920&height=1080","name":"야훔","location":"Seoul, South Korea.","website":"http://blog.thereis.xyz"}
{
  "posting_json_metadata": {
    "profile": {
      "profile_image": "http://dn-s-story.kakao.co.kr/dn/exUqf/hyuEIvJ7Rf/nKYptaTKKkylcaeO8pOzu1/img_s.jpg?width=442&height=448&convert=150x150",
      "cover_image": "http://dn-l1-story.kakao.co.kr/dn/bxqhtN/hyuH5QUyJd/CkOxx1k7sIM58UdRbOVUM0/img_l.jpg?width=1920&height=1080",
      "name": "야훔",
      "location": "Seoul, South Korea.",
      "website": "http://blog.thereis.xyz"
    }
  },
  "json_metadata": {
    "profile": {
      "profile_image": "http://dn-s-story.kakao.co.kr/dn/exUqf/hyuEIvJ7Rf/nKYptaTKKkylcaeO8pOzu1/img_s.jpg?width=442&height=448&convert=150x150",
      "cover_image": "http://dn-l1-story.kakao.co.kr/dn/bxqhtN/hyuH5QUyJd/CkOxx1k7sIM58UdRbOVUM0/img_l.jpg?width=1920&height=1080",
      "name": "야훔",
      "location": "Seoul, South Korea.",
      "website": "http://blog.thereis.xyz"
    }
  }
}

Auth Keys

Owner
Single Signature
Public Keys
STM7YVYhm6w2bFvyL1JwemGy6rdF2PpZfrBq5y2MT9jy6FYeAqdhQ1/1
Active
Single Signature
Public Keys
STM8XW5Je1BKj3VZP8QVjrLQH97YbT9eX1gnUbknvD9ZMy1Dvuhbr1/1
Posting
Single Signature
Public Keys
STM7iyGSUiJWeSWget67QR5eR6SNLA9YzPakbz6ssMY9XvLYNpDDF1/1
Memo
STM7yrL4A3xo3vg7hh9haaiMNwq4J26V5d145c9Ch6kEtVKaBeeXo
{
  "owner": {
    "account_auths": [],
    "key_auths": [
      [
        "STM7YVYhm6w2bFvyL1JwemGy6rdF2PpZfrBq5y2MT9jy6FYeAqdhQ",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "active": {
    "account_auths": [],
    "key_auths": [
      [
        "STM8XW5Je1BKj3VZP8QVjrLQH97YbT9eX1gnUbknvD9ZMy1Dvuhbr",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "posting": {
    "account_auths": [],
    "key_auths": [
      [
        "STM7iyGSUiJWeSWget67QR5eR6SNLA9YzPakbz6ssMY9XvLYNpDDF",
        1
      ]
    ],
    "weight_threshold": 1
  },
  "memo": "STM7yrL4A3xo3vg7hh9haaiMNwq4J26V5d145c9Ch6kEtVKaBeeXo"
}

Witness Votes

0 / 30
No active witness votes.
[]