JPQL (Java Persistence Query Language)

JOY๐ŸŒฑยท2023๋…„ 4์›” 10์ผ
0

๐Ÿธ JPA

๋ชฉ๋ก ๋ณด๊ธฐ
5/8
post-thumbnail

๐Ÿ’โ€โ™€๏ธ JPQL์ด๋ž€,
์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด ์ง€ํ–ฅ ์ฟผ๋ฆฌ

  • SQL๋ณด๋‹ค ๊ฐ„๊ฒฐํ•˜๋ฉฐ DBMS์— ์ƒ๊ด€์—†์ด ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅ
    (๋ฐฉ์–ธ์„ ํ†ตํ•ด ํ•ด๊ฒฐ๋˜๋ฉฐ ํ•ด๋‹น DBMS์— ๋งž๋Š” SQL์„ ์‹คํ–‰. ํŠน์ • DBMS์— ์˜์กด X)
  • JPQL์€ find() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•œ ์กฐํšŒ์™€ ๋‹ค๋ฅด๊ฒŒ ํ•ญ์ƒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— SQL์„ ์‹คํ–‰ํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒ
    (์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์ด๋ฏธ ์กด์žฌํ•˜๋ฉด ๊ธฐ์กด ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์กฐํšŒํ•œ ๊ฒƒ์€ ๋ฒ„๋ฆผ)
  • JPQL์€ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์งˆ์˜ํ•˜๊ณ  SQL์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์„ ๋Œ€์ƒ์œผ๋กœ ์งˆ์˜ํ•จ. ์ฆ‰, JPQL์€ ๊ฒฐ๊ตญ SQL๋กœ ๋ณ€ํ™˜๋จ

๐Ÿ™‹โ€ JPQL ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

  • ์ž‘์„ฑํ•œ JPQL(๋ฌธ์ž์—ด)์„ em.createQuery ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์ฟผ๋ฆฌ ๊ฐ์ฒด๋กœ ๋งŒ๋“ฌ. ์ฟผ๋ฆฌ ๊ฐ์ฒด๋Š” TypedQuery, Query๋กœ ๋‘ ๊ฐ€์ง€๊ฐ€ ์กด์žฌ
    - TypedQuery : ๋ฐ˜ํ™˜ํ•  ํƒ€์ž…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ง€์ •ํ•˜๋Š” ๋ฐฉ์‹์ผ ๋•Œ ์‚ฌ์šฉ(์ฟผ๋ฆฌ ๊ฐ์ฒด์˜ ๋ฉ”์†Œ๋“œ ์‹คํ–‰ ๊ฒฐ๊ณผ๋กœ ์ง€์ •ํ•œ ํƒ€์ž…์ด ๋ฐ˜ํ™˜๋จ)
    - Query : ๋ฐ˜ํ™˜ํ•  ํƒ€์ž…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ง€์ •ํ•  ์ˆ˜ ์—†์„ ๋•Œ ์‚ฌ์šฉ(์ฟผ๋ฆฌ ๊ฐ์ฒด ๋ฉ”์†Œ๋“œ์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋กœ Object or Object[]์ด ๋ฐ˜ํ™˜๋จ)
  • ์ฟผ๋ฆฌ ๊ฐ์ฒด์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์†Œ๋“œ getSingleResult() ๋˜๋Š” getResultList()๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์กฐํšŒ
    - getSingleResult() : ๊ฒฐ๊ณผ๊ฐ€ ์ •ํ™•ํžˆ ํ•œ ํ–‰์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ (์—†๊ฑฐ๋‚˜ ๋งŽ์œผ๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ)
    - getResultList() : ๊ฒฐ๊ณผ๊ฐ€ 2ํ–‰ ์ด์ƒ์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋ฉฐ ์ปฌ๋ ‰์…˜์„ ๋ฐ˜ํ™˜ (๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด ๋นˆ ์ปฌ๋ ‰์…˜ ๋ฐ˜ํ™˜)

๐Ÿ‘€ Simple

โœ… TypedQuery / Query ๋‹จ์ผ๋ฉ”๋‰ด์กฐํšŒ

createQuery() getSingleResult()

โ—ผ TypedQuery

@Test
public void TypedQuery๋ฅผ_์ด์šฉํ•œ_๋‹จ์ผ๋ฉ”๋‰ด_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// when
	String jpql ="SELECT m.menuName FROM section01_menu AS m WHERE m.menuCode = 181";
	TypedQuery<String> query = entityManager.createQuery(jpql, String.class);
		
	String resultMenuName = query.getSingleResult();
		
	// then
	assertEquals("์งœ์žฅ๋ฐ”๋‚˜๋‚˜์Šค๋ฌด๋””", resultMenuName);
		
}

โ—ผ Query

@Test
public void Query๋ฅผ_์ด์šฉํ•œ_๋‹จ์ผ๋ฉ”๋‰ด_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// when
	String jpql ="SELECT m.menuName FROM section01_menu AS m WHERE m.menuCode = 181";
	Query query = entityManager.createQuery(jpql);		// ๊ฒฐ๊ณผ ๊ฐ’์˜ ํƒ€์ž…์„ ๋ช…์‹œํ•˜์ง€ ์•Š์Œ(์˜ˆ์ธก๋ถˆ๊ฐ€์ด๊ธฐ ๋•Œ๋ฌธ)
		
	Object resultMenuName = query.getSingleResult();	// ๊ฒฐ๊ณผ ๊ฐ’์€ Object๋กœ ๋ฐ˜ํ™˜ ๋จ
		
	// then
	assertTrue(resultMenuName instanceof String); // String ํƒ€์ž…์˜ ์ธ์Šคํ„ด์Šค์ธ์ง€ ํ™•์ธ
	assertEquals("์งœ์žฅ๋ฐ”๋‚˜๋‚˜์Šค๋ฌด๋””", resultMenuName);
		
}

โœ… TypedQuery ๋‹จ์ผ ํ–‰ ์กฐํšŒ

@Test
public void JPQL์„_์ด์šฉํ•œ_๋‹จ์ผํ–‰_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// when
	String jpql = "SELECT m FROM section01_menu AS m WHERE m.menuCode = 666";
	TypedQuery<Menu> query = entityManager.createQuery(jpql, Menu.class);	// ๋ฐ˜ํ™˜ ํƒ€์ž…์„ row์™€ ๋งคํ•‘ํ•  ์—”ํ‹ฐํ‹ฐ ํƒ€์ž…์œผ๋กœ ์„ค์ •

	Menu foundMenu = query.getSingleResult();
		
	// then
	assertEquals(666, foundMenu.getMenuCode());
	System.out.println(foundMenu);
		
}

๐Ÿ’ป Mini Console

Menu [menuCode=666, menuName=Owner์‚ฐ๋‚™์ง€ํฌ๋กœํ”Œ, menuPrice=15000, categoryCode=4, orderableStatus=Y]

โœ… TypedQuery / Query ์—ฌ๋Ÿฌ ํ–‰ ์กฐํšŒ

getResultList() forEach()

โ—ผ TypedQuery

@Test
public void TypedQuery๋ฅผ_์ด์šฉํ•œ_์—ฌ๋Ÿฌํ–‰_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// when
	String jpql = "SELECT m FROM section01_menu AS m";
	TypedQuery<Menu> query = entityManager.createQuery(jpql, Menu.class);	// ๋ฐ˜ํ™˜ ํƒ€์ž…์„ row์™€ ๋งคํ•‘ํ•  ์—”ํ‹ฐํ‹ฐ ํƒ€์ž…์œผ๋กœ ์„ค์ •

	List<Menu> foundMenuList = query.getResultList();
		
	// then
	assertNotNull(foundMenuList);
	foundMenuList.forEach(System.out::println);
		
}

โ—ผ Query

