๐ ํต์ฌ: Lombok์ ์ฝ๋ ์๋ ์์ฑ์ ํตํด ๊ฐ๋ฐ ์์ฐ์ฑ๊ณผ ์ฝ๋ ํ์ง์ ๋์์ ๋์ด์ฌ๋ ค ์ค๋๋ค.
@Getter / @Setter@Getter @Setter
public class Memo {
private int id;
private String title;
private String content;
}
// ์ฌ์ฉ ์์ (๋จ์ ํ
์คํธ)
public void testGetterSetter() {
Memo memo = new Memo();
memo.setTitle("Hello");
assert "Hello".equals(memo.getTitle());
}
@NoArgsConstructor / @AllArgsConstructor@NoArgsConstructor: ๋งค๊ฐ๋ณ์ ์๋ ๊ธฐ๋ณธ ์์ฑ์ ์์ฑ@AllArgsConstructor: ๋ชจ๋ ํ๋๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ ์์ฑ์ ์์ฑ@NoArgsConstructor
@AllArgsConstructor
public class CustomerRank {
private int customerId;
private BigDecimal totalSpent;
private int rank;
}
// ์ฌ์ฉ ์์
public void testConstructors() {
CustomerRank empty = new CustomerRank(); // NoArgs
CustomerRank full = new CustomerRank(1, new BigDecimal("100.00"), 1);
assert full.getCustomerId() == 1;
}
@RequiredArgsConstructorfinal ๋๋ @NonNull ํ๋๋ง ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ ์์ฑ์ ์์ฑfinal ํ๋ ์๋ ์ฃผ์
ํ์ธ, IDE์์ ์์ฑ์ ํ์ธ@RequiredArgsConstructor
public class DashboardController {
private final DashboardRepository repo;
}
// ์ฌ์ฉ ์์
public void testRequiredArgs() {
DashboardRepository repo = new DashboardRepository(...);
DashboardController ctrl = new DashboardController(repo);
assert ctrl.getRepo() == repo;
}
@ToStringtoString() ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ์์ฑexclude, of, callSuper ๋ฑ์ผ๋ก ํ๋ ์ง์ ๊ฐ๋ฅSystem.out.println() ์ผ๋ก ์ถ๋ ฅ@ToString(includeFieldNames = true)
public class DailySales {
private LocalDate saleDate;
private BigDecimal totalAmount;
private int orderCount;
}
// ์ฌ์ฉ ์์
public void testToString() {
DailySales ds = new DailySales(LocalDate.now(), new BigDecimal("50.00"), 5);
System.out.println(ds); // DailySales(saleDate=..., totalAmount=50.00, orderCount=5)
}
@EqualsAndHashCodeequals() ๋ฐ hashCode() ๋ฉ์๋๋ฅผ ์๋ ์์ฑexclude, onlyExplicitlyIncluded ๋ฑ์ผ๋ก ํ๋ ์ง์ equals() ์ง์ ํธ์ถ@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Memo {
@EqualsAndHashCode.Include
private int id;
private String title;
private String content;
}
// ์ฌ์ฉ ์์
public void testEqualsHashCode() {
Memo m1 = new Memo(1, "A", "B");
Memo m2 = new Memo(1, "X", "Y");
assert m1.equals(m2);
assert m1.hashCode() == m2.hashCode();
}
@Data@Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode๋ฅผ ํ ๋ฒ์ ํฌํจ@Data
public class CustomerRank {
private final int customerId;
private BigDecimal totalSpent;
private int rank;
}
// ์ฌ์ฉ ์์: getter, setter, toString, equals ๋ชจ๋ ์ฆ์ ์ฌ์ฉ ๊ฐ๋ฅ
public void testDataAnnotation() {
CustomerRank cr = new CustomerRank(1);
cr.setTotalSpent(new BigDecimal("200"));
System.out.println(cr);
assert cr.getCustomerId() == 1;
}
@Builderbuilder() ๋ฉ์๋ ํธ์ถ@Builder
public class DailySales {
private LocalDate saleDate;
private BigDecimal totalAmount;
private int orderCount;
}
// ์ฌ์ฉ ์์
public void testBuilder() {
DailySales ds = DailySales.builder()
.saleDate(LocalDate.of(2025,1,1))
.totalAmount(new BigDecimal("1000.00"))
.orderCount(20)
.build();
assert ds.getOrderCount() == 20;
}
@RequiredArgsConstructor + final ํ๋ โ ๊น๋ํ ์ปจํธ๋กค๋ฌ/์๋น์ค ์ ์ธ@Data + @NoArgsConstructor + @AllArgsConstructor๋ก ๋น ๋ฅด๊ฒ ์ ์@ToString(exclude="password") ์ฒ๋ผ ๋ฏผ๊ฐ ์ ๋ณด ์ ์ธ ๊ฐ๋ฅpostgreSql๋ก ๊ตฌ์ฑ๋ ๋ก์ปฌ db์ ํ ์ด๋ธ ์์ฑ
powerShell > postgreSQLpsql -U postgres
CREATE USER edu_system WITH PASSWORD 'password';
\du
CREATE DATABASE edu_system OWNER edu_system;
\l
\q
psql -U edu_system -W
CREATE TABLE teacher (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE student (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
score INT NOT NULL,
teacher_id INT REFERENCES teacher(id)
);
RDB 1:n ๊ฐ์๊ฑฐ ์ฒจ์ ๋ฐฐ์ธ๋ ์ด์ง๋ฌ์ ๋๋ฐ..
reference ๋ฅผ teacher ํ ์ด๋ธ์ ๋๋ student ํ ์ด๋ธ์ ๋๋ ๋น๊ตํด์ ๋ณด๋๊น ํ ์๋ฟ์๋ค.. ๋น์ฐํ student์ ๋ฌ์ผ ์กฐํํ ๋ ํธํ๋ค. teacher๊ฐ ๋ชจ๋ ํ์์ ๊ฐ๊ณ ์์ผ๋ฉด ํ๋ ๊ฒ์ํ ๋ ๋ชจ๋ ๋ฐฐ์ด์ ์ํํด์ผ ํ๋๊น ์๋์ง๊ฐ ๋ ๋ค์ด ๋นํจ์จ์ ์ด๋ค.
HTTP ๋ฉ์๋๋ ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์ด๋ค ๋์์ ์์ฒญํ๋์ง ๋ํ๋ด๋ ๋ฐฉ์์ด๋ค.
REST API ์ค๊ณ์์๋ ์ด ๋ฉ์๋๋ค์ด ๋งค์ฐ ์ค์ํ ์ญํ ์ ํ๋ค.
| ๋ฉ์๋ | ์๋ฏธ | ์ค๋ช |
|---|---|---|
| GET | ์กฐํ | ๋ฆฌ์์ค๋ฅผ ์์ฒญ (์ฝ๊ธฐ ์ ์ฉ, ์์ ํจ) |
| POST | ์์ฑ | ์๋ฒ์ ์๋ก์ด ๋ฆฌ์์ค๋ฅผ ์์ฑ (๋ณดํต html form์ผ๋ก ๋ฐ์์ค๊ณ ์๋ค.) |
| PUT | ์์ (์ ์ฒด) | ๋ฆฌ์์ค๋ฅผ ์ ์ฒด ์์ |
| PATCH | ์์ (์ผ๋ถ) | ๋ฆฌ์์ค ์ผ๋ถ ํ๋๋ง ์์ |
| DELETE | ์ญ์ | ๋ฆฌ์์ค ์ ๊ฑฐ |
๐ง model
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Teacher {
private Integer id;
private String name;
}
๐๏ธ repository - jdbc๋ก db์ ์ ์ฅ
@Repository
@RequiredArgsConstructor
//@NoArgsConstructor
public class TeacherRepository {
private final JdbcTemplate jdbcTemplate;
public int save(Teacher teacher) {
return jdbcTemplate.update(
"INSERT INTO teacher (name) VALUES (?)",
teacher.getName()
);
}
}
๐ก controller
@Controller
@RequestMapping("/teachers")
@RequiredArgsConstructor
public class StudentController {
private final TeacherRepository teacherRepository;
@GetMapping
public String list() {
return "teacher-list";
}
@GetMapping("/add")
public String addForm() {
return "teacher-form";
}
@PostMapping("/add")
public String add(@ModelAttribute Teacher teacher) {
teacherRepository.save(teacher);
return "redirect:/teachers";
}
}
@RequestMapping("/teachers")
@GetMapping() -> teacher-list.html
@GetMapping("/add") -> teacher-form.html
@PostMapping("/add") -> (repository)save -> redirect to teacher-list.html
๐จ styles.css
/* ์ ์ฒด ๋ฐฐ๊ฒฝ & ๊ธฐ๋ณธ ํฐํธ */
body {
margin: 0;
padding: 20px;
font-family: 'Helvetica Neue', Arial, sans-serif;
background-color: #FFF8F0; /* ์ฐํ ๋ฒ ์ด์ง */
color: #5A4E4D; /* ๋ถ๋๋ฌ์ด ๋ธ๋ผ์ด */
}
/* ์ปจํ
์ด๋ */
.container {
max-width: 960px;
margin: 0 auto;
}
/* ์ ๋ชฉ */
h1 {
text-align: center;
margin-bottom: 24px;
font-size: 2rem;
color: #A27B5C; /* ํ์คํ
๋ธ๋ผ์ด */
}
/* ๋ด๋น๊ฒ์ด์
๋งํฌ */
a {
text-decoration: none;
color: #7DAEA3; /* ํ์คํ
๋ฏผํธ */
margin-right: 12px;
}
a:hover {
text-decoration: underline;
color: #60948C;
}
/* ๋ฒํผ */
button, .btn {
display: inline-block;
padding: 8px 16px;
font-size: 0.95rem;
border: none;
border-radius: 8px;
background-color: #E3C9C1; /* ํ์คํ
ํํฌ */
color: #5A4E4D;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover, .btn:hover {
background-color: #D3B6AE;
}
/* ํ */
table {
width: 100%;
border-collapse: collapse;
margin-top: 16px;
background-color: #FFF5E4; /* ์์ฃผ ์ฐํ ์ด๊ตฌ */
}
th, td {
padding: 12px;
border: 1px solid #E3C9C1;
text-align: center;
}
th {
background-color: #F6E8E1; /* ํ์คํ
์ด๊ตฌ */
color: #7D5A50;
}
/* ํผ */
form, .form-container {
max-width: 600px;
margin: 0 auto 24px auto;
padding: 20px;
background-color: #FEF9F5; /* ๊ฑฐ์ ํฐ์์ ๊ฐ๊น์ด ์ด๊ตฌ */
border: 1px solid #E3C9C1;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
input[type="text"],
input[type="number"],
select {
width: 100%;
padding: 10px;
margin-top: 6px;
margin-bottom: 16px;
border: 1px solid #E3C9C1;
border-radius: 6px;
background-color: #FFFBF8;
font-size: 1rem;
}
input:focus,
select:focus {
outline: none;
border-color: #7DAEA3; /* ํฌ์ปค์ค ์ ํ์คํ
๋ฏผํธ */
}
/* ์ก์
๋งํฌ(์ทจ์) */
.cancel {
margin-left: 12px;
color: #A27B5C;
}
.cancel:hover {
color: #7D5A50;
}
๐ผ๏ธ teacher-form.html
<body>
<h1>๊ต์ฌ ๋ฑ๋ก</h1>
<p>
<a href="/">ํ์ผ๋ก</a>
<a href="/teachers">๊ต์ฌ ๋ชฉ๋ก</a>
</p>
<form th:action="@{/teachers/add}" th:object="${teacher}" method="post">
<p><label>์ด๋ฆ: <input type="text" th:field="*{name}" required/></label></p>
<p>
<button type="submit">์ ์ฅํ๊ธฐ</button>
<a href="/teachers">์ทจ์ํ๊ธฐ</a>
</p>
</form>
</body>
<button type="submit">th:action="@{/teachers/add}" method="post" ์ ํจ๊ป ๋์๐ผ๏ธ teacher-list.html
<body>
<h1>๐งโ๐ซ ๊ต์ฌ ๋ชฉ๋ก</h1>
<p>
<a href="/">๐ ํ์ผ๋ก</a>
<a href="/teachers/add">โ ๊ต์ฌ ๋ฑ๋ก</a>
</p>
</body>
๐๏ธ repository
private final RowMapper<Teacher> mapper = (resultSet, rowNum) ->
Teacher.builder()
.id(resultSet.getInt("id"))
.name(resultSet.getString("name"))
.build();
public List<Teacher> findAll() {
return jdbcTemplate.query("SELECT * FROM teacher ORDER BY name", mapper);
}
findAll : jdbc sql๋ก SELECT * FROM teacher ORDER BY name ๋ก row๋ฅผ mapper์ ๋๊ฒจ์ค๋ค.
RowMapper ๊ฐ resultSet์ผ๋ก ๋ฐ์ดํฐ row๋ฅผ Teacher ๊ฐ์ฒด์ ๋ฐ์ธ๋ฉ
ร query๋ก ๋๊ฒจ๋ฐ์ row ์๋งํผ ๋ฐ๋ณต
builder ๋ฅผ ์ฐ์ง ์์ผ๋ฉด :
private final RowMapper<DailySales> dailySalesRowMapper = (resultSet, rowNum) ->
new DailySales(
resultSet.getDate("sale_date").toLocalDate(),
resultSet.getBigDecimal("total_amount"),
resultSet.getInt("order_count")
);
๐ก controller
/teachers ๋ก ๋ค์ด๊ฐ๋ฉด Model์ teacherRepository.findAll()๋ก ๋ฐ์์จ List<Teacher>๋ฅผ ๋ด์ ํด๋ผ์ด์ธํธ์ ๋ณด๋ธ๋ค.@Controller
@RequestMapping("/teachers")
@RequiredArgsConstructor
public class TeacherController {
private final TeacherRepository teacherRepository;
@GetMapping
public String list(Model model) {
model.addAttribute("teachers", teacherRepository.findAll());
return "teacher-list";
}
}
๐ผ๏ธ teacher-list.html
<body>
<h1>๐งโ๐ซ ๊ต์ฌ ๋ชฉ๋ก</h1>
<p>
<a href="/">๐ ํ์ผ๋ก</a>
<a href="/teachers/add">โ ๊ต์ฌ ๋ฑ๋ก</a>
</p>
<table>
<thead>
<tr>
<td>ID</td>
<td>์ด๋ฆ</td>
<td>์ก์
</td>
</tr>
</thead>
<tbody th:each="teacher: ${teachers}">
<tr>
<td th:text="${teacher.id}"></td>
<td th:text="${teacher.name}"></td>
<td>
<a th:href="@{'/teachers/edit/' + ${teacher.id}}">์์ </a>
</td>
</tr>
</tbody>
</table>
</body>
<tbody th:each="teacher: ${teachers}">List<Teacher>)์์ ํ๋์ฉ ๋ฐ์ tr์ ๋งคํ๐๏ธ repository
public Teacher findById(int id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM teacher WHERE id=?",
mapper,
id);
// ํ๋ ์ฐพ์๋๋ queryForObject ์ฐ๊ณ ๋ฐ์ธ๋ฉ์ ์ธ๋ฒ์ฌ arg๋ถํฐ
}
public int <update(Teacher teacher) {
return jdbcTemplate.update(
"UPDATE teacher SET name = ? WHERE id = ?",
teacher.getName(),
teacher.getId()
);
}
๐ก controller
@GetMapping("/edit/{id}")
public String editForm(@PathVariable int id, Model model) {
model.addAttribute("teacher", teacherRepository.findById(id));
return "teacher-form";
}
@PostMapping("/edit")
public String edit(@ModelAttribute Teacher teacher) {
teacherRepository.update(teacher);
return "redirect:/teachers";
}
/edit/{id} ๋ก ๋ค์ด์ค๋ฉด findById๋ก Teacher ๊ฐ์ฒด ๋ฐ์์ Model๋ก teacher-form.html์ ๋๊ธฐ๊ฒ๋ ์ ์๐ผ๏ธ teacher-form.html
<form th:action="${teacher.id} == null ? @{/teachers/add} : @{/teachers/edit}" th:object="${teacher}" method="post">
<input type="hidden" th:field="*{id}">
<p><label>์ด๋ฆ: <input type="text" th:field="*{name}" required/></label></p>
<p>
<button type="submit">์ ์ฅํ๊ธฐ</button>
<a href="/teachers">์ทจ์ํ๊ธฐ</a>
</p>
</form>
๐ก controller
@PostMapping("/edit")
public String edit(@ModelAttribute Teacher teacher) {
teacherRepository.update(teacher);
return "redirect:/teachers";
}
th:object="${teacher}" Teacher ๋๊ธฐ๊ณ ์์ผ๋ฏ๋ก @ModelAttribute๋ก ๋ฐ์์์ repository update๐๏ธ repository
public int delete(int id) {
return jdbcTemplate.update(
"DELETE FROM teacher WHERE id = ?",
id
);
}
๐ก controller
@GetMapping("/delete/{id}")
public String delete(@PathVariable int id) {
teacherRepository.delete(id);
return "redirect:/teachers";
}
๐ผ๏ธ teacher-list.html
<a th:href="@{'/teachers/edit/' + ${teacher.id}}">์์ </a>
<a th:href="@{'/teachers/delete/' + ${teacher.id}}">์ญ์ </a>


