├── .gitignore
├── LICENCE
├── README-EN.md
├── README.md
├── pic
├── custom_flow.png
├── protocol_flow.png
└── three_layer.png
├── pom.xml
└── src
└── main
├── java
└── com
│ └── shawn
│ ├── Application.java
│ ├── constant
│ ├── ErrorCode.java
│ ├── PageConstant.java
│ └── ResourceNameConstant.java
│ ├── model
│ ├── dto
│ │ ├── CustomUserDetails.java
│ │ ├── Error.java
│ │ └── PaginatedResult.java
│ └── entity
│ │ ├── Book.java
│ │ ├── BookStore.java
│ │ ├── BookStoreWithBooks.java
│ │ ├── BookWithBookStore.java
│ │ └── User.java
│ ├── monitor
│ ├── PerformanceMonitor.java
│ └── ServiceMonitor.java
│ ├── repository
│ ├── BookRepository.java
│ ├── BookStoreRepository.java
│ ├── UserRepository.java
│ └── mybatis
│ │ ├── BookMapper.java
│ │ ├── BookStoreMapper.java
│ │ └── UserMapper.java
│ ├── security
│ ├── AuthorizationServerConfiguration.java
│ ├── ResourceServerConfiguration.java
│ └── WebSecurityConfiguration.java
│ ├── service
│ ├── BookService.java
│ ├── BookStoreService.java
│ ├── UserService.java
│ └── impl
│ │ ├── BookServiceImpl.java
│ │ ├── BookStoreServiceImpl.java
│ │ └── UserServiceImpl.java
│ ├── util
│ └── PageUtil.java
│ └── web
│ ├── controller
│ └── BookController.java
│ └── exception
│ ├── ExceptionHandlerControllerAdvice.java
│ ├── ParameterIllegalException.java
│ ├── ResourceNotFoundException.java
│ └── ServerInternalErrorException.java
└── resources
├── application.properties
├── com
└── shawn
│ └── repository
│ └── mybatis
│ ├── BookMapper.xml
│ ├── BookStoreMapper.xml
│ └── UserMapper.xml
└── db
└── hsqldb
├── data.sql
└── schema.sql
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | custom_flow.vsdx
3 |
4 | ### IntelliJ IDEA ###
5 | .idea
6 | *.iws
7 | *.iml
8 | *.ipr
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Xiaoyue Xiao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-EN.md:
--------------------------------------------------------------------------------
1 | # SpringBoot-MyBatis
2 |
3 | It is a back-end RESTful framework, which integrated by Spring Boot and MyBatis. Containing a basic configuration and a implementation of "book management" module, it could be modified to satify all your requirements. It is simple enough to understand how each module works.
4 |
5 | - [中文文档](README.md)
6 | - [English documents](README-EN.md)
7 |
8 | ## Technics
9 |
10 | - Spring Boot
11 | - MyBatis
12 | - MySQL or HSQLDB (a in-memory database)
13 | - Maven
14 | - Commons Logging
15 | - Java 8 Lambda Expressions
16 | - Lombok
17 | - Apache Commons Lang
18 |
19 | ## References
20 |
21 | - [Understanding Spring Web Application Architecture: The Classic Way](https://www.petrikainulainen.net/software-development/design/understanding-spring-web-application-architecture-the-classic-way/)
22 | - [MyBatis](http://www.mybatis.org/mybatis-3/zh/index.html)
23 | - [mybatis – MyBatis 3 | Mapper XML Files](http://www.mybatis.org/mybatis-3/sqlmap-xml.html#Result_Maps)
24 | - [Mybatis关联查询一对一和一对多的实现 - 林炳文Evankaka的专栏 - 博客频道 - CSDN.NET](http://blog.csdn.net/evankaka/article/details/45674101)
25 | - [Mybatis 鉴别器 - jordandandan的专栏 - 博客频道 - CSDN.NET](http://blog.csdn.net/jordandandan/article/details/50253893)
26 | - [Exception Handling in Spring MVC](http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc)
27 | - [Spring Boot中Web应用的统一异常处理 | 程序猿DD](http://didispace.com/springbootexception/)
28 | - [java - Spring Boot REST service exception handling - Stack Overflow](http://stackoverflow.com/questions/28902374/spring-boot-rest-service-exception-handling)
29 | - [Getting Started · Managing Transactions](http://spring.io/guides/gs/managing-transactions/)
30 | - [HTTP Tutorial](https://www.tutorialspoint.com/http/index.htm)
31 | - [ResponseEntity (Spring Framework 4.3.4.RELEASE API)](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html)
32 | - [ServletUriComponentsBuilder (Spring Framework 4.3.4.RELEASE API)](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/support/ServletUriComponentsBuilder.html)
33 | - [Optional (Java Platform SE 8 )](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
34 | - [Java 8 Optional类深度解析 - ImportNew](http://www.importnew.com/6675.html)
35 | - [Spring REST: Exception handling on a @Controller level](http://fruzenshtein.com/spring-rest-exception-handling-1/)
36 | - [Spring REST: Exception handling on a @ControllerAdvice level](http://fruzenshtein.com/spring-rest-exception-handling-2/)
37 | - [Spring REST: Exception handling on a @ControllerAdvice level](http://fruzenshtein.com/spring-rest-exception-handling-3/)
38 | - [Spring MVC中文翻译文档](http://mvc.linesh.tw/)
39 | - [22. Web MVC framework](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/mvc.html)
40 | - [Spring Boot Reference Guide](http://docs.spring.io/spring-boot/docs/current/reference/html/index.html)
41 | - [MIME 参考手册](http://www.w3school.com.cn/media/media_mimeref.asp)
42 | - [MIME - Wikipedia](https://en.wikipedia.org/wiki/MIME)
43 | - [多用途互联网邮件扩展 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E5%A4%9A%E7%94%A8%E9%80%94%E4%BA%92%E8%81%AF%E7%B6%B2%E9%83%B5%E4%BB%B6%E6%93%B4%E5%B1%95)
44 | - [MIME协议分析 - 彭令鹏(bripengandre)的专栏 - 博客频道 - CSDN.NET](http://blog.csdn.net/bripengandre/article/details/2192982)
45 | - [Difference between CR LF, LF and CR line break types? - Stack Overflow](http://stackoverflow.com/questions/1552749/difference-between-cr-lf-lf-and-cr-line-break-types)
46 | - [Newline - Wikipedia](https://en.wikipedia.org/wiki/Newline)
47 | - [http HEAD vs GET performance - Stack Overflow](http://stackoverflow.com/questions/16539269/http-head-vs-get-performance)
48 | - [HTTP 缓存 | Web | Google Developers](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn)
49 | - [http - How to control web page caching, across all browsers? - Stack Overflow](http://stackoverflow.com/questions/49547/how-to-control-web-page-caching-across-all-browsers/2068407#2068407)
50 | - [The BalusC Code: Webapplication performance tips and tricks](http://balusc.omnifaces.org/2009/09/webapplication-performance-tips-and.html)
51 | - [Yahoo前端优化十四条军规 - 51CTO.COM](http://developer.51cto.com/art/201207/347525_all.htm)
52 | - [Best Practices for Speeding Up Your Web Site - Yahoo Developer Network](https://developer.yahoo.com/performance/rules.html)
53 | - [HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP)
54 | - [解決問題的第一步: Authentication vs. Authorization](http://2010end.blogspot.com/2010/12/authentication-vs-authorization.html)
55 | - [Repositories | GitHub Developer Guide](https://developer.github.com/v3/repos/)
56 | - [RESTful API 设计最佳实践 - 文章 - 伯乐在线](http://blog.jobbole.com/41233/)
57 | - [Best Practices for Designing a Pragmatic RESTful API | Vinay Sahni](http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api)
58 | - [POST /eff/you/this/is/the/right/url - RESTful API Design](http://blog.cloud-elements.com/post-effyouthisistherighturl-restful-api-design)
59 | - [418: I'm a teapot, and other bad API responses - RESTful API Design](http://blog.cloud-elements.com/418-im-a-teapot-and-other-bad-api-responses-restful-api-design)
60 | - [Error Handling: RESTful API Design Part III](http://blog.cloud-elements.com/error-handling-restful-api-design-part-iii)
61 | - [11. Aspect Oriented Programming with Spring](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html)
62 | - [Chapter 6. 使用Spring进行面向切面编程(AOP)](http://shouce.jb51.net/spring/aop.html)
63 | - [AOP with Spring Framework](https://www.tutorialspoint.com/spring/aop_with_spring.htm)
64 | - [StopWatch (Apache Commons Lang 3.4 API)](https://commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/apache/commons/lang3/time/StopWatch.html)
65 | - [Java 日志管理最佳实践](http://www.ibm.com/developerworks/cn/java/j-lo-practicelog/)
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpringBoot-MyBatis
2 |
3 | 最近由于实训,发现很多同学都在开发 Java 后台,因此少不了重复繁杂的配置工作,这个框架就是为了减少这些不必要的工作。它集成了 **Spring Boot** 和 **MyBatis**(后期我又集成了 **Spring Security OAuth** 以支持 OAuth 2.0),并实现了一个类似“书籍管理”的模块(支持 **RESTful API**)以供参考,框架简单易懂。完全可以根据自己的需要,修改这个框架,以实现自己想实现的功能。
4 |
5 | - [中文文档](README.md)
6 | - [English documents](README-EN.md)
7 |
8 | ## 向导
9 |
10 | 1. [启动](#启动)
11 | 2. [技术](#技术)
12 | 3. [细节](#细节)
13 | 1. [文件目录](#文件目录)
14 | 2. [架构](#架构)
15 | 3. [Lombok](#lombok)
16 | 4. [日志记录](#日志记录)
17 | 5. [性能监控](#性能监控)
18 | 6. [OAuth 2.0](#oauth-20)
19 | 7. [Lambda 表达式](#lambda-表达式)
20 | 8. [未完待续……](#未完待续)
21 | 4. [引用](#引用)
22 |
23 | ## 启动
24 |
25 | > 注意:你需要确保你的系统上已安装了 Git 和 Maven(也就是在命令行窗口中键入 git 或 mvn 后,能有非错误性的提示),否则接下来的步骤将无法进行。
26 |
27 | 首先,你应该要将这个代码库克隆到你的磁盘上,并进入此目录。启动命令行窗口,键入:
28 |
29 | ```
30 | git clone https://github.com/ShawnyXiao/SpringBoot-MyBatis.git
31 | cd SpringBoot-MyBatis
32 | ```
33 |
34 | 然后,启动应用(初次启动的话,这个过程会需要稍微久一点的时间)。在命令行窗口中键入:
35 |
36 | ```
37 | mvn spring-boot:run
38 | ```
39 |
40 | 此时,你可以尝试发出一个 HTTP 请求。例如,利用浏览器向你的 Web 应用发出一个 HTTP GET 请求,在地址栏键入:
41 |
42 | ```
43 | http://localhost:8080/books/1
44 | ```
45 |
46 | 你将会看到,你的 Web 应用通过 JSON 字符串来做出响应:
47 |
48 | ```json
49 | {
50 | "id": 1,
51 | "name": "社会研究方法教程",
52 | "author": "袁方",
53 | "price": 68,
54 | "topic": "社会学",
55 | "publishDate": 1425139200000,
56 | "bookStoreId": 1
57 | }
58 | ```
59 |
60 | ## 技术
61 |
62 | - Spring Boot
63 | - MyBatis
64 | - Spring Security OAuth
65 | - MySQL or HSQLDB (a in-memory database)
66 | - Maven
67 | - Commons Logging
68 | - Java 8 Lambda Expressions
69 | - Lombok
70 | - Apache Commons Lang
71 | - Apache Commons Collection4
72 |
73 | ## 细节
74 |
75 | 这个项目其实有点像一个 Demo 项目。我没有将 Spring Boot、MyBatis 和其他框架提供的功能再封装一层(虽然可以这样做),以提供更加应用级的接口。这是因为我觉得这些框架已经提供了很好的封装度和灵活度,完全只需要“掌控”他们,再“利用”他们,你就能实现你想要的。既然是一个 Demo 项目,所以对于初学者,还是有必要讲解一下其中的“**门道**”。对于我个人来说,这一个部分就有点像在回忆:“我当时**怎么做**的,**为什么这样做**”。(顺序有点乱,请根据需要阅读)
76 |
77 | ### 文件目录
78 |
79 | ```
80 | `-- src
81 | |-- main
82 | | |-- java
83 | | | `-- com
84 | | | `-- shawn
85 | | | |-- constant
86 | | | |-- model
87 | | | | |-- dto
88 | | | | `-- entity
89 | | | |-- monitor
90 | | | |-- repository
91 | | | | `-- mybatis
92 | | | |-- service
93 | | | | `-- impl
94 | | | |-- security
95 | | | |-- util
96 | | | `-- web
97 | | | |-- controller
98 | | | |-- excetpion
99 | | | `-- filter
100 | | `-- resources
101 | | |-- com
102 | | | `-- shawn
103 | | | `-- repository
104 | | | `-- mybatis
105 | | `-- db
106 | | `-- hsqldb
107 | `-- test
108 | `-- ...(& so on)
109 | ```
110 |
111 | - ```src/main/java/com/shawn/constant```: 该目录放置了各种常量类
112 | - ```src/main/java/com/shawn/model```: 该目录放置了各种模型类,其子目录 dto 放置了 DTO(Data Trasfer Object)类,其另一子目录 entity 放置了实体类
113 | - ```src/main/java/com/shawn/monitor```: 该目录放置了各种监测类
114 | - ```src/main/java/com/shawn/repository```: 该目录放置了数据库增删改查的接口,其子目录 impl 放置了这些接口的实现类
115 | - ```src/main/java/com/shawn/service```: 该目录下放置服务(一个服务对应于一些业务逻辑的集合)的接口,其子目录 impl 放置了这些接口的实现类
116 | - ```src/main/java/com/shawn/security```: 该目录放置了 Spring Security OAuth 的相关配置
117 | - ```src/main/java/com/shawn/util```: 该目录放置了各种工具类
118 | - ```src/main/java/com/shawn/web```: 该目录放置了和网络层相关的一切,包括控制器、异常处理、过滤器等等
119 | - ```src/main/resources/com/shawn/repository/mybatis```: 该目录放置了 MyBatis 的映射器 XML 文件
120 | - ```src/main/resources/db```: 该目录放置了有关内存数据库的脚本,其子目录 hsqldb 放置了 HSQL 的数据库脚本
121 |
122 | ### 架构
123 |
124 | 为了节约时间就不谈架构的重要性了,那我们先把关注点放在 Web 应用的**职责**上(Web 应用应该做些什么):
125 |
126 | - 它应该能够处理用户的输入,并且返回正确的相应给用户
127 | - 它应该拥有一套异常处理机制,来应对错误发生的时候
128 | - 它应该拥有一个事务管理策略
129 | - 它应该能够对用户进行认证和授权
130 | - 它应该要实现业务逻辑,为用户提供服务
131 | - 它应该要能操纵数据库和其他资源
132 | - ……
133 |
134 | 那么,怎样才能,既实现这些职责,又达到低耦合高内聚(提供一定的封装度和灵活度)的要求?**三层架构**可以支持这一切,它的概览如下图所示:
135 |
136 | 
137 |
138 | - **Web层**:Web 应用的最顶层。它负责处理用户输入以及返回正确的相应给用户;处理其他层抛出的异常并向用户反映错误的发生;对用户进行认证,拒绝未认证的用户访问。
139 | - **Service层**:Web 应用的中间层。它应该组织业务逻辑,为 Web 层提供服务;使得所有服务都是事务性的(要么完成,要么什么都没做);负责用户的授权。
140 | - **Repository层**:Web 应用的最底层。它负责操纵数据库,以实现对数据库的增删改查。
141 |
142 | 那么,这三层的组件要如何交互呢?最佳实践是:**上层组件使用下层组件,使用模型(Model)作为交互媒介**。模型包括两种:数据传输对象(DTO)和领域模型(Domain Model)。
143 |
144 | - **DTO**: 一种用户可见的数据容器。它用来传输用户可见的数据,屏蔽了 Web 应用的内部实现。
145 | - **Domain Model**: 具有领域特征的数据容器。一般来说,它对应于数据库中的表,它代表了 Web 应用的内部实现,应该对用户透明。
146 |
147 | ### Lombok
148 |
149 | 一直以来,都觉得 POJO 类里面的 get、set 方法使得代码显得非常臃肿,不像 C# 里从语言层面支持了 get 访问器和 set 访问器。那么在 Java 中有没有类似可以消除这样冗余代码的技巧呢?答案就是 **Lombok**。Lombok 提供了简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 Java 代码。举个例子:
150 |
151 | 不使用 Lombok:
152 |
153 | ```java
154 | public class BookStore {
155 |
156 | private long id;
157 | private String name;
158 | private String address;
159 |
160 | public BookStore() {
161 | }
162 |
163 | public long getId() {
164 | return id;
165 | }
166 |
167 | public BookStore setId(long id) {
168 | this.id = id;
169 | return this;
170 | }
171 |
172 | public String getName() {
173 | return name;
174 | }
175 |
176 | public BookStore setName(String name) {
177 | this.name = name;
178 | return this;
179 | }
180 |
181 | public String getAddress() {
182 | return address;
183 | }
184 |
185 | public BookStore setAddress(String address) {
186 | this.address = address;
187 | return this;
188 | }
189 |
190 | }
191 | ```
192 |
193 | 使用 Lombok:
194 |
195 | ```java
196 | import lombok.Getter;
197 | import lombok.NoArgsConstructor;
198 | import lombok.Setter;
199 | import lombok.experimental.Accessors;
200 |
201 | @Accessors(chain = true)
202 | @NoArgsConstructor
203 | @Getter
204 | @Setter
205 | public class BookStore {
206 |
207 | private long id;
208 | private String name;
209 | private String address;
210 |
211 | }
212 | ```
213 |
214 | 在本项目中使用的 Lombok 注解有:
215 |
216 | - ```@Accessors```
217 | - ```@NoArgsConstructor```
218 | - ```@Getter```
219 | - ```@Setter```
220 | - ```@ToString```
221 | - ```@CommonsLog```
222 |
223 | ### 日志记录
224 |
225 | 曾经,我一直使用控制台输出(也就是 ```System.out.println```)来让我的程序告诉我它在编译期和运行期做了些什么。这样的确是简单,但是往往伴随着显著的劣势。举两个例子:
226 |
227 | - 在开发环境中,我想看到调试的信息,但在生产环境中,我并不想看到任何调试信息
228 | - 我想将所有信息输出到一个文件,以便我有空了就打开这个文件,看下哪里出错了
229 |
230 | 上面的两个例子足以显示使用控制台输出是无法满足我们的需求的,那么怎样做才是最佳实践呢?答案就是**使用日志框架**。日志框架为我们提供了日志开关、日志级别配置、日志格式配置等等,带来了适度的灵活性和封装性。引用前面说过的一句话:只需要“掌控”他们,再“利用”他们,你就能实现你想要的。
231 |
232 | 本项目使用了 Spring Boot 默认提供的 **Commons Logging**。对于一切想要记录日志的类,只需要在它的头上使用 Lombok 提供的注解 ```@CommonsLog```,便能使用日志记录功能了。举个例子:
233 |
234 | ```java
235 | @CommonsLog
236 | public class XxxClass {
237 | public void XxxMethod() {
238 | log.info("This is an info log.");
239 | log.error("This is an error log.");
240 | }
241 | }
242 | ```
243 |
244 | 这段代码会产生怎样的效果呢?效果如下:
245 |
246 | ```
247 | 2017-01-01 13:35:52.698 INFO 13184 --- [nio-8080-exec-1] com.shawn.xxx.XxxClass : This is an info log.
248 | 2017-01-01 13:35:52.738 ERROR 13184 --- [nio-8080-exec-1] com.shawn.xxx.XxxClass : This is an error log.
249 | ```
250 |
251 | 本项目对日志记录的有效配置全部位于 ```src/main/resources/application.properties``` 下,包括日志级别的配置、输出到日志文件的配置等等,如下:
252 |
253 | ```
254 | ...
255 |
256 | ### Logging ###
257 | # Log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF)
258 | logging.level.root=INFO
259 | logging.level.org.springframework=INFO
260 | logging.level.org.springframework.web=INFO
261 | logging.level.org.mybatis=INFO
262 | logging.level.com.shawn=DEBUG
263 | # File output
264 | project.name=SpringBoot-Mybatis
265 | logging.file=/${project.name}/logs/SpringBoot-Mybatis.log
266 |
267 | ...
268 | ```
269 |
270 | 记录日志只是有效地利用日志的第一步,更重要的是如何对程序运行时产生的日志进行**处理和分析**。这样的处理和分析的能力对于实际系统的维护尤其重要。典型的场景包括:
271 |
272 | - 当日志中包含满足特定条件的记录时,触发相应的通知机制,比如邮件或短信通知
273 | - 在程序运行出现错误时,快速定位潜在的问题源
274 |
275 | ### 性能监控
276 |
277 | 大概5个月之前,我参与的一个项目中存在了一个严重的性能问题,但是在开发和测试过程中并没有人发现这个问题,而是系统上线后,客户发现并提出,导致这个性能问题造成了严重的后果。因此,非常有必要对开发、测试以及上线的系统进行**性能监控**,及时做出补救措施,以造成不必要的后果。
278 |
279 | 那么,我们到底应该监控什么呢,或者说性能到底是什么呢?对于 Web 应用来说,性能就是 Web 系统采取某个动作(动作也就是 Web 系统对每个请求所执行的操作的集合)所消耗的时间。了解到我们应该监控 Web 系统的每个动作,那么更进一步,怎么才能监控每个动作呢?本项目采用的是 **Spring AOP** 技术。三个步骤如下:
280 |
281 | - 使用 ```@Aspect``` 注解声明一个切面(Aspect)。如下:
282 |
283 | ```java
284 | @Aspect
285 | public class XxxAspect {
286 | }
287 | ```
288 |
289 | - 在切面中声明一个切入点(Pointcut),切入点声明包含两个部分:一个包含名字和任意参数的签名;一个切入点表达式,该表达式决定了我们关注哪个方法的执行。如下:
290 |
291 | ```java
292 | @Pointcut("execution(* xxxMethod(..))") // 切入点表达式
293 | private void monitorXxxMethod() {} // 切入点签名
294 | ```
295 |
296 | - 在切面中声明通知(Advice)。举一个声明环绕通知的例子:
297 |
298 | ```java
299 | @Around("monitorXxxMethod") // 使用了第二点中申明的切入点
300 | public Object doSomething(ProceedingJoinPoint pjp) throws Throwable {
301 | // 在方法执行前,做某些操作
302 | Object retVal = pjp.proceed();
303 | // 在方法执行后,做某些操作
304 | return retVal;
305 | }
306 | ```
307 |
308 | 根据上述三个步骤,在本项目中,性能监测代码是这样的:
309 |
310 | ```java
311 | @CommonsLog
312 | @Aspect
313 | @Component
314 | public class PerformanceMonitor {
315 |
316 | /**
317 | * A join point is in the controller layer if the method is
318 | * modified by public and defined in a type in the
319 | * com.shawn.service package or any sub-package under that
320 | * and modified by public.
321 | */
322 | @Pointcut("execution(public * com.shawn.web.controller..*(..))")
323 | private void controllerLayer() {
324 | }
325 |
326 | /**
327 | * Monitor the elapsed time of method on controller layer, in
328 | * order to detect performance problems as soon as possible.
329 | * If elapsed time > 1 s, log it as an error. Otherwise, log it
330 | * as an info.
331 | */
332 | @Around("controllerLayer()")
333 | public Object monitorElapsedTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
334 | // Timing the method in controller layer
335 | StopWatch stopWatch = new StopWatch();
336 | stopWatch.start();
337 | Object result = proceedingJoinPoint.proceed();
338 | stopWatch.stop();
339 |
340 | // Log the elapsed time
341 | double elapsedTime = stopWatch.getTime() / 1000;
342 | Signature signature = proceedingJoinPoint.getSignature();
343 | String infoString = "[" + signature.toShortString() + "][Elapsed time: " + elapsedTime + " s]";
344 | if (elapsedTime > 1) {
345 | log.error(infoString + "[Note that it's time consuming!]");
346 | } else {
347 | log.info(infoString);
348 | }
349 |
350 | // Return the result
351 | return result;
352 | }
353 |
354 | }
355 | ```
356 |
357 | 因此,系统每执行一个动作(也就是,每响应一个请求所执行的操作),在日志上都会记录下它所消耗的时间,若超过1秒,则会以 ```error``` 级别记录日志。如下:
358 |
359 | ```
360 | 2017-01-03 22:58:19.431 ERROR 6384 --- [nio-8080-exec-9] com.shawn.monitor.PerformanceMonitor : [BookController.postBook(..)][Elapsed time: 1.457 s][Note that it's time consuming!]
361 | 2017-01-03 22:58:47.875 INFO 6384 --- [io-8080-exec-10] com.shawn.monitor.PerformanceMonitor : [BookController.getBooks(..)][Elapsed time: 0.656 s]
362 | 2017-01-03 22:59:16.356 INFO 6384 --- [nio-8080-exec-1] com.shawn.monitor.PerformanceMonitor : [BookController.putBook(..)][Elapsed time: 0.618 s]
363 | 2017-01-03 22:59:51.259 INFO 6384 --- [nio-8080-exec-3] com.shawn.monitor.PerformanceMonitor : [BookController.deleteBook(..)][Elapsed time: 0.016 s]
364 | ```
365 |
366 | ### OAuth 2.0
367 |
368 | 为了使服务器的资源受到保护,也就是只让信任的客户端访问受保护的资源,本项目选择 Spring Security OAuth 来集成 OAuth 2.0 来保护我们服务器的资源。
369 |
370 | 只有了解了 OAuth 2.0 的运行流程,我们才能正确的使用它。所以,首先,我们先来了解一下 OAuth 2.0 的运行流程。它的运行流程如下图,摘自 [RFC 6749](http://www.rfcreader.com/#rfc6749)。
371 |
372 | 
373 |
374 | OAuth 2.0 有4种授权方式,分别是:授权码模式(authorization code),简化模式(implicit),密码模式(resource owner password credentials)和客户端模式(client credentials),本项目只采用密码模式。因此,基于上述流程以及密码授权模式,本项目做出了相应的定制,如下图:
375 |
376 | 
377 |
378 | 既然清楚了运行流程,那么接下来要进行的是对 Spring Security OAuth 的配置,涉及到这些的类有:
379 |
380 | - ```com.shawn.model.dto.CustomUserDetails```: 该类是一个模型类,实现了 ```UserDetails``` 接口。它主要负责传送用户的认证信息,包括:用户名, 密码, 该用户所拥有的权限等等
381 | - ```com.shawn.security.AuthorizationServerConfiguration```: 该类是一个配置类,继承了 ```AuthorizationServerConfigurerAdapter```。它主要负责授权服务器的配置,包括:信任的客户端信息的管理、请求令牌的 URL 的配置、 令牌的管理、如何认证用户的配置、对于请求令牌的 URL 的安全约束的配置等等
382 | - ```com.shawn.security.ResourceServerConfiguration```: 该类是一个配置类,继承了 ```ResourceServerConfigurerAdapter```。他主要负责资源服务器的配置,包括:对于请求资源的 URL 的安全约束的配置等等
383 | - ```com.shawn.security.WebSecurityConfiguration```: 该类是一个配置类,继承了 ```GlobalAuthenticationConfigurerAdapter```。它主要负责有关认证的配置,包括:用户的认证信息的获取等等
384 | - ```com.shawn.service.UserService```: 该类是一个服务类的接口,继承了 ```UserDetailsService``` 接口
385 | - ```com.shawn.service.impl.UserServiceImpl```: 该类是 ```UserService``` 接口的实现类
386 |
387 | 有了这些配置,我们实现的效果是:
388 |
389 | - 获取 book 资源(查)的请求一律不需要认证
390 | - 对 book 资源进行修改的请求(增删改)需要认证
391 | - 对 user 资源的所有请求(增删改查)都需要认证
392 |
393 | #### 使用途径
394 |
395 | 首先,我们尝试访问不需要认证的资源:
396 |
397 | ```
398 | curl http://localhost:8080/books/1
399 | ```
400 |
401 | 正如我们所期待的,服务器返回了 ID 为 1 的 book 资源给客户端,如下:
402 |
403 | ```json
404 | {
405 | "id": 1,
406 | "name": "社会研究方法教程",
407 | "author": "袁方",
408 | "price": 68.0,
409 | "topic": "社会学",
410 | "publishDate": 1425139200000,
411 | "bookStoreId": 1
412 | }
413 | ```
414 |
415 | 接下来,我们尝试不带认证信息的访问需要认证的资源(尝试删除 ID 为 1 的 book):
416 |
417 | ```
418 | curl http://localhost:8080/books/1 -X DELETE
419 | ```
420 |
421 | 我们收到如下的 JSON 字符串响应,告诉我们需要认证了才能访问这个资源:
422 |
423 | ```json
424 | {
425 | "error": "unauthorized",
426 | "error_description": "Full authentication is required to access this resource"
427 | }
428 | ```
429 |
430 | 为了访问受保护的资源,我们需要先向授权服务器请求访问令牌(access token):
431 |
432 | ```
433 | curl http://localhost:8080/oauth/token -X POST -u client:fucksecurity -d "grant_type=password&username=shawn&password=fucksecurity"
434 | ```
435 |
436 | 授权服务器验证了我们的客户端和用户信息,验证成功后将我们需要的令牌(token)信息作为响应传回:
437 |
438 | ```json
439 | {
440 | "access_token": "ca741611-a30e-4504-b84e-fdf9cec0da9a",
441 | "token_type": "bearer",
442 | "refresh_token": "1a1fb46e-8ab4-4a3b-84c4-e70892eaa570",
443 | "expires_in": 43199,
444 | "scope": "read write"
445 | }
446 | ```
447 |
448 | 接下来,我们可以使用上个请求返回的 ```access_token```,操作受保护的资源:
449 |
450 | ```
451 | curl http://localhost:8080/books/1 -X DELETE -H "Authorization: Bearer ca741611-a30e-4504-b84e-fdf9cec0da9a"
452 | ```
453 |
454 | 响应成功返回,为了验证 ID 为 1 的 book 确实被删除,我们尝试获取 ID 为 1 的 book 信息:
455 |
456 | ```
457 | curl http://localhost:8080/books/1
458 | ```
459 |
460 | 响应的 HTTP 状态码为 404,并传回了以下 JSON 字符串,这说明 ID 为 1 的 book 确实已经被删除:
461 |
462 | ```json
463 | {
464 | "code": 1003,
465 | "message": "Book with id 1 is not found."
466 | }
467 | ```
468 |
469 | 最后,随着时间(在本项目中是 43199 秒)的消逝,```access_token``` 会过期。可以使用曾经请求访问令牌时返回的 ```refresh_token``` 来获取一个新的 ```access_token```:
470 |
471 | ```
472 | curl http://localhost:8080/oauth/token -X POST -u client:fucksecurity -d "grant_type=refresh_token&refresh_token=1a1fb46e-8ab4-4a3b-84c4-e70892eaa570"
473 | ```
474 |
475 | ### Lambda 表达式
476 |
477 | 为了使代码可读性更强、更简洁,本项目大量的使用了 **Lambda 表达式**。为了体现 Lambda 表达式的优势,我们来看一下对比:
478 |
479 | 不使用 Lambda 表达式:
480 |
481 | ```java
482 | Runnable r = new Runnable() {
483 | @Override
484 | public void run(){
485 | System.out.println("I just want to tell you why to use lambda expressions.");
486 | }
487 | };
488 | ```
489 |
490 | 使用 Lambda 表达式:
491 |
492 | ```java
493 | Runnable r = () -> System.out.println("I just want to tell you why to use lambda expressions.");
494 | ```
495 |
496 | 所以接下来,我们一起来了解一下 Lambda 表达式语法和适用场景。
497 |
498 | #### Lambda 表达式的语法
499 |
500 | 一个 Lambda 表达式包含以下几个组成部分:
501 |
502 | 1. **一个逗号分隔,小括号包围的形参集合**。你可以忽略参数的数据类型;如果只有一个参数,你还可以忽略小括号。
503 | 合法的例子:
504 | ```
505 | (int a, int b)
506 | (a, b)
507 | (int a)
508 | a
509 | ()
510 | ```
511 | 不合法的例子:
512 | ```
513 | (String a, b)
514 | (a, String b)
515 | int a
516 | ```
517 |
518 | 2. **一个箭头符号**。
519 | **唯一**合法的例子:
520 | ```
521 | ->
522 | ```
523 | 不合法的例子:
524 | ```
525 | - >
526 | -->
527 | -<
528 | <-
529 | ```
530 |
531 | 3. **一个主体**。要么是一个表达式,要么是一个由大括号包围语句块。如果 Lambda 表达式实现的是一个 void 方法,而且只有一条语句,那么可以忽略大括号。
532 | 合法的例子:
533 | ```
534 | a == b
535 | {return a == b;}
536 | {result = (a == b); return result;}
537 | System.out.println("Hello, world!")
538 | ```
539 | 不合法的例子:
540 | ```
541 | a == b;
542 | {return a == b}
543 | ```
544 |
545 | 现在讲上面3个部分组合起来,实现**完整的 Lambda 表达式**。例如:
546 |
547 | ```java
548 | (a, b) -> a == b
549 | (int a, int b) -> {result = (a == b); return result;}
550 | a -> {System.out.println(a);}
551 | () -> System.out.println("Hello, world!")
552 | ```
553 |
554 | #### 哪类情况适合使用 Lambda 表达式
555 |
556 | 1. 如果你想对某些变量做一些处理,而你又想降这些处理封装起来,那么你应该使用 Lambda 表达式。举个例子:当你想对一个集合的每一个元素做过滤处理的时候,你就应该使用 Lambda 表达式了。(下面的例子涉及一些 Stream API,这里只需要关注 ```filter()``` 方法)
557 | ```
558 | double average = cars.stream()
559 | .filter(c -> c.isBMW() && c.isRed)
560 | .mapToDoule(c -> c.getSpeed())
561 | .averge()
562 | .getAsDouble();
563 | ```
564 |
565 | 2. 如果你只想要一个简单的**函数式接口**的实例(只想要实现函数式接口中的唯一的抽象方法),那么你应该使用 Lambda 表达式。举个例子:
566 | ```
567 | button.setOnAction(
568 | event -> System.out.println("Hello World!")
569 | );
570 | ```
571 |
572 | 在本项目中,第一类情况大量的出现。例如,```com.shawn.web.controller.BookController``` 中的一段代码:
573 |
574 | ```java
575 | ...
576 |
577 | @GetMapping("/{bookId}")
578 | public ResponseEntity> getBookById(@PathVariable Long bookId) {
579 | return bookService
580 | .getBookById(bookId)
581 | .map(ResponseEntity::ok)
582 | .orElseThrow(() -> new ResourceNotFoundException()
583 | .setResourceName(ResourceNameConstant.BOOK)
584 | .setId(bookId));
585 | }
586 |
587 | ...
588 | ```
589 |
590 | ### 未完待续……
591 |
592 | ## 引用
593 |
594 | - [Understanding Spring Web Application Architecture: The Classic Way](https://www.petrikainulainen.net/software-development/design/understanding-spring-web-application-architecture-the-classic-way/)
595 | - [MyBatis](http://www.mybatis.org/mybatis-3/zh/index.html)
596 | - [mybatis – MyBatis 3 | Mapper XML Files](http://www.mybatis.org/mybatis-3/sqlmap-xml.html#Result_Maps)
597 | - [Mybatis关联查询一对一和一对多的实现 - 林炳文Evankaka的专栏 - 博客频道 - CSDN.NET](http://blog.csdn.net/evankaka/article/details/45674101)
598 | - [Mybatis 鉴别器 - jordandandan的专栏 - 博客频道 - CSDN.NET](http://blog.csdn.net/jordandandan/article/details/50253893)
599 | - [Exception Handling in Spring MVC](http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc)
600 | - [Spring Boot中Web应用的统一异常处理 | 程序猿DD](http://didispace.com/springbootexception/)
601 | - [java - Spring Boot REST service exception handling - Stack Overflow](http://stackoverflow.com/questions/28902374/spring-boot-rest-service-exception-handling)
602 | - [Getting Started · Managing Transactions](http://spring.io/guides/gs/managing-transactions/)
603 | - [HTTP Tutorial](https://www.tutorialspoint.com/http/index.htm)
604 | - [ResponseEntity (Spring Framework 4.3.4.RELEASE API)](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html)
605 | - [ServletUriComponentsBuilder (Spring Framework 4.3.4.RELEASE API)](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/support/ServletUriComponentsBuilder.html)
606 | - [Optional (Java Platform SE 8 )](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
607 | - [Java 8 Optional类深度解析 - ImportNew](http://www.importnew.com/6675.html)
608 | - [Spring REST: Exception handling on a @Controller level](http://fruzenshtein.com/spring-rest-exception-handling-1/)
609 | - [Spring REST: Exception handling on a @ControllerAdvice level](http://fruzenshtein.com/spring-rest-exception-handling-2/)
610 | - [Spring REST: Exception handling on a @ControllerAdvice level](http://fruzenshtein.com/spring-rest-exception-handling-3/)
611 | - [Spring MVC中文翻译文档](http://mvc.linesh.tw/)
612 | - [22. Web MVC framework](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/mvc.html)
613 | - [Spring Boot Reference Guide](http://docs.spring.io/spring-boot/docs/current/reference/html/index.html)
614 | - [MIME 参考手册](http://www.w3school.com.cn/media/media_mimeref.asp)
615 | - [MIME - Wikipedia](https://en.wikipedia.org/wiki/MIME)
616 | - [多用途互联网邮件扩展 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E5%A4%9A%E7%94%A8%E9%80%94%E4%BA%92%E8%81%AF%E7%B6%B2%E9%83%B5%E4%BB%B6%E6%93%B4%E5%B1%95)
617 | - [MIME协议分析 - 彭令鹏(bripengandre)的专栏 - 博客频道 - CSDN.NET](http://blog.csdn.net/bripengandre/article/details/2192982)
618 | - [Difference between CR LF, LF and CR line break types? - Stack Overflow](http://stackoverflow.com/questions/1552749/difference-between-cr-lf-lf-and-cr-line-break-types)
619 | - [Newline - Wikipedia](https://en.wikipedia.org/wiki/Newline)
620 | - [http HEAD vs GET performance - Stack Overflow](http://stackoverflow.com/questions/16539269/http-head-vs-get-performance)
621 | - [HTTP 缓存 | Web | Google Developers](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn)
622 | - [http - How to control web page caching, across all browsers? - Stack Overflow](http://stackoverflow.com/questions/49547/how-to-control-web-page-caching-across-all-browsers/2068407#2068407)
623 | - [The BalusC Code: Webapplication performance tips and tricks](http://balusc.omnifaces.org/2009/09/webapplication-performance-tips-and.html)
624 | - [Yahoo前端优化十四条军规 - 51CTO.COM](http://developer.51cto.com/art/201207/347525_all.htm)
625 | - [Best Practices for Speeding Up Your Web Site - Yahoo Developer Network](https://developer.yahoo.com/performance/rules.html)
626 | - [HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP)
627 | - [解決問題的第一步: Authentication vs. Authorization](http://2010end.blogspot.com/2010/12/authentication-vs-authorization.html)
628 | - [Repositories | GitHub Developer Guide](https://developer.github.com/v3/repos/)
629 | - [RESTful API 设计最佳实践 - 文章 - 伯乐在线](http://blog.jobbole.com/41233/)
630 | - [Best Practices for Designing a Pragmatic RESTful API | Vinay Sahni](http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api)
631 | - [POST /eff/you/this/is/the/right/url - RESTful API Design](http://blog.cloud-elements.com/post-effyouthisistherighturl-restful-api-design)
632 | - [418: I'm a teapot, and other bad API responses - RESTful API Design](http://blog.cloud-elements.com/418-im-a-teapot-and-other-bad-api-responses-restful-api-design)
633 | - [Error Handling: RESTful API Design Part III](http://blog.cloud-elements.com/error-handling-restful-api-design-part-iii)
634 | - [11. Aspect Oriented Programming with Spring](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html)
635 | - [Chapter 6. 使用Spring进行面向切面编程(AOP)](http://shouce.jb51.net/spring/aop.html)
636 | - [AOP with Spring Framework](https://www.tutorialspoint.com/spring/aop_with_spring.htm)
637 | - [StopWatch (Apache Commons Lang 3.4 API)](https://commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/apache/commons/lang3/time/StopWatch.html)
638 | - [Java 日志管理最佳实践](http://www.ibm.com/developerworks/cn/java/j-lo-practicelog/)
639 | - [rest - RESTful Authentication - Stack Overflow](http://stackoverflow.com/questions/319530/restful-authentication)
640 | - [理解OAuth 2.0 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)
641 | - [RESTful authentication using Spring Security on Spring Boot, and jQuery as a web client – Codes And Notes](http://www.codesandnotes.be/2014/10/31/restful-authentication-using-spring-security-on-spring-boot-and-jquery-as-a-web-client/)
642 | - [Secure Spring REST API using OAuth2 - WebSystique](http://websystique.com/spring-security/secure-spring-rest-api-using-oauth2/)
643 | - [royclarkson/spring-rest-service-oauth: A simple OAuth protected REST service built with Spring Boot and Spring Security OAuth](https://github.com/royclarkson/spring-rest-service-oauth)
644 | - [Introduction | Spring Security 参考手册](https://vincentmi.gitbooks.io/spring-security-reference-zh/content/index.html)
645 | - [Tutorial · Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/)
646 | - [了解OAuth2.0](https://developers.douban.com/wiki/?title=oauth2)
647 | - [Spring Security:结合 Spring-OAuth,支持 Mysql 数据库(基于SpringBoot) · Issue #59 · pzxwhc/MineKnowContainer](https://github.com/pzxwhc/MineKnowContainer/issues/59)
648 | - [Spring Security OAuth](https://projects.spring.io/spring-security-oauth/docs/oauth2.html)
649 | - [spring-security-oauth/tests/annotation/multi at master · spring-projects/spring-security-oauth](https://github.com/spring-projects/spring-security-oauth/tree/master/tests/annotation/multi)
650 | - [Lambda Expressions (The Java™ Tutorials > Learning the Java Language > Classes and Objects)](https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html)
651 | - [Java SE 8: Lambda Quick Start](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html)
652 | - [如何选择开源许可证? - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html)
653 |
--------------------------------------------------------------------------------
/pic/custom_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShawnyXiao/SpringBoot-MyBatis/38157e859e2eff68851e665619671f757d50d3b6/pic/custom_flow.png
--------------------------------------------------------------------------------
/pic/protocol_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShawnyXiao/SpringBoot-MyBatis/38157e859e2eff68851e665619671f757d50d3b6/pic/protocol_flow.png
--------------------------------------------------------------------------------
/pic/three_layer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShawnyXiao/SpringBoot-MyBatis/38157e859e2eff68851e665619671f757d50d3b6/pic/three_layer.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.shawn
8 | SpringBoot-MyBatis
9 | 0.0.1-SNAPSHOT
10 |
11 |
12 | SpringBoot-MyBatis
13 | A back-end RESTful framework, integrated by Spring Boot and MyBatis
14 |
15 |
16 |
17 | org.springframework.boot
18 | spring-boot-starter-parent
19 | 1.4.2.RELEASE
20 |
21 |
22 |
23 |
24 | 1.8
25 | UTF-8
26 | UTF-8
27 |
28 |
29 | 1.1.1
30 | 5.1.40
31 | 3.4
32 | 4.1
33 |
34 |
35 |
36 |
37 |
38 | org.springframework.boot
39 | spring-boot-starter
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-web
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-aop
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-starter-security
52 |
53 |
54 |
55 |
56 | org.springframework.security.oauth
57 | spring-security-oauth2
58 |
59 |
60 |
61 |
62 | org.mybatis.spring.boot
63 | mybatis-spring-boot-starter
64 | ${mybatis-spring-boot-starter.version}
65 |
66 |
67 |
68 |
69 | mysql
70 | mysql-connector-java
71 | ${mysql-connector-java.version}
72 |
73 |
74 |
75 |
76 | org.hsqldb
77 | hsqldb
78 | runtime
79 |
80 |
81 |
82 |
83 | org.projectlombok
84 | lombok
85 | provided
86 |
87 |
88 |
89 |
90 | org.apache.commons
91 | commons-lang3
92 | ${commons-lang3.version}
93 |
94 |
95 | org.apache.commons
96 | commons-collections4
97 | ${commons-collections4.version}
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | org.springframework.boot
107 | spring-boot-maven-plugin
108 |
109 |
110 | -Dfile.encoding=UTF-8
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/Application.java:
--------------------------------------------------------------------------------
1 | package com.shawn;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | /**
7 | * SpringBoot-MyBatis Application.
8 | *
9 | * @author Xiaoyue Xiao
10 | */
11 | @SpringBootApplication
12 | public class Application {
13 |
14 | public static void main(String[] args) throws Exception {
15 | SpringApplication.run(Application.class, args);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/constant/ErrorCode.java:
--------------------------------------------------------------------------------
1 | package com.shawn.constant;
2 |
3 | /**
4 | * @author Xiaoyue Xiao
5 | */
6 | public class ErrorCode {
7 |
8 | public static final int SERVER_INTERNAL_ERROR = 1000;
9 | public static final int PARAMETER_MISSING_ERROR = 1001;
10 | public static final int PARAMETER_ILLEGAL_ERROR = 1002;
11 | public static final int RESOURCE_NOT_FOUND_ERROR = 1003;
12 |
13 | /**
14 | * Prevent instantiation
15 | */
16 | private ErrorCode() {
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/constant/PageConstant.java:
--------------------------------------------------------------------------------
1 | package com.shawn.constant;
2 |
3 | /**
4 | * @author Xiaoyue Xiao
5 | */
6 | public class PageConstant {
7 |
8 | public static final int PAGE = 1; // Default page number
9 | public static final int PER_PAGE = 10; // Default size of per page
10 |
11 | /**
12 | * Prevent instantiation
13 | */
14 | private PageConstant() {
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/constant/ResourceNameConstant.java:
--------------------------------------------------------------------------------
1 | package com.shawn.constant;
2 |
3 | /**
4 | * @author Xiaoyue Xiao
5 | */
6 | public class ResourceNameConstant {
7 |
8 | public static final String BOOK = "book";
9 |
10 | /**
11 | * Prevent instantiation
12 | */
13 | private ResourceNameConstant() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/dto/CustomUserDetails.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.dto;
2 |
3 | import com.shawn.model.entity.User;
4 | import lombok.Getter;
5 | import lombok.ToString;
6 | import org.apache.commons.collections4.CollectionUtils;
7 | import org.apache.commons.lang3.StringUtils;
8 | import org.springframework.security.core.GrantedAuthority;
9 | import org.springframework.security.core.userdetails.UserDetails;
10 |
11 | import java.util.Collection;
12 | import java.util.Collections;
13 | import java.util.HashSet;
14 | import java.util.Set;
15 |
16 | /**
17 | * @author Xiaoyue Xiao
18 | */
19 | @Getter
20 | @ToString
21 | public class CustomUserDetails extends User implements UserDetails {
22 |
23 | private static final long serialVersionUID = 1702923242319850756L;
24 |
25 | private final boolean enabled;
26 | private final boolean accountNonExpired;
27 | private final boolean credentialsNonExpired;
28 | private final boolean accountNonLocked;
29 | private final Set authorities;
30 |
31 | public CustomUserDetails(User user, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection extends GrantedAuthority> authorities) {
32 | if (user != null
33 | && !StringUtils.isBlank(user.getUsername())
34 | && !StringUtils.isBlank(user.getPassword())) {
35 | setUsername(user.getUsername());
36 | setPassword(user.getPassword());
37 | this.enabled = enabled;
38 | this.accountNonExpired = accountNonExpired;
39 | this.credentialsNonExpired = credentialsNonExpired;
40 | this.accountNonLocked = accountNonLocked;
41 | this.authorities = Collections.unmodifiableSet(new HashSet<>(CollectionUtils.emptyIfNull(authorities)));
42 | } else {
43 | throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/dto/Error.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.dto;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @author Xiaoyue Xiao
13 | */
14 | @Accessors(chain = true)
15 | @NoArgsConstructor
16 | @Getter
17 | @Setter
18 | @ToString
19 | public class Error implements Serializable {
20 |
21 | private static final long serialVersionUID = 7660756960387438399L;
22 |
23 | private int code; // Error code
24 | private String message; // Error message
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/dto/PaginatedResult.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.dto;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @author Xiaoyue Xiao
13 | */
14 | @Accessors(chain = true)
15 | @NoArgsConstructor
16 | @Getter
17 | @Setter
18 | @ToString
19 | public class PaginatedResult implements Serializable {
20 |
21 | private static final long serialVersionUID = 6191745064790884707L;
22 |
23 | private int currentPage; // Current page number
24 | private int totalPage; // Number of total pages
25 | private Object data; // Paginated resources
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/entity/Book.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.entity;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.io.Serializable;
10 | import java.util.Date;
11 |
12 | /**
13 | * @author Xiaoyue Xiao
14 | */
15 | @Accessors(chain = true)
16 | @NoArgsConstructor
17 | @Getter
18 | @Setter
19 | @ToString
20 | public class Book implements Serializable {
21 |
22 | private static final long serialVersionUID = 8604990093149376515L;
23 |
24 | private Long id;
25 | private String name;
26 | private String author;
27 | private Double price;
28 | private String topic;
29 | private Date publishDate;
30 |
31 | private Long bookStoreId;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/entity/BookStore.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.entity;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @author Xiaoyue Xiao
13 | */
14 | @Accessors(chain = true)
15 | @NoArgsConstructor
16 | @Getter
17 | @Setter
18 | @ToString
19 | public class BookStore implements Serializable {
20 |
21 | private static final long serialVersionUID = 1183385713216587274L;
22 |
23 | private long id;
24 | private String name;
25 | private String address;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/entity/BookStoreWithBooks.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.entity;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * @author Xiaoyue Xiao
13 | */
14 | @Accessors(chain = true)
15 | @NoArgsConstructor
16 | @Getter
17 | @Setter
18 | @ToString(callSuper = true)
19 | public class BookStoreWithBooks extends BookStore {
20 |
21 | private static final long serialVersionUID = -740463675258248874L;
22 |
23 | private List books;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/entity/BookWithBookStore.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.entity;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | /**
10 | * @author Xiaoyue Xiao
11 | */
12 | @Accessors(chain = true)
13 | @NoArgsConstructor
14 | @Getter
15 | @Setter
16 | @ToString(callSuper = true)
17 | public class BookWithBookStore extends Book {
18 |
19 | private static final long serialVersionUID = -4858710159989616286L;
20 |
21 | private BookStore bookStore;
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/model/entity/User.java:
--------------------------------------------------------------------------------
1 | package com.shawn.model.entity;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 | import lombok.experimental.Accessors;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @author Xiaoyue Xiao
13 | */
14 | @Accessors(chain = true)
15 | @NoArgsConstructor
16 | @Getter
17 | @Setter
18 | @ToString
19 | public class User implements Serializable {
20 |
21 | private static final long serialVersionUID = 7698862379923111158L;
22 |
23 | private Long id;
24 | private String username;
25 | private String password;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/monitor/PerformanceMonitor.java:
--------------------------------------------------------------------------------
1 | package com.shawn.monitor;
2 |
3 | import lombok.extern.apachecommons.CommonsLog;
4 | import org.apache.commons.lang3.time.StopWatch;
5 | import org.aspectj.lang.ProceedingJoinPoint;
6 | import org.aspectj.lang.Signature;
7 | import org.aspectj.lang.annotation.Around;
8 | import org.aspectj.lang.annotation.Aspect;
9 | import org.aspectj.lang.annotation.Pointcut;
10 | import org.springframework.stereotype.Component;
11 |
12 | /**
13 | * @author Xiaoyue Xiao
14 | */
15 | @CommonsLog
16 | @Aspect
17 | @Component
18 | public class PerformanceMonitor {
19 |
20 | /**
21 | * A join point is in the controller layer if the method is
22 | * modified by public and defined in a type in the
23 | * com.shawn.service package or any sub-package under that
24 | * and modified by public.
25 | */
26 | @Pointcut("execution(public * com.shawn.web.controller..*(..))")
27 | private void controllerLayer() {
28 | }
29 |
30 | /**
31 | * Monitor the elapsed time of method on controller layer, in
32 | * order to detect performance problems as soon as possible.
33 | * If elapsed time > 1 s, log it as an error. Otherwise, log it
34 | * as an info.
35 | */
36 | @Around("controllerLayer()")
37 | public Object monitorElapsedTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
38 | // Timing the method in controller layer
39 | StopWatch stopWatch = new StopWatch();
40 | stopWatch.start();
41 | Object result = proceedingJoinPoint.proceed();
42 | stopWatch.stop();
43 |
44 | // Log the elapsed time
45 | double elapsedTime = stopWatch.getTime() / 1000.0;
46 | Signature signature = proceedingJoinPoint.getSignature();
47 | String infoString = "[" + signature.toShortString() + "][Elapsed time: " + elapsedTime + " s]";
48 | if (elapsedTime > 1) {
49 | log.error(infoString + "[Note that it's time consuming!]");
50 | } else {
51 | log.info(infoString);
52 | }
53 |
54 | // Return the result
55 | return result;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/monitor/ServiceMonitor.java:
--------------------------------------------------------------------------------
1 | package com.shawn.monitor;
2 |
3 | import com.shawn.web.exception.ServerInternalErrorException;
4 | import lombok.extern.apachecommons.CommonsLog;
5 | import org.aspectj.lang.JoinPoint;
6 | import org.aspectj.lang.Signature;
7 | import org.aspectj.lang.annotation.AfterThrowing;
8 | import org.aspectj.lang.annotation.Aspect;
9 | import org.aspectj.lang.annotation.Pointcut;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.util.Arrays;
13 |
14 | /**
15 | * @author Xiaoyue Xiao
16 | */
17 | @CommonsLog
18 | @Aspect
19 | @Component
20 | public class ServiceMonitor {
21 |
22 | /**
23 | * A join point is in the service layer if the method is defined
24 | * in a type in the com.shawn.service package or any sub-package
25 | * under that.
26 | */
27 | @Pointcut("execution(* com.shawn.service..*(..))")
28 | private void serviceLayer() {
29 | }
30 |
31 | /**
32 | * Monitor whether exception is thrown in service layer. If exception
33 | * has been thrown, in order to detecting it conveniently, log the
34 | * situation where it happened. Then create a server internal error
35 | * exception and throw it out.
36 | */
37 | @AfterThrowing(pointcut = "com.shawn.monitor.ServiceMonitor.serviceLayer()", throwing = "e")
38 | public void monitorException(JoinPoint joinPoint, Throwable e) {
39 | // Log the situation where exception happened
40 | Object[] args = joinPoint.getArgs();
41 | Signature signature = joinPoint.getSignature();
42 | log.error("[" + signature.toShortString() + "]" + Arrays.toString(args) + "[" + e.toString() + "]");
43 |
44 | // Throw a new server internal error exception
45 | throw new ServerInternalErrorException();
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/repository/BookRepository.java:
--------------------------------------------------------------------------------
1 | package com.shawn.repository;
2 |
3 | import com.shawn.model.entity.Book;
4 | import com.shawn.model.entity.BookWithBookStore;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @author Xiaoyue Xiao
10 | */
11 | public interface BookRepository {
12 |
13 | Book selectBookById(Long id);
14 |
15 | List selectBooksByAuthor(String author);
16 |
17 | List selectBooksByLowPriceAndHighPrice(Double lowPrice, Double highPrice);
18 |
19 | List selectAllBooks();
20 |
21 | List selectBooksByPage(Integer offset, Integer perPage);
22 |
23 | BookWithBookStore selectBookWithBookStoreById(Long id);
24 |
25 | Integer selectCount();
26 |
27 | Integer insertBook(Book book);
28 |
29 | Integer updateBookOnNameById(Book book);
30 |
31 | Integer deleteBookById(Long id);
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/repository/BookStoreRepository.java:
--------------------------------------------------------------------------------
1 | package com.shawn.repository;
2 |
3 | import com.shawn.model.entity.BookStore;
4 | import com.shawn.model.entity.BookStoreWithBooks;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @author Xiaoyue Xiao
10 | */
11 | public interface BookStoreRepository {
12 |
13 | BookStore selectBookStoreById(Long id);
14 |
15 | List selectAllBookStores();
16 |
17 | BookStoreWithBooks selectBookStoreWithBooksById(Long id);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/repository/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.shawn.repository;
2 |
3 | import com.shawn.model.entity.User;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * @author Xiaoyue Xiao
9 | */
10 | public interface UserRepository {
11 |
12 | User selectUserById(Long id);
13 |
14 | User selectUserByUsername(String username);
15 |
16 | List selectAllUsers();
17 |
18 | Integer insertUser(User user);
19 |
20 | Integer updateUserOnPasswordById(User user);
21 |
22 | Integer deleteUserById(Long id);
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/repository/mybatis/BookMapper.java:
--------------------------------------------------------------------------------
1 | package com.shawn.repository.mybatis;
2 |
3 | import com.shawn.model.entity.Book;
4 | import com.shawn.repository.BookRepository;
5 | import org.apache.ibatis.annotations.Mapper;
6 | import org.apache.ibatis.annotations.Param;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * @author Xiaoyue Xiao
12 | */
13 | @Mapper
14 | public interface BookMapper extends BookRepository {
15 |
16 | @Override
17 | List selectBooksByLowPriceAndHighPrice(@Param("lowPrice") Double lowPrice, @Param("highPrice") Double highPrice);
18 |
19 | @Override
20 | List selectBooksByPage(@Param("offset") Integer offset, @Param("perPage") Integer perPage);
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/repository/mybatis/BookStoreMapper.java:
--------------------------------------------------------------------------------
1 | package com.shawn.repository.mybatis;
2 |
3 | import com.shawn.repository.BookStoreRepository;
4 | import org.apache.ibatis.annotations.Mapper;
5 |
6 | /**
7 | * @author Xiaoyue Xiao
8 | */
9 | @Mapper
10 | public interface BookStoreMapper extends BookStoreRepository {
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/repository/mybatis/UserMapper.java:
--------------------------------------------------------------------------------
1 | package com.shawn.repository.mybatis;
2 |
3 | import com.shawn.repository.UserRepository;
4 | import org.apache.ibatis.annotations.Mapper;
5 |
6 | /**
7 | * @author Xiaoyue Xiao
8 | */
9 | @Mapper
10 | public interface UserMapper extends UserRepository {
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/security/AuthorizationServerConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.shawn.security;
2 |
3 | import com.shawn.service.UserService;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.context.annotation.Primary;
8 | import org.springframework.security.authentication.AuthenticationManager;
9 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
10 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
11 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
12 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
13 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
14 | import org.springframework.security.oauth2.provider.token.TokenStore;
15 | import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
16 |
17 | /**
18 | * @author Xiaoyue Xiao
19 | */
20 | @Configuration
21 | @EnableAuthorizationServer
22 | public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
23 |
24 | @Autowired
25 | private TokenStore tokenStore;
26 |
27 | @Autowired
28 | private AuthenticationManager authenticationManager;
29 |
30 | @Autowired
31 | private UserService userService;
32 |
33 | @Override
34 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
35 | clients
36 | .inMemory()
37 | .withClient("client")
38 | .authorizedGrantTypes("password", "refresh_token")
39 | .scopes("read", "write")
40 | .secret("fucksecurity");
41 | }
42 |
43 | @Override
44 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
45 | endpoints
46 | .tokenStore(tokenStore)
47 | .authenticationManager(authenticationManager)
48 | .userDetailsService(userService);
49 | }
50 |
51 | @Bean
52 | public TokenStore tokenStore() {
53 | return new InMemoryTokenStore();
54 | }
55 |
56 | @Bean
57 | @Primary
58 | public DefaultTokenServices tokenServices() {
59 | DefaultTokenServices tokenServices = new DefaultTokenServices();
60 | tokenServices.setSupportRefreshToken(true); // support refresh token
61 | tokenServices.setTokenStore(tokenStore); // use in-memory token store
62 | return tokenServices;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/security/ResourceServerConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.shawn.security;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.http.HttpMethod;
5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
7 | import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
8 |
9 | /**
10 | * @author Xiaoyue Xiao
11 | */
12 | @Configuration
13 | @EnableResourceServer
14 | public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
15 |
16 | @Override
17 | public void configure(HttpSecurity http) throws Exception {
18 | http
19 | .authorizeRequests()
20 | .antMatchers("/users/**").authenticated()
21 | .antMatchers(HttpMethod.GET, "/books/**").permitAll()
22 | .anyRequest().authenticated();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/security/WebSecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.shawn.security;
2 |
3 | import com.shawn.service.UserService;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
7 | import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
8 |
9 | @Configuration
10 | public class WebSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter {
11 |
12 | private final UserService userService;
13 |
14 | @Autowired
15 | public WebSecurityConfiguration(UserService userService) {
16 | this.userService = userService;
17 | }
18 |
19 | @Override
20 | public void init(AuthenticationManagerBuilder auth) throws Exception {
21 | auth.userDetailsService(userService);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/service/BookService.java:
--------------------------------------------------------------------------------
1 | package com.shawn.service;
2 |
3 | import com.shawn.model.entity.Book;
4 | import com.shawn.model.entity.BookWithBookStore;
5 |
6 | import java.util.List;
7 | import java.util.Optional;
8 |
9 | /**
10 | * @author Xiaoyue Xiao
11 | */
12 | public interface BookService {
13 |
14 | Optional getBookById(Long id);
15 |
16 | List getBooksByAuthor(String author);
17 |
18 | List getBooksByPage(Integer page, Integer perPage);
19 |
20 | List getAllBookNames();
21 |
22 | Optional getBookWithBookStoreById(Long id);
23 |
24 | Integer getTotalPage(Integer perPage);
25 |
26 | boolean saveBook(Book book);
27 |
28 | boolean modifyBookOnNameById(Book book);
29 |
30 | boolean deleteBookById(Long id);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/service/BookStoreService.java:
--------------------------------------------------------------------------------
1 | package com.shawn.service;
2 |
3 | import com.shawn.model.entity.BookStore;
4 | import com.shawn.model.entity.BookStoreWithBooks;
5 |
6 | import java.util.List;
7 | import java.util.Optional;
8 |
9 | /**
10 | * @author Xiaoyue Xiao
11 | */
12 | public interface BookStoreService {
13 |
14 | Optional getBookStoreById(Long id);
15 |
16 | List getAllBookStoreNames();
17 |
18 | Optional getBookStoreWithBooksById(Long id);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/service/UserService.java:
--------------------------------------------------------------------------------
1 | package com.shawn.service;
2 |
3 | import com.shawn.model.entity.User;
4 | import org.springframework.security.core.userdetails.UserDetailsService;
5 |
6 | import java.util.Optional;
7 |
8 | /**
9 | * @author Xiaoyue Xiao
10 | */
11 | public interface UserService extends UserDetailsService {
12 |
13 | Optional getUserById(Long id);
14 |
15 | boolean saveUser(User user);
16 |
17 | boolean modifyUserOnPasswordById(User user);
18 |
19 | boolean deleteUserById(Long id);
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/service/impl/BookServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.shawn.service.impl;
2 |
3 | import com.shawn.model.entity.Book;
4 | import com.shawn.model.entity.BookWithBookStore;
5 | import com.shawn.repository.BookRepository;
6 | import com.shawn.service.BookService;
7 | import com.shawn.util.PageUtil;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | import java.util.List;
13 | import java.util.Optional;
14 | import java.util.stream.Collectors;
15 |
16 | /**
17 | * @author Xiaoyue Xiao
18 | */
19 | @Service
20 | public class BookServiceImpl implements BookService {
21 |
22 | private final BookRepository bookRepository;
23 |
24 | @Autowired
25 | public BookServiceImpl(BookRepository bookRepository) {
26 | this.bookRepository = bookRepository;
27 | }
28 |
29 | @Override
30 | public Optional getBookById(Long id) {
31 | return Optional.ofNullable(bookRepository.selectBookById(id));
32 | }
33 |
34 | @Override
35 | public List getBooksByAuthor(String author) {
36 | return bookRepository.selectBooksByAuthor(author);
37 | }
38 |
39 | @Override
40 | public List getBooksByPage(Integer page, Integer perPage) {
41 | Integer offset = PageUtil.calculateOffset(page, perPage);
42 | return bookRepository.selectBooksByPage(offset, perPage);
43 | }
44 |
45 | @Override
46 | public List getAllBookNames() {
47 | return bookRepository
48 | .selectAllBooks()
49 | .stream()
50 | .map(Book::getName)
51 | .collect(Collectors.toList());
52 | }
53 |
54 | @Override
55 | public Optional getBookWithBookStoreById(Long id) {
56 | return Optional.ofNullable(bookRepository.selectBookWithBookStoreById(id));
57 | }
58 |
59 | @Override
60 | public Integer getTotalPage(Integer perPage) {
61 | return PageUtil.calculateTotalPage(bookRepository.selectCount(), perPage);
62 | }
63 |
64 | @Override
65 | @Transactional
66 | public boolean saveBook(Book book) {
67 | return bookRepository.insertBook(book) > 0;
68 | }
69 |
70 | @Override
71 | @Transactional
72 | public boolean modifyBookOnNameById(Book book) {
73 | return bookRepository.updateBookOnNameById(book) > 0;
74 | }
75 |
76 | @Override
77 | @Transactional
78 | public boolean deleteBookById(Long id) {
79 | return bookRepository.deleteBookById(id) > 0;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/service/impl/BookStoreServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.shawn.service.impl;
2 |
3 | import com.shawn.model.entity.BookStore;
4 | import com.shawn.model.entity.BookStoreWithBooks;
5 | import com.shawn.repository.BookStoreRepository;
6 | import com.shawn.service.BookStoreService;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.stereotype.Service;
9 |
10 | import java.util.List;
11 | import java.util.Optional;
12 | import java.util.stream.Collectors;
13 |
14 | /**
15 | * @author Xiaoyue Xiao
16 | */
17 | @Service
18 | public class BookStoreServiceImpl implements BookStoreService {
19 |
20 | private final BookStoreRepository bookStoreRepository;
21 |
22 | @Autowired
23 | public BookStoreServiceImpl(BookStoreRepository bookStoreRepository) {
24 | this.bookStoreRepository = bookStoreRepository;
25 | }
26 |
27 | @Override
28 | public Optional getBookStoreById(Long id) {
29 | return Optional.ofNullable(bookStoreRepository.selectBookStoreById(id));
30 | }
31 |
32 | @Override
33 | public List getAllBookStoreNames() {
34 | return bookStoreRepository
35 | .selectAllBookStores()
36 | .stream()
37 | .map(BookStore::getName)
38 | .collect(Collectors.toList());
39 | }
40 |
41 | @Override
42 | public Optional getBookStoreWithBooksById(Long id) {
43 | return Optional.ofNullable(bookStoreRepository.selectBookStoreWithBooksById(id));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/service/impl/UserServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.shawn.service.impl;
2 |
3 | import com.shawn.model.dto.CustomUserDetails;
4 | import com.shawn.model.entity.User;
5 | import com.shawn.repository.UserRepository;
6 | import com.shawn.service.UserService;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.security.core.userdetails.UserDetails;
9 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
10 | import org.springframework.stereotype.Service;
11 |
12 | import java.util.Optional;
13 |
14 | /**
15 | * @author Xiaoyue Xiao
16 | */
17 | @Service
18 | public class UserServiceImpl implements UserService {
19 |
20 | private final UserRepository userRepository;
21 |
22 | @Autowired
23 | public UserServiceImpl(UserRepository userRepository) {
24 | this.userRepository = userRepository;
25 | }
26 |
27 | @Override
28 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
29 | User user = userRepository.selectUserByUsername(username);
30 |
31 | if (user == null) {
32 | throw new UsernameNotFoundException("Could not find the user '" + username + "'");
33 | }
34 |
35 | // Not involve authorities, so pass null to authorities
36 | return new CustomUserDetails(user, true, true, true, true, null);
37 | }
38 |
39 | @Override
40 | public Optional getUserById(Long id) {
41 | return Optional.ofNullable(userRepository.selectUserById(id));
42 | }
43 |
44 | @Override
45 | public boolean saveUser(User user) {
46 | return userRepository.insertUser(user) > 0;
47 | }
48 |
49 | @Override
50 | public boolean modifyUserOnPasswordById(User user) {
51 | return userRepository.updateUserOnPasswordById(user) > 0;
52 | }
53 |
54 | @Override
55 | public boolean deleteUserById(Long id) {
56 | return userRepository.deleteUserById(id) > 0;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/util/PageUtil.java:
--------------------------------------------------------------------------------
1 | package com.shawn.util;
2 |
3 | import com.shawn.web.exception.ParameterIllegalException;
4 |
5 | /**
6 | * @author Xiaoyue Xiao
7 | */
8 | public class PageUtil {
9 |
10 | /**
11 | * Calculate offset for LIMIT clause in SQL. If page < 1, it will return 0.
12 | *
13 | * @param page page number
14 | * @param perPage size of per page
15 | * @return offset
16 | */
17 | public static int calculateOffset(int page, int perPage) {
18 | return calculateOffset(page, perPage, 0);
19 | }
20 |
21 | /**
22 | * Calculate offset for LIMIT clause in SQL. If page < 1, it will return defaultValue.
23 | *
24 | * @param page page number
25 | * @param perPage size of per page
26 | * @param defaultValue default return value, if page < 1
27 | * @return offset
28 | */
29 | public static int calculateOffset(int page, int perPage, int defaultValue) {
30 | return page < 1 ? defaultValue : (page - 1) * perPage;
31 | }
32 |
33 | /**
34 | * Calculate total number of pages.
35 | *
36 | * @param rowCount count of rows
37 | * @param perPage size of per page
38 | * @return total number of pages
39 | */
40 | public static int calculateTotalPage(int rowCount, int perPage) {
41 | return (rowCount % perPage == 0) ? (rowCount / perPage) : (rowCount / perPage + 1);
42 | }
43 |
44 | /**
45 | * Parse page from String to int.
46 | *
47 | * @param pageString origin
48 | * @param defaultValue default page, if pageString == null
49 | * @return parsed page
50 | */
51 | public static int parsePage(String pageString, int defaultValue) {
52 | return parseParameter(pageString, defaultValue);
53 | }
54 |
55 | /**
56 | * Parse size of per page from String to int.
57 | *
58 | * @param perPageString origin
59 | * @param defaultValue default size of per page, if perPageString == null
60 | * @return parsed size of per page
61 | */
62 | public static int parsePerPage(String perPageString, int defaultValue) {
63 | return parsePage(perPageString, defaultValue);
64 | }
65 |
66 | /**
67 | * A helper method, parsing parameter about pagination.
68 | * If the string is null, return the default value.
69 | * If the string is not a number or the number < 1,
70 | * throw a parameter illegal exception.
71 | *
72 | * @param parameterString origin
73 | * @param defaultValue default value, if parameterString == null
74 | * @return parsed parameter
75 | */
76 | private static int parseParameter(String parameterString, int defaultValue) {
77 | if (parameterString == null) {
78 | return defaultValue;
79 | }
80 |
81 | int parameter;
82 | try {
83 | parameter = Integer.parseInt(parameterString);
84 | } catch (Exception e) {
85 | throw new ParameterIllegalException();
86 | }
87 | if (parameter < 1) {
88 | throw new ParameterIllegalException();
89 | }
90 | return parameter;
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/web/controller/BookController.java:
--------------------------------------------------------------------------------
1 | package com.shawn.web.controller;
2 |
3 | import com.shawn.constant.PageConstant;
4 | import com.shawn.constant.ResourceNameConstant;
5 | import com.shawn.model.dto.PaginatedResult;
6 | import com.shawn.model.entity.Book;
7 | import com.shawn.service.BookService;
8 | import com.shawn.util.PageUtil;
9 | import com.shawn.web.exception.ResourceNotFoundException;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.http.HttpStatus;
12 | import org.springframework.http.ResponseEntity;
13 | import org.springframework.web.bind.annotation.*;
14 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
15 |
16 | import java.net.URI;
17 |
18 | /**
19 | * @author Xiaoyue Xiao
20 | */
21 | @RestController
22 | @RequestMapping("/books")
23 | public class BookController {
24 |
25 | private BookService bookService;
26 |
27 | @Autowired
28 | public BookController(BookService bookService) {
29 | this.bookService = bookService;
30 | }
31 |
32 | @GetMapping
33 | public ResponseEntity> getBooks(@RequestParam(value = "page", required = false) String pageString,
34 | @RequestParam(value = "per_page", required = false) String perPageString) {
35 | // Parse request parameters
36 | int page = PageUtil.parsePage(pageString, PageConstant.PAGE);
37 | int perPage = PageUtil.parsePerPage(perPageString, PageConstant.PER_PAGE);
38 |
39 | return ResponseEntity
40 | .ok(new PaginatedResult()
41 | .setData(bookService.getBooksByPage(page, perPage))
42 | .setCurrentPage(page)
43 | .setTotalPage(bookService.getTotalPage(perPage)));
44 | }
45 |
46 | @GetMapping("/{bookId}")
47 | public ResponseEntity> getBookById(@PathVariable Long bookId) {
48 | return bookService
49 | .getBookById(bookId)
50 | .map(ResponseEntity::ok)
51 | .orElseThrow(() -> new ResourceNotFoundException()
52 | .setResourceName(ResourceNameConstant.BOOK)
53 | .setId(bookId));
54 | }
55 |
56 | @PostMapping
57 | public ResponseEntity> postBook(@RequestBody Book book) {
58 | bookService.saveBook(book);
59 |
60 | URI location = ServletUriComponentsBuilder
61 | .fromCurrentRequest()
62 | .path("/{id}")
63 | .buildAndExpand(book.getId())
64 | .toUri();
65 |
66 | return ResponseEntity
67 | .created(location)
68 | .body(book);
69 |
70 | }
71 |
72 | @PutMapping("/{bookId}")
73 | public ResponseEntity> putBook(@PathVariable Long bookId, @RequestBody Book book) {
74 | assertBookExist(bookId);
75 |
76 | bookService.modifyBookOnNameById(book.setId(bookId));
77 |
78 | return ResponseEntity
79 | .status(HttpStatus.OK)
80 | .body(book);
81 | }
82 |
83 | @DeleteMapping("/{bookId}")
84 | public ResponseEntity> deleteBook(@PathVariable Long bookId) {
85 | assertBookExist(bookId);
86 |
87 | bookService.deleteBookById(bookId);
88 |
89 | return ResponseEntity
90 | .noContent()
91 | .build();
92 | }
93 |
94 | /********************************** HELPER METHOD **********************************/
95 | private void assertBookExist(Long bookId) {
96 | bookService
97 | .getBookById(bookId)
98 | .orElseThrow(() -> new ResourceNotFoundException()
99 | .setResourceName(ResourceNameConstant.BOOK)
100 | .setId(bookId));
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/web/exception/ExceptionHandlerControllerAdvice.java:
--------------------------------------------------------------------------------
1 | package com.shawn.web.exception;
2 |
3 | import com.shawn.constant.ErrorCode;
4 | import com.shawn.model.dto.Error;
5 | import lombok.extern.apachecommons.CommonsLog;
6 | import org.apache.catalina.servlet4preview.http.HttpServletRequest;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.web.bind.annotation.ControllerAdvice;
10 | import org.springframework.web.bind.annotation.ExceptionHandler;
11 |
12 | /**
13 | * @author Xiaoyue Xiao
14 | */
15 | @CommonsLog
16 | @ControllerAdvice
17 | class ExceptionHandlerControllerAdvice {
18 |
19 | @ExceptionHandler(ResourceNotFoundException.class)
20 | public ResponseEntity> resourceNotFoundExceptionHandler(HttpServletRequest request, ResourceNotFoundException e) {
21 | logError(request, e);
22 |
23 | return ResponseEntity
24 | .status(HttpStatus.NOT_FOUND)
25 | .body(new Error()
26 | .setCode(ErrorCode.RESOURCE_NOT_FOUND_ERROR)
27 | .setMessage(e.getMessage()));
28 | }
29 |
30 | @ExceptionHandler(ParameterIllegalException.class)
31 | public ResponseEntity> parameterIllegalExceptionHandler(HttpServletRequest request, ParameterIllegalException e) {
32 | logError(request, e);
33 |
34 | return ResponseEntity
35 | .status(HttpStatus.BAD_REQUEST)
36 | .body(new Error()
37 | .setCode(ErrorCode.PARAMETER_ILLEGAL_ERROR)
38 | .setMessage("An invalid value was specified for one of the query parameters in the request URL."));
39 | }
40 |
41 | @ExceptionHandler(ServerInternalErrorException.class)
42 | public ResponseEntity> serverInternalErrorExceptionHandler(HttpServletRequest request, ServerInternalErrorException e) {
43 | logError(request, e);
44 |
45 | return ResponseEntity
46 | .status(HttpStatus.INTERNAL_SERVER_ERROR)
47 | .body(new Error()
48 | .setCode(ErrorCode.RESOURCE_NOT_FOUND_ERROR)
49 | .setMessage("The server encountered an internal error. Please retry the request."));
50 | }
51 |
52 | @ExceptionHandler(Exception.class)
53 | public ResponseEntity> exceptionHandler(HttpServletRequest request, Exception e) {
54 | logError(request, e);
55 |
56 | return ResponseEntity
57 | .status(HttpStatus.INTERNAL_SERVER_ERROR)
58 | .body(new Error()
59 | .setCode(ErrorCode.SERVER_INTERNAL_ERROR)
60 | .setMessage("The server met an unexpected error. Please contact administrators."));
61 | }
62 |
63 | /********************************** HELPER METHOD **********************************/
64 | private void logError(HttpServletRequest request, Exception e) {
65 | log.error("[URI: " + request.getRequestURI() + "]"
66 | + "[error: " + e.getMessage() + "]");
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/web/exception/ParameterIllegalException.java:
--------------------------------------------------------------------------------
1 | package com.shawn.web.exception;
2 |
3 | /**
4 | * @author Xiaoyue Xiao
5 | */
6 | public class ParameterIllegalException extends RuntimeException {
7 |
8 | private static final long serialVersionUID = 8197086462208138875L;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/web/exception/ResourceNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.shawn.web.exception;
2 |
3 | import lombok.Setter;
4 | import lombok.experimental.Accessors;
5 | import org.apache.commons.lang3.StringUtils;
6 |
7 | /**
8 | * @author Xiaoyue Xiao
9 | */
10 | @Accessors(chain = true)
11 | @Setter
12 | public class ResourceNotFoundException extends RuntimeException {
13 |
14 | private static final long serialVersionUID = -2565431806475335331L;
15 |
16 | private String resourceName;
17 | private Long id;
18 |
19 | @Override
20 | public String getMessage() {
21 | return StringUtils.capitalize(resourceName) + " with id " + id + " is not found.";
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/shawn/web/exception/ServerInternalErrorException.java:
--------------------------------------------------------------------------------
1 | package com.shawn.web.exception;
2 |
3 | /**
4 | * @author Xiaoyue Xiao
5 | */
6 | public class ServerInternalErrorException extends RuntimeException {
7 |
8 | private static final long serialVersionUID = -1168987160778410810L;
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | ### In-memory database ###
2 | database=hsqldb
3 | spring.datasource.schema=classpath*:db/${database}/schema.sql
4 | spring.datasource.data=classpath*:db/${database}/data.sql
5 |
6 | ### Database for MySQL ###
7 | #spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_mybatis
8 | #spring.datasource.username=root
9 | #spring.datasource.password=root
10 | #spring.datasource.driver-class-name=com.mysql.jdbc.Driver
11 |
12 | ### Server ###
13 | server.port=8080
14 |
15 | ### MyBatis ###
16 | mybatis.mapper-locations=classpath*:com/shawn/repository/mybatis/*Mapper.xml
17 | mybatis.type-aliases-package=com.shawn.model
18 |
19 | ### Logging ###
20 | # Log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF)
21 | #logging.level.root=INFO
22 | #logging.level.org.springframework=INFO
23 | #logging.level.org.springframework.web=INFO
24 | #logging.level.org.mybatis=INFO
25 | #logging.level.com.shawn=DEBUG
26 | # File output
27 | project.name=SpringBoot-Mybatis
28 | logging.file=/${project.name}/logs/SpringBoot-Mybatis.log
--------------------------------------------------------------------------------
/src/main/resources/com/shawn/repository/mybatis/BookMapper.xml:
--------------------------------------------------------------------------------
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 | INSERT INTO
28 | book (book_store_id, name, author, price, topic, publish_date)
29 | VALUES
30 | (#{bookStoreId}, #{name}, #{author}, #{price}, #{topic}, #{publishDate})
31 |
32 |
33 |
34 | UPDATE
35 | book
36 | SET
37 | name = #{name}
38 | WHERE
39 | id = #{id}
40 |
41 |
42 |
43 | DELETE FROM
44 | book
45 | WHERE
46 | id = #{id}
47 |
48 |
49 |
57 |
58 |
66 |
67 |
75 |
76 |
82 |
83 |
90 |
91 |
104 |
105 |
111 |
112 |
--------------------------------------------------------------------------------
/src/main/resources/com/shawn/repository/mybatis/BookStoreMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
26 |
27 |
33 |
34 |
50 |
51 |
--------------------------------------------------------------------------------
/src/main/resources/com/shawn/repository/mybatis/UserMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | INSERT INTO
13 | user (id, username, password)
14 | VALUES
15 | (#{id}, #{username}, #{password})
16 |
17 |
18 |
19 | UPDATE
20 | user
21 | SET
22 | name = #{name}
23 | WHERE
24 | id = #{id}
25 |
26 |
27 |
28 | DELETE FROM
29 | user
30 | WHERE
31 | id = #{id}
32 |
33 |
34 |
42 |
43 |
51 |
52 |
58 |
59 |
--------------------------------------------------------------------------------
/src/main/resources/db/hsqldb/data.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO book_store VALUES ('1', '新华书店', '湖北省武汉市洪山区文秀街131号');
2 |
3 | INSERT INTO book VALUES ('1', '1', '社会研究方法教程', '袁方', '68.00', '社会学', '2015-03-01');
4 | INSERT INTO book VALUES ('2', '1', '算法', '高德纳', '108.00', '数据结构', '2014-02-13');
5 | INSERT INTO book VALUES ('3', '1', 'Java核心技术Ⅰ', 'Cay', '93.00', '编程语言', '2011-06-14');
6 | INSERT INTO book VALUES ('4', '1', '现代操作系统', 'William', '56.50', '操作系统', '2016-08-23');
7 | INSERT INTO book VALUES ('5', '1', 'Head First设计模式', 'Freeman', '32.00', '设计模式', '2013-10-15');
8 | INSERT INTO book VALUES ('6', '1', '学习OpenCV', 'Bradski', '46.00', '技术', '2014-02-13');
9 | INSERT INTO book VALUES ('7', '1', '小王子', '周克希', '15.00', '文学', '2008-07-13');
10 | INSERT INTO book VALUES ('8', '1', 'Effective Java', 'Bloch', '38.00', '编程语言', '2014-12-03');
11 | INSERT INTO book VALUES ('9', '1', '编程珠玑', 'Jon', '36.00', '数据结构', '2013-12-03');
12 | INSERT INTO book VALUES ('10', '1', 'SQL必知必会', 'Ben', '13.00', '数据库', '2015-08-26');
13 | INSERT INTO book VALUES ('11', '1', '编译器设计', 'Kelth', '59.00', '编译器', '2014-08-13');
14 |
15 | INSERT INTO user VALUES ('1', 'shawn', 'fucksecurity');
16 |
17 | commit;
--------------------------------------------------------------------------------
/src/main/resources/db/hsqldb/schema.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE book IF EXISTS;
2 | DROP TABLE book_store IF EXISTS;
3 | DROP TABLE user IF EXISTS;
4 |
5 | CREATE TABLE book (
6 | id BIGINT IDENTITY PRIMARY KEY,
7 | book_store_id BIGINT,
8 | name VARCHAR(80),
9 | author VARCHAR(80),
10 | price DECIMAL(10,2),
11 | topic VARCHAR(80),
12 | publish_date DATE
13 | );
14 |
15 | CREATE TABLE book_store (
16 | id BIGINT IDENTITY PRIMARY KEY,
17 | name VARCHAR(80),
18 | address VARCHAR(80)
19 | );
20 |
21 | CREATE TABLE user (
22 | id BIGINT IDENTITY PRIMARY KEY,
23 | username VARCHAR(80),
24 | password VARCHAR(80)
25 | );
--------------------------------------------------------------------------------