@Test
public void Query๋ฅผ_์ด์šฉํ•œ_์—ฌ๋Ÿฌํ–‰_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// when
	String jpql = "SELECT m FROM section01_menu AS m";
	Query query = entityManager.createQuery(jpql);

	List<Menu> foundMenuList = query.getResultList(); // query๋Š” Object ํƒ€์ž…์ด์ง€๋งŒ, List<Menu>๋กœ ๋‹ค์šด์บ์ŠคํŒ… ์‹œ์ผœ์„œ ๊ทธ๋Œ€๋กœ ๋ฐ›๊ธฐ ๊ฐ€๋Šฅ
		
	// then
	assertNotNull(foundMenuList);
	foundMenuList.forEach(System.out::println);
		
}

๐Ÿ’ป Mini Console

Menu [menuCode=61, menuName=์งœ์žฅ๋ฐ”๋‚˜๋‚˜์Šค๋ฌด๋””, menuPrice=20000, categoryCode=7, orderableStatus=Y]
Menu [menuCode=81, menuName=์น˜์ฆˆ๋˜ฅ์•„์ด์Šคํฌ๋ฆผ, menuPrice=7000, categoryCode=7, orderableStatus=Y]
Menu [menuCode=101, menuName=๋ฏธ๊ณต๊ฐœ๊ณ„์ ˆ๋ฉ”๋‰ด, menuPrice=10000, categoryCode=5, orderableStatus=Y]
...

โœ… ์—ฐ์‚ฐ์ž๋ฅผ ํ™œ์šฉํ•œ ์กฐํšŒ

โ—ผ DISTINCT

@Test
public void distinct๋ฅผ_ํ™œ์šฉํ•œ_์ค‘๋ณต์ œ๊ฑฐ_์—ฌ๋Ÿฌ_ํ–‰_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// when
	String jpql = "SELECT DISTINCT m.categoryCode FROM section01_menu AS m";
	TypedQuery<Integer> query = entityManager.createQuery(jpql, Integer.class);
	List<Integer> categoryCodeList = query.getResultList();
		
	// then
	assertNotNull(categoryCodeList);
	categoryCodeList.forEach(System.out::println);
		
}

โ—ผ IN

@Test
public void in_์—ฐ์‚ฐ์ž๋ฅผ_ํ™œ์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	/* categoryCode๊ฐ€ 6, 10์ธ ๋ฉ”๋‰ด ๋ชฉ๋ก ์กฐํšŒ ์ถœ๋ ฅ */
	// when
	String jpql = "SELECT m FROM section01_menu AS m WHERE m.categoryCode IN (6, 10)";
	Query query = entityManager.createQuery(jpql);
	List<Menu> menuList = query.getResultList();
		
	// then
	assertNotNull(menuList);
	menuList.forEach(System.out::println);

}

โ—ผ LIKE

@Test
public void like_์—ฐ์‚ฐ์ž๋ฅผ_ํ™œ์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	/* ๋งˆ๋Š˜์ด ๋“ค์–ด๊ฐ€๋Š” ๋ฉ”๋‰ด๋ช…์„ ๊ฐ€์ง„ ๋ฉ”๋‰ด ๋ชฉ๋ก ์กฐํšŒ ์ถœ๋ ฅ */
	// when
	String jpql = "SELECT m FROM section01_menu AS m WHERE menuName LIKE '%๋งˆ๋Š˜%'";
	Query query = entityManager.createQuery(jpql);
	List<Menu> menuList = query.getResultList();
		
	// then
	assertNotNull(menuList);
	menuList.forEach(System.out::println);
		
}

๐Ÿ‘€ Parameter

setParameter()

๐Ÿ™‹โ€ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • ์ด๋ฆ„ ๊ธฐ์ค€ ํŒŒ๋ผ๋ฏธํ„ฐ(named parameters)
    : ๋‹ค์Œ์— ์ด๋ฆ„ ๊ธฐ์ค€ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ง€์ •
  • ์œ„์น˜ ๊ธฐ์ค€ ํŒŒ๋ผ๋ฏธํ„ฐ(positional parameters)
    ? ๋‹ค์Œ์— ๊ฐ’์„ ์ฃผ๊ณ  ์œ„์น˜ ๊ฐ’์€ 1๋ถ€ํ„ฐ ์‹œ์ž‘

โœ… ์ด๋ฆ„ ๊ธฐ์ค€ ํŒŒ๋ผ๋ฏธํ„ฐ

@Test
public void ์ด๋ฆ„_๊ธฐ์ค€_ํŒŒ๋ผ๋ฏธํ„ฐ_๋ฐ”์ธ๋”ฉ_๋ฉ”๋‰ด_๋ชฉ๋ก_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// given
	String menuNameParameter = "์น˜์ฆˆ๋˜ฅ์•„์ด์Šคํฌ๋ฆผ";
		
	// when
	String jpql = "SELECT m FROM section02_menu m WHERE m.menuName = :menuName";
		
	List<Menu> menuList = entityManager.createQuery(jpql, Menu.class)
									   .setParameter("menuName", menuNameParameter) // jpql ์ฟผ๋ฆฌ๋ฌธ ์•ˆ์˜ :menuName๊ณผ ๋™์ผํ•˜๊ฒŒ ์ž‘์„ฑํ•ด์•ผํ•จ
									   .getResultList();
		
	// then
	assertNotNull(menuList);
	menuList.forEach(System.out::println);
		
}

๐Ÿ’ป Mini Console

Menu [menuCode=81, menuName=์น˜์ฆˆ๋˜ฅ์•„์ด์Šคํฌ๋ฆผ, menuPrice=7000, categoryCode=7, orderableStatus=Y]

โœ… ์œ„์น˜ ๊ธฐ์ค€ ํŒŒ๋ผ๋ฏธํ„ฐ

@Test
public void ์œ„์น˜_๊ธฐ์ค€_ํŒŒ๋ผ๋ฏธํ„ฐ_๋ฐ”์ธ๋”ฉ_๋ฉ”๋‰ด_๋ชฉ๋ก_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
		
	// given
	String menuNameParameter = "์•„๋ณด์นด๋„์Šค๋ฌด๋””";
		
	// when
	String jpql = "SELECT m FROM section02_menu m WHERE m.menuName = ?1";
		
	List<Menu> menuList = entityManager.createQuery(jpql, Menu.class)
				   					   .setParameter(1, menuNameParameter) // jpql ์ฟผ๋ฆฌ๋ฌธ ์•ˆ์˜ ?1์— ๋“ค์–ด๊ฐˆ ์œ„์น˜ ๋ฐ ๋ณ€์ˆ˜ ์ž‘์„ฑ
				   					   .getResultList();

	// then
	assertNotNull(menuList);
	menuList.forEach(System.out::println);
		
}

๐Ÿ’ป Mini Console

Menu [menuCode=999, menuName=์•„๋ณด์นด๋„์Šค๋ฌด๋””, menuPrice=20000, categoryCode=333, orderableStatus=Y]

๐Ÿ‘€ Projection

๐Ÿ’โ€โ™€๏ธ Projection์ด๋ž€,
SELECT ์ ˆ์— ์กฐํšŒํ•  ๋Œ€์ƒ์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ
(SELECT {ํ”„๋กœ์ ์…˜ ๋Œ€์ƒ} FROM)


