https://www.jetbrains.com/ko-kr/idea/download/#section=windows
community 버전 다운로드(d-download)
설치시 - add bin, add open, .java , shortcut 이정도 체크하고 나머진 다 디폴트값으로 진행
예)
maven, java, 2.7.10 (snapshot은 되도록 x), Artifact
: spring-demo, Package name:com.example, jar, 11버전,Dependencies :Spring Web
=>generate - d -download - springstartprj폴더 만들어서 저장
압축은 workspace-boorspring폴더에 해제
이를 intelij에서 open으로 경로 눌러서 열기
src-main-resources-static에는 html, css등의 파일이 들어간다.
src-main-resources-application.properties에는 설정정보가 있다.
src-main-resources-templates에는 view파일을 저장한다.
워크스페이스를 스프링부트 압축 풀어둔 폴더로 잡고
포트 번호를 바꿔서 실행하면 되는데 포트번호를 바꾸는 방법은
src/main/resource
아래 application properties
에서
server.port=80
등으로 포트 번호 바꿔주면 오류없이 된다.
세팅- 플러그인 - 롬복 인스톨(최신버전에는 이미 내장되어 있다.) - pom.xml
에 dependancy
추가 후 재접속 하면 인스톨 된다.
https://dev.mysql.com/downloads/installer/
에서 두번째 거 다운
d-download-dbms
에 다운 후 설치
다 next , execute
등 하고 설치 workbench
체크
포트번호 3306
create database shop default character set utf8 collate utf8_general_ci;
//shop이라는 database 생성함.
cf. utf8
로하면 이모지가 사용 안될수도 있긴함. utf8mb4
를 쓰면 해결되지만 버전이 안맞을수도 있긴하다.
show databases;
// 데이터베이스 목록 보기
use shop;
// 데이터베이스 사용
select database();
// 사용중인 데이터베이스 확인
#서버 설정
server.port=8081 //포트는 겹치지 않기위해 다른 것으로 바꾸기
#데이터베이스 연결 설정
//mysql일때 연결방법
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shop?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=java1234
#jpa setting
spring.jpa.hibernate.ddl-auto= create-drop
/*
보통 개발할 때 create를 사용한다. 이렇게 하면 테스트를 할 때 deleteall안해줘도 된다.
데이터가 있으면 지우고 채워주는 것.
create-drop을 할경우 table을 다 지우면서 마무리한다.
update를 할 경우 data가 있어도 지우는 과정을 먼저 하지 않고 새롭게 data를 추가한다.
따라서 맨처음 table을 만들 때는 create를 쓰고 그다음에 update를 한다면 data가 계속 쌓이게 된다.
*/
spring.jpa.properties.hibernate.show_sql = true //기타 옵션등
spring.jpa.properties.hibernate.format_sql = true //기타 옵션등
logging.level.org.hibernate.type.descriptor.sql= trace//기타 옵션등
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect //mysql사용시 필수 설정
스프링 설정정보를 압축해제하여 오픈
src-java-com.shop(예)-이경로에 패키지로 각각
constant,entity,repository를 만든다.
constant에는 enum으로 상수를 설정한다.
예)
public enum ItemSellStatus {
SELL,SOLD_OUT //static 필드고 파이널이다.
}
entity에는 생성할 테이블이름, 내용등의 설정을 적는다.
예)
@Entity //Item 클래스를 entity로 선언
@Table(name = "item") //DB에 저장될 테이블명을 item으로한다는 뜻.
@Getter
@Setter
@ToString
public class Item {
@Id //entity에는 primaryKey가 꼭 필요한데 @Id가 붙으면 pk이다. 아래 Long id가 pk라는 것을 의미하는 것이다.
@Column(name = "item_id") //컬럼명이 item_id이다.
@GeneratedValue(strategy =GenerationType.AUTO)
private Long id; //상품코드
@Column(nullable = false,length = 50)
/*DB에 not null제약 조건을 걸어주지만 유효성 검사는 해주지 않는다.
@NotNull의 경우 제약조건 설정과 유효성 검사를 같이해주기 때문에 좀 더 안전하게 사용가능하긴 하다.*/
private String ItemNm; //상품명
@Column(name = "price",nullable = false)
private int price; //가격
@Column(nullable = false)
private int stockNumber; //재고수량
@Lob //255이상의 문자를 저장하고 싶을 때 주로사용한다.
@Column(nullable = false)
private String itemDetail; //상품 상세설명
@Enumerated(EnumType.STRING)
/*type이string인경우 enum의 name값을 db에 저장한다.
cf. ordinal의 경우 순서를 나타내는 숫자를 db에 저장한다.*/
private ItemSellStatus itemSellStatus;
private LocalDateTime regTime;
private LocalDateTime updateTime;
}
repository에는 인터페이스를 만든다.
public interface ItemRepository extends JpaRepository<Item,Long> {
List<Item> findByPrice(int i); //이는 필수가 아니고 find를 해보기 위해 임의로 만든 부분
}
인터페이스로 repository
만들고 반드시JpaRepository
상속받아야 한다.
또한, @Entity
선언한 클래스 이름이랑, @Id
선언한 데이터 타입을 적어야 한다.
(예시의 경우 Long
타입이었다)
이후 인터페이스 이름에서 마우스 우클릭-goto-Test를 하면 Testclass
가 생성된다.
(src-test-repository
-이경로)
@SpringBootTest //Test를 위해 필수로 넣어야 할 @이다.
//@TestPropertySource(locations = "classpath:application-Test.properties")
//참고할 연결 설정의 경로를 적는다. 보통은 application.properties가 기본값이다.
class ItemRepositoryTest {
@Autowired
ItemRepository itemRepository; //인터페이스를 가져온다.
@Test //Test하기위해 적어준다.
@DisplayName(value = "상품 저장 테스트") //테스트 이름 간결하게 보여줌
public void createItemTest(){
Item item = new Item();
item.setItemNm("테스트 상품");
item.setPrice(10000);
item.setItemDetail("테스트 상품 상세 설명");
item.setItemSellStatus(ItemSellStatus.SELL);
item.setStockNumber(100);
item.setRegTime(LocalDateTime.now());
item.setUpdateTime(LocalDateTime.now());
Item savedItem = itemRepository.save(item);
System.out.println(savedItem.toString());
assertEquals(1,itemRepository.count());
// assertEquals(0,itemRepository.count());
List<Item> list= itemRepository.findByPrice(10000); //필수는 아닌 부분
for (Item getitem:list ){
System.out.println("=============================="+getitem);
}
}
ItemRepository.java
List<Item> findByIdGreaterThanOrderByIdDesc(Long id, Pageable pg);
ItemRepositoryTest.java
@Test
@DisplayName(value = "findByIdGreaterThanOrderByIdDesc 테스트")
public void findByIdGreaterThanOrderByIdDescTest(){
this.createItemTest(); //data생성하는 테스트
Pageable paging = PageRequest.of(1,5);
// 기본 문법형태, 0page부터 시작인거라서 사실상 2페이지의 5개의 내용을 보겠다는 의미
List<Item> itemList = itemRepository.findByIdGreaterThanOrderByIdDesc(5L,paging);
/*select * from item where id>5L 이뜻에서 내림차순으로 보겠다는 뜻이다.
즉, 5보다 큰data를 내림차순으로 보는데 2페이지에 해당하는 부분에서 5개만큼을 보겠다는 의미이다.*/
for (Item item : itemList){
System.out.println("====2page 자료 ==> " +item.toString());
//100개의 data가 있었다고 할 때 95~91번째 자료가 출력된다.
}
}
/*cf. findByIdGreaterThanOrderByIdDescPrice를 쓰면
(이 예제의 경우 id의 중복값이 없지만 )
id가 중복될 때는 Price기준으로 정렬한다는 의미이다.
원래 PriceAsc이지만 Asc는 기본값이라서 생략 가능하다.*/
ItemRepository.java
List<Item> findByIdGreaterThan(Pageable pageable, Long id);
ItemRepositoryTest.java
예1)
@Test
@DisplayName(value = "pageable을 이용한 단일컬럼 정렬 테스트")
public void findByIdGreaterThanTest() {
this.createItemTest();
Pageable paging = PageRequest.of(0,5, Sort.Direction.DESC,"id");
//내림차순을 id기준으로 하겠다는 의미이다.
List<Item> itemList = itemRepository.findByIdGreaterThan(paging,5L);
for (Item item : itemList){
System.out.println("====1page 자료 ==> " +item.toString());
}
}
예2)
@Test
@DisplayName(value = "pageable을 이용한 다중컬럼 정렬 테스트")
public void findByIdGreaterThanTest2() {
this.createItemTest();
Pageable paging = PageRequest.of(0,5,
Sort.by(new Sort.Order(Sort.Direction.DESC,"id"),
new Sort.Order(Sort.Direction.ASC,"price"))
//여러개의 경우사용하며 id기준으로 내림차순하고 price기준으로 오름차순한다.
);
List<Item> itemList = itemRepository.findByIdGreaterThan(paging,5L);
for (Item item : itemList){
System.out.println("====1page 자료 ==> " +item.toString());
}
}
ItemRepository.java
예1)
@Query("select i from Item i") //=select * from Item ,별명 i를 주고 *자리에 i를 넣어주었다.
List<Item> findByItemDetail();
예2)
@Query("select i from Item i where i.itemDetail like %:itemDetail% order by i.price desc")
List<Item> findByItemDetail(@Param("itemDetail") String itemDetail);
//where절 아래 i.itemDetail로 넣어야 하며 값을 넣을 때는 :를 사용한다.
//like %%는 %%안의 내용이 같은 부분을 찾으라는 코드이다.
//sql문법을 기존 방식으로 사용하려면 예3)과 같이 nativeQuery=true를 넣어주면 된다.
예3)
@Query(value = "select * from Item",nativeQuery = true) //쿼리문 방식은 동일하고 속성을 추가하였다.
List<Item> findNativeAll();
cf)
예2)의 경우 @Entity
에서 가져오기 때문에 Item(item x)
으로 써야한다.
예3)의 경우 Item, item
모두 가능하다.
성능면에서는 예3)처럼 하는 것이 더 좋긴 하지만
oracle, mysql등 sql문법이 바뀌는 경우 예3)의 방법은 쿼리문을 다시 바꿔줘야 하는 문제가 생길 수 있다.
예2)의 경우 sql문법이 달라도 문제없이 작동한다.
따라서 유지보수 측면에서 본다면 예2)와 같은 JPQL문법을 사용하는 것이 좋다.
cf)
ItemQueryString.java //쿼리문을 저장할 인터페이스
public interface ItemQueryString {
String findByItemDetail="select i from Item i where i.itemDetail
like %:itemDetail% order by i.price desc";
String findNativeAll="select * from item";
//인터페이스로 쿼리문을 만들어두고
}
ItemRepository.java
@Query(ItemQueryString.findByItemDetail) //쿼리를 직접 적기 대신 가져오는 방식으로 사용해도 좋다.
List<Item> findByItemDetail(@Param("itemDetail") String itemDetail);
@Query(value = ItemQueryString.findNativeAll,nativeQuery = true)
List<Item> findNativeAll();
문자열로 이루어진 쿼리문은 오류가 날경우 바로 해결하기 어렵다.
따라서 그부분의 단점을 보완하기 위한 방법이다.
우선 pom.xml설정 추가가 필요하다.
1. dependency설정
<!-- https://mvnrepository.com/artifact/com.querydsl/querydsl-jpa -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<!-- <version>4.3.1</version> 버전이 추가되면 오류 생김-->
</dependency>
<!-- https://mvnrepository.com/artifact/com.querydsl/querydsl-apt -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<!-- <version>4.3.1</version> 버전이 추가되면 오류 생김-->
</dependency>
2. plugin설정
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
plugins
에 추가로 위의 내용을 적는다.
플러그인 설정까지 넣은 후
우측 maven
에서 reload
를 하여 다운로드 후 compile
을 더블클릭 해준다.
만약 문제가 있는경우 file-projectStructure
에서
modules
에서 target-generated-sources
폴더 선택 후 sources
누르고
(파란색 폴더로 바뀜) apply
후 ok
해준다.
이후 정상적으로 complie
된 경우 target-java-Qitem.java
파일이 생긴다.
cf) @Entity
가 붙은 클래스가 있는 만큼 생긴다.
이 파일이 있는 경우
인터페이스에서 설정할 필요없이 테스트 파일에서도 바로 사용가능하다.
예)
@Autowired
EntityManager em; // 이것도 추가로 가져와야 한다
@Test
@DisplayName("Querydsl을 이용한 조회테스트1")
public void querydslTest(){
this.createItemTest();
JPAQueryFactory queryFactory =new JPAQueryFactory(em);
QItem qItem=QItem.item;
JPAQuery<Item> query = queryFactory.selectFrom(qItem) //여기까지는 거의 고정
.where(qItem.itemSellStatus.eq(ItemSellStatus.SELL))
.where(qItem.itemDetail.like("%"+"상품 상세"+"%")) //조건추가 가능
.orderBy(qItem.price.desc());//정렬도 가능
List<Item> itemList=query.fetch();
for (Item item : itemList){
System.out.println("====1page 자료 ==> " +item.toString());
}
}
예2)
@Test
@DisplayName("Querydsl을 이용한 id값을 이용해서 조회테스트2")
public void querydslSelectOneTest(){
this.createItemTest();
JPAQueryFactory queryFactory =new JPAQueryFactory(em);
QItem qItem=QItem.item;
JPAQuery<Item> query = queryFactory.selectFrom(qItem) //여기까지 고정
.where(qItem.id.eq(3L));
Item itemOne=query.fetchOne(); //하나의 자료라 fetchOne
System.out.println("====1page 자료 ==> " +itemOne.toString());
}
확장자명은 .html
을 사용한다. 단, 사용시 namespace
를 달아줘야 한다. 예) <html xmlns:th="http://www.thymeleaf.org">
이후 일반적인 태그처럼 사용한다. th:
.jsp
의 경우 서버 사이드 렌더링을 하지않으면 정상적인 화면 출력이 어렵지만 Tymeleaf
는 .html
기반이라서 서버 사이드 렌더링을 하지 않아도 정상적인 화면 출력이 가능하다
src/resources/templates
위치에 thymeleaf
를 사용한 html
파일을 넣는다. (cf. 타임리프는 동적인 파일)
templates
은 spring
기준 views
와 같다고 보면 된다.
static
에는 css,js
등 고정적인 파일이 들어간다.
+spring boot devtools
(서버 다시 안켜도 반영됨), live reload
(새로고침 안해도 리로드 됨,로그인 필요) 적용해주기
타임리프 예제
itemDto
안에 데이터가 저장된 형태로 보내진 상태일 때
<span th:text="${itemDto.itemNm}"></span>
태그안에 속성 th:text="${}"
를 사용하면 태그안의 내용 대신에 ${}
안에 해당하는 데이터 값이 출력된다.
dependency를 추가한다. (spring-boot-starter-security)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
추가 후 maven reload
모든 요청에 대하여 로그인이 필요하도록 한다.
id는 user로 정해져 있으며 pw는 console창에 나온다.
한번 로그인 되면 또 할필요는 없으며, localhost/logout
으로 갈경우 로그아웃이 가능하다.
설정정보가 들어있음을 나타내는 어노테이션이다.
cf) extends WebSecurityConfigurerAdapter의 상속을 받아야 한다. (이전 버전)
@EnableWebSecurity
을 추가하면 상속받지 않아도 사용가능하다.
이후 db에 등록되는 pw를 암호화하여 저장하도록, Bean에 등록한다. (본인이 만들어도 되긴하다.)
@Bean
protected PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@RequiredArgsConstructor //이 어노테이션을 붙이고
public class MemberService {
private final MemberRepository memberRepository;
}
final
혹은 @nonnull
을 넣어주면 생성자를 통한 주입이 가능하다.
기본생성자가 없다는 단점이있다. 왜냐면 final
을 넣으면 생성과 동시에 값이 입력되어야 하기 때문.
그러나 요즘 추세는 생성자를 통한 주입이다.
@Autowired
와 같은 역할을 한다.
@Transactional은 로직을 처리하다가 오류가 발생할 경우 데이터를 로직이 수행되기 전으로 롤백시켜준다.
또한, 테스트에서 사용될 경우 테스트 종료 후 롤백시켜준다.
MockMvc (목업) 가짜 Mvc