├── .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 | ![Three Layer architecture](pic/three_layer.png) 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 | ![协议的流程](pic/protocol_flow.png) 373 | 374 | OAuth 2.0 有4种授权方式,分别是:授权码模式(authorization code),简化模式(implicit),密码模式(resource owner password credentials)和客户端模式(client credentials),本项目只采用密码模式。因此,基于上述流程以及密码授权模式,本项目做出了相应的定制,如下图: 375 | 376 | ![定制的流程](pic/custom_flow.png) 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 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 | ); --------------------------------------------------------------------------------