๐Ÿ™‹โ€ ํ”„๋กœ์ ์…˜ ๋Œ€์ƒ์˜ ๋ฐฉ์‹

  • ์—”ํ‹ฐํ‹ฐ ํ”„๋กœ์ ์…˜
    • ์›ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ”๋กœ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Œ
    • ์กฐํšŒ๋œ ์—”ํ‹ฐํ‹ฐ๋Š” '์˜์†์„ฑ ์ปจํ…์ŠคํŠธ'๊ฐ€ ๊ด€๋ฆฌ (์—”ํ‹ฐํ‹ฐ ํ”„๋กœ์ ์…˜๋งŒ!)
  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ํ”„๋กœ์ ์…˜
    • ์—”ํ‹ฐํ‹ฐ์™€ ๊ฑฐ์˜ ๋น„์Šทํ•˜๊ฒŒ ์‚ฌ์šฉ๋˜๋ฉฐ ์กฐํšŒ์˜ ์‹œ์ž‘์ ์ด ๋  ์ˆ˜ ์—†์Œ
      => from ์ ˆ์— ์‚ฌ์šฉ ๋ถˆ๊ฐ€
    • ์ฃผ๋กœ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ์š”์†Œ๋“ค๋งŒ์„ ์กฐํšŒํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉ
    • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ๊ด€๋ฆฌ๋˜์ง€ ์•Š์Œ
  • ์Šค์นผ๋ผ ํƒ€์ž… ํ”„๋กœ์ ์…˜
    • ์ˆซ์ž, ๋ฌธ์ž, ๋‚ ์งœ ๊ฐ™์€ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ํƒ€์ž…
    • ์Šค์นผ๋ผ ํƒ€์ž…์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ๊ด€๋ฆฌ๋˜์ง€ ์•Š์Œ
  • new ๋ช…๋ น์–ด๋ฅผ ํ™œ์šฉํ•œ ํ”„๋กœ์ ์…˜
    • ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ๋‹จ์ˆœ ๊ฐ’๋“ค์„ DTO๋กœ ๋ฐ”๋กœ ์กฐํšŒํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ new ํŒจํ‚ค์ง€๋ช….DTO๋ช…์„ ์“ฐ๋ฉด ํ•ด๋‹น DTO๋กœ ๋ฐ”๋กœ ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜ ์žˆ์Œ
    • new ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•œ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ๊ด€๋ฆฌ๋˜์ง€ ์•Š์Œ

โœ… ์—”ํ‹ฐํ‹ฐ ํ”„๋กœ์ ์…˜

โ—ผ ๋‹จ์ผ ์—”ํ‹ฐํ‹ฐ

@Test
public void ๋‹จ์ผ_์—”ํ‹ฐํ‹ฐ_ํ”„๋กœ์ ์…˜_ํ…Œ์ŠคํŠธ() {
    	
    // when
    String jpql = "SELECT m FROM section03_menu m";
    List<Menu> menuList = entityManager.createQuery(jpql, Menu.class).getResultList();
    	
    // then
    assertNotNull(menuList);
    /* ์—”ํ‹ฐํ‹ฐ ํ”„๋กœ์ ์…˜์€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ ๋จ */
    EntityTransaction tran = entityManager.getTransaction();
    tran.begin();
    menuList.get(1).setMenuName("test");
    tran.commit();
    	
}

โ—ผ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„ ์—”ํ‹ฐํ‹ฐ

/* BiDirectionCategory ์—”ํ‹ฐํ‹ฐ */ 
@Entity(name="section03_bidirection_category")
@Table(name="TBL_CATEGORY")
public class BiDirectionCategory {

    @Id
    @Column(name="CATEGORY_CODE")
    private int categoryCode;

    @Column(name="CATEGORY_NAME")
    private String categoryName;

    @Column(name="REF_CATEGORY_CODE")
    private Integer refCategoryCode;

    @OneToMany(mappedBy = "category")
    private List<BiDirectionMenu> menuList;
    
    /* ์ƒ์„ฑ์ž, getter&setter, toString(๋ฐ˜๋“œ์‹œ menuList ์ถœ๋ ฅ ๋ถ€๋ถ„์€ ์ œ๊ฑฐํ•  ๊ฒƒ) */
    
}
/* BiDirectionMenu ์—”ํ‹ฐํ‹ฐ */
@Entity(name="section03_bidirection_menu")
@Table(name="TBL_MENU")
public class BiDirectionMenu {

    @Id
    @Column(name="MENU_CODE")
    private int menuCode;

    @Column(name="MENU_NAME")
    private String menuName;

    @Column(name="MENU_PRICE")
    private int menuPrice;

    @ManyToOne
    @JoinColumn(name="CATEGORY_CODE")
    private BiDirectionCategory category;

    @Column(name="ORDERABLE_STATUS")
    private String orderableStatus;
    
    /* ์ƒ์„ฑ์ž, getter&setter, toString */

}
@Test
public void ์–‘๋ฐฉํ–ฅ_์—ฐ๊ด€๊ด€๊ณ„_์—”ํ‹ฐํ‹ฐ_ํ”„๋กœ์ ์…˜_ํ…Œ์ŠคํŠธ() {
    	
    // given
    int menuCodeParameter = 3;
    	
    // when
    String jpql = "SELECT m.category FROM section03_bidirection_menu m WHERE m.menuCode = :menuCode";
    BiDirectionCategory categoryOfMenu = entityManager.createQuery(jpql, BiDirectionCategory.class)
    												  .setParameter("menuCode", menuCodeParameter)
    												  .getSingleResult();
    	
    // then
    assertNotNull(categoryOfMenu);
    System.out.println(categoryOfMenu);
    /* ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„์— ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ๊ฒฝ์šฐ ์—ญ๋ฐฉํ–ฅ ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰์ด ๊ฐ€๋Šฅ */
    assertNotNull(categoryOfMenu.getMenuList());
    categoryOfMenu.getMenuList().forEach(System.out::println);
    	
}

๐Ÿ’ป Mini Console

BiDirectionMenu{menuCode=143, menuName='ํฌ๋ฆผ์น˜์ฆˆ๋–ก', menuPrice=6000, category=BiDirectionCategory{categoryCode=10, categoryName='๊ธฐํƒ€', refCategoryCode=2}, orderableStatus='Y'}
BiDirectionMenu{menuCode=2, menuName='์šฐ๋Ÿญ๋ฝˆ์‚ด์ ค๋ฆฌ', menuPrice=5000, category=BiDirectionCategory{categoryCode=10, categoryName='๊ธฐํƒ€', refCategoryCode=2}, orderableStatus='Y'}
...

โœ… ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ํ”„๋กœ์ ์…˜

@Embedded @Embeddable

/* EmbeddedMenu ์—”ํ‹ฐํ‹ฐ */
@Entity(name="section03_embedded_menu")
@Table(name="TBL_MENU")
public class EmbeddedMenu {
	
	@Id							
	@Column(name="MENU_CODE")
	private int menuCode;
	
	@Embedded // ๊ฐ’ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์— ์ ์Œ
	private MenuInfo menuInfo;
	
	@Column(name="CATEGORY_CODE")
	private int categoryCode;
	@Column(name="ORDERABLE_STATUS")
	private String orderableStatus;
    
    /* ๊ธฐ๋ณธ ์…‹ํŒ… */
    
}
/* MenuInfo ํด๋ž˜์Šค */
/* ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…(embedded type, ๋ณตํ•ฉ ๊ฐ’ ํƒ€์ž… ๋˜๋Š” ๋‚ด์žฅ ํƒ€์ž…) 
 * ์ƒˆ๋กœ์šด ๊ฐ’ ํƒ€์ž…์„ ์ง์ ‘ ์ •์˜ํ•œ ๊ฒƒ์œผ๋กœ ์ฃผ๋กœ ๊ธฐ๋ณธ ๊ฐ’ ํƒ€์ž…์„ ๋ชจ์•„์„œ ๋งŒ๋“  ํ•˜๋‚˜์˜ ํƒ€์ž…์„ ๋งํ•จ.
 * ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ ์ค‘ ์ผ๋ถ€๋ถ„์„ ํ•˜๋‚˜์˜ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์œผ๋กœ ์ •์˜ํ•˜๋ฉด ์•Œ์•„๋ณด๊ธฐ ์‰ฝ๊ณ , ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’๊ฒŒ ๋””์ž์ธ ํ•  ์ˆ˜ ์žˆ์–ด ์œ ์ง€๋ณด์ˆ˜์— ์šฉ์ด
 * */
