├── .gitignore ├── 2019-09-03.md ├── 2019-09-04.md ├── 2019-09-05.md ├── 2019-09-06.md ├── 2019-09-07.md ├── 2019-09-08.md ├── 2019-10-05.md ├── 2019-10-10.md ├── 2019-10-19.md ├── 2019-11-30.md ├── 2019-12-01.md ├── 2019-12-05.md ├── README.md ├── SUMMARY.md ├── Template └── springboot-react-template.zip ├── appendix ├── JpaAuditing.md ├── README.md ├── Thymeleaf_LocalDateTime.md ├── gradle_error_solution.md ├── heroku-deploy.md ├── mysql_error_solution.md ├── querydsl.md ├── rds_kst_time.md ├── react_error_solution.md ├── spring-rename.md └── springsecurity-ajax-csrf-solution.md └── guestbook ├── .classpath ├── .project ├── .settings ├── .jsdtscope ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.apt.core.prefs ├── org.eclipse.jdt.core.prefs ├── org.eclipse.m2e.core.prefs ├── org.eclipse.wst.common.component ├── org.eclipse.wst.common.project.facet.core.xml ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.wst.jsdt.ui.superType.name └── org.eclipse.wst.validation.prefs ├── pom.xml └── src └── main ├── java └── kr │ └── or │ └── connect │ └── guestbook │ ├── config │ ├── ApplicationConfig.java │ ├── DBConfig.java │ └── WebMvcContextConfiguration.java │ ├── controller │ ├── GuestbookApiController.java │ └── GuestbookController.java │ ├── dao │ ├── GuestbookDao.java │ ├── GuestbookDaoSqls.java │ ├── GuestbookDaoTest.java │ └── LogDao.java │ ├── dto │ ├── Guestbook.java │ └── Log.java │ └── service │ ├── GuestbookService.java │ └── impl │ ├── GuestbookServiceImpl.java │ └── GuestbookServiceTest.java └── webapp └── WEB-INF ├── views ├── index.jsp └── list.jsp └── web.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # 무시할 파일 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /2019-09-03.md: -------------------------------------------------------------------------------- 1 | # 1. SpringBoot 처음 사용할 때 + Lombok 설명 2 | 3 | ## 포트번호에 충돌이 생기면? 4 | 5 | 프로젝트를 반복적으로 실행하면 문제가 생길 수 있다. 이럴 경우에 아래와 같이 메세지가 발생한다. 6 | 7 | ```text 8 | ******************************* 9 | Application Failed to Start 10 | ******************************* 11 | Description: 12 | The Tomcat connector configured to listen on port 8080 failed to start. The port 13 | may already be in use or the connector may be misconfigured. 14 | 15 | Action: 16 | ``` 17 | 18 | 해결방안 2가지가 있다. 19 | 20 | 1. application.properties 에서 server.port 를 변경한다. 21 | 2. window 경우 작업관리자 , osx 경우 ps -ef 에서 실행중인 JAVA 프로그램을 찾아 종료 후 재시작 한다. 22 | 23 | ## STS에서 Tomcat을 따로 설치해야 되나? 24 | 25 | 설치할 필요가 없다. 기존의 경우 \[tomcat\]\(\[[http://tomcat.apache.org/"tomcat\]\(http://tomcat.apache.org/"tomcat\)"\](http://tomcat.apache.org/"tomcat]%28http://tomcat.apache.org/"tomcat%29"\)\) 사이트에 접속하여 tomcat.zip을 받고 이클립스나 STS에서 Server를 추가해서 실습을 진행했다. 하지만 내장된 Tomcat이 있기때문에 따로 설치는 필요없다. 26 | 27 | ## @RestController 의 역할 28 | 29 | JSP, HTML 같은 별도의 View를 제공하지 않고 문자열 데이터를 브라우저에 전송 30 | 31 | ## 기본 패키지가 아닌 패키지에 작성한 코드를 스프링에서 사용하고 싶다면? 32 | 33 | @ComponentScan 을 사용하면 된다. 34 | 35 | ```text 36 | @SpringBootApplication 37 | @ComponentScan(basePackages={"org.djlee","org.djunnni"}); 38 | public class BootApplication { 39 | public static void main(String[] args){ 40 | SpringApplication.run(BootApplication.class,args); 41 | } 42 | 43 | } 44 | ``` 45 | 46 | ## Lombok의 장점과 문제점 47 | 48 | 장점 : 49 | 50 | 1. 기존의 Getter, Setter , toString 에 대한걸 어노테이션으로 대체가 가능하다. 51 | 2. 코드의 간결성 52 | 3. @Data 의 장점 : Getter,Setter, equal\(\), hashcode\(\), toString\(\) 등 파라미터없는 기본 생성자까지 자동생성 해준다. 53 | 54 | 원치 않는 속성을 출력하고 싶지 않을 때 아래와 같이 쓰면 된다. 55 | 56 | ```text 57 | @ToString(exclude={"val1"}) 58 | ``` 59 | 60 | 문제점 : 61 | 62 | @Data를 사용할 때 주의해야 됩니다!! 63 | 64 | @Data는 장점에서 말한 사항에 대해 묶음으로 제공하는 어노테이션이지만 ORM\(Object Relation Mapping\)에서 주의해야 됩니다. 65 | 66 | ORM은 객체간 관계를 가지는 조합의 형태로, 테이블 간 연관관계를 표현합니다. 67 | 68 | 이 경우에 부모와 자식의 toString\(\)에서 문제가 발생하는데 상호호출문제로 인하여 무한루프에 빠져 StackOverflow가 발생할 수 있습니다. 69 | 70 | Test Case 71 | 72 | ```text 73 | public class Member { 74 | private String id; 75 | private String pw; 76 | private School school; 77 | 78 | @Override 79 | public String toString() { 80 | return "Member [id = " +id + ",pw = "+ pw + ", school = " + school + "]"; 81 | } 82 | } 83 | ``` 84 | 85 | ```text 86 | public class School { 87 | private String name; 88 | private Member member; 89 | 90 | @Override 91 | public String toString() { 92 | return "School [name = " +name + ",member = "+ member + "]"; 93 | } 94 | } 95 | ``` 96 | 97 | \*\* 권고사항 : 라인이 길어지더라도 @Data 대신 @Getter @Setter @ToString을 따로 쓰는 이유는 여기에 있습니다. 98 | 99 | -------------------------------------------------------------------------------- /2019-09-04.md: -------------------------------------------------------------------------------- 1 | # 2. Spring Data JPA 2 | 3 | ## Spring Data JPA 4 | 5 | 다양한 데이터베이스에 종속적인 SQL문 없이도 개발이 가능하기 때문에 개발의 생산성을 높일 수 있다. 6 | 7 | 장점 : 8 | 9 | * 데이터베이스 관련 코드에 대한 유연함 10 | * 데이터베이스와 독립적 관계 11 | 12 | 단점 : 13 | 14 | * 학습곡선이 큼 15 | * 근본적인 객체지향 설계 사상이 반영되야함 16 | * 특정 DB의 강력함을 활용할 수 없다. => 데이터베이스의 독립적 개발이 불가능 17 | 18 | ## 프로젝트 생성이후 DataSource를 지정해줘야 함 19 | 20 | 현재 프로젝트가 실행이 안되는 이유는 스프링 부트 내에 JDBC 등의 설정이 포함되어있는데 이에대한 설정이 전혀 없기 때문에 사용자가 지정해주어야 함 21 | 22 | * 방법 23 | * application.properties를 이용해 필요한 구성 설정 24 | * @Bean과 같은 어노테이션을 통해 Java 코드로 필요한 객체 구성 25 | 26 | ### application.properties 설정법 27 | 28 | ```text 29 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 30 | spring.datasource.url=jdbc:mysql://localhost:3306/{DB명} 31 | spring.datasource.username= 32 | spring.datasource.password= 33 | ``` 34 | 35 | ### 엔티티 클래스 설계 36 | 37 | 1. 객체지향 설계대로 클래스들을 설계 38 | 2. @id,@Column 등을 이용해서 각종 제약조건을 추가,설정 39 | 3. 엔티티간 연관관계 설정 40 | 41 | ## @Id 와 @GeneratedValue는 같이? 42 | 43 | @Id는 해당 칼럼의 식별키를 의미 함으로써 @GeneratedValue는 식별키를 어떤 전략으로 세울지 명명하는 것이다. 44 | 45 | @GeneratedValue 는 Auto, Table, Sequence, Identity 가 있지만 Data 베이스의 종류에 상관없이 자동으로 잡아주는 Auto를 많이 이용한다. 46 | 47 | ### application.properties JPA 설정 48 | 49 | ```text 50 | #스키마 생성 51 | spring.jpa.hibernate.ddl-auto= 52 | #DDL 생성 시 데이터베이스의 고유기능을 사용하는가? 53 | spring.jpa.generate-ddl=false 54 | #실행되는 SQL문을 보여줄 것인가? 55 | spring.jpa.show-sql=true 56 | #데이터베이스는 무엇을 사용하는가? 57 | spring.jpa.database=mysql 58 | #로그레벨 59 | logging.level.org.hibernate=info 60 | #Mysql 상세지정 61 | spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 62 | ``` 63 | 64 | ### 스키마 생성 옵션 65 | 66 | * create 기존테이블 삭제 후 재 생성 67 | * create-drop create와 같으나 종료시점에 테이블 drop 68 | * update 변경된 부분만 반영 \* 주로 이거 사용 69 | * validate 엔티티와 테이블이 정상적으로 매핑되었는지 확인 70 | * none 사용하지 않음 71 | 72 | ## JPA 처리 담당하는 Repository 인터페이스 설계 73 | 74 | 일반적으로 사용한 DAO가 JPA에서는 Repository로 보면 될것같다. 75 | 76 | Spring Data JPA의 인터페이스 구조는 아래와 같다. 77 | 78 | ```text 79 | PagingAndSortingRepository -> CrudRepository -> Repository 80 | ``` 81 | 82 | T= 엔티티 타입 클래스 ID= PK 넘버 83 | 84 | Repository 인터페이스는 사실상 아무기능이 없기때문에 주로 사용하는 CRUD 작업을 위주로 하는 CrudRepository 인터페이스나 페이징 처리, 검색 처리 등을 할 수 있는 PagingAndSortingRepository 인터페이스를 이용 85 | 86 | ### 조회 테스트시 기존과 바뀐점 87 | 88 | * 조회 테스트 코드는 findById\(\) 메소드를 이용한다. \(1.5전까지는 FindOne\(\) 이용\) 89 | 90 | ### Hibernate 방언 설정 91 | 92 | JPA는 기본적으로 Hibernate라는 JPA 구현체를 사용한다. Hibernate는 내부적으로 지정되는 DB에 맞게 SQL문을 생성하는 DIalect가 존재한다. 93 | 94 | ```text 95 | spring.jpa.database=mysql 96 | spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 97 | ``` 98 | 99 | ### 좀더 자세한 실행 로그를 보고싶을때? 100 | 101 | application.properties 에서 102 | 103 | logging.level.org.hibernate=info -> debug 로 변경 104 | 105 | -------------------------------------------------------------------------------- /2019-09-05.md: -------------------------------------------------------------------------------- 1 | # 3. Spring Data JPA 를 이용한 쿼리 연습 2 | 3 | ## 쿼리 메소드 리턴 타입 4 | 5 | Page , Slice, List 같은 Collection 형태가 된다. 6 | 7 | repository에 작성할때, 쿼리 예시 8 | 9 | ```text 10 | public List findBoardByTitle(String title); 11 | ``` 12 | 13 | find + { 찾고자 하는 클래스 } + By + { 무엇으로 찾을 지 } \(type\) 14 | 15 | ## findBy를 통해 특정 칼럼을 처리하는 법 16 | 17 | 1. 18 | 19 | ```text 20 | Collection findBy + 속성 이름(속성 타입) 21 | ex) 22 | public Collection findByWriter(String writer); 23 | ``` 24 | 25 | 2. 26 | 27 | ```text 28 | @Test 29 | public void testByWriter() { 30 | Collection results = repo.findByWriter("user00"); 31 | 32 | reuslts.forEach( 33 | board -> System.out.println(board) 34 | ); 35 | } 36 | ``` 37 | 38 | ## Page import 에 해깔림이 있으시는 분 39 | 40 | ```text 41 | - Pageable = import org.springframework.data.domain.Pageable; 42 | - PageRequest = import org.springframework.data.domain.PageRequest; 43 | - Page = import org.springframework.data.domain.Page; 44 | ``` 45 | 46 | ## JPA Paging 이 필요할 때 사용하려면 47 | 48 | ```text 49 | ex) 50 | public List findByBnoGreaterThanOrderByBnoDesc(Long bno,Pageable paging) 51 | ``` 52 | 53 | 코드는 기존과 동일하지만, 파라미터에 Pageable이 적용되었고 Collection<> 에서 List<> 로 변경되었습니다. 54 | 55 | * Pageable 인터페이스가 적용되는 경우는 리턴타입이 Slice, Page, List 타입이다. 56 | 57 | ## 페이징에 가장 많이 사용되는 PageRequest 58 | 59 | PageRequest는 SpringBoot 2.0으로 넘어오면서 PageRequest.of\(\)를 사용하고 있다. 60 | 61 | ## JPA 와 Spring MVC와 연동할 때 Page 타입을 쓰자. 62 | 63 | Page 타입을 이용하면 Spring MVC와 연동할 때 상당한 편리함을 주는데 지금부터 알아보자. 64 | 65 | ```text 66 | public interface BoardRepository extends CrudRepository{ 67 | 68 | public Page findByBnoGreaterThan(Long bno,Pageable paging); 69 | } 70 | ``` 71 | 72 | ```text 73 | ex) Test java case 74 | @Test 75 | public void testBnoPagingSort(){ 76 | //Spring boot 2.0 77 | Pageable paging = PageRequest.of(0,10,Sort.Direction.ASC,"bno"); 78 | 79 | Page result = repo.findByBnoGreaterThan(0L,paging); 80 | 81 | System.out.println(result.getSize()); 82 | System.out.println(result.getTotalPages()); 83 | System.out.println(result.getTotlalElements()); 84 | System.out.println(result.nextPageable()); 85 | 86 | List list = result.getContent(); 87 | list.forEach(board -> System.out.prinln(board)); 88 | } 89 | ``` 90 | 91 | 위 코드를 실행하게 되면 아래처럼 결과가 나온다. 92 | 93 | ```text 94 | 10 95 | 20 96 | 200 97 | Page request [number:1, size 10, sort: bno: ASC] 98 | Board(bno=1 ...) => //getContent를 통해 객체를 받아올수 있다. 99 | ``` 100 | 101 | ## @Query 이용법 102 | 103 | 왜? @Query 어노테이션을 사용할까? 104 | 105 | PK\(Primary Key\)를 이용하여 Full table Scan을 하게 되면 모든 데이터에 대해 검색을 통해 처리되기 때문에 성능이 나쁠 수 있다는 견해가 있다. \(하지만 병렬 처리가 효과적으로 처리되면 더 빠른 경우도 있음\) 106 | 107 | 'where bno > 0 ' 과 같은 표현이 JPA 메소드에 표현하기에는 어색함 -> @query 를 사용하게 됨 108 | 109 | ```text 110 | @Query("Select b from board b where b.title like %?1% and b.bno > 0 order by b.bno desc") 111 | public List findByTitle(String title); 112 | ``` 113 | 114 | %?1%을 살펴보면 '?'는 JDBC의 PreparedStatement와 동일 '?1'은 첫번째로 전달되는 파라미터를 의미한다. 115 | 116 | * 실행결과 Query 117 | 118 | ```text 119 | Hibernate: select board0_.bno as bno1_0_, board0_.content as content2_0_, board0_.regdate as regdate3_0_, board0_.title as title4_0_, board0_.updatedate as updateda5_0_, board0_.writer as writer6_0_ from tbl_boards board0_ where (board0_.title like ?) and board0_.bno>0 order by board0_.bno desc 120 | ``` 121 | 122 | ### Query의 장점 123 | 124 | * 리턴값이 반드시 엔티티 타입이 아니라 필요한 몇 개의 칼럼만 추출할 수 있다. 125 | * nativeQuery 속성을 지정해서 데이터베이스에 사용하는 SQL을 그대로 사용할 수 있다. 126 | * Repository에 지정된 엔티티 타입뿐아니라 필요한 엔티티 타입을 다양하게 사용할 수 있다. 127 | 128 | ### Query Cost 란? 129 | 130 | Query가 실행되는 데 걸리는 예상 수행시간 131 | 132 | ## @Param 사용법 133 | 134 | ```text 135 | @Query("SELECT b FROM Board b where b.content like %:content% AND b.bno>0 order by b.bno desc") 136 | public List findByContent(@Param("content") String content); 137 | ``` 138 | 139 | ```text 140 | @Test 141 | public void testByContent2() { 142 | repo.findByContent("18").forEach(board->System.out.println(board)); 143 | } 144 | ``` 145 | 146 | ## 필요한 칼럼만 추출하는 경우 147 | 148 | 특정 칼럼만 가져오고 싶다면 아래와 같은 방식으로 작성하면 된다. 149 | 150 | ```text 151 | @Query("select b.bno, b.title, b.writer, b.regdate"+" from Board b WHERE b.title like %?1% and b.bno>0 order by b.bno desc") 152 | public List findByTitle2(String title); 153 | ``` 154 | 155 | ```text 156 | @Test 157 | public void testBytitle17() { 158 | repo.findByTitle2("17").forEach(arr->System.out.println(Arrays.toString(arr))); 159 | } 160 | ``` 161 | 162 | ## @Query 와 Paging 처리/정렬 163 | 164 | ```text 165 | @Query("select b from Board b WHERE b.bno > 0 order by b.bno desc") 166 | public List findByPage(Pageable pageable); 167 | ``` 168 | 169 | ```text 170 | @Test 171 | public void testByPaging() { 172 | Pageable pageable = PageRequest.of(0, 10); 173 | 174 | repo.findByPage(pageable).forEach(board->System.out.println(board)); 175 | } 176 | ``` 177 | 178 | ## @Query를 이용할 때 주의할 점 179 | 180 | @Query에 대한 해석은 프로젝트 로딩 시점에서 이루어진다. 따라서 @Query의 내용은 프로젝트가 시작되면서 검증되기 때문에 만일 @Query의 내용물이 잘못될 경우에는 프로젝트가 정상적으로 실행되지 않는다. 181 | 182 | * 권고 : @Query를 하나씩 작성하고, 프로젝트를 실행하는 과정을 거치는 것이 좋다. 183 | 184 | -------------------------------------------------------------------------------- /2019-09-06.md: -------------------------------------------------------------------------------- 1 | # 4. JPA 연관관계 처리 2 | 3 | ## JPA 연관관계 처리의 순서 4 | 5 | 1\) 필요한 각각의 클래스를 정의 2\) 각 클래스의 연관관계에 대해 설정을 추가한다. A. '일대일','다대다' 등 연관관계 B. 단방향,양방향 설정 3\) 데이터베이스상 원하는 형태의 테이블이 만들어지는지 확인 4\) 테스트 코드를 통해서 정상작동 여부 확인 6 | 7 | ### 순수 데이터와 동작 데이터에 대한 설명 \( 본인의 생각 \) 8 | 9 | 순수 데이터 : 아무런 사전 조건이 없는 순수 데이터를 의미한다. 10 | 11 | * 독립적인 라이프 사이클을 유지 12 | * 고객의 요구사항에 대한 모든 행위의 주어나 목적어 13 | 14 | ex\) 회원 테이블 15 | 16 | ```text 17 | User{ 18 | private String name; 19 | private String email; 20 | } 21 | ``` 22 | 23 | 동작 데이터 : 요구사항에 대한 '동작'을 담당하는 데이터를 의미한다. 24 | 25 | * 두개의 순수 데이터 사이의 관계를 정의한다고 본다 26 | 27 | ex\) 메일을 보내는 동작여부 확인 DB 28 | 29 | ```text 30 | Send_to_Destination { 31 | private String sender; 32 | private String sender_email; 33 | private String receiver; 34 | private String receiver_email; 35 | private Date sending_time; 36 | } 37 | ``` 38 | 39 | ## 연관관계 설정 40 | 41 | * 일대일\(One to One\) 42 | * 일대다\(One to Many\) 43 | * 다대다\(Many to Many\) 44 | 45 | 이러한 설계과정은 ERD\(Entity Relation Diagram\)을 통해서 확인하고 구성해야된다. 46 | 47 | * JPA를 이용할 경우 반드시 "방향성"에 대한 설정이 필요하다! 48 | 49 | ## SpringBoot 2.0 이상의 findOne\(\) 50 | 51 | 스프링부트 2.0 에서 findOne 메소드는 findById\(\)로 변경되었다. 또한 return 타입은 Optional 인데 Optional 타입은 null을 대신한다고 생각하는게 좋다. Optional의 말은 그대로 존재할 수 있고 존재하지 않을 수 도 있다는 의미이다. 과거 코드에서는 일일이 Null을 체크했지만 optional 부터는 null에 대해 신경쓰지 않아도 된다. 52 | 53 | Optional을 쓸때는 주로 get\(\) 이나 ifPresent\(\)를 사용하여 결과를 반환받거나 처리하게 된다. 54 | 55 | ## spring.jpa.database-platform을 지정해야 되는 이유! 56 | 57 | 이거에 대해 지정하지 않으면 MyISAM으로 지정되는데 이는 데이터 무결성을 제대로 체크하지 않는다. 또한 Auto로 지정되어있기 때문에 Mysql을 사용할 때 아래와 같이 지정하자. 58 | 59 | ```text 60 | spring.jpa.database-platform = org.hibernate.dialect.MySQL5InnoDBDialect 61 | ``` 62 | 63 | ## @Id의 GenerationType.AUTO 와 GenerationType.IDENTITY 64 | 65 | GenerationType.AUTO 타입을 사용하면 DB에 hibernate\_sequence라는 테이블이 생기게 되어 이 번호를 유지하는 방식으로 진행된다. 이렇게 되면 IDENTITY 타입으로 변경하더라도 auto\_increment로 처리되지 않기때문에 테이블을 삭제후 새로 만들어주어야 한다. 66 | 67 | 따라서 IDENTITY 타입을 쓸때는 hibernate\_sequence 에 대해 확인하고 사용하자. 68 | 69 | ## JPA join 처리 70 | 71 | @Query에서 사용하는 JPQL 자체가 테이블을 보는 것이 아니라, 클래스를 보고 작성하기 때문에 hibernate 5.0.X 경우 참조관계가 없는 다른 엔티티를 사용하는 것이 불가능하다. 하지만 SpringBoot 2.0이상에선 hibernate 5.2.X에서는 참조관계가 없어도 'ON'을 이용해서 Left OUTER JOIN을 처리할 수 있다. 72 | 73 | * SpringBoot 2.0 이상일 때, 74 | * Member.java 75 | 76 | ~~~ @Getter @Setter @Entity @ToString @Table\(name="tbl\_members"\) @EqualsAndHashCode\(of="uid"\) public class Member { 77 | 78 | @Id private String uid; private String upw; private String uname; 79 | 80 | } 81 | 82 | ```text 83 | * Profile.java 84 | ``` 85 | 86 | @Getter @Setter @ToString\(exclude = "member"\) @Entity @Table\(name="tbl\_profile"\) @EqualsAndHashCode\(of="fname"\) public class profile { 87 | 88 | ```text 89 | @Id 90 | @GeneratedValue(strategy= GenerationType.IDENTITY) 91 | private Long fno; 92 | 93 | private String fname; 94 | private boolean current; 95 | 96 | @ManyToOne 97 | private Member member; 98 | ``` 99 | 100 | } 101 | 102 | ```text 103 | * MemberRepository 104 | ``` 105 | 106 | @Query\("Select m.uid, count\(p\) from Member m LEFT OUTER JOIN profile p "+" ON m.uid = p.member where m.uid = ?1 GROUP BY m"\) public List getMemberWithProfileCount\(String uid\); 107 | 108 | ```text 109 | * Test case 110 | ``` 111 | 112 | @Test public void testFetchJoin1\(\) { List result = memberRepo.getMemberWithProfileCount\("user1"\); 113 | 114 | ```text 115 | result.forEach(arr-> System.out.println(Arrays.toString(arr))); 116 | } 117 | ``` 118 | 119 | ```text 120 | - SpringBoot 2.0 이하일 때, 121 | 122 | 두 객체 클래스간에 서로의 객체를 만들고 참조를 해주어야 한다. 123 | 124 | 즉, Member.java 에 profile class에 대한 참조가 필요하다는 점이다. 아래 코드 참조 125 | ``` 126 | 127 | @OneToMany private Profile profile; 128 | 129 | ```text 130 | ## Cascading 필요성 131 | 132 | 영속성 전이를 통해서 연관관계가 있는 정보를 같이 삭제하게 하기 위함이다. 133 | 134 | 예를 들어, 유저가 회원가입해서 출석부에 출석을 했다. 해당 유저가 만약 회원탈퇴를 한다면 출석부에 역시 유저가 사라져야 된다. 그렇기에 유저의 정보를 지우기 위해서 영속성 전이에 대해 처리가 필요하다. 135 | ``` 136 | 137 | @OneToMany\(cascade = CascadeType.ALL\) @JoinColumn\(name="pdsno"\) private List files; 138 | 139 | ```text 140 | ## @Modifying , @Transaction 141 | 142 | @Modifying의 경우 기본 @Query에서는 Select 만 지원을 한다 그렇기 때문에 @Modyfying이 Insert,Update,Delete 작업을 처리하게 지원을 해준다. 143 | 이와같은 Update나 Delete 작업을 하기위해서는 코드에는 @Transaction로 트렌젝션 처리가 필요하다. 하지만 처리되지 않을경우가 있는데 이는 144 | ``` 145 | 146 | Rolled Back transaction for ... 147 | 148 | ~~~ 롤백처리가 되었기 때문이다. 이러한 롤백처리를 막기위해 위에 @Commit을 필요로한다. 149 | 150 | -------------------------------------------------------------------------------- /2019-09-07.md: -------------------------------------------------------------------------------- 1 | # 5. Thymeleaf 사용법 2 | 3 | ## Thymeleaf 사용법 4 | 5 | 기존의 JSP에서 벗어나 템플릿 기반의 화면 처리가 지원된다. 따라서 6 | 7 | FreeMarker , Mustache, Thymeleaf를 이용해 확장자가 html인 페이지를 개발할 수 있다. 8 | 9 | ### Tymeleaf를 이용하기 위해 필요한 사항 10 | 11 | 1. 별도의 라이브러리\(STS4 에서 프로젝트 생성시에 추가하면 됨\) 12 | 2. application.properties에 필요한 설정 13 | 14 | 이유 : Thymeleaf로 개발된 화면을 수정하면 매번 프로젝트를 재시작해야되기 때문에 서버 내부에 Cache 보관 못하게 끄자. 15 | 16 | ```text 17 | spring.thymeleaf.cache=false 18 | ``` 19 | 20 | Thymleaf는 html 파일에서 추가 해야될 것 21 | 22 | ```text 23 | 24 | 생략 ... 25 | 26 | ``` 27 | 28 | 1. 페이지 수정시에 DevTools 를 추가하고 쓰자 29 | 30 | 자동으로 스프링부트를 재시작 해주기때문에 사용함. 31 | 32 | 1. Thymeleaf 플러그인 설치하기 33 | 34 | 자체 기능을 사용하기 위해서는 템플릿 코드를 쓰면되지만 좀 더 편하게 활용하려면 추가 플러그인이 필요하다. 35 | 36 | 설치관련 사이트 : [thymeleaf github](https://github.com/thymeleaf/thymeleaf-extras-eclipse-plugin) 37 | 38 | 해당사이트에서 URL 을 보고 STS4 -> help -> install new softwares 39 | 40 | New Repository -> name : thymeleaf , location : 사이트에 나옴 -> 라이센스 동의 -> reboot 41 | 42 | ## Model 파라미터 전달법 43 | 44 | thymeleaf 는 Controller에서 Model을 이용하여 attribute를 전달할 수 있다. 45 | 46 | * Controller 47 | 48 | ```text 49 | @GetMapping("/1") 50 | public void sample(Model model){ 51 | MemberVO vo = 52 | new MemberVO(p1,p2,p3); 53 | model.addAttribute("member",vo); 54 | } 55 | ``` 56 | 57 | * View 58 | 59 | ```text 60 |

