Querydsl 사용하려면 세팅 필수
package com.spring.jpastudy.config;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
동적 쿼리
@Test
@DisplayName("동적 쿼리를 사용한 간단한 아이돌 조회")
void dynamicTestOne() {
String name = "김채원";
String genderParam = "여";
Integer minAge = 20;
Integer maxAge = 25;
BooleanBuilder booleanBuilder = new BooleanBuilder();
if (name != null) {
booleanBuilder.and(idol.idolName.eq(name));
}
if (genderParam != null) {
booleanBuilder.and(idol.gender.eq(genderParam));
}
if (minAge != null) {
booleanBuilder.and(idol.age.goe(minAge));
}
if (maxAge != null) {
booleanBuilder.and(idol.age.loe(maxAge));
}
List<Idol> result = factory
.selectFrom(idol)
.where(booleanBuilder)
.fetch();
assertFalse(result.isEmpty());
}
동적 정렬
@Test
@DisplayName("동적 정렬을 사용한 아이돌 조회")
void dynamicTest2() {
String sortBy = "age";
boolean ascending = false;
OrderSpecifier<?> specifier = null;
switch (sortBy) {
case "age":
specifier = ascending ? idol.age.asc() : idol.age.desc();
break;
case "idolName":
specifier = ascending ? idol.idolName.asc() : idol.idolName.desc();
break;
case "groupName":
specifier = ascending ? idol.group.groupName.asc() : idol.group.groupName.desc();
break;
}
List<Idol> result = factory
.selectFrom(idol)
.orderBy(specifier)
.fetch();
assertFalse(result.isEmpty());
}
jpa를 통해 api 만들어보기
커스텀impl
- queryDsl을 사용하려면 config 파일에서 등록이 되어있어야 한다.
- repository
package com.spring.jpastudy.event.repository;
public interface EventRepository
extends JpaRepository<Event, Long>, EventRepositoryCustom {
}
- EventRepositoryCustom.interface
package com.spring.jpastudy.event.repository;
public interface EventRepositoryCustom {
List<Event> findEvents(String sort);
}
- EventRepositoryCustomImpl.java
- 실질적인 로직은 여기서 구현
package com.spring.jpastudy.event.repository;
import static com.spring.jpastudy.event.entity.QEvent.*;
@Repository
@RequiredArgsConstructor
@Slf4j
public class EventRepositoryCustomImpl implements EventRepositoryCustom {
private final JPAQueryFactory factory;
@Override
public List<Event> findEvents(String sort) {
return factory
.selectFrom(event)
.orderBy(specifier(sort))
.fetch()
;
}
private OrderSpecifier<?> specifier(String sort) {
switch (sort) {
case "date":
return event.date.desc();
case "title":
return event.title.asc();
default:
return null;
}
}
}
package com.spring.jpastudy.event.entity;
@Getter
@ToString
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name="tbl_event")
public class Event {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ev_id")
private Long id;
@Column(name = "ev_title", nullable = false, length = 50)
private String title;
@Column(name = "ev_desc")
private String description;
@Column(name = "ev_image_path")
private String image;
@Column(name = "ev_start_date")
private LocalDate date;
@CreationTimestamp
private LocalDateTime createdAt;
}
package com.spring.jpastudy.event.service;
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class EventService {
private final EventRepository eventRepository;
public List<Event> getEvents(String sort) {
return eventRepository.findEvents(sort);
}
public List<Event> saveEvent(EventSaveDto dto) {
Event savedEvent = eventRepository.save(dto.toEntity());
log.info("saved event: {}", savedEvent);
return getEvents("date");
}
}
- EventController (restApi)
package com.spring.jpastudy.event.controller;
import java.util.List;
@RestController
@RequestMapping("/events")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin
public class EventController {
private final EventService eventService;
@GetMapping
public ResponseEntity<?> getList(String sort) {
List<Event> events = eventService.getEvents(sort);
return ResponseEntity.ok().body(events);
}
@PostMapping
public ResponseEntity<?> register(@RequestBody EventSaveDto dto) {
List<Event> events = eventService.saveEvent(dto);
return ResponseEntity.ok().body(events);
}
}
React Router
- 리액트는 html파일이 하나여서, 링크 이동이 불가능
-> router를 사용해서 새로고침 없이 링크의 전환을 할 수 있다.
npm install react-router-dom
으로 라이브러리 설치
- app.js
import React from 'react';
import Home from './components/RouteExample/pages/home';
import Products from './components/RouteExample/pages/Products';
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
const router = createBrowserRouter([
{ path: '/', element: <Home /> },
{ path: '/products', element: <Products /> },
]);
const App = () => {
return (
<div>
<RouterProvider router={router} />
</div>
);
};
export default App;
- home.js (components)
- a태그를 사용해서 링크의 전환을 하면 새로고침이 일어난다.
그러면 모든 상태값이 초기화 될 수 있어서 Link태그를 사용.
import React from 'react';
import { Link } from "react-router-dom"
const Home = () => {
console.log("home!")
return (
<>
<h1>My Home Page</h1>
<p>
{}
<Link to='/products'>Products</Link>페이지로 이동하기
</p>
</>
);
};
export default Home;
import React from 'react';
import { Link } from "react-router-dom"
const Products = () => {
console.log("products!")
return (
<>
<h1>My Products Page</h1>
<p>
{}
<Link to='/'>Home</Link>페이지로 이동하기
</p>
</>
)
;
};
export default Products;
중첩 router
- 헤더, 푸터 등 정적인 컴포넌트는 어디 페이지에 가도 고정이 되어야 함
- 중첩라우터를 활용
- app.js
import React from 'react';
import Home from './components/RouteExample/pages/home';
import Products from './components/RouteExample/pages/Products';
import RootLayout from './components/RouteExample/layout/RootLayout';
import ErrorPage from './components/RouteExample/pages/ErrorPage'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
const router = createBrowserRouter([
{
path: '/base',
element: <RootLayout />,
errorElement: <ErrorPage />,
children: [
{ path: '', element: <Home /> },
{ path: 'products', element: <Products /> },
]
},
]);
const App = () => {
return (
<div>
<RouterProvider router={router} />
</div>
);
};
export default App;
- RootLayout.js
- 실질적 페이지. 여기서 header, footer, 동적 컴포넌트들을 배치
import React from 'react';
import MainNavigation from './MainNavigation';
import { Outlet } from 'react-router-dom'
const RootLayout = () => {
return (
<>
<MainNavigation/>
{}
<main>
{}
<Outlet/>
</main>
{}
</>
);
};
export default RootLayout;
NavLink
- styles.active가 붙으면 현재 있는 페이지에 활성화 표시되는 css가 적용
- Link태그 말고 NavLink를 import해와서 사용하는데, NavLink를 사용하게되면 className에 함수를 넣을 수 있다.
- isActive는 내장되어 있는 객체를 디스트럭처링 해서 가져온 것
- MainNavigation.js
import React from 'react';
import { NavLink } from 'react-router-dom'
import styles from './MainNavigation.module.scss'
const MainNavigation = () => {
const activeFn = ({ isActive }) => {
return isActive ? styles.active : undefined;
}
return (
<header className={styles.header}>
<nav>
<ul className={styles.list}>
<li>
{}
<NavLink to={''} className={activeFn} end>Home</NavLink>
</li>
<li>
{}
<NavLink to={'products'} className={activeFn}>Products</NavLink>
</li>
</ul>
</nav>
</header>
);
};
export default MainNavigation;
상대경로
import React from 'react';
import { Link } from "react-router-dom"
const Products = () => {
console.log("products!")
return (
<>
<h1>My Products Page</h1>
<p>
{}
<Link to=".." end>Home</Link>페이지로 이동하기
</p>
</>
)
;
};
export default Products;
import React from 'react';
import { Link } from "react-router-dom"
const Home = () => {
console.log("home!")
return (
<>
<h1>My Home Page</h1>
<p>
{}
<Link to='products'>Products</Link>페이지로 이동하기
</p>
</>
);
};
export default Home;
동적 라우팅, 파라미터값 읽기
- app.js에 products/:prodId ... (상세보기) 경로 추가
children: [
{ index: true, element: <Home /> },
{ path: 'products', element: <Products /> },
{ path: 'products/:prodId/page/:pageNo', element: <ProductDetail />}
]
- ProductDetail.js
- react-router-dom에서 useParams import.
import React from 'react';
import { useParams } from 'react-router-dom'
const ProductDetail = () => {
const params = useParams();
console.log(params)
return (
<>
<h1>제품 상세보기 화면</h1>
<p>
제품ID: {params.prodId}, 페이지번호: {params.pageNo}
</p>
</>
);
};
export default ProductDetail;
const Products = () => {
console.log("products!")
return (
<>
<h1>My Products Page</h1>
<ul>
{
DUMMY_PRODUCTS.map(prod => (
<li key={prod.id}>
<Link to={`{${prod.id}/page/10}`} >{prod.name}</Link>
</li>
))
}
</ul>
</>
)
;
};