@Embeddable // ๊ฐ’ ํƒ€์ž…์„ ์ •์˜ํ•˜๊ธฐ ์œ„ํ•œ ์–ด๋…ธํ…Œ์ด์…˜
public class MenuInfo {

	/* '๋ฉ”๋‰ด๋ช…'๊ณผ '๊ฐ€๊ฒฉ'์ด ๋งŽ์ด ์‚ฌ์šฉ๋œ๋‹ค๋Š” ๊ฐ€์ • ํ•˜์— ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ์˜ˆ์‹œ ์ž‘์„ฑ */
	@Column(name="MENU_NAME")
	private String menuName;
	
	@Column(name="MENU_PRICE")
	private int menuPrice;
    
    /* ๊ธฐ๋ณธ ์…‹ํŒ… */
    
}
@Test
public void ์ž„๋ฒ ๋””๋“œ_ํƒ€์ž…_ํ”„๋กœ์ ์…˜_ํ…Œ์ŠคํŠธ() {
    	
    // when
    String jpql = "SELECT m.menuInfo FROM section03_embedded_menu m";
    List<MenuInfo> menuInfoList = entityManager.createQuery(jpql, MenuInfo.class).getResultList();
    	
    // then
    assertNotNull(menuInfoList);
    menuInfoList.forEach(System.out::println);
    	
}

๐Ÿ’ป Mini Console

MenuInfo [menuName=JPA์‚ฐ ์งฌ๋ฝ•๋ง› ๋ง‰๊ฑธ๋ฆฌ, menuPrice=15000]
MenuInfo [menuName=์•„๋ณด์นด๋„์Šค๋ฌด๋””, menuPrice=20000]
MenuInfo [menuName=์งœ์žฅ๋ฐ”๋‚˜๋‚˜์Šค๋ฌด๋””, menuPrice=20000]
MenuInfo [menuName=์น˜์ฆˆ๋˜ฅ์•„์ด์Šคํฌ๋ฆผ, menuPrice=7000]
...

โœ… ์Šค์นผ๋ผ ํƒ€์ž… ํ”„๋กœ์ ์…˜

โ—ผ TypedQuery๋ฅผ ์ด์šฉํ•œ ์Šค์นผ๋ผ ํƒ€์ž… (๋‹จ์ผํ–‰)

@Test
public void TypedQuery๋ฅผ_์ด์šฉํ•œ_์Šค์นผ๋ผ_ํƒ€์ž…_ํ”„๋กœ์ ์…˜_ํ…Œ์ŠคํŠธ() {
    	
    // when
    String jpql = "SELECT c.categoryName FROM section03_category c";
    List<String> categoryNameList = entityManager.createQuery(jpql, String.class).getResultList();
    	
    // then
    assertNotNull(categoryNameList);
    categoryNameList.forEach(System.out::println);
   }

โ—ผ Query๋ฅผ ์ด์šฉํ•œ ์Šค์นผ๋ผ ํƒ€์ž… (๋‹ค์ค‘์—ด)

/* ์กฐํšŒํ•˜๋ ค๋Š” ์ปฌ๋Ÿผ ๊ฐ’์ด 1๊ฐœ์ธ ๊ฒฝ์šฐ TypeQuery๋กœ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ๋‹จ์ผ ๊ฐ’์— ๋Œ€ํ•ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋‹ค์ค‘ ์—ด ์ปฌ๋Ÿผ์„ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ ํƒ€์ž…์„ ์ง€์ •ํ•˜์ง€ ๋ชป ํ•จ 
 * ๊ทธ๋•Œ๋Š” TypedQuery ๋Œ€์‹  Query๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Object[]๋กœ ํ–‰์˜ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ ๋ฐ›์•„ ์‚ฌ์šฉ
 * */
@Test
public void Query๋ฅผ_์ด์šฉํ•œ_์Šค์นผ๋ผ_ํƒ€์ž…_ํ”„๋กœ์ ์…˜_ํ…Œ์ŠคํŠธ() {
    	
    // when
    String jpql = "SELECT c.categoryCode, c.categoryName FROM section03_category c";
    List<Object[]> categoryList = entityManager.createQuery(jpql).getResultList();
    	
    // then
    /* row : forEach๋ฅผ ์‚ฌ์šฉ ์‹œ, Object[]์˜ ํ•œ ํ–‰์”ฉ ์ถœ๋ ฅ์ด ๋˜๋ฏ€๋กœ ๊ทธ๊ฒƒ(row)์„ for๋ฌธ์œผ๋กœ ๋‹ค์‹œ column ๊นŒ์ง€ ์ถœ๋ ฅ */
    assertNotNull(categoryList);
    categoryList.forEach(row -> { 
    	for(Object column : row) 
    		System.out.print(column + " "); 
    });
}
    
/* Object๋ฅผ ์ผ์ผํžˆ ์ธ๋ฑ์Šค๋ณ„๋กœ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋‹ค์šด์บ์ŠคํŒ… ํ•˜๋Š” ๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ ๋•Œ๋ฌธ์— new ๋ช…๋ น์–ด๋ฅผ ํ™œ์šฉํ•œ ํ”„๋กœ์ ์…˜์„ ๋” ๋งŽ์ด ํ™œ์šฉ */

๐Ÿ’ป Mini Console

1 ์‹์‚ฌ 2 ์Œ๋ฃŒ 3 ๋””์ €ํŠธ 4 ํ•œ์‹ 5 ์ค‘์‹ 6 ์ผ์‹ 7 ํ“จ์ „ ...

โœ… new ๋ช…๋ น์–ด๋ฅผ ํ™œ์šฉํ•œ ํ”„๋กœ์ ์…˜

/* CategoryInfo ํด๋ž˜์Šค */
/* ์ด ํด๋ž˜์Šค๋Š” ์—”ํ‹ฐํ‹ฐ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์Œ
 * ๋‹ค๋งŒ ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ ์ •๋ณด๋ฅผ ์˜๋ฏธ ์žˆ๋Š” ๋‹จ์œ„๋กœ ๋ฌถ๊ธฐ ์œ„ํ•œ ์šฉ๋„์˜ ํด๋ž˜์Šค ('4. new ๋ช…๋ น์–ด๋ฅผ ํ™œ์šฉํ•œ ํ”„๋กœ์ ์…˜'์— ์‚ฌ์šฉ๋จ) */
public class CategoryInfo {

	private int categoryCode;
	private String categoryName;
    
    /* ๊ธฐ๋ณธ ์…‹ํŒ… */
    
}
@Test
public void new_๋ช…๋ น์–ด๋ฅผ_ํ™œ์šฉํ•œ_ํ”„๋กœ์ ์…˜_ํ…Œ์ŠคํŠธ() {
    	
    // when
    /* CategoryInfo ํด๋ž˜์Šค์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ƒ์„ฑ์ž๋ฅผ ํ˜ธ์ถœํ•ด์„œ ํ•ด๋‹น ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜๋ฐ›์Œ
     * (FROM ์ ˆ์€ ํ•ด๋‹น ์ปฌ๋Ÿผ์ด ์‹ค์ œ๋กœ ์กด์žฌํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๋ช…์„ ์ž‘์„ฑ) */
    String jpql = "SELECT new com.greedy.section03.projection.CategoryInfo(c.categoryCode, c.categoryName) FROM section03_category c";
    List<CategoryInfo> categoryInfoList = entityManager.createQuery(jpql, CategoryInfo.class).getResultList();
    	
    // then
    assertNotNull(categoryInfoList);
    categoryInfoList.forEach(System.out::println);
    	
}

๐Ÿ’ป Mini Console

CategoryInfo [categoryCode=1, categoryName=์‹์‚ฌ]
CategoryInfo [categoryCode=2, categoryName=์Œ๋ฃŒ]
CategoryInfo [categoryCode=3, categoryName=๋””์ €ํŠธ]
...