61 | ``` 62 | 63 | ## each 문을 통해서 얻을 수 있는 점 64 | 65 | ```text 66 | 67 | 68 | 69 | ``` 70 | 71 | 아래의 요소들 추가가능 72 | 73 | ```text 74 | 75 | 76 | // 대상의 인덱스번호 78 | // 첫번째 마지막 80 | 81 | ``` 82 | 83 | ## Thymeleaf 의 유틸리티 객체 84 | 85 | * 기본 표현식 객체 86 | * **ctx** 87 | * **vars** 88 | * **locale** 89 | * **httpServletRequest** 90 | * **httpSession** 91 | * 표현식 유틸 객체 92 | * **dates** 93 | * **calendars** 94 | * **numbers** 95 | * **strings** 96 | * **objects** 97 | * **bools** 98 | * **arrays** 99 | * **lists** 100 | * **sets** 101 | * **maps** 102 | * **aggregates** 103 | * **messages** 104 | 105 | ### 유틸객체 사용법 106 | 107 | Controller 108 | 109 | ```text 110 | model.addAttribute("now",new Date()); 111 | ``` 112 | 113 | View 114 | 115 | ```text 116 |

117 |
118 |

[[${timeVal}]]

119 |
120 | ``` 121 | 122 | ### thymeleaf 링크 처리 123 | 124 | WAS 상 경로를 줄때 '/'을 중심으로 둘 경우가 있기 때문에 문제가 발생할 수 있다. 125 | 126 | Thymeleaf는 경로 처리문제를 해결하기 위해 @{} 을 사용한다. 장점은 현재경로를 기반으로 해서 일처리를 진행한다. 절대경로로 처리하고 싶으면 안에다가 절대경로를 지정해도 된다. 127 | 128 | 현재 경로 : /test 129 | 130 | ```text 131 | 1. @{/sample1} => /test/sample1 132 | 2. @{~/sample1} => /sample1 133 | ``` 134 | 135 | 현재 경로 : / 136 | 137 | ```text 138 | 1. @{/sample1} => /sample1 139 | 2. @{~/sample1} => /sample1 140 | ``` 141 | 142 | * Get 방식 파라미터 전달방법 143 | 144 | ```text 145 | @{/sample1(p1='aaa')} => /sample1?p1='aaa' 146 | ``` 147 | 148 | ## thymeleaf 레이아웃 기능 149 | 150 | ```text 151 | https://html5boilerplate.com/ 에서 레이아웃 재사용을 위한 탬플릿을 사용할 수 있음. 152 | 153 | 154 | 155 | nz.net.ultraq.thymeleaf 156 | thymeleaf-layout-dialect 157 | 2.2.1 158 | 159 | ``` 160 | 161 | -------------------------------------------------------------------------------- /2019-09-08.md: -------------------------------------------------------------------------------- 1 | # 6. Spring MVC 를 이용한 게시판 구현연습 2 | 3 | ## Spring MVC를 이용한 웹앱 제작\(게시판연습\) 4 | 5 | 프로젝트 기본구조 중 추가한 라이브러리 6 | 7 | ```text 8 | Devtools 9 | lombok 10 | JPA 11 | Mysql 12 | Thymeleaf 13 | Web 14 | ``` 15 | 16 | 추가적으로 thymleaf의 레이아웃 라이브러리를 받았다. 17 | 18 | ```text 19 | 20 | 21 | nz.net.ultraq.thymeleaf 22 | thymeleaf-layout-dialect 23 | 2.3.0 24 | 25 | ``` 26 | 27 | application.properties 에 기본적으로 들어갈 내용 28 | 29 | ```text 30 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 31 | spring.datasource.url = jdbc:mysql://localhost:3306/{DB명}?useSSL=false 32 | spring.datasource.username={유저이름} 33 | spring.datasource.password={비밀번호} 34 | 35 | spring.jpa.hibernate.ddl-auto=update 36 | spring.jpa.generate-ddl=true 37 | spring.jpa.show-sql=true 38 | spring.jpa.database=mysql 39 | spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 40 | 41 | logging.level.org.hibernate=true 42 | 43 | spring.thymeleaf.cache=false 44 | logging.level.org.springframework.web=info 45 | logging.level.org.djlee=info 46 | ``` 47 | 48 | ## PageableDefault를 통해 컨트롤러의 적용방법 49 | 50 | ```text 51 | @GetMapping("/list") 52 | public void list( 53 | @PageableDefault( 54 | direction=Sort.Direction.DESC, 55 | sort="bno", 56 | size=10, 57 | page = 0) Pageable page) { 58 | 59 | log.info(page); 60 | } 61 | ``` 62 | 63 | 장점 64 | 65 | * 쉽게 parameter 를 전달하여 게시판을 만들 수 있다는 장점이 있다. 66 | 67 | 단점 68 | 69 | * url에서 sort,size,page에 대한 조작을 parameter로 전달하기 때문에 고의적인 공격에 취약하다. 70 | 71 | 대처법 72 | 73 | * 별도의 파라미터를 수집해서 처리하는 Value Object를 생성하여 문제를 해결한다. 74 | 75 | ### PageVO 76 | 77 | ```text 78 | package org.xxx.vo; 79 | 80 | import org.springframework.data.domain.PageRequest; 81 | import org.springframework.data.domain.Pageable; 82 | import org.springframework.data.domain.Sort; 83 | import org.springframework.data.domain.Sort.Direction; 84 | 85 | public class PageVO { 86 | private static final int DEFAULT_SIZE =10; 87 | private static final int DEFAULT_MAX_SIZE=50; 88 | 89 | private int page; 90 | private int size; 91 | 92 | public PageVO() { 93 | this.page=1; 94 | this.size=DEFAULT_SIZE; 95 | } 96 | public int getPage() { 97 | return page; 98 | } 99 | public void setPage(int page) { 100 | this.page = page < 0? 1 : page; 101 | } 102 | public int getSize() { 103 | return size; 104 | } 105 | public void setSize(int size) { 106 | this.size = size < DEFAULT_SIZE || size >DEFAULT_MAX_SIZE? DEFAULT_SIZE : size; 107 | } 108 | public Pageable makePageable(int direction, String... props) { 109 | Sort.Direction dir = direction == 0 ? Sort.Direction.DESC : Sort.Direction.ASC; 110 | return PageRequest.of(this.page -1, this.size,dir,props); 111 | } 112 | } 113 | ``` 114 | 115 | Controller 116 | 117 | ```text 118 | @Autowired 119 | WebBoardRepository repo; 120 | 121 | @GetMapping("/list") 122 | public void list(PageVO vo,Model model) { 123 | 124 | Pageable page = vo.makePageable(0, "bno"); 125 | 126 | Page result = repo.findAll(repo.makePredicate(null, null), page); 127 | 128 | 129 | log.info(""+page); 130 | log.info(""+result); 131 | 132 | model.addAttribute("result",result); 133 | 134 | } 135 | ``` 136 | 137 | View 화면 138 | 139 | ```text 140 |
141 |
List Page
142 |
143 |