๐Ÿ‘€ Paging

โœ… Paging API ํ™œ์šฉ

setFirstResult() setMaxResults()

@Test
public void ํŽ˜์ด์ง•_API๋ฅผ_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    // given
    int offset = 15;	// ์กฐํšŒ๋ฅผ ๊ฑด๋„ˆ ๋›ธ ํ–‰์˜ ์ˆ˜ (limit์ด 5๋ผ๋Š” ๊ฐ€์ •ํ•˜์—, 0์ด๋ฉด ์ฒซ ํŽ˜์ด์ง€, 5๋ฉด ๋‘ ๋ฒˆ์งธ ํŽ˜์ด์ง€)
    int limit = 5;		// ์กฐํšŒํ•  ํ–‰์˜ ์ˆ˜
    	
    // when
    String jpql = "SELECT m FROM section04_menu m ORDER BY m.menuCode DESC";
    	
    /* ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด offset๊ณผ limit์„ ํ™œ์šฉํ•˜๋Š” ๋ฌธ๋ฒ•์œผ๋กœ ์‹คํ–‰๋˜์–ด ์žˆ๋Š”๋ฐ, ์ด๋Š” ์˜ค๋ผํด 12๋ฒ„์ „ ์ดํ›„ ์ถ”๊ฐ€๋œ ๋ฌธ๋ฒ•
     * ์˜ค๋ผํด 11์ดํ•˜ ๋ฒ„์ „์—์„œ๋Š” rownum์„ ์ด์šฉํ•œ ์ธ๋ผ์ธ๋ทฐ ๋ฐฉ์‹์œผ๋กœ ์ฟผ๋ฆฌ๊ฐ€ ๋™์ž‘ */
    /* TypedQuery์— offset๊ณผ limit์„ set ํ•˜๊ธฐ */
    List<Menu> menuList = entityManager.createQuery(jpql, Menu.class)
    								   .setFirstResult(offset)	// ์กฐํšŒ๋ฅผ ์‹œ์ž‘ํ•  ์œ„์น˜(0๋ถ€ํ„ฐ ์‹œ์ž‘)
    								   .setMaxResults(limit)	// ์กฐํšŒํ•  ๋ฐ์ดํ„ฐ์˜ ์ˆ˜
    								   .getResultList();
    	
    // then
    assertNotNull(menuList);
    menuList.forEach(System.out::println);
}

๐Ÿ’ป Mini Console

Menu [menuCode=101, menuName=๋ฏธ๊ณต๊ฐœ๊ณ„์ ˆ๋ฉ”๋‰ด, menuPrice=10000, categoryCode=5, orderableStatus=Y]
Menu [menuCode=81, menuName=์น˜์ฆˆ๋˜ฅ์•„์ด์Šคํฌ๋ฆผ, menuPrice=7000, categoryCode=7, orderableStatus=Y]
Menu [menuCode=61, menuName=์งœ์žฅ๋ฐ”๋‚˜๋‚˜์Šค๋ฌด๋””, menuPrice=20000, categoryCode=7, orderableStatus=Y]
Menu [menuCode=21, menuName=๋Œ๋ฏธ๋‚˜๋ฆฌ๋ฐฑ์„ค๊ธฐ, menuPrice=5000, categoryCode=11, orderableStatus=Y]
Menu [menuCode=20, menuName=๋งˆ๋ผ๊น์‡ผํ•œ๋ผ๋ด‰, menuPrice=22000, categoryCode=5, orderableStatus=Y]

๐Ÿ‘€ Group function

๐Ÿ™‹โ€ JPQL์˜ ๊ทธ๋ฃนํ•จ์ˆ˜(COUNT, MAX, MIN, SUM, AVG) ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

  • ๊ทธ๋ฃนํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์€ ๊ฒฐ๊ณผ ๊ฐ’์ด ์ •์ˆ˜๋ฉด Long, ์‹ค์ˆ˜๋ฉด Double๋กœ ๋ฐ˜ํ™˜๋จ
  • ๊ฐ’์ด ์—†๋Š” ์ƒํƒœ์—์„œ count๋ฅผ ์ œ์™ธํ•œ ๊ทธ๋ฃน ํ•จ์ˆ˜๋Š” null์ด ๋˜๊ณ  count๋งŒ 0์ด ๋จ
    ๋”ฐ๋ผ์„œ ๋ฐ˜ํ™˜ ๊ฐ’์„ ๋‹ด๊ธฐ ์œ„ํ•ด ์„ ์–ธํ•˜๋Š” ๋ณ€์ˆ˜ ํƒ€์ž…์„ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•์œผ๋กœ ํ•˜๊ฒŒ ๋˜๋ฉด, ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ ์–ธ๋ฐ•์‹ฑ ํ•  ๋•Œ NPE๊ฐ€ ๋ฐœ์ƒ (Wrapper ํด๋ž˜์Šค๋กœ ๋ณ€๊ฒฝํ•  ๊ฒƒ)
  • ๊ทธ๋ฃน ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ์ž๋ฃŒํ˜•์€ Long or Doubleํ˜•์ด๊ธฐ ๋Œ€๋ฌธ์— Having์ ˆ์—์„œ ๊ทธ๋ฃน ํ•จ์ˆ˜ ๊ฒฐ๊ณผ ๊ฐ’๊ณผ ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์€ Long or Double๋กœ ํ•ด์•ผํ•จ

โœ… COUNT

@Test
public void ํŠน์ •_์นดํ…Œ๊ณ ๋ฆฌ์˜_๋“ฑ๋ก๋œ_๋ฉ”๋‰ด_์ˆ˜_์กฐํšŒ() {
    	
    // given
    int categoryCodeParameter = 4; 
    	
    // when
    String jpql = "SELECT COUNT(m.menuPrice) FROM section05_menu m WHERE m.categoryCode = :categoryCode";
    long countOfMenu = entityManager.createQuery(jpql, Long.class) /* ๋ฐ˜ํ™˜๊ฐ’์€ ์ •์ˆ˜์ผํ…Œ๋‹ˆ Long */
    								.setParameter("categoryCode", categoryCodeParameter)
    								.getSingleResult();
    	
    // then
    assertTrue(countOfMenu >= 0);
    System.out.println(countOfMenu); /* ์—†๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ž…๋ ฅํ•ด๋„ 0 ์ถœ๋ ฅ (NPE ๋ฐœ์ƒ X) */
}

โœ… SUM

COUNT ์ด์™ธ์˜ ๊ทธ๋ฃนํ•จ์ˆ˜ ์‚ฌ์šฉ ์˜ˆ์‹œ

assertThrows() assertDoesNotThrow()

@Test
public void count๋ฅผ_์ œ์™ธํ•œ_๋‹ค๋ฅธ_๊ทธ๋ฃนํ•จ์ˆ˜์˜_์กฐํšŒ๊ฒฐ๊ณผ๊ฐ€_์—†๋Š”_๊ฒฝ์šฐ_ํ…Œ์ŠคํŠธ() {
    	
    // given
    int categoryCodeParameter = 2; 
    	
    // when
    String jpql = "SELECT SUM(m.menuPrice) FROM section05_menu m WHERE m.categoryCode = :categoryCode";
    	
    // then
    /* NPE๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ์˜ˆ์ƒ */
    assertThrows(NullPointerException.class, () -> {
    		
    	/* ๋ฐ˜ํ™˜ ๊ฐ’์„ ๋‹ด์„ ๋ณ€์ˆ˜์˜ ํƒ€์ž…์„ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•์œผ๋กœ ํ•˜๋Š” ๊ฒฝ์šฐ Wrapper ํƒ€์ž…์„ ์–ธ๋ฐ•์‹ฑ ํ•˜๋Š” ๊ณผ์ •์—์„œ NPE๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋จ */
    	long sumOfPrice = entityManager.createQuery(jpql, Long.class)
    								   .setParameter("categoryCode", categoryCodeParameter)
    								   .getSingleResult();
    		
    });
    /* NPE๊ฐ€ ๋ฐœ์ƒํ•˜์ง€์•Š์Œ์„ ์˜ˆ์ƒ */
    assertDoesNotThrow(() -> {
    		
    	/* ๋ฐ˜ํ™˜ ๊ฐ’์„ ๋‹ด๋Š” ๋ณ€์ˆ˜๋ฅผ Wrapper ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•ด์•ผ null ๊ฐ’์ด ๋ฐ˜ํ™˜ ๋˜์–ด๋„ NPE๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ */
    	Long sumOfPrice = entityManager.createQuery(jpql, Long.class)
    								   .setParameter("categoryCode", categoryCodeParameter)
    								   .getSingleResult();
    		
    });
}

โœ… GROUP BY์™€ HAVING์ ˆ์„ ์ด์šฉํ•œ SUM

@Test
public void groupby์ ˆ๊ณผ_having์ ˆ์„_์‚ฌ์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    // given
    long minPrice = 50000L;		// ๊ทธ๋ฃน ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ Long์ด๋ฏ€๋กœ ๋น„๊ต๋ฅผ ์œ„ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋„ Long ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด์•ผํ•จ

    // when
    String jpql = "SELECT m.categoryCode, SUM(m.menuPrice) "
    			+ " FROM section05_menu m "
    			+ " GROUP BY m.categoryCode "
    			+ " HAVING SUM(m.menuPrice) >= :minPrice";
    	
    List<Object[]> sumPriceOfCategoryList = entityManager.createQuery(jpql, Object[].class)
    													 .setParameter("minPrice", minPrice)
    													 .getResultList();
    	
    // then
    assertNotNull(sumPriceOfCategoryList);
    sumPriceOfCategoryList.forEach(row -> {
    	for(Object column : row) System.out.print(column + " ");
    });
    	
}

๐Ÿ’ป Mini Console

6 64000 7 77000 10 54000 4 80000

๐Ÿ‘€ Join

๐Ÿ™‹โ€ JOIN์˜ ์ข…๋ฅ˜

  • ์ผ๋ฐ˜ JOIN : ์ผ๋ฐ˜์ ์ธ SQL ์กฐ์ธ์„ ์˜๋ฏธ(๋‚ด๋ถ€ ์กฐ์ธ, ์™ธ๋ถ€ ์กฐ์ธ, ์ปฌ๋ ‰์…˜ ์กฐ์ธ, ์„ธํƒ€ ์กฐ์ธ)
  • ํŽ˜์น˜ JOIN : JPQL์—์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ์—ฐ๊ด€ ๋œ ์—”ํ‹ฐํ‹ฐ๋‚˜ ์ปฌ๋ ‰์…˜์„ ํ•œ ๋ฒˆ์— ์กฐํšŒ ๊ฐ€๋Šฅ
    - ์ง€์—ฐ ๋กœ๋”ฉ์ด ์•„๋‹Œ ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ JOIN FETCH ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉ

โœ… ์ผ๋ฐ˜ JOIN

/* Category ์—”ํ‹ฐํ‹ฐ */
@Entity(name="section06_category")
@Table(name="TBL_CATEGORY")
public class Category {

    @Id
    @Column(name="CATEGORY_CODE")
    private int categoryCode;

    @Column(name="CATEGORY_NAME")
    private String categoryName;

    @Column(name="REF_CATEGORY_CODE")
    private Integer refCategoryCode;

    @OneToMany(mappedBy = "category")
    private List<Menu> menuList;
    
     /* ์ƒ์„ฑ์ž, getter&setter, toString(๋ฐ˜๋“œ์‹œ menuList ์ถœ๋ ฅ ๋ถ€๋ถ„์€ ์ œ๊ฑฐํ•  ๊ฒƒ) */

}
/* Menu ์—”ํ‹ฐํ‹ฐ */
@Entity(name="section06_menu")
@Table(name="TBL_MENU")
public class Menu {

    @Id
    @Column(name="MENU_CODE")
    private int menuCode;

    @Column(name="MENU_NAME")
    private String menuName;

    @Column(name="MENU_PRICE")
    private int menuPrice;

    @ManyToOne
    @JoinColumn(name="CATEGORY_CODE")
    private Category category;

    @Column(name="ORDERABLE_STATUS")
    private String orderableStatus;
    
    /* ์ƒ์„ฑ์ž, getter&setter, toString(๋ฐ˜๋“œ์‹œ menuList ์ถœ๋ ฅ ๋ถ€๋ถ„์€ ์ œ๊ฑฐํ•  ๊ฒƒ) */
    
}

โ—ผ ๋‚ด๋ถ€ JOIN

@Test
public void ๋‚ด๋ถ€์กฐ์ธ์„_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    /* Menu ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ์กฐํšŒ๋งŒ ์ผ์–ด๋‚˜๊ณ  Category ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ์กฐํšŒ๋Š” ๋‚˜์ค‘์— ํ•„์š”ํ•  ๋•Œ ์ผ์–ด๋‚จ 
     * select์˜ ๋Œ€์ƒ์€ ์˜์†ํ™”ํ•˜์—ฌ ๊ฐ€์ ธ์˜ค์ง€๋งŒ join์˜ ๋Œ€์ƒ์€ ์˜์†ํ™”ํ•˜์—ฌ ๊ฐ€์ ธ์˜ค์ง€ ์•Š์Œ */
    	
    // when
    String jpql = "SELECT m FROM section06_menu m JOIN m.category c";
    List<Menu> menuList = entityManager.createQuery(jpql, Menu.class).getResultList();
    	
    // then
    assertNotNull(menuList);
    menuList.forEach(System.out::println);
}

โ—ผ ์™ธ๋ถ€ JOIN

@Test
public void ์™ธ๋ถ€์กฐ์ธ์„_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    // when
    String jpql = "SELECT m.menuName, c.categoryName"
    			+ " FROM section06_menu m"
    			+ " RIGHT JOIN m.category c"
    			+ " ORDER BY m.category.categoryCode"; /* category ํ•„๋“œ์˜ categoryCode๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ๊ฐ€๋Šฅ */
    List<Object[]> menuList = entityManager.createQuery(jpql, Object[].class).getResultList();
    	
    // then
    assertNotNull(menuList);
    menuList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print("[" + col + "]")); /* Object ๋ฐฐ์—ด(row)์„ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“ค๊ณ  forEach๋กœ ์ฒ˜๋ฆฌ */
    	System.out.println(); /* ํ•˜๋‚˜์˜ ํ–‰์ด ์ถœ๋ ฅ๋˜๋ฉด ๊ฐœํ–‰ */
    });
}

๐Ÿ’ป Mini Console

[JPA์‚ฐ ์งฌ๋ฝ•๋ง› ๋ง‰๊ฑธ๋ฆฌ][ํ“จ์ „]
[์งœ์žฅ๋ฐ”๋‚˜๋‚˜์Šค๋ฌด๋””][ํ“จ์ „]
[์น˜์ฆˆ๋˜ฅ์•„์ด์Šคํฌ๋ฆผ][ํ“จ์ „]
[์ƒํผ์˜ค์ด๋ผ๋–ผ][์ปคํ”ผ]
...

โ—ผ ์ปฌ๋ ‰์…˜ JOIN