[[${result}]]

144 |
[[${result.content}]]
145 |
146 |
147 | ``` 148 | 149 | ## Jquery에서 href 속성을 갖고 싶은데 못갖을 떄 해결법 150 | 151 | 예를들어 a 태그에서 href의 속성을 그대로 쓰고 싶다면 어떻게 해야 될까? 152 | 153 | ```text 154 | $('a').click((e)=> { 155 | var target = e.target(); // 현재 타겟에 대한 obj가 필요하다. 156 | e.preventDefault(); // 버블링을 막기위해 필요하다. 157 | target.attr('href') // 이제 사용가능 158 | }); 159 | ``` 160 | 161 | 생각보다 attr 속성의 href를 받기에 오류가 발생할 때도 많은데 이럴때 jQuery를 이용한다면 대상에 대해 obj를 만들어 쓸 생각을 하자. 162 | 163 | -------------------------------------------------------------------------------- /2019-10-05.md: -------------------------------------------------------------------------------- 1 | # 7. Spring Security 4 JDBC 를 이용한 로그인 인증방법 2 | 3 | * 인터넷에 너무 복잡하게 하시는 것 같아 간단하게 쓰실 분들에게 좋을 것 같습니다. 4 | 5 | ## 시나리오 6 | 7 | SpringSecurity4 를 이용하여 JDBC 로그인을 손쉽게 구현을 해봅니다. 8 | 9 | JPA, BCrypt 를 사용하였습니다. 10 | 11 | * Spring Security Config 가 제일 중요합니다. 12 | 13 | ## Member Model 14 | 15 | ```text 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | private String mid; 21 | private String mpw; 22 | 23 | @OneToMany(cascade=CascadeType.ALL,fetch = FetchType.EAGER) 24 | @JoinColumn(name="mem_no") 25 | private List roles; 26 | ``` 27 | 28 | Member 모델에는 다음과 같이 mid\(유저 아이디\) , mpw\(유저 패스워드\), Role\(권한\) => 저는 권한을 여러개 주고 싶어서 이렇게 한것이기 때문에 상황에 맞게 바꾸시면 됩니다 29 | 30 | ## Member Role 31 | 32 | ```text 33 | public class MemberRole { 34 | 35 | @Id 36 | @GeneratedValue(strategy = GenerationType.IDENTITY) 37 | private Long fno; 38 | 39 | private String roleName; 40 | } 41 | ``` 42 | 43 | Member Role은 Member의 권한을 나타냅니다. 44 | 45 | * SpringBoot role 조건 46 | 47 | DB에 저장하실 때는 ROLE\_ADMIN, ROLE\_MEMBER 와 같이 저장하셔야 합니다. 48 | 49 | SecurityConfig 하단에서 더 설명합니다. 50 | 51 | ## Login View 52 | 53 | ```text 54 |
55 | 56 | 57 | 58 |
59 | ``` 60 | 61 | 로그인 뷰는 다음과 같이 합니다. 62 | 63 | ## Controller 64 | 65 | ```text 66 | @RequestMapping("/login") 67 | public void login() { 68 | 69 | } 70 | // 로그인 실패처리 71 | @GetMapping("/login?error") 72 | public String fail() throws IOException{ 73 | return "redirect:/?error"; 74 | } 75 | ``` 76 | 77 | 본인의 경우 다음과 같이 Controller를 구성하였습니다. 78 | 79 | ## Spring Security Config \*\* 80 | 81 | ```text 82 | @Override 83 | public void configure(HttpSecurity http) throws Exception { 84 | 85 | http.authorizeRequests().formLogin().loginPage("/") 86 | .loginProcessingUrl("/login").permitAll() 87 | .failureUrl("/?error") // default 88 | .usernameParameter("username") 89 | .passwordParameter("password") 90 | .permitAll() 91 | .defaultSuccessUrl("/admin/home") 92 | } 93 | 94 | @Autowired 95 | protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 96 | final String usernameQuery = "select mid as username,mpw as password,enabled from {Member테이블 이름} where mid=?"; 97 | final String authQuery = "select a.mid as username,b.role_name as authority from {Member테이블 이름} as a , {MemberRole 테이블 이름} as b where a.id=b.mem_no and a.mid=?"; 98 | auth.jdbcAuthentication() 99 | .dataSource(dataSource) 100 | .usersByUsernameQuery(usernameQuery) 101 | .authoritiesByUsernameQuery(authQuery) 102 | .passwordEncoder(passwordEncoder()); 103 | } 104 | 105 | @Bean 106 | public PasswordEncoder passwordEncoder() { 107 | return new BCryptPasswordEncoder(); 108 | } 109 | ``` 110 | 111 | URL에 대한 권한을 설정할 때 hasRole\("{권한}"\) 함수를 사용하게 되는데 이때, {권한}에는 ADMIN,MEMBER 등등이 사용됩니다. 112 | 113 | 하지만 DB에 저장하실때는 'ROLE\_' 을 꼭 붙여야 됩니다.!! 114 | 115 | 다음 코드를 확인하셔서 진행하시는 프로젝트를 성공하시기 바랍니다. 감사합니다. 116 | 117 | -------------------------------------------------------------------------------- /2019-10-10.md: -------------------------------------------------------------------------------- 1 | # 8. Maven -> Gradle 변경작업 2 | 3 | 빌드와 테스트에 소요시간을 해결하는데 Gradle이 장점이라는 소식을 많이 들었다. 따라서 현재 만들고 있는 Maven 프로젝트를 Gradle로 전환해보는 작업을 해보려고한다. 4 | 5 | ## Maven 6 | 7 | ```text 8 | Apache Maven 2004년 출시 9 | - 빌드를 쉽게 제작 10 | - pom.xml을 통한 정형화된 빌드 시스템 11 | - 뛰어난 프로젝트 정보 제공( Reference source, mailing lists, Dependency etc..) 12 | - 개발 가이드라인 제공 13 | - 새로운 기능을 쉽게 설치하고 업데이트 받을 수 있음(https://mvnrepository.com/) 14 | ``` 15 | 16 | ## Gradle 17 | 18 | ```text 19 | Ant 와 Maven의 장점을 모두 모아 2012년 출시 20 | android os 빌드 도구 21 | - Ant 처럼 유연한 범용 빌드 도구 22 | - Maven을 사용할 수 있는 변환가능 컨벤션 프레임 워크 23 | - 멀티 프로젝트에 사용가능 24 | - 강력한 의존성 관리(apache Ivy) 25 | - Maven 과 Ivy repository 완전 지원 26 | - 원격 저장소 , pom , Ivy 파일 없이 연결되는 의존성 관리 27 | - 그루비 문법(Groovy Scripts) 28 | - 빌드를 위한 풍부한 도메인 모델 제공 29 | ``` 30 | 31 | ## 왜 Gradle 을 써보려고 하는가? 32 | 33 | * Build 동적 요소를 XML로 정의하기엔 어려움이 많다. 34 | * 설정 내용이 길어지고 가독성 떨어짐 35 | * 의존관계가 복잡한 프로젝트 설정 부적절 36 | * 상속구조를 이용한 멀티 모듈 구현 37 | * 특정 설정을 소수의 모듈에서 공유하기 위해 부모 프로젝트를 생성하여 상속해야 됨 38 | * Gradle은 Groovy 를 사용하기에 동적 빌드는 Groovy 스크립트로 작성하면 된다. 39 | * Gradle이 Maven 보다 빌드 속도가 엄청빠르다. 40 | * 아직은 여전히 Maven이 접근성이 좋아 많이들 사용하고 있다. 41 | * 하지만 추가적으로 Gradle로 변경해 보는것도 좋은 경험으로 보인다. 42 | 43 | 참고 사이트 : [bkim Maven vs Gradle](https://bkim.tistory.com/13) 44 | 45 | * 부록에 있는 SOLUTION 을 같이 참고해 주세요. 46 | 47 | ## 설치하기 48 | 49 | 1. 설치법 50 | 51 | ```text 52 | brew update && brew install gradle 53 | ``` 54 | 55 | 2. 본인이 쓰고 있는 버전 56 | 57 | ```text 58 | ------------------------------------------------------------ 59 | Gradle 5.6.2 60 | ------------------------------------------------------------ 61 | 62 | Build time: 2019-09-05 16:13:54 UTC 63 | 64 | Kotlin: 1.3.41 65 | Groovy: 2.5.4 66 | Ant: Apache Ant(TM) version 1.9.14 compiled on March 12 2019 67 | JVM: 1.8.0_221 (Oracle Corporation 25.221-b11) 68 | ``` 69 | 70 | ## 정리 71 | 72 | 본인은 Gradle이 아마 모듈관리성이 좋아서 사용될 것으로 보고 있다. 개발 기회가 있다면 모듈관리에 대해서도 해보고 싶다. 73 | 74 | 참고 : [https://gradle.org/gradle-vs-maven-performance/](https://gradle.org/gradle-vs-maven-performance/) 75 | 76 | -------------------------------------------------------------------------------- /2019-10-19.md: -------------------------------------------------------------------------------- 1 | # 9. React 구성하기 2 | 3 | 이번에는 SpringBoot와 React를 이용해 볼 예정이다. 4 | 5 | 환경 = OSX , JAVA8, Visual Studio code 6 | 7 | VScode에 대해 환경을 설치하는 방법은 [gradle\_error\_solution](https://github.com/Djunnni/Springboot-Summary/blob/master/Appendix/gradle_error_solution.md) 하단에 Visual Studio Code 설치법에 대해 언급했다.\(Maven도 가능하니 따라서 하면 됨\) 8 | 9 | 스프링 부트를 통해 서버 API 구축, UI를 REACT에서 담당 10 | 11 | ```text 12 | Front-end : React 13 | Back-end : SpringBoot 14 | ``` 15 | 16 | 이제 SpringBoot 웹 프로젝트 생성해보자! 17 | 18 | ## 1. code 에서 ++p 누른다. 19 | 20 | * spring initializr: Generate Maven 생성 21 | * Java 선택 22 | * Package , Artifact id 설정 23 | * SpringBoot Version 선택 24 | * Dependency : DevTools, Spring Web Starter Web 선택 25 | * DevTools : 코드가 변경되면 자동으로 Rebuild를 지원해준다. 26 | * 생성 27 | 28 | ## 2. Project를 한번 실행해 본다. 29 | 30 | JSP 와 JSTL을 사용하기 위해 라이브러리를 추가 31 | 32 | pom.xml > dependencies 내부에 33 | 34 | ```text 35 | 36 | 37 | org.apache.tomcat 38 | tomcat-jasper 39 | 9.0.24 40 | 41 | 42 | 43 | javax.servlet 44 | jstl 45 | provided 46 | 47 | ``` 48 | 49 | SpringBoot를 실행해 본다. 방법: vscode terminal에서 'mvn spring-boot:run' 입력하면 서버가 실행됨 50 | 51 | ## 3. React 환경 추가 52 | 53 | 1. 터미널에서 'npm init' >> package.json 생성 \(질문들이 나오는데 전부 enter\) 54 | 2. react 의존 라이브러리 설치 55 | 56 | ```text 57 | npm i react react-dom 58 | npm i @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader style-loader webpack webpack-cli -D 59 | ``` 60 | 61 | ## 4. webpack 설정 62 | 63 | webpack을 통해 react 개발 할 때, 자바스크립트 기능과 jsp에 포함할 .js파일 생성가능 프로젝트의 루트경로에 webpack.config.js 파일을 만든다. 64 | 65 | ```text 66 | var path = require('path') 67 | 68 | module.exports = { 69 | context: path.resolve(__dirname,'src/main/jsx'), 70 | entry: { 71 | main: './MainPage.jsx', 72 | page1: './Page1Page.jsx' 73 | }, 74 | devtool: 'sourcemaps', 75 | cache: true, 76 | output: { 77 | path: __dirname, 78 | filename: './src/main/webapp/js/react/[name].bundle.js' 79 | }, 80 | mode:'none', 81 | module: { 82 | rules: [{ 83 | test: /\.jsx?$/, 84 | exclude: /(node_modules)/, 85 | use: { 86 | loader: 'babel-loader', 87 | options: { 88 | presets: ['@babel/preset-env','@babel/preset-react'] 89 | } 90 | } 91 | },{ 92 | test: /\.css$/, 93 | use: ['style-loader','css-loader'] 94 | }] 95 | } 96 | }; 97 | ``` 98 | 99 | * 코드 내용 100 | * React 소스 경로를 src/main/jsx로 설정 101 | * MainPage와 Page1Page.jsx 빌드 102 | * 빌드 결과 js 파일들을 src/main/webapp/js/react아래 \[페이지 이름\].bundle.js 로 설정 103 | 104 | ## 5. 서버 코드 개발 105 | 106 | 1. MainController.java 파일을 생성한다. 107 | 108 | ~~~ 109 | 110 | package com.example.demo.Controller; 111 | 112 | import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; 113 | 114 | @Controller public class MainController{ 115 | 116 | ```text 117 | @GetMapping("/{name}.html") 118 | public String page(@PathVariable String name,Model model){ 119 | model.addAttribute("pageName",name); 120 | return "page"; 121 | } 122 | ``` 123 | 124 | } 125 | 126 | ```text 127 | 2. src/main 에 webapp 폴더를 만들고 webapp 안에 jsp 와 css 폴더를 만든다. 128 | ``` 129 | 130 | src main webapp jsp 131 | css 132 | 133 | ```text 134 | src/main/webapp/jsp/page.jsp 135 | ``` 136 | 137 | <%@ page language="java" contentType="text/html; charset=utf-8"%> <!doctype html> 138 | 139 | ${pageName} 140 | 141 | ```text 142 | 143 |
144 | 145 | 146 | ``` 147 | 148 | </html> 149 | 150 | ```text 151 | src/main/webapp/css/custom.css 152 | ``` 153 | 154 | .main { font-size: 1.5em; border-bottom:solid 1px black; } .page1 { font-size:0.8em; background-color:orange; } 155 | 156 | ```text 157 | ### 6. 클라이언트 코드 개발 158 | src/main에 jsx 폴더를 만들고 MainPage.jsx 와 Page1Page.jsx 2가지 jsx 파일을 만든다. 159 | 160 | src/main/jsx/MainPage.jsx 161 | ``` 162 | 163 | // custom.css 파일 import import '../webapp/css/custom.css'; 164 | 165 | import React from 'react'; import ReactDom from 'react-dom'; 166 | 167 | class MainPage extends React.Component { render\(\){ return demo 메인페이지; } } ReactDom.render\(,document.getElementById\('root'\)\); 168 | 169 | ```text 170 | src/main/jsx/Page1Page.jsx 171 | ``` 172 | 173 | // custom.css 파일 import import '../webapp/css/custom.css'; 174 | 175 | import React from 'react'; import ReactDom from 'react-dom'; 176 | 177 | class Page1Page extends React.Component { render\(\){ return demo page1페이지; } } ReactDom.render\(,document.getElementById\('root'\)\); 178 | 179 | ```text 180 | 이제 우리가 만든 클라이언트 페이지를 서버 구동 후 볼 수 있도록 빌드 시켜야 한다. 181 | 182 | ### 7. 클라이언트 스크립트 빌드시키기 183 | 184 | jsx 파일을 수정할 때 마다 자동적으로 빌드를 시켜주는 것이 필요. 185 | 186 | webpack의 watch 명령을 통해 가능하도록 만들 수 있다. 187 | 188 | vscode terminal에서 아래와 같이 입력 189 | ``` 190 | 191 | node\_modules/.bin/webpack --watch -d -d : 개발 시 -p : 운영 시 192 | 193 | ```text 194 | 터미널에 정상적으로 빌드되는 걸 확인 할 수 있을 것이다. 195 | ( src/main/webapp/js/react 아래에 bundle.js 가 생성됨을 확인하면 됨) 196 | 197 | 이제 'package.json'에 script를 등록하면 간편하게 빌드와 서버실행을 할 수 있다. 198 | ``` 199 | 200 | "script": { "test": "echo \"Error: no test specified\" && exit1", "start": "mvn spring-boot:run", "watch": "node\_modules/.bin/webpack --watch -d" } 201 | 202 | ~~~ 이제 빌드를 할 때, 'npm run watch'로 하면 되고, 'npm run start'로 서버를 실행 할 수 있다. 203 | 204 | * 해당버전은 OSX 대상이므로 윈도우는 위 부분을 수정해주어야 됨!! 205 | 206 | 빌드가 이루어 졌기 때문에 이지 우리가 만든 페이지를 확인할 수 있다. 207 | 208 | * MainPage: [http://localhost:8080/main.html](http://localhost:8080/main.html) 209 | * Page1Page: [http://localhost:8080/page1.html](http://localhost:8080/page1.html) 210 | 211 | 해당 경로로 들어가면 작성한 jsx파일로 작성한 모습이 보인다. 212 | 213 | ## PS 214 | 215 | Template에 조언사항을 적어놓았습니다. 확인하시면 좀 더 쉽게 이해하실 수 있습니다. 216 | 217 | Gyoogle 님의 글에서 OSX에서 사용할 때로 변경해 놓았습니다. 218 | 219 | Window를 사용하시는 분은 Gyoogle에서 참고해주세요 :\) 220 | 221 | * 참고 사이트 : [Gyoogle](https://kim6394.tistory.com/226) 222 | * 에러가 발생할 시 대처법 : [SpringBoot React 에러 해결방법](https://github.com/Djunnni/Springboot-Summary/blob/master/Appendix/react_error_solution.md) 223 | * Template 다운로드 : [SpringBoot React Template](https://github.com/Djunnni/Springboot-Summary/blob/master/Template/springboot-react-template.zip) 224 | 225 | -------------------------------------------------------------------------------- /2019-11-30.md: -------------------------------------------------------------------------------- 1 | # 10. SpringSecurity 인증 후 로그인 객체는 어떻게? 2 | 3 | SpringBoot 에서 Security 인증 후에 우리는 어떻게 로그인 한 객체의 정보를 받아올 수 있을까? 4 | 5 | 1. Bean을 통해 사용자 정보를 가져온다. 6 | 2. Controller 에서 사용자 정보를 얻는다. 7 | 3. @Authentication Principal 을 사용한다. 8 | 9 | * 설명은 하지만 본인이 실제로 해본 작업은 2번이다. 2번에 대해서는 작업한 내용을 보면서 설명한다. 10 | 11 | ## 1. Bean을 통해 가져오기 12 | SecurityContextHolder를 통해 가져오는 방법이다. 13 | ~~~ 14 | Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 15 | UserDetails userDetails = (UserDetails)principal; 16 | String username = principal.getUsername(); 17 | String password = principal.getPassword(); 18 | ~~~ 19 | 20 | ## 2. Controller 에서 사용자 정보를 얻는다. 21 | Principal 객체에 접근해 정보를 가져온다. 22 | ~~~ 23 | @Controller 24 | public class SecurityController { 25 | @GetMapping("/username") 26 | @ResponseBody 27 | public String currentUserName(Principal principal) { 28 | return principal.getName(); 29 | } 30 | } 31 | ~~~ 32 | 33 | 본인의 경우 다음처럼 활용을 했었다. 34 | Authentication을 통해서 현재 로그인한 사용자의 id를 통해 DB에서 사용자 내역을 받아오게 했다. 35 | ~~~ 36 | public int addActivity(@RequestParam("name") String name,Activity activity,Hash hash,Authentication authentication) { 37 | //현재 로그인한 유저의 정보를 받아옵니다. 38 | UserDetails userDetails = (UserDetails) authentication.getPrincipal(); 39 | Member m = memRepo.findOneByMid(userDetails.getUsername()); 40 | List activityList = m.getActivities(); 41 | ~~~ 42 | JDBC Authorization을 사용했을 때, 아래와 같이 userDetails 정보를 받아왔음. 43 | ~~~ 44 | org.springframework.security.core.userdetails.User@5b2ffc6: 45 | Username: djunnni; 46 | Password: [PROTECTED]; 47 | Enabled: true; 48 | AccountNonExpired: true; 49 | credentialsNonExpired: true; 50 | AccountNonLocked: true; 51 | Granted Authorities: ROLE_MEMBER 52 | ~~~ 53 | 54 | ## 3. @Authentication Principal 을 사용한다. 55 | 56 | 이에 대해서는 [[Spring Security] 현재 로그인한 사용자 정보 가져오기](https://itstory.tk/entry/Spring-Security-%ED%98%84%EC%9E%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EB%B3%B4-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0)를 참고하길 바란다. 57 | 58 | -------------------------------------------------------------------------------- /2019-12-01.md: -------------------------------------------------------------------------------- 1 | ## SpringBoot에서 Amazon SES 서비스 사용하기 2 | ### 2019-12-01 기준 동작 성공 3 | AWS를 이용한 App을 만들기 위해 작업을 하다가 메일 인증을 진행해야 되서 SES 서비스를 사용해 보려고 했다. 4 | 5 | ## 1. SES 란? 6 | 7 | Amazon Simple Email Service를 말하며 OutBound만 가능한 이메일 서비스다. 8 | 9 | 예를 들어, 특별 행사 안내 등의 마케팅 이메일, 주문 확인서 등의 거래 이메일, 뉴스레터 등의 기타 통신문을 발송할 수 있습니다. Amazon SES를 사용하여 메일을 수신하면 이메일 자동 응답기, 이메일 구독 해제 시스템, 수신 이메일에서 고객 지원 티켓을 생성하는 애플리케이션과 같은 소프트웨어 솔루션을 개발할 수 있습니다. 10 | 11 | 자세한 사항은 [AWS SES](https://docs.aws.amazon.com/ko_kr/ses/latest/DeveloperGuide/Welcome.html)에서 알아보길 바란다. 12 | 13 | ## 1-1. 사용하기 이전 사전 작업!! (중요) 14 | SES는 아웃바운드만 지원을 한다. 그렇기에 사용자가 미리 이전에 작업을 해야될 부분이 존재한다. 15 | SES는 두가지로 Identity Management를 지원하는데 16 | 1) domain 17 | 2) email address(다른 이메일) 18 | 이렇게 두가지 이다. 19 | 20 | 여기서 1번에 대해서는 부록으로 기록하고 일단 2번의 경우로 진행하겠다. (아직 진행과정에서 문제가 있어 부록으로 뺍니다.) 21 | 22 | 1. 이메일에 대해 'Verify a new Email Address' 버튼을 클릭하여 본인의 이메일을 등록한다. 23 | 2. [여기](https://console.aws.amazon.com/iam/home?#/security_credential)를 눌러 API를 사용하기위해 Security Credential 을 발급받는다. 24 | 25 | * 발급 과정 26 | 3번째 위치해 있는 Access Keys (Access key ID and secret access key) 를 눌러 "Create New Access Key"를 클릭합니다. 키를 다운로드 받고 잘 저장해 둡니다. => 해당키는 다른데에서도 인증에 쓰일 수 있으니 보관 꼭!! 27 | 28 | key를 다운로드 받으면 csv 파일인데 vi 명령어로 열어보면 29 | ~~~ 30 | AWSAccessKeyId = AKIA*******Q******* 31 | AWSSecretKey = DUo1********************gW5*** 32 | ~~~ 33 | 같이 보입니다. 이 키를 (~/.aws/credentials)에 맞게 저장합니다. 34 | => 본인의 경우 EC2와 함께 사용해서 자동배포를 시킬거라 따로 저장을 하지 않고 진행했습니다. (밑에서 확인!) 35 | 36 | ## 2. SES를 사용하기 위해 추가해야 할 dependency 37 | 인터넷에 보면 많은 예시들이 있긴하지만 2019년에 들어 따라해본 결과 업데이트 됨에 따라 credential 인증 부분에 문제가 있었다. 따라서 본인이 수행해본 결과대로 작성을 하겠다. 38 | 39 | ~~~ 40 | 41 | 42 | com.amazonaws 43 | aws-java-sdk-ses 44 | 1.11.227 45 | 46 | 47 | 48 | software.amazon.awssdk 49 | ses 50 | 2.10.25 51 | 52 | ~~~ 53 | 54 | ## 3. SenderDto 만들기 55 | ~~~ 56 | @Getter 57 | public class SenderDto { 58 | private String from; 59 | private List to = new ArrayList<>(); 60 | private String subject; 61 | private String content; 62 | 63 | @Builder 64 | public SenderDto(String from, List to, String subject, String content) { 65 | this.from = from; 66 | this.to = to; 67 | this.subject = subject; 68 | this.content = content; 69 | } 70 | 71 | public void addTo(String email){ 72 | this.to.add(email); 73 | } 74 | 75 | public SendEmailRequest toSendRequestDto(){ 76 | Destination destination = new Destination() 77 | .withToAddresses(this.to); 78 | 79 | Message message = new Message() 80 | .withSubject(createContent(this.subject)) 81 | .withBody(new Body() 82 | .withHtml(createContent(this.content))); // content body는 HTML 형식으로 보내기 때문에 withHtml을 사용합니다. 83 | 84 | return new SendEmailRequest() 85 | .withSource(this.from) 86 | .withDestination(destination) 87 | .withMessage(message); 88 | } 89 | 90 | private Content createContent(String text) { 91 | return new Content() 92 | .withCharset("UTF-8") 93 | .withData(text); 94 | } 95 | } 96 | ~~~ 97 | 98 | ## 4. Sender 만들기 99 | ~~~ 100 | @Slf4j 101 | public class Sender { 102 | // @Value로도 받을 수 있습니다. 인터넷에 찾아보세요! 103 | // @Value("${AWSAccessKeyId}") 104 | // private String AWS_ACCESS_KEY_ID; 105 | // 106 | // @Value("${AWSSecretKey}") 107 | // private String AWS_SECRET_KEY; 108 | 109 | public void send(SenderDto senderDto){ 110 | try { 111 | log.info("Attempting to send an email through Amazon SES by using the AWS SDK for Java..."); 112 | // 아래 부분에 위에서 받은 ID, Key를 집어 넣습니다. 113 | // 인증방식은 제가 고쳐서 진행했습니다. 114 | BasicAWSCredentials awsCreds = new BasicAWSCredentials("AKIA*******Q*******","DUo1********************gW5***"); 115 | AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(awsCreds); 116 | try { 117 | // 아래와 같이 인증방식을 변경함. 118 | credentialsProvider.getCredentials(); 119 | } catch (Exception e) { 120 | throw new AmazonClientException( 121 | "Cannot load the credentials from the credential profiles file. " + 122 | "Please make sure that your credentials file is at the correct " + 123 | "location (~/.aws/credentials), and is in valid format.", 124 | e); 125 | } 126 | 127 | AmazonSimpleEmailService client = AmazonSimpleEmailServiceClientBuilder.standard() 128 | .withCredentials(credentialsProvider) 129 | // 자신이 설정한 리전으로 변경할 것!! 130 | .withRegion("us-east-1") 131 | .build(); 132 | 133 | // Send the email. 134 | client.sendEmail(senderDto.toSendRequestDto()); 135 | log.info("Email sent!"); 136 | 137 | } catch (Exception ex) { 138 | log.error("The email was not sent."); 139 | log.error("Error message: " + ex.getMessage()); 140 | throw new AmazonClientException( 141 | ex.getMessage(), 142 | ex); 143 | } 144 | } 145 | } 146 | ~~~ 147 | 148 | ## 5. Test Case 만들어 실행해보기 149 | ~~~ 150 | @RunWith(SpringRunner.class) 151 | @SpringBootTest(classes= AttendenceAppApplication.class) 152 | public class emailTest { 153 | @Test 154 | public void sendMail() { 155 | Sender sender = new Sender(); 156 | SenderDto dto = new SenderDto("djunnni@gmail.com",Lists.newArrayList("djunnni@gmail.com"), "테스트", "안녕하세요"); 157 | 158 | sender.send(dto); 159 | } 160 | } 161 | ~~~ 162 | 163 | 결과는 재대로 이메일이 전송되었음을 확인 할 수 있습니다. 164 | 165 | 하지만!! 여기서 문제점이 있습니다. 바로 SES는 초기에 200건 까지는 내부 인증 고객에게만 제공하는 규칙이 설정되어있습니다. 166 | 내부고객에게만 제공하는 규칙을 => 아무에게나 보낼 수 있도록 변경해야 됩니다. 167 | 168 | 이 방법에 대해서는 "1. sand box 해제하기" 를 아래에서 읽어주세요. 169 | 170 | [AWS SES (Simple Email Service) Spring Boot 프로젝트에서 사용하기](https://jojoldu.tistory.com/246)를 참고해서 진행해 주세요. 171 | 172 | 저도 위에 분의 코드를 따라하다가 credential 부븐에서 제가 자체로 수정을 하였습니다. 173 | => 고친이유 : EC2로 자동배포를 할 예정이여서 .aws/credential을 넣을 필요없이 바로 static하게 넣어서 사용했음. 174 | 175 | ** 부록에서는 thymleaf의 양식으로 메일로 보내는 코드를 작성해보고 위에서 말씀드린 도메인을 이용한 메일보내기를 해보도록 하겠습니다. 176 | -------------------------------------------------------------------------------- /2019-12-05.md: -------------------------------------------------------------------------------- 1 | ## 12. SpringBoot에서 ElasticBeanStalk ebextensions 파일 만들기 2 | 3 | Springboot에서 작업을 하다가 ElasticBeanStalk 을 쓰려고 하면 환경설정을 해야될 경우가 있다. 4 | 5 | 특히 시간이 ElasticBeanStalk 에서는 GMT +0 으로 되어있어 한국에서 쓰려고 하면 KST 로 맞추어 주어야 된다. 6 | 7 | 그러려면 ElasticBeanStalk 에서 말하고 있는 .ebextensions에 대해 설정을 해야된다. 8 | 9 | 1. 로컬타임 시간 바꾸기 10 | 11 | 1) pom.xml 하단에 플러그인 추가 12 | => src/main/ebextenstions를 war로 압축한 뒤 .ebextemsions로 만들겠다는 의미다. 13 | ~~~ 14 | 15 | maven-war-plugin 16 | 17 | 18 | 19 | src/main/ebextensions 20 | .ebextensions 21 | true 22 | 23 | 24 | 25 | 26 | ~~~ 27 | 28 | 2) 1번을 마치면 war 압축을 풀어보면 아래와 같다. 29 | ~~~ 30 | .WEB-INF/ 31 | .META-INF/ 32 | .ebextensions/ 33 | - 01-timezone.config => 이 파일을 만들것이다. 34 | ~~~ 35 | 36 | 3) 01-timezone.config 37 | =>인터넷에 예시도 많지만 따라해본 결과 모두 Command rm not 이라는 'rm' 명령어가 없다고 에러가 발생했다. 38 | 따라서 본인이 실행해본 결과 이것이 제일 정확하다. 39 | ~~~ 40 | commands: 41 | 01link_seoul_zone: 42 | command: "ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime" 43 | ~~~ 44 | 45 | 2. 로컬타임 시간바꾸기 ( aws에서 명령어로 바꾸기 ) 46 | 47 | ElasticBeanstalk configuration > Software Configuration > Container Options 48 | JVM command line options에 아래를 추가 하면 된다. 49 | -Duser.timezone=Asia/Seoul 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## 목표 4 | 5 | SpringBoot 2.0을 공부하면서 몰랐던 점과 요약정리에 대한 Summary를 올립니다. 6 | 7 | ## 개발환경 8 | 9 | OSX\(Mac OS Mojave\), Spring Tool suite4, JAVA 8 10 | 11 | ## 목차 12 | 13 | * [2019-09-03](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-09-03.md) : SpringBoot 처음 사용할 때 + Lombok 설명 14 | * [2019-09-04](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-09-04.md) : Spring Data JPA 15 | * [2019-09-05](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-09-05.md) : Spring Data JPA를 이용한 쿼리 활용 16 | * [2019-09-06](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-09-06.md) : JPA 연관관계 처리 17 | * [2019-09-07](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-09-07.md) : Thymeleaf 사용법 18 | * [2019-09-08](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-09-08.md) : Spring MVC 를 이용한 게시판 구현연습 19 | * [2019-10-05](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-10-05.md) : SpringSecurity4 JDBC Authorize 20 | * [2019-10-10](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-10-10.md) : Maven 에서 Gradle 로 전환하기 21 | * [2019-10-19](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-10-19.md) : React + SpringBoot 프로젝트 만들기 22 | * [2019-11-30](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-11-30.md) : SpringSecurity 인증 후 로그인 객체는 어떻게? 23 | * [2019-12-01](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-12-01.md) : SpringBoot에서 Amazon SES 서비스 사용하기 24 | * [2019-12-05](https://github.com/Djunnni/Springboot-Summary/blob/master/2019-12-05.md) : SpringBoot에서 ElasticBeanStalk ebextensions 파일 만들기 25 | 26 | ## 부록 27 | 28 | * [Querydsl](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/querydsl.md) : Querydsl 설치법 및 사용법 29 | * [Mysql\_error\_solution](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/mysql_error_solution.md) : MYSQL 오류 발생시에 상황과 대처법 30 | * [SpringSecurity Ajax CSRF solution](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/SpringSecurity-Ajax-CSRF-solution.md) : SpringSecurity 에서 Ajax 사용시 403 Forbidden 해결하는 방법\(CSRF\) 31 | * [Heroku Deploy](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/heroku-deploy.md) : SpringBoot - Heroku deploy 32 | * [Spring rename](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/spring-rename.md) : SpringBoot에서 Package, project name 변경하기 33 | * [Gradle Error solution](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/gradle_error_solution.md) : Eclipse 내부 Gradle 문제 상황과 해결방법 34 | * [React Error solution](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/react_error_solution.md) : React 연동과정에서 발생하는 문제 상황과 해결방법 35 | * [thymeleaf LocalDateTime](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/Thymeleaf_LocalDateTime.md) : Thymeleaf에서 LocalDateTime 사용하는 방법 36 | * [JpaAuditing 사용하기](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/JpaAuditing.md) : JpaAuditing을 통한 시간 동기화 37 | * [AWS RDS timezone과 charset 변경](https://github.com/Djunnni/Springboot-Summary/blob/master/appendix/rds_kst_time.md) : AWS RDS timezone과 charset 변경 38 | 39 | ## Template 40 | 41 | 프로젝트 템플릿을 저장한 공간입니다. 편하게 활용하세요 : \) 42 | 43 | ## Contact 44 | 45 | Summary를 만드는데 여러분의 의견을 참고하고 싶습니다. 더 좋은 Summary를 만들기 위해 많은 연락 부탁드립니다. 46 | 47 | E-mail : djunnni@gmail.com 48 | 49 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [README](README.md) 4 | * [1. SpringBoot 처음 사용할 때 + Lombok 설명](2019-09-03.md) 5 | * [2. Spring Data JPA](2019-09-04.md) 6 | * [3. Spring Data JPA 를 이용한 쿼리 연습](2019-09-05.md) 7 | * [4. JPA 연관관계 처리](2019-09-06.md) 8 | * [5. Thymeleaf 사용법](2019-09-07.md) 9 | * [6. Spring MVC 를 이용한 게시판 구현연습](2019-09-08.md) 10 | * [7. Spring Security 4 JDBC 를 이용한 로그인 인증방법](2019-10-05.md) 11 | * [8. Maven -> Gradle 변경작업](2019-10-10.md) 12 | * [9. React 구성하기](2019-10-19.md) 13 | * [10. SpringSecurity 인증 후 로그인 객체는 어떻게?](2019-11-30.md) 14 | * [11. SpringBoot에서 Amazon SES 서비스 사용하기](2019-12-01.md) 15 | * [12. SpringBoot에서 ElasticBeanStalk ebextensions 파일 만들기](2019-12-05.md) 16 | * [오류 상황과 대처법](appendix/README.md) 17 | * [1. Querydsl](appendix/querydsl.md) 18 | * [2. MYSQL 오류 대처법](appendix/mysql_error_solution.md) 19 | * [3.Ajax CSRF 대처법](appendix/springsecurity-ajax-csrf-solution.md) 20 | * [4. Heroku 배포하기](appendix/heroku-deploy.md) 21 | * [5. 프로젝트 이름 변경](appendix/spring-rename.md) 22 | * [6. Gradle 오류 대처법](appendix/gradle_error_solution.md) 23 | * [7. React 오류 대처법](appendix/react_error_solution.md) 24 | * [8. Tymeleaf LocalDateTime](appendix/Thymeleaf_LocalDateTime.md) 25 | * [9. JpaAuditing을 통한 시간 동기화](appendix/JpaAuditing.md) 26 | * [10. AWS RDS timezone과 charset 변경](appendix/rds_kst_time.md) 27 | 28 | -------------------------------------------------------------------------------- /Template/springboot-react-template.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djunnni/Springboot-Summary/539141cd9042dbbd2e2a5cb3836c93a53c841972/Template/springboot-react-template.zip -------------------------------------------------------------------------------- /appendix/JpaAuditing.md: -------------------------------------------------------------------------------- 1 | # 9. JpaAuditing을 통한 시간 동기화 2 | 3 | Jpa 와 Hibernate를 이용해 Mysql을 사용하고 있다. 4 | 5 | 그런데? 문제가 발생 -> 데이터를 수정했는데 시간이 바뀌질 않아?? -> 무엇이 문제였을까? 6 | 7 | ## 1. 문제점 8 | 9 | 1. Query를 Repository에서 바로 구현해서 쓰면 시간이 고쳐지질 않았다. 10 | => Jpa를 이용해 save 방식으로 해결했다. 11 | 12 | ## 2. Entity 설정 13 | 시간에 대해서는 어떠한 Domain에서도 모두 사용하기에 아래와 같이 구성했다. 14 | LocalDateTime을 사용했으므로 [8장](https://github.com/Djunnni/Springboot-Summary/blob/master/Appendix/Thymeleaf_LocalDateTime.md)을 참고해라. 15 | 이 때 EntityListener로 AuditingEntityListener를 가져와서 쓸 것이다. 16 | ~~~ 17 | @Getter 18 | @MappedSuperclass 19 | @EntityListeners(AuditingEntityListener.class) 20 | public abstract class BaseTimeEntity { 21 | 22 | @CreatedDate 23 | private LocalDateTime regdate; 24 | 25 | @LastModifiedDate 26 | private LocalDateTime modifieddate; 27 | } 28 | ~~~ 29 | ## 3. 예시 Domain 설정 30 | equalsAndHashCode를 사용할 경우 extends 로 받은 BaseTimeEntity로 인해 warning이 발생할 것이다. 31 | callSuper를 통해 문제를 해결할 수 있는데 이 callSuper는 부모 클래스까지 비교해서 참과 거짓을 분간할 건지 의사를 묻는다고 보면 된다. 32 | ~~~ 33 | @EqualsAndHashCode(of="ano",callSuper = false) 34 | public class Activity extends BaseTimeEntity{ 35 | 36 | @Id 37 | @GeneratedValue(strategy = GenerationType.IDENTITY) 38 | private Long ano; 39 | 40 | // =========== user list =========== // 41 | @OneToMany(cascade=CascadeType.ALL,fetch = FetchType.LAZY) 42 | @JoinColumn(name="act_no") 43 | private List users; 44 | 45 | } 46 | ~~~ 47 | ## 4. AppApplication.class 설정 48 | 아래처럼 @EnableJpaAuditing 과 EntityListener가 run과 함께 동작하도록 설정 49 | ~~~ 50 | @SpringBootApplication 51 | @EnableJpaAuditing 52 | @EntityListeners(AuditingEntityListener.class) 53 | public class TestAppApplication { 54 | 55 | @Bean 56 | public Java8TimeDialect java8TimeDialect() { 57 | return new Java8TimeDialect(); 58 | } 59 | 60 | public static void main(String[] args) { 61 | SpringApplication.run(AttendenceAppApplication.class, args); 62 | } 63 | 64 | } 65 | ~~~ 66 | ## 5. 예시를 통한 결과 확인 67 | 예시처럼 Repository를 통해 하나의 Activity를 받고 @setter를 통해 save하면 시간이 변경된다. 68 | ~~~ 69 | @PostMapping(value= {"/test"}) 70 | @Transactional 71 | @ResponseBody 72 | public int testActivity(@PathVariable String url) { 73 | Activity act = actRepo.findOneByURLLike(url); 74 | act.setFinished(!act.isFinished()); 75 | try { 76 | actRepo.save(act); 77 | } catch(Exception e) { 78 | return 500; 79 | } 80 | return 200; 81 | } 82 | ~~~ 83 | 84 | ## 6. 결과 예시 85 | +-----+---------------------+---------------------+-------+-----------------------------+----------+--------+ 86 | | ano | modifieddate | regdate | aname | url | finished | mem_no | 87 | +-----+---------------------+---------------------+-------+-----------------------------+----------+--------+ 88 | | 1 | 2019-12-02 21:24:40 | 2019-12-02 21:18:07 | asd | XXXXXXXXXXXXXXXXXXX | 0 | 1 | 89 | +-----+---------------------+---------------------+-------+-----------------------------+----------+--------+ 90 | 1 row in set (0.00 sec) 91 | 92 | -------------------------------------------------------------------------------- /appendix/README.md: -------------------------------------------------------------------------------- 1 | # 오류 상황과 대처법 2 | 3 | -------------------------------------------------------------------------------- /appendix/Thymeleaf_LocalDateTime.md: -------------------------------------------------------------------------------- 1 | # 8. Thymeleaf LocalDateTime 2 | 3 | Java 8에서 TimeStamp보다 LocalDateTime을 쓰라고 권장하고 있다. 4 | 5 | 이에 따라 수정한 과정을 설명하고 OneToMany에서 수정시간 고치는 방법을 기술한다. 6 | 7 | ## 1. thymeleaf 추가 Dependency 8 | java8 thymeleaf에서 LocalDateTime 형식을 #date를 써서 가져오려고 하면 Error 가 발생한다. 이를 해결하기 위한 9 | 방법이 #temporals를 통해 가져오는데 이를 설정하기 위해 다음과 같은 Dependency가 필요하다. 10 | 11 | ~~~ 12 | 13 | org.thymeleaf.extras 14 | thymeleaf-extras-java8time 15 | 3.0.1.RELEASE 16 | 17 | ~~~ 18 | 19 | ## 2. AppApplication.class 에 추가하기 20 | 아래의 Bean을 넣어주어야 작동을 한다. 21 | 22 | ~~~ 23 | @Bean 24 | public Java8TimeDialect java8TimeDialect() { 25 | return new Java8TimeDialect(); 26 | } 27 | ~~~ 28 | 29 | 30 | ## 3. thymeleaf 구문 31 | 예시) 처럼 #temporals를 기존 #dates 처럼 쓰면 된다. 32 | ~~~ 33 | 34 | 출석시간 35 | [[${#temporals.format(user.regdate)}]] 36 | 37 | ~~~ -------------------------------------------------------------------------------- /appendix/gradle_error_solution.md: -------------------------------------------------------------------------------- 1 | # 6. Gradle 오류 대처법 2 | 3 | ### Gradle 환경 변경시 문제점 솔루션 4 | 5 | STS4 환경에서 실행할 때, 6 | 7 | ### 1. Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0 8 | 9 | #### 문제점 10 | 11 | Gradle의 버전에 의해 build를 할수 없는 상황이다. 12 | 13 | buildShip 버전과 Gradle 이 3.0 하위버전에서 지원을 하기 때문에 이럴 때는 Gradle의 레벨을 낮춰야 된다. 14 | 15 | #### 해결방법 16 | 17 | gradle > wrapper > gradle-wrapper.properties에서 distributionUrl 의 gradle 버전을 낮춘다. 18 | 19 | => 본인이 보기에는 STS4와 Gradle의 버전지원이 낮아 보여서 환경을 vscode \(gradle 5.0써도 오류 x\) 로 바꾸게 되었습니다. 20 | 21 | ### 2. Could not fetch model of type buildenvironment using gradle installation 22 | 23 | #### 문제점 24 | 25 | Gradle이 안맞거나 market place에서 gradle pack을 다운로드 받지 않은 환경일 수 있다. 26 | 27 | #### 해결방법 28 | 29 | eclipse market place에서 gradle을 입력하고 30 | 31 | 1. Gradle IDE pack 다운로드 32 | 2. BuildShip Gradle Integration 다운로드 33 | 34 | ### 3. Could not install Gradle distribution from ... 35 | 36 | #### 문제점 37 | 38 | Gradle에 해당주소의 자원이 없거나 https의 오류 또는 방화벽, Gradle의 설정에 문제일 수 있다. 39 | 40 | #### 해결방법 41 | 42 | 1. **1 번 문제처럼 distributionUrl 에서 https -> http로 refresh 해보자** 43 | 2. 방화벽 문제일 수 있으니 확인해보자 44 | 3. gradle에 대해 설정이 따로 필요할 수 있다. 45 | 46 | 오른쪽 버튼 > properties > gradle > override setting > gradle distribution 버전과 java 버전 바꿔주기 47 | 48 | ### STS4에서 오류가 너무 나서 진행을 못하겠다? => Visual Studio Code 갈아타기 49 | 50 | 본인의 경우 STS4 에서 계속 해봐도 쉽게 고쳐지지 않고 Gradle을 다운그레이드 해보고 진행해도 오류가 있어 vscode로 갈아타기로 했다. 51 | 52 | * 아직 해보는 중이지만 확실히 오류는 줄은 것 같다. 53 | 54 | #### 설치하기 55 | 56 | extension에서 아래의 이름을 받는다. 57 | 58 | 1. Java Extension Pack 59 | 2. Spring initializr Java Support 60 | 3. Spring Boot Tools 61 | 4. Spring Boot extension Pack 62 | 5. Spring Boot DashBoard // DashBoard가 보여서 쉽게 실행할 수 있게 해준다. 63 | 64 | #### Project 생성하기 65 | 66 | osx 기준 \) 67 | 68 | Shift + Commend + p 누른다. 1. > spring initializr: Generate Maven or Gradle 생성 2. > Java, Kotlin, Groovy 중 선택 3. > Package 이름 설정 4. > Artifact Id 설정 5. > SpringBoot version 설정 6. > Dependencies 추가하기 // STS4 만큼 편리하다 7. > 생성 69 | 70 | #### Project 실행하기 71 | 72 | build.gradle 을 수정하고 나서 console 창에서 \( Commend + '~' 누르면 콘솔창 뜸 \) 1. gradle build 입력 2. gradle bootRun 입력 73 | 74 | 또는 75 | 76 | 1. Application.java 에서 run 을 눌러 실행하면 됨 77 | 78 | -------------------------------------------------------------------------------- /appendix/heroku-deploy.md: -------------------------------------------------------------------------------- 1 | # 4. Heroku 배포하기 2 | 3 | ## jawsDB 생성하기 \(한글 지원\) 4 | 5 | * Resources에서 jawsDB를 설치한다. 6 | * heroku config -a {app 이름}으로 jawsDB의 설정을 확인하고 연결한다. 7 | 8 | ```text 9 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 10 | spring.datasource.url = jdbc:mysql://{DB 설정} ?reconnect=true&useUnicode=true&characterEncoding=UTF-8 11 | spring.datasource.username={id} 12 | spring.datasource.password={password} 13 | spring.datasource.sql-script-encoding=UTF-8 14 | ``` 15 | 16 | ## clearDB 생성하기 \(DB에 한글 안쓰고 영어만 쓸경우에 추천\) 17 | 18 | * heroku에 가입하고 Resources 에서 clearDB를 설치합니다. -> 카드를 등록해야만 사용가능함 19 | * clearDB를 설치한 이후에 계정에 대한 정보를 얻으려면 아래와 같이 수행 20 | 21 | 1\) 자신의 콘솔에서는 heroku에 대한 설치후 사용가능 22 | 23 | ```text 24 | heroku config --app 25 | ``` 26 | 27 | => clearDB에 대한 url , name , passwd 확인가능 28 | 29 | 2\) heroku Resources에 생성된 clearDB 옆에 설정버튼을 클릭 아이디와 비밀번호를 확인할 수 있음 30 | 31 | * JPA 와 사용법 32 | 33 | ```text 34 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 35 | spring.datasource.url = jdbc:mysql:?reconnect=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&useSSL=false&useUnicode=true&characterEncoding=UTF-8 36 | spring.datasource.username= 37 | spring.datasource.password= 38 | ``` 39 | 40 | ## Heroku Deploy 설정법 41 | 42 | 본인의 경우 STS에서 실행하였으므로 다음방법으로 실행하였습니다. 43 | 44 | STS에서 Project -> Export -> War -> 생성된 war 파일의 경로에서 45 | 46 | ```text 47 | heroku war:deploy {war 파일이름}.war --app {heroku에 생성된 앱 이름} 48 | ``` 49 | 50 | 이렇게 진행하면 자동으로 deploy가 되어 실행가능 51 | 52 | \*\* 혹시 github으로 push해서 사용하신분은 연락좀 주세요. 하다가 오류가 발생했었습니다. 53 | 54 | ```text 55 | && 같이 이 문제를 해결해보실 분도 연락주세요 && 56 | ``` 57 | 58 | ## Heroku vim 설치 및 사용법 59 | 60 | 헤로쿠에서 vim을 쓰려고 하면 고질적으로 문제가 발생할 때가 있다. 61 | 62 | 그래서 사람들이 찾아보션 nano 쓰라고 하는데 본인은 vi, vim이 너무 편해 이 문제를 해결해보고 싶었다. 63 | 64 | 자신의 콘솔 65 | 66 | ```text 67 | 1. heroku plugins:install @jasonheecs/heroku-vim -app {앱 이름} 68 | 2. heroku vim -a {앱 이름} 69 | ``` 70 | 71 | 이렇게 진행하면 바로 될 때도 있고 안될때도 있다. 72 | 73 | ```text 74 | mkdir ~/vim 75 | cd ~/vim 76 | curl 'https://s3.amazonaws.com/bengoa/vim-static.tar.gz' | tar -xz 77 | export VIMRUNTIME="$HOME/vim/runtime" 78 | export PATH="$HOME/vim:$PATH" 79 | cd - 80 | bash 81 | on ⬢ {앱이름}... up, run.3055 (Free) 82 | ``` 83 | 84 | 1Dyno 만 지원하기 때문에 console에 접속은 1개만 가능하다. 85 | 86 | nano 사용법 참고: [http://www.compulsivecoders.com/tech/how-to-edit-a-file-on-heroku-dynos-using-nano-or-vim/](http://www.compulsivecoders.com/tech/how-to-edit-a-file-on-heroku-dynos-using-nano-or-vim/) 87 | 88 | ## clearDB mysql의 my.cnf 변경하기\(실패\) 89 | 90 | my.cnf가 latin으로 잡혀서 한글 텍스트가 깨지는 경우가 발생한다. ??? 이렇게 나오는 데 일단 database의 config를 확인해보았다. 91 | 92 | 명령어 확인 하는 방법 ! 93 | 94 | ```text 95 | mysql > show variables where Variable_name Like 'c%'; 96 | ``` 97 | 98 | heroku 결과 99 | 100 | ```text 101 | +--------------------------+----------------------------+ 102 | | Variable_name | Value | 103 | +--------------------------+----------------------------+ 104 | | character_set_client | utf8mb4 | 105 | | character_set_connection | utf8mb4 | 106 | | character_set_database | latin1 | 107 | | character_set_filesystem | binary | 108 | | character_set_results | utf8mb4 | 109 | | character_set_server | latin1 | 110 | | character_set_system | utf8 | 111 | | character_sets_dir | /usr/share/mysql/charsets/ | 112 | | collation_connection | utf8mb4_general_ci | 113 | | collation_database | latin1_swedish_ci | 114 | | collation_server | latin1_swedish_ci | 115 | | completion_type | NO_CHAIN | 116 | | concurrent_insert | AUTO | 117 | | connect_timeout | 10 | 118 | +--------------------------+----------------------------+ 119 | ``` 120 | 121 | 본인의 MAC 결과 122 | 123 | ```text 124 | | character_set_client | utf8mb4 | 125 | | character_set_connection | utf8mb4 | 126 | | character_set_database | utf8mb4 | 127 | | character_set_filesystem | binary | 128 | | character_set_results | utf8mb4 | 129 | | character_set_server | utf8mb4 | 130 | | character_set_system | utf8 | 131 | | character_sets_dir | /usr/local/Cellar/mysql/8.0.17/share/mysql/charsets/ | 132 | | check_proxy_users | OFF | 133 | | collation_connection | utf8mb4_general_ci | 134 | | collation_database | utf8mb4_general_ci | 135 | | collation_server | utf8mb4_general_ci | 136 | | completion_type | NO_CHAIN | 137 | | concurrent_insert | AUTO | 138 | | connect_timeout | 10 | 139 | | core_file | OFF | 140 | | create_admin_listener_thread | OFF | 141 | | cte_max_recursion_depth | 1000 142 | ``` 143 | 144 | 이제, utf8mb4로 변경을 해보자!! 145 | 146 | 1. 일단 임시방편으로 SET 명령어로 각각을 utf8mb4로 설정했었다. => 결과 : 실패 \(입력시 ???\) 147 | 2. 프로젝트 내에 utf에 대한 설정을 확인 => 결과 : 이상없었음 148 | 3. application.properties -> spring.datasource.url 에 DB 뒤에 파라미터 추가 입력 => 결과 : 실패 \(입력시 ???\) 149 | * 입력내용 150 | 151 | ```text 152 | (reconnect=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul&useSSL=false&useUnicode=true&characterEncoding=UTF-8) 153 | ``` 154 | 4. spring.datasource.tomcat.connection-properties=useUnicode=true;characterEncoding=utf-8; 추가 => 결과 : 실패 155 | 5. 아무래도 DB의 collation을 latin1 에서 utf8mb4로 바꿀 수 없는 구조이다. 156 | 6. Database 의 타임존은 UTC로 되어있으나 heroku localtime 만 바꾸면 자동으로 잡아줌 157 | 158 | ## heroku local time 변경하기 159 | 160 | 처음에 UTC 로 설정되어있기 때문에 deploy할 때, KST로 변경시켜주어야 한다. 161 | 162 | ```text 163 | heroku config:add TZ="Asia/Seoul" --app [APPNAME] 164 | ``` 165 | 166 | -------------------------------------------------------------------------------- /appendix/mysql_error_solution.md: -------------------------------------------------------------------------------- 1 | # 2. MYSQL 오류 대처법 2 | 3 | ## 1. mysql Public Key Retrieval is not allowed 4 | 5 | 해결법 : allowPublicKeyRetrieval=true 을 url에 파라미터로 추가 6 | 7 | ## 2. mysql KST 설정 문제 8 | 9 | 해결법 : serverTimezone=Asia/Seoul 추가 또는 mysql server에서 my.ini 파일에 10 | 11 | ```text 12 | default-time-zone="+9:00" 추가하기 13 | ``` 14 | 15 | ## 3. ERROR 2002 \(HY000\): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' 설정법 16 | 17 | 환경: OSX 18 | 19 | 1. brew를 통해서 mysql 을 삭제한다. 20 | 2. 해결법 21 | 22 | brew service stop mysql 23 | 24 | brew uninstall mysql 25 | 26 | rm -rf /usr/local/var/mysql rm /usr/local.etc.my.cnf 27 | 28 | 3. brew install mysql \(설치 이후 HY000 2002 에러가 뜰 수 있는데 이는 디비서버가 열려있지 않아서 발생한문제\) 29 | 4. 해결법 30 | 31 | mysql.server start 32 | 33 | -------------------------------------------------------------------------------- /appendix/querydsl.md: -------------------------------------------------------------------------------- 1 | # 1. Querydsl 2 | 3 | ## Querydsl을 통한 동적 SQL 처리법 4 | 5 | @Querydsl 을 사용하기 위해서는 3가지 단계를 거처야 한다. 6 | 7 | 1. pom.xml 의 라이브러리 + Maven 설정 변경 및 실행 8 | 2. Predicate 개발 9 | 3. Repository를 통한 실행 10 | 11 | 개발자는 SQL을 직접 처리하지 않고 , Querydsl을 통해서 필요조건을 처리할 수 있다. 12 | 13 | ## 설치법 14 | 15 | [maven repository](https://mvnrepository.com/) 에서 querydsl을 찾는데 16 | 17 | 여기서 패키지 'com.querydsl' 버전을 이용한다. 18 | 19 | pom.xml에 를 아래처럼 추가한다. 20 | 21 | ```text 22 | 23 | 24 | com.querydsl 25 | querydsl-jpa 26 | 4.1.4 27 | 28 | 29 | com.querydsl 30 | querydsl-apt 31 | 4.1.4 32 | provided 33 | 34 | ``` 35 | 36 | <최근 4.2.1 이 나왔는데 4.1.4도 사용자가 많아 이걸로 한다> 37 | 38 | Querydsl은 JPA를 처리하기 위해 Entity class를 생성하는 방식이다. 따라서 Qdomain 클래스를 생성하는 작업을 위한 코드생성기가 필요한데 pom.xml에서 을 추가해준다. 39 | 40 | ```text 41 | 42 | 43 | com.mysema.maven 44 | apt-maven-plugin 45 | 1.1.3 46 | 47 | 48 | 49 | process 50 | 51 | 52 | target/generated-sources/java 53 | com.querydsl.apt.jpa.JPAAnnotationProcessor 54 | 55 | 56 | 57 | 58 | ``` 59 | 60 | \*\* Querydsl plugin에서 execution 오류 처리법 61 | 62 | * 오류내용 63 | 64 | ```text 65 | Execution default of goal com.mysema.maven:apt-maven-plugin:1.1.3:process failed: Plugin com.mysema.maven:apt-maven-plugin:1.1.3 66 | or one of its dependencies could not be resolved: Failed to collect dependencies at com.mysema.maven:apt-maven-plugin:jar:1.1.3 -> 67 | org.apache.commons:commons-io:jar:1.3.2 (com.mysema.maven:apt-maven-plugin:1.1.3:process:default:generate-sources) 68 | ``` 69 | 70 | * 처리과정 71 | 72 | \(1번과정은 생략해도 됩니다.\) 73 | 74 | * 메이븐 리소스 플러그인 추가 75 | 76 | ```text 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-resources-plugin 81 | 3.0.2 82 | 83 | ``` 84 | 85 | * 프로젝트 우클릭 이후 RunAs -> Maven install\(build\) 해도 됨 , Maven -> update project 86 | 87 | ## 실행 Test 코드 88 | 89 | ```text 90 | @Test 91 | public void testPredicate() { 92 | String type="t"; 93 | String keyword="17"; 94 | 95 | BooleanBuilder builder = new BooleanBuilder(); 96 | 97 | QBoard board = QBoard.board; 98 | 99 | if(type.equals("t")) { 100 | builder.and(board.title.like("%"+keyword+"%")); 101 | } 102 | builder.and(board.bno.gt(0L)); 103 | 104 | Pageable pageable = PageRequest.of(0, 10); 105 | Page result = repo.findAll(builder, pageable); 106 | System.out.println(result.getSize()); 107 | List list = result.getContent(); 108 | list.forEach(b->System.out.println(b)); 109 | } 110 | ``` 111 | 112 | 실행 Console 내용 113 | 114 | ```text 115 | Hibernate: select board0_.bno as bno1_0_, board0_.content as content2_0_, board0_.regdate as regdate3_0_, board0_.title as title4_0_, board0_.updatedate as updateda5_0_, board0_.writer as writer6_0_ from tbl_boards board0_ where (board0_.title like ? escape '!') and board0_.bno>? limit ? 116 | Hibernate: select count(board0_.bno) as col_0_0_ from tbl_boards board0_ where (board0_.title like ? escape '!') and board0_.bno>? 117 | ``` 118 | 119 | -------------------------------------------------------------------------------- /appendix/rds_kst_time.md: -------------------------------------------------------------------------------- 1 | # 10. AWS RDS timezone과 charset 변경 2 | 3 | ### 1. RDS 파라미터그룹 만들기 4 | 5 | RDS 콘솔에 들어와 좌측에 보면 파라미터 그룹이 있다. 6 | 7 | 파라미터 그룹을 생성하고 들어오면 8 | 9 | '파라미터 편집' <= 누르기 10 | 11 | 12 | ### 2. 파라미터 편집하기 13 | 14 | 1. time_zone 검색 15 | => Asia/Seoul 로 변경 16 | 17 | 2. char 검색 18 | => 아래처럼 변경 19 | ~~~ 20 | character_set_client = utf8mb4 21 | character_set_connection = utf8mb4 22 | character_set_database = utf8mb4 23 | character_set_filesystem = binary 24 | character_set_results = utf8mb4 25 | character_set_server = utf8mb4 26 | ~~~ 27 | 28 | 3. collation 검색 29 | => 아래처럼 변경 30 | ~~~ 31 | collation_connection = utf8mb4_general_ci 32 | collation_server = utf8mb4_general_ci 33 | ~~~ 34 | 35 | #### 왜? utf8이 아닌 utf8mb4 ?? 36 | utf8mb4는 이모티콘까지 추가되어있어 utf8보다 좀 더 추가된 필드를 갖기때문 37 | 38 | 4. 변경사항 저장 39 | 40 | ### 3. 파라미터 적용하기 41 | 42 | RDS 생성 또는 수정에서 43 | 44 | 데이터베이스 옵션 > DB 파라미터 그룹 > [설정한 파라미터그룹 이름 설정] 45 | 46 | 적용 47 | 48 | 49 | -------------------------------------------------------------------------------- /appendix/react_error_solution.md: -------------------------------------------------------------------------------- 1 | # 7. React 오류 대처법 2 | 3 | ## 1. 프로젝트를 실행했는데 404 에러가 뜬다? 4 | 5 | 문제 : jsp를 사용하는데 있어 prefix 와 suffix 설정을 안했다. 6 | 7 | 해결 방법 : src > main > resources > application.properties 파일에 다음을 추가한다. 8 | 9 | ```text 10 | # prefix 경로 : src/main/webapp/jsp/ 11 | spring.mvc.view.prefix=/jsp/ 12 | # suffix : .jsp 13 | spring.mvc.view.suffix=.jsp 14 | ``` 15 | 16 | ## 2. 서버포트 문제로 8080이 되질 않으면? 17 | 18 | 해결 방법 : src > main > resources > application.properties 파일에 다음을 추가한다. 19 | 20 | ```text 21 | server.port = {원하는 숫자로 변경하면 됩니다.} 22 | ``` 23 | 24 | ## 3. package.json에 script를 설정했는데 작동하질 않는다? 25 | 26 | 문제 : 27 | 28 | -scripts에 대해 위에서 정의되어 있는데 아래에서 재정의를 할 경우 29 | 30 | * scripts에 설정한 명령어가 실제로 존재하지 않을 경우 31 | 32 | 해결방법 : 따로 실행을 일단 해보고 scripts에 다시 넣어본다. 33 | 34 | -------------------------------------------------------------------------------- /appendix/spring-rename.md: -------------------------------------------------------------------------------- 1 | # 5. 프로젝트 이름 변경 2 | 3 | ## 1. Project 복사하고 붙여넣기 4 | 5 | 원하는 위치에 복사한 다음 우버튼 누르고 Rename으로 원하는 이름으로 변경 6 | 7 | ## 2. Context 명 변경 8 | 9 | 프로젝트 >> 마우스 우클릭 >> Properties >> Web project Settings 10 | 11 | 에서 Context root 이름 변경한다. 12 | 13 | ## 3. Rename Maven Artiface 실행 14 | 15 | 프로젝트 >> 마우스 우클릭 >> Refactor >> Rename Maven Artifact 16 | 17 | ## 4. 패키지 명 변경 18 | 19 | 패키지 선택 후 >> 마우스 우클릭 >> Refactor >> Rename 20 | 21 | ## 5. WEB-INF/ xml 파일 내용 수정 \(STS 에서는 따로 작업하진 않았었음\) 22 | 23 | 내부 내용에서 project 이름 바꿔주기 24 | 25 | ## 6. pom.xml Project와 Artifact 명 확인하고 바꾸기 26 | 27 | ## 7. Project 폴더 내부 org.eclipse.wst.common.component 수정하기 28 | 29 | Project 폴더 내부 org.eclipse.wst.common.component 수정하기 30 | 31 | * Property value 와 deploy-name 확인할 것 !! 32 | 33 | -------------------------------------------------------------------------------- /appendix/springsecurity-ajax-csrf-solution.md: -------------------------------------------------------------------------------- 1 | # 3.Ajax CSRF 대처법 2 | 3 | ## Spring Security Ajax 호출시 CSRF 403 Forbidden 에러 솔루션 4 | 5 | Spring Security를 이용할 경우 Ajax 호출 시에 403 Forbidden 에러가 발생합니다. 6 | 7 | 저같은 경우에는 SecurityConfig의 Path권한 문제인 줄 알았는데 계속해서 확인을 해보니 CSRF쪽 문제같아 여러 사이트를 찾아보고 솔루션을 적어보게 되었습니다. 8 | 9 | CSRF : Cross-site Request fogery 의 의미로 A서비스에 로그인하면 브라우저에 A 서비스의 로그인 관련 쿠키정보가 남게 됩니다. 이후 동일 브라우저에서 악의적인 코드가 있는 B서비스에 접속하게 되면 B는 A의 서비스로 임의의 권한이 필요한 Request를 전송할 수 있습니다. 10 | 11 | 이 문제는 과거 유명 경매 사이트인 옥션에서 발생한 개인정보 유출사건에 사용된 방식입니다. 그렇기에 개발자 분들은 이문제를 CSRF 토큰을 통해서 권한문제를 해결해야 합니다. 12 | 13 | 보통 http.csrf\(\).disable\(\) 처럼 csrf에 대한 disable에 대해서 언급하는데 이럴 경우에 보안문제가 발생할 수 있기에 추천하지 않습니다. 14 | 15 | ## CSRF 가 enable 상태에서 403 Forbidden에러가 발생하지 않게 하려면? 16 | 17 | Ajax 요청 Header에 CSRF 토큰 정보를 포함해서 전송하면 됩니다. 18 | 19 | * 내에 csrf meta tag 추가 20 | 21 | ```text 22 | 23 | 24 | ``` 25 | 26 | * ajax 호출 시에 사용하는 xhr에 request header를 이 정보를 이용하도록 설정 27 | 28 | ```text 29 | var token = $("meta[name='_csrf']").attr("content"); 30 | var header = $("meta[name='_csrf_header']").attr("content"); 31 | $(function() { 32 | $(document).ajaxSend(function(e, xhr, options) { 33 | xhr.setRequestHeader(header,token); 34 | }); 35 | }); 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /guestbook/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /guestbook/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | guestbook 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.wst.common.project.facet.core.builder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jem.workbench.JavaEMFNature 31 | org.eclipse.wst.common.modulecore.ModuleCoreNature 32 | org.eclipse.jdt.core.javanature 33 | org.eclipse.m2e.core.maven2Nature 34 | org.eclipse.wst.common.project.facet.core.nature 35 | org.eclipse.wst.jsdt.core.jsNature 36 | 37 | 38 | -------------------------------------------------------------------------------- /guestbook/.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/resources=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.compliance=1.8 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 6 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 7 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 8 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 9 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 10 | org.eclipse.jdt.core.compiler.processAnnotations=disabled 11 | org.eclipse.jdt.core.compiler.release=disabled 12 | org.eclipse.jdt.core.compiler.source=1.8 13 | -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /guestbook/.settings/org.eclipse.wst.validation.prefs: -------------------------------------------------------------------------------- 1 | disabled=06target 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /guestbook/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | kr.or.connect 5 | guestbook 6 | war 7 | 0.0.1-SNAPSHOT 8 | guestbook Maven Webapp 9 | http://maven.apache.org 10 | 11 | UTF-8 12 | 5.2.20.RELEASE 13 | 2.10.2 14 | 15 | 16 | 17 | 18 | 19 | org.springframework 20 | spring-context 21 | ${spring.version} 22 | 23 | 24 | 25 | org.springframework 26 | spring-webmvc 27 | ${spring.version} 28 | 29 | 30 | 31 | javax.servlet 32 | javax.servlet-api 33 | 3.1.0 34 | provided 35 | 36 | 37 | 38 | javax.servlet.jsp 39 | javax.servlet.jsp-api 40 | 2.3.1 41 | provided 42 | 43 | 44 | 45 | javax.servlet 46 | jstl 47 | 1.2 48 | 49 | 50 | 51 | org.springframework 52 | spring-jdbc 53 | ${spring.version} 54 | 55 | 56 | 57 | org.springframework 58 | spring-tx 59 | ${spring.version} 60 | 61 | 62 | 63 | mysql 64 | mysql-connector-java 65 | 8.0.18 66 | 67 | 68 | 69 | 70 | org.apache.commons 71 | commons-dbcp2 72 | 2.1.1 73 | 74 | 75 | 76 | com.fasterxml.jackson.core 77 | jackson-databind 78 | ${jackson2.version} 79 | 80 | 81 | 82 | com.fasterxml.jackson.datatype 83 | jackson-datatype-jdk8 84 | ${jackson2.version} 85 | 86 | 87 | 88 | junit 89 | junit 90 | 3.8.1 91 | test 92 | 93 | 94 | 95 | guestbook 96 | 97 | 98 | 99 | org.apache.maven.plugins 100 | maven-compiler-plugin 101 | 3.6.1 102 | 103 | 1.8 104 | 1.8 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/config/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @Configuration 8 | @ComponentScan(basePackages = { "kr.or.connect.guestbook.dao", "kr.or.connect.guestbook.service"}) 9 | @Import({ DBConfig.class }) 10 | public class ApplicationConfig { 11 | 12 | } -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/config/DBConfig.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.config; 2 | 3 | 4 | import javax.sql.DataSource; 5 | 6 | import org.apache.commons.dbcp2.BasicDataSource; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 10 | import org.springframework.transaction.PlatformTransactionManager; 11 | import org.springframework.transaction.annotation.EnableTransactionManagement; 12 | import org.springframework.transaction.annotation.TransactionManagementConfigurer; 13 | 14 | @Configuration 15 | @EnableTransactionManagement 16 | public class DBConfig implements TransactionManagementConfigurer { 17 | private String driverClassName = "com.mysql.jdbc.Driver"; 18 | 19 | private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul"; 20 | 21 | private String username = "root"; 22 | 23 | private String password = "root"; 24 | 25 | @Bean 26 | public DataSource dataSource() { 27 | BasicDataSource dataSource = new BasicDataSource(); 28 | dataSource.setDriverClassName(driverClassName); 29 | dataSource.setUrl(url); 30 | dataSource.setUsername(username); 31 | dataSource.setPassword(password); 32 | return dataSource; 33 | } 34 | 35 | @Override 36 | public PlatformTransactionManager annotationDrivenTransactionManager() { 37 | return transactionManger(); 38 | } 39 | 40 | @Bean 41 | public PlatformTransactionManager transactionManger() { 42 | return new DataSourceTransactionManager(dataSource()); 43 | } 44 | } -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/config/WebMvcContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; 7 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 8 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 9 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 11 | import org.springframework.web.servlet.view.InternalResourceViewResolver; 12 | 13 | @Configuration 14 | @EnableWebMvc 15 | @ComponentScan(basePackages = { "kr.or.connect.guestbook.controller" }) 16 | public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter{ 17 | 18 | @Override 19 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 20 | registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926); 21 | registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926); 22 | registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926); 23 | } 24 | 25 | // default servlet handler를 사용하게 합니다. 26 | @Override 27 | public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { 28 | configurer.enable(); 29 | } 30 | 31 | @Override 32 | public void addViewControllers(final ViewControllerRegistry registry) { 33 | System.out.println("addViewControllers가 호출됩니다. "); 34 | registry.addViewController("/").setViewName("index"); 35 | } 36 | 37 | @Bean 38 | public InternalResourceViewResolver getInternalResourceViewResolver() { 39 | InternalResourceViewResolver resolver = new InternalResourceViewResolver(); 40 | resolver.setPrefix("/WEB-INF/views/"); 41 | resolver.setSuffix(".jsp"); 42 | return resolver; 43 | } 44 | } -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/controller/GuestbookApiController.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | import kr.or.connect.guestbook.dto.Guestbook; 22 | import kr.or.connect.guestbook.service.GuestbookService; 23 | 24 | @RestController 25 | @RequestMapping(path="/guestbooks") 26 | public class GuestbookApiController { 27 | @Autowired 28 | GuestbookService guestbookService; 29 | 30 | @GetMapping 31 | public Map list(@RequestParam(name="start", required=false, defaultValue="0") int start) { 32 | 33 | List list = guestbookService.getGuestbooks(start); 34 | 35 | int count = guestbookService.getCount(); 36 | int pageCount = count / GuestbookService.LIMIT; 37 | if(count % GuestbookService.LIMIT > 0) 38 | pageCount++; 39 | 40 | List pageStartList = new ArrayList<>(); 41 | for(int i = 0; i < pageCount; i++) { 42 | pageStartList.add(i * GuestbookService.LIMIT); 43 | } 44 | 45 | Map map = new HashMap<>(); 46 | map.put("list", list); 47 | map.put("count", count); 48 | map.put("pageStartList", pageStartList); 49 | 50 | return map; 51 | } 52 | 53 | @PostMapping 54 | public Guestbook write(@RequestBody Guestbook guestbook, 55 | HttpServletRequest request) { 56 | String clientIp = request.getRemoteAddr(); 57 | // id가 입력된 guestbook이 반환된다. 58 | Guestbook resultGuestbook = guestbookService.addGuestbook(guestbook, clientIp); 59 | return resultGuestbook; 60 | } 61 | 62 | @DeleteMapping("/{id}") 63 | public Map delete(@PathVariable(name="id") Long id, 64 | HttpServletRequest request) { 65 | String clientIp = request.getRemoteAddr(); 66 | 67 | int deleteCount = guestbookService.deleteGuestbook(id, clientIp); 68 | return Collections.singletonMap("success", deleteCount > 0 ? "true" : "false"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/controller/GuestbookController.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.ModelMap; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.ModelAttribute; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestParam; 15 | 16 | import kr.or.connect.guestbook.dto.Guestbook; 17 | import kr.or.connect.guestbook.service.GuestbookService; 18 | 19 | @Controller 20 | public class GuestbookController { 21 | @Autowired 22 | GuestbookService guestbookService; 23 | 24 | @GetMapping(path="/list") 25 | public String list(@RequestParam(name="start", required=false, defaultValue="0") int start, 26 | ModelMap model) { 27 | 28 | // start로 시작하는 방명록 목록 구하기 29 | List list = guestbookService.getGuestbooks(start); 30 | 31 | // 전체 페이지수 구하기 32 | int count = guestbookService.getCount(); 33 | int pageCount = count / GuestbookService.LIMIT; 34 | if(count % GuestbookService.LIMIT > 0) 35 | pageCount++; 36 | 37 | // 페이지 수만큼 start의 값을 리스트로 저장 38 | // 예를 들면 페이지수가 3이면 39 | // 0, 5, 10 이렇게 저장된다. 40 | // list?start=0 , list?start=5, list?start=10 으로 링크가 걸린다. 41 | List pageStartList = new ArrayList<>(); 42 | for(int i = 0; i < pageCount; i++) { 43 | pageStartList.add(i * GuestbookService.LIMIT); 44 | } 45 | 46 | model.addAttribute("list", list); 47 | model.addAttribute("count", count); 48 | model.addAttribute("pageStartList", pageStartList); 49 | 50 | return "list"; 51 | } 52 | 53 | @PostMapping(path="/write") 54 | public String write(@ModelAttribute Guestbook guestbook, 55 | HttpServletRequest request) { 56 | String clientIp = request.getRemoteAddr(); 57 | System.out.println("clientIp : " + clientIp); 58 | guestbookService.addGuestbook(guestbook, clientIp); 59 | return "redirect:list"; 60 | } 61 | } -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/dao/GuestbookDao.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.dao; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import javax.sql.DataSource; 9 | 10 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 11 | import org.springframework.jdbc.core.RowMapper; 12 | import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; 13 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 14 | import org.springframework.jdbc.core.namedparam.SqlParameterSource; 15 | import org.springframework.jdbc.core.simple.SimpleJdbcInsert; 16 | import org.springframework.stereotype.Repository; 17 | 18 | import kr.or.connect.guestbook.dto.Guestbook; 19 | 20 | import static kr.or.connect.guestbook.dao.GuestbookDaoSqls.*; 21 | 22 | @Repository 23 | public class GuestbookDao { 24 | private NamedParameterJdbcTemplate jdbc; 25 | private SimpleJdbcInsert insertAction; 26 | private RowMapper rowMapper = BeanPropertyRowMapper.newInstance(Guestbook.class); 27 | 28 | public GuestbookDao(DataSource dataSource) { 29 | this.jdbc = new NamedParameterJdbcTemplate(dataSource); 30 | this.insertAction = new SimpleJdbcInsert(dataSource) 31 | .withTableName("guestbook") 32 | .usingGeneratedKeyColumns("id"); 33 | } 34 | 35 | public List selectAll(Integer start, Integer limit) { 36 | Map params = new HashMap<>(); 37 | params.put("start", start); 38 | params.put("limit", limit); 39 | return jdbc.query(SELECT_PAGING, params, rowMapper); 40 | } 41 | 42 | 43 | public Long insert(Guestbook guestbook) { 44 | SqlParameterSource params = new BeanPropertySqlParameterSource(guestbook); 45 | return insertAction.executeAndReturnKey(params).longValue(); 46 | } 47 | 48 | public int deleteById(Long id) { 49 | Map params = Collections.singletonMap("id", id); 50 | return jdbc.update(DELETE_BY_ID, params); 51 | } 52 | 53 | public int selectCount() { 54 | return jdbc.queryForObject(SELECT_COUNT, Collections.emptyMap(), Integer.class); 55 | } 56 | } -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/dao/GuestbookDaoSqls.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.dao; 2 | 3 | public class GuestbookDaoSqls { 4 | public static final String SELECT_PAGING = "SELECT id, name, content, regdate FROM guestbook ORDER BY id DESC limit :start, :limit"; 5 | public static final String DELETE_BY_ID = "DELETE FROM guestbook WHERE id = :id"; 6 | public static final String SELECT_COUNT = "SELECT count(*) FROM guestbook"; 7 | } 8 | -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/dao/GuestbookDaoTest.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.dao; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 7 | 8 | import kr.or.connect.guestbook.config.ApplicationConfig; 9 | import kr.or.connect.guestbook.dto.Log; 10 | 11 | public class GuestbookDaoTest { 12 | 13 | public static void main(String[] args) { 14 | // TODO Auto-generated method stub 15 | ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class); 16 | GuestbookDao guestbookDao = ac.getBean(GuestbookDao.class); 17 | LogDao logDao = ac.getBean(LogDao.class); 18 | Log log = new Log(); 19 | log.setIp("127.0.0.1"); 20 | log.setMethod("insert"); 21 | log.setRegdate(new Date()); 22 | logDao.insert(log); 23 | // Guestbook guestbook = new Guestbook(); 24 | // guestbook.setName("강경미"); 25 | // guestbook.setContent("반갑습니다. 여러분."); 26 | // guestbook.setRegdate(new Date()); 27 | // Long id = guestbookDao.insert(guestbook); 28 | // System.out.println("id : " + id); 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/dao/LogDao.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.dao; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; 6 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 7 | import org.springframework.jdbc.core.namedparam.SqlParameterSource; 8 | import org.springframework.jdbc.core.simple.SimpleJdbcInsert; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import kr.or.connect.guestbook.dto.Log; 12 | 13 | @Repository 14 | public class LogDao { 15 | private NamedParameterJdbcTemplate jdbc; 16 | private SimpleJdbcInsert insertAction; 17 | 18 | public LogDao(DataSource dataSource) { 19 | this.jdbc = new NamedParameterJdbcTemplate(dataSource); 20 | this.insertAction = new SimpleJdbcInsert(dataSource) 21 | .withTableName("log") 22 | .usingGeneratedKeyColumns("id"); 23 | } 24 | 25 | public Long insert(Log log) { 26 | SqlParameterSource params = new BeanPropertySqlParameterSource(log); 27 | return insertAction.executeAndReturnKey(params).longValue(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/dto/Guestbook.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.dto; 2 | 3 | import java.util.Date; 4 | 5 | public class Guestbook { 6 | private Long id; 7 | private String name; 8 | private String content; 9 | private Date regdate; 10 | public Long getId() { 11 | return id; 12 | } 13 | public void setId(Long id) { 14 | this.id = id; 15 | } 16 | public String getName() { 17 | return name; 18 | } 19 | public void setName(String name) { 20 | this.name = name; 21 | } 22 | public String getContent() { 23 | return content; 24 | } 25 | public void setContent(String content) { 26 | this.content = content; 27 | } 28 | public Date getRegdate() { 29 | return regdate; 30 | } 31 | public void setRegdate(Date regdate) { 32 | this.regdate = regdate; 33 | } 34 | @Override 35 | public String toString() { 36 | return "Guestbook [id=" + id + ", name=" + name + ", content=" + content + ", regdate=" + regdate + "]"; 37 | } 38 | } -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/dto/Log.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.dto; 2 | 3 | import java.util.Date; 4 | 5 | public class Log { 6 | private Long id; 7 | private String ip; 8 | private String method; 9 | private Date regdate; 10 | public Long getId() { 11 | return id; 12 | } 13 | public void setId(Long id) { 14 | this.id = id; 15 | } 16 | public String getIp() { 17 | return ip; 18 | } 19 | public void setIp(String ip) { 20 | this.ip = ip; 21 | } 22 | public String getMethod() { 23 | return method; 24 | } 25 | public void setMethod(String method) { 26 | this.method = method; 27 | } 28 | public Date getRegdate() { 29 | return regdate; 30 | } 31 | public void setRegdate(Date regdate) { 32 | this.regdate = regdate; 33 | } 34 | @Override 35 | public String toString() { 36 | return "Log [id=" + id + ", ip=" + ip + ", method=" + method + ", regdate=" + regdate + "]"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/service/GuestbookService.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.service; 2 | 3 | import java.util.List; 4 | 5 | import kr.or.connect.guestbook.dto.Guestbook; 6 | 7 | public interface GuestbookService { 8 | public static final Integer LIMIT = 5; 9 | public List getGuestbooks(Integer start); 10 | public int deleteGuestbook(Long id, String ip); 11 | public Guestbook addGuestbook(Guestbook guestbook, String ip); 12 | public int getCount(); 13 | } 14 | -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/service/impl/GuestbookServiceImpl.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.service.impl; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import kr.or.connect.guestbook.dao.GuestbookDao; 11 | import kr.or.connect.guestbook.dao.LogDao; 12 | import kr.or.connect.guestbook.dto.Guestbook; 13 | import kr.or.connect.guestbook.dto.Log; 14 | import kr.or.connect.guestbook.service.GuestbookService; 15 | 16 | @Service 17 | public class GuestbookServiceImpl implements GuestbookService{ 18 | @Autowired 19 | GuestbookDao guestbookDao; 20 | 21 | @Autowired 22 | LogDao logDao; 23 | 24 | @Override 25 | @Transactional 26 | public List getGuestbooks(Integer start) { 27 | List list = guestbookDao.selectAll(start, GuestbookService.LIMIT); 28 | return list; 29 | } 30 | 31 | @Override 32 | @Transactional(readOnly=false) 33 | public int deleteGuestbook(Long id, String ip) { 34 | int deleteCount = guestbookDao.deleteById(id); 35 | Log log = new Log(); 36 | log.setIp(ip); 37 | log.setMethod("delete"); 38 | log.setRegdate(new Date()); 39 | logDao.insert(log); 40 | return deleteCount; 41 | } 42 | 43 | @Override 44 | @Transactional(readOnly=false) 45 | public Guestbook addGuestbook(Guestbook guestbook, String ip) { 46 | guestbook.setRegdate(new Date()); 47 | Long id = guestbookDao.insert(guestbook); 48 | guestbook.setId(id); 49 | 50 | // if(1 == 1) 51 | // throw new RuntimeException("test exception"); 52 | // 53 | Log log = new Log(); 54 | log.setIp(ip); 55 | log.setMethod("insert"); 56 | log.setRegdate(new Date()); 57 | logDao.insert(log); 58 | 59 | 60 | return guestbook; 61 | } 62 | 63 | @Override 64 | public int getCount() { 65 | return guestbookDao.selectCount(); 66 | } 67 | 68 | 69 | } -------------------------------------------------------------------------------- /guestbook/src/main/java/kr/or/connect/guestbook/service/impl/GuestbookServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.or.connect.guestbook.service.impl; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 7 | 8 | import kr.or.connect.guestbook.config.ApplicationConfig; 9 | import kr.or.connect.guestbook.dto.Guestbook; 10 | import kr.or.connect.guestbook.service.GuestbookService; 11 | 12 | public class GuestbookServiceTest { 13 | 14 | public static void main(String[] args) { 15 | ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class); 16 | GuestbookService guestbookService = ac.getBean(GuestbookService.class); 17 | 18 | Guestbook guestbook = new Guestbook(); 19 | guestbook.setName("kang kyungmi22"); 20 | guestbook.setContent("반갑습니다. 여러분. 여러분이 재미있게 공부하고 계셨음 정말 좋겠네요^^22"); 21 | guestbook.setRegdate(new Date()); 22 | Guestbook result = guestbookService.addGuestbook(guestbook, "127.0.0.1"); 23 | System.out.println(result); 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /guestbook/src/main/webapp/WEB-INF/views/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" 2 | pageEncoding="UTF-8"%> 3 | <% 4 | response.sendRedirect("list"); 5 | %> -------------------------------------------------------------------------------- /guestbook/src/main/webapp/WEB-INF/views/list.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" 2 | pageEncoding="UTF-8"%> 3 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 4 | 5 | 6 | 7 | 8 | 방명록 목록 9 | 10 | 11 | 12 |

방명록

13 |
방명록 전체 수 : ${count } 14 |
15 |
16 | 17 | 18 | 19 | ${guestbook.id }
20 | ${guestbook.name }
21 | ${guestbook.content }
22 | ${guestbook.regdate }
23 |
24 |
25 |
26 | 27 | 28 | ${status.index +1 }    29 | 30 | 31 |
32 |
33 |
34 | name :
35 | 36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /guestbook/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring JavaConfig Sample 5 | 6 | contextClass 7 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 8 | 9 | 10 | 11 | contextConfigLocation 12 | kr.or.connect.guestbook.config.ApplicationConfig 13 | 14 | 15 | 16 | org.springframework.web.context.ContextLoaderListener 17 | 18 | 19 | 20 | 21 | mvc 22 | org.springframework.web.servlet.DispatcherServlet 23 | 24 | 25 | contextClass 26 | org.springframework.web.context.support.AnnotationConfigWebApplicationContext 27 | 28 | 29 | 30 | contextConfigLocation 31 | kr.or.connect.guestbook.config.WebMvcContextConfiguration 32 | 33 | 34 | 1 35 | 36 | 37 | mvc 38 | / 39 | 40 | 41 | 42 | encodingFilter 43 | org.springframework.web.filter.CharacterEncodingFilter 44 | 45 | 46 | encoding 47 | UTF-8 48 | 49 | 50 | 51 | encodingFilter 52 | /* 53 | 54 | --------------------------------------------------------------------------------