@Test
public void ์ปฌ๋ ‰์…˜์กฐ์ธ์„_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    /* ์ปฌ๋ ‰์…˜ ์กฐ์ธ์€ ์˜๋ฏธ์ƒ ๋ถ„๋ฅ˜ ๋œ ๊ฒƒ์œผ๋กœ ์ปฌ๋ ‰์…˜์„ ์ง€๋‹ˆ๊ณ  ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์กฐ์ธํ•˜๋Š” ๊ฒƒ์„ ๋งํ•จ */
    	
    // when
    String jpql = "SELECT c.categoryName, m.menuName"
    			+ " FROM section06_category c"
    			+ " LEFT JOIN c.menuList m"; /* category ์—”ํ‹ฐํ‹ฐ์˜ menuList๋ผ๋Š” ์ปฌ๋ ‰์…˜์„ ์กฐ์ธ */
    List<Object[]> categoryList = entityManager.createQuery(jpql, Object[].class).getResultList();
    	
    // then
    assertNotNull(categoryList);
    categoryList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print("[" + col + "]")); /* Object ๋ฐฐ์—ด(row)์„ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“ค๊ณ  forEach๋กœ ์ฒ˜๋ฆฌ */
    	System.out.println(); /* ํ•˜๋‚˜์˜ ํ–‰์ด ์ถœ๋ ฅ๋˜๋ฉด ๊ฐœํ–‰ */
    });
}

๐Ÿ’ป Mini Console

[ํ“จ์ „][JPA์‚ฐ ์งฌ๋ฝ•๋ง› ๋ง‰๊ฑธ๋ฆฌ]
[์ง€์ค‘ํ•ด][์•„๋ณด์นด๋„์Šค๋ฌด๋””]
[ํ“จ์ „][์งœ์žฅ๋ฐ”๋‚˜๋‚˜์Šค๋ฌด๋””]
[ํ“จ์ „][์น˜์ฆˆ๋˜ฅ์•„์ด์Šคํฌ๋ฆผ] 

โ—ผ ์„ธํƒ€ JOIN

@Test
public void ์„ธํƒ€์กฐ์ธ์„_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    /* ์„ธํƒ€ ์กฐ์ธ์€ ์กฐ์ธ๋˜๋Š” ๋ชจ๋“  ๊ฒฝ์šฐ์˜ ์ˆ˜๋ฅผ ๋‹ค ๋ฐ˜ํ™˜ํ•˜๋Š” ํฌ๋กœ์Šค ์กฐ์ธ๊ณผ ๊ฐ™์Œ
     * FROM ์ ˆ์— ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋‚˜์—ดํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํฌ๋กœ์Šค ์กฐ์ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ 
     * */
    	
    // when
    String jpql = "SELECT c.categoryName, m.menuName"
    			+ " FROM section06_category c, section06_menu m"; /* FROM ์ ˆ์— ๋‘ ์—”ํ‹ฐํ‹ฐ ๋ชจ๋‘ ์ž‘์„ฑ */
    List<Object[]> categoryList = entityManager.createQuery(jpql, Object[].class).getResultList();
    	
    // then
    assertNotNull(categoryList);
    categoryList.forEach(row -> {
    	Stream.of(row).forEach(col -> System.out.print("[" + col + "]")); /* Object ๋ฐฐ์—ด(row)์„ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋งŒ๋“ค๊ณ  forEach๋กœ ์ฒ˜๋ฆฌ */
    	System.out.println(); /* ํ•˜๋‚˜์˜ ํ–‰์ด ์ถœ๋ ฅ๋˜๋ฉด ๊ฐœํ–‰ */
    });
}

โœ… ํŽ˜์น˜ JOIN

@Test
public void ํŽ˜์น˜์กฐ์ธ์„_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    /* ํŽ˜์น˜ ์กฐ์ธ์„ ํ•˜๋ฉด ์ฒ˜์Œ SQL ์‹คํ–‰ ํ›„ ๋กœ๋”ฉํ•  ๋•Œ ์กฐ์ธ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค ์กฐํšŒํ•œ ๋’ค์— ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋Œ€๋ฌธ์— ์ฟผ๋ฆฌ ์‹คํ–‰ ํšŸ์ˆ˜๊ฐ€ ์ค„์–ด๋“ค๊ฒŒ ๋จ
     * ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ์„ฑ๋Šฅ ํ–ฅ์ƒ ! 
     * */
    	
    // when
    String jpql = "SELECT m FROM section06_menu m JOIN FETCH m.category c"; /* JOIN ๋’ค์— FETCH ์ถ”๊ฐ€ */
    List<Menu> menuList = entityManager.createQuery(jpql, Menu.class).getResultList();
    	
    // then
    assertNotNull(menuList);
    menuList.forEach(System.out::println);
    	
    /* ๊ฒ€์ƒ‰ ์กฐ๊ฑด(where์ ˆ)์—์„œ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๊ตณ์ด FETCH ์กฐ์ธ์„ ์‚ฌ์šฉํ•  ํ•„์š”์—†์Œ
     * ํ•˜์ง€๋งŒ select์ ˆ์—์„œ ์กฐํšŒํ•  ์ปฌ๋Ÿผ์ด๋ผ๋ฉด FETCH ์กฐ์ธ์„ ์‚ฌ์šฉํ•˜์—ฌ SELECT๋ฌธ ์ฒซ ์‹คํ–‰ ์‹œ, ํ•œ๊บผ๋ฒˆ์— ์กฐํšŒํ•ด์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์—ฌ
     * ์„ฑ๋Šฅ๋ฉด์—์„œ ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ๋„๋ก ํ•จ (์ฆ‰, ๋ชฉ์ ์— ๋”ฐ๋ผ FETCH๊ฐ€ ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ์ง€๋งŒ ํ•ญ์ƒ์€ ์•„๋‹˜) */
}

๐Ÿ‘€ Sub query

๐Ÿ’โ€โ™€๏ธ JPQL๋„ SQL์ฒ˜๋Ÿผ ์„œ๋ธŒ ์ฟผ๋ฆฌ๋ฅผ ์ง€์›ํ•˜์ง€๋งŒ select, from ์ ˆ์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ณ  where, having์ ˆ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

โœ… ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•œ ์กฐํšŒ

@Test
public void ์„œ๋ธŒ์ฟผ๋ฆฌ๋ฅผ_์ด์šฉํ•œ_๋ฉ”๋‰ด_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    // given
    String categoryNameParameter = "ํ•œ์‹";
    	
    // when
    String jpql = "SELECT m "
    			+ "FROM section07_menu m "
    			+ "WHERE m.categoryCode = (SELECT c.categoryCode "
    									+ "FROM section07_category c "
    									+ "WHERE c.categoryName = :categoryName)";
    List<Menu> menuList = entityManager.createQuery(jpql, Menu.class)
    								   .setParameter("categoryName", categoryNameParameter)
    								   .getResultList();
    	
    // then
    assertNotNull(menuList);
    menuList.forEach(System.out::println);
}

๐Ÿ‘€ Named query

๐Ÿ™‹โ€ ๋™์ ์ฟผ๋ฆฌ? ์ •์ ์ฟผ๋ฆฌ?

  • ๋™์ ์ฟผ๋ฆฌ : ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ๋ฐฉ์‹์ฒ˜๋Ÿผ EntityManager๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ JPQL์„ ๋ฌธ์ž์—ด๋กœ ๋Ÿฐํƒ€์ž„ ์‹œ์ ์— ๋™์ ์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ์‹
    (๋™์ ์œผ๋กœ ๋งŒ๋“ค์–ด์งˆ ์ฟผ๋ฆฌ๋ฅผ ์œ„ํ•œ ์กฐ๊ฑด์‹์ด๋‚˜ ๋ฐ˜๋ณต๋ฌธ์€ ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Œ)
  • ์ •์ ์ฟผ๋ฆฌ : ๋ฏธ๋ฆฌ ์ฟผ๋ฆฌ๋ฅผ ์ •์˜ํ•˜๊ณ  ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋งํ•˜๋ฉฐ ๋ฏธ๋ฆฌ ์ •์˜ํ•œ ์ฝ”๋“œ๋Š” ์ด๋ฆ„์„ ๋ถ€์—ฌํ•ด์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋จ
    ์ด๋ฅผ NamedQuery๋ผ๊ณ  ํ•จ. ์–ด๋…ธํ…Œ์ด์…˜ ๋ฐฉ์‹๊ณผ xml ๋ฐฉ์‹ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ ์ฟผ๋ฆฌ๊ฐ€ ๋ณต์žกํ• ์ˆ˜๋ก xml ๋ฐฉ์‹์ด ์„ ํ˜ธ๋จ.

โœ… ๋™์  ์ฟผ๋ฆฌ

๐Ÿ™‹โ€ ๋™์  SQL์„ ์ž‘์„ฑํ•˜๊ธฐ์— JPQL์€ ๋งŽ์ด ์–ด๋ ค์›€. ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฟผ๋ฆฌ๋ฅผ ๋งค๋ฒˆ ์‹คํ–‰ํ•ด์„œ ํ™•์ธํ•ด์•ผํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ์Œ
Criteria๋‚˜ queryDSL์„ ํ™œ์šฉํ•˜๋ฉด ๋ณด๋‹ค ํŽธ๋ฆฌํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ฟผ๋ฆฌ ์ž‘์„ฑ ์‹œ ์ปดํŒŒ์ผ ์—๋Ÿฌ๋กœ ์ž˜๋ชป ๋œ ๋ถ€๋ถ„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด ์ž‘์„ฑํ•˜๊ธฐ๋„ ํŽธํ•จ
๋งˆ์ด๋ฐ”ํ‹ฐ์Šค๋ฅผ ํ˜ผ์šฉํ•˜๊ฑฐ๋‚˜ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค์˜ ๊ตฌ๋ฌธ ๋นŒ๋” API๋ฅผ ํ™œ์šฉํ•ด๋„ ์ข‹์Œ

@Test
public void ๋™์ ์ฟผ๋ฆฌ๋ฅผ_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    // given
    /* Menu ํ…Œ์ด๋ธ”์— ์žˆ๋Š” ๋ชจ๋“  ๊ฐ’ ๊ฐ€์ ธ์˜ด */
//  String searchName = ""; 
//  int searchCategoryCode = 0;
    	
    /* ์นดํ…Œ๊ณ ๋ฆฌ ์ƒ๊ด€์—†์ด ์น˜์ฆˆ๊ฐ€ ํฌํ•จ๋œ ๋ชจ๋“  ๋ฉ”๋‰ด ๊ฐ€์ ธ์˜ด */
//  String searchName = "์น˜์ฆˆ";
//  int searchCategoryCode = 0;
    	
    /* 7๋ฒˆ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์น˜์ฆˆ๊ฐ€ ํฌํ•จ๋œ ๋ฉ”๋‰ด๋“ค ๊ฐ€์ ธ์˜ด */
    String searchName = "์น˜์ฆˆ";
    int searchCategoryCode = 7;
    	
    // when
    StringBuilder jpql = new StringBuilder("SELECT m FROM section08_menu m "); /* ์ด ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™” ํ•ด๋‘๊ณ  ๋‹ค๋ฅธ ๊ตฌ๋ฌธ๋“ค์„ appendํ•  ๊ฒƒ */
    if(searchName != null && !searchName.isEmpty() && searchCategoryCode > 0) {
    	jpql.append("WHERE ");
    	jpql.append("m.menuName LIKE '%' || :menuName || '%' ");
    	jpql.append("AND ");
    	jpql.append("m.categoryCode = :categoryCode ");
    } else {
    	/* ๋‘˜ ์ค‘์— ํ•˜๋‚˜๋งŒ ์žˆ์„ ๊ฒฝ์šฐ */
    	if(searchName != null && !searchName.isEmpty()) {
    		jpql.append("WHERE ");
    		jpql.append("m.menuName LIKE '%' || :menuName || '%' ");
    	} else if(searchCategoryCode > 0) {
    		jpql.append("WHERE ");
    		jpql.append("m.categoryCode = :categoryCode ");
    	}
    }
    	
    TypedQuery<Menu> query = entityManager.createQuery(jpql.toString(), Menu.class); /* jpql์„ toString()ํ•˜์—ฌ ์ฟผ๋ฆฌ ๋งŒ๋“ค๊ธฐ */
    	
    if(searchName != null && !searchName.isEmpty() && searchCategoryCode > 0) {
    	query.setParameter("menuName", searchName);
    	query.setParameter("categoryCode", searchCategoryCode);
    		
    } else {
    	/* ๋‘˜ ์ค‘์— ํ•˜๋‚˜๋งŒ ์žˆ์„ ๊ฒฝ์šฐ */
    	if(searchName != null && !searchName.isEmpty()) {
    		query.setParameter("menuName", searchName);
    	} else if(searchCategoryCode > 0) {
    		query.setParameter("categoryCode", searchCategoryCode);
    	}
    }
    	
    List<Menu> menuList = query.getResultList();
    	
    // then
    assertNotNull(menuList);
    menuList.forEach(System.out::println);
    	
}

โœ… ์ •์  ์ฟผ๋ฆฌ (NamedQuery)

โ—ผ ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜

@NamedQueries @NamedQuery name query

/* Menu ์—”ํ‹ฐํ‹ฐ */
@Entity(name="section08_menu")
@Table(name="TBL_MENU")
/* ๋„ค์ž„๋“œ์ฟผ๋ฆฌ ์ง€์ • */
@NamedQueries({
	@NamedQuery(name="section08_menu.selectMenuList", query="SELECT m FROM section08_menu m")
})
public class Menu {

}
@Test
public void ์–ด๋…ธํ…Œ์ด์…˜_๊ธฐ๋ฐ˜_๋„ค์ž„๋“œ์ฟผ๋ฆฌ๋ฅผ_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    // when
    /* createNamedQuery()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ •ํ•ด๋‘” ๋„ค์ž„๋“œ์ฟผ๋ฆฌ๋ฅผ name์„ ๋ถˆ๋Ÿฌ์˜ด */
    List<Menu> menuList = entityManager.createNamedQuery("section08_menu.selectMenuList", Menu.class).getResultList();
    
    // then
    assertNotNull(menuList);
    menuList.forEach(System.out::println);
}

โ—ผ XML ๊ธฐ๋ฐ˜

์กฐ๊ธˆ ๋” ๋ณต์žกํ•œ ํ˜•ํƒœ์˜ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” xml ๋ฐฉ์‹์„ ๋” ์„ ํ˜ธ
(๋ฌธ์ž์—ด๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ๋ณต์žกํ•˜๊ธฐ ๋•Œ๋ฌธ)

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
	<named-query name="section08_menu.selectMenuNameByCode">
		<query>
			SELECT
			       m
			  FROM section08_menu m
			 WHERE m.menuCode = :menuCode
		</query>
	</named-query>
</entity-mappings>

persistence.xml

@Test
public void xml_๊ธฐ๋ฐ˜_๋„ค์ž„๋“œ์ฟผ๋ฆฌ๋ฅผ_์ด์šฉํ•œ_์กฐํšŒ_ํ…Œ์ŠคํŠธ() {
    	
    // given
    int menuCodeParameter = 81;
    	
    // when
    Menu foundMenu = entityManager.createNamedQuery("section08_menu.selectMenuNameByCode", Menu.class) /* xmlํŒŒ์ผ ์•ˆ์˜ ์ฟผ๋ฆฌ๋ฌธ์˜ name ์†์„ฑ๊ณผ ์ผ์น˜์‹œ์ผœ์•ผํ•จ */
    							  .setParameter("menuCode", menuCodeParameter)
    							  .getSingleResult();
    
    	// then
    	assertNotNull(foundMenu);
    	System.out.println("foundMenu : " + foundMenu);
    }
profile
Tiny little habits make me

0๊ฐœ์˜ ๋Œ“๊ธ€