├── settings.gradle ├── src └── main │ ├── resources │ ├── application.yml │ ├── templates │ │ ├── book │ │ │ └── list.ftl │ │ └── error.ftl │ └── import.sql │ └── groovy │ └── com │ └── esay │ └── springboot │ └── bms │ ├── service │ ├── BookService.groovy │ └── BookServiceImpl.groovy │ ├── domain │ └── Book.groovy │ ├── Application.groovy │ ├── advice │ └── ExceptionHandlerAdvice.groovy │ ├── mapper │ └── BookMapper.java │ ├── config │ └── MybatisConfig.groovy │ └── controller │ └── BookController.groovy └── README.md /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'bms' 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:mysql://localhost:3306/bms?serverTimezone=UTC&useSSL=false 4 | username: root 5 | password: root 6 | driver-class-name: com.mysql.cj.jdbc.Driver 7 | 8 | 9 | server: 10 | port: 8009 11 | 12 | message: Hello, BMS! 13 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/service/BookService.groovy: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms.service 2 | 3 | import com.esay.springboot.bms.domain.Book 4 | 5 | /** 6 | * Created by jack on 2017/4/15. 7 | */ 8 | interface BookService { 9 | List findByState(String state) 10 | 11 | List findAll() 12 | 13 | Book insert(Book book) 14 | } 15 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/domain/Book.groovy: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms.domain 2 | /** 3 | * Created by jack on 2017/4/15. 4 | */ 5 | class Book { 6 | Long id; 7 | String name; 8 | String isbn; 9 | String author; 10 | String press; 11 | // Date in_date; 12 | // Date out_date; 13 | Date inDate; 14 | Date outDate; 15 | String state; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/book/list.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | <#list books as book> 7 |

8 |
  • 书名: ${book.name}
  • 9 |
  • 作者: ${book.author}
  • 10 |
  • 出版社: ${book.press}
  • 11 |
  • 借出时间: ${book.outDate?string('yyyy/MM/dd HH:mm:ss')}
  • 12 |
  • 还书时间: ${book.inDate?string('yyyy/MM/dd HH:mm:ss')}
  • 13 |
  • 状态: ${book.state}
  • 14 | 15 |
    16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `bms`.`book` ( 2 | `id` INT NOT NULL AUTO_INCREMENT, 3 | `name` VARCHAR(100) NOT NULL, 4 | `isbn` VARCHAR(100) NOT NULL, 5 | `author` VARCHAR(45) NOT NULL, 6 | `press` VARCHAR(45) NOT NULL, 7 | `in_date` DATETIME NULL, 8 | `out_date` DATETIME NULL, 9 | `state` VARCHAR(45) NULL, 10 | PRIMARY KEY (`id`)); 11 | 12 | INSERT INTO `bms`.`book` (`name`, `isbn`, `author`, `press`) VALUES ('极简SpringBoot教程', '88888888', '陈光剑', '电子工业出版社'); 13 | 14 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/Application.groovy: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms 2 | 3 | import org.mybatis.spring.annotation.MapperScan 4 | import org.springframework.boot.SpringApplication 5 | import org.springframework.boot.autoconfigure.SpringBootApplication 6 | 7 | /** 8 | * Created by jack on 2017/4/15. 9 | */ 10 | @SpringBootApplication 11 | @MapperScan('com.esay.springboot.bms.mapper') 12 | class Application { 13 | static void main(String[] args) { 14 | SpringApplication.run(Application.class, args) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/service/BookServiceImpl.groovy: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms.service 2 | 3 | import com.esay.springboot.bms.domain.Book 4 | import com.esay.springboot.bms.mapper.BookMapper 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.stereotype.Service 7 | 8 | /** 9 | * Created by jack on 2017/4/15. 10 | */ 11 | @Service 12 | class BookServiceImpl implements BookService { 13 | @Autowired 14 | BookMapper bookMapper 15 | 16 | List findByState(String state) { 17 | bookMapper.findByState(state) 18 | } 19 | 20 | 21 | List findAll() { 22 | bookMapper.findAll() 23 | } 24 | 25 | Book insert(Book book) { 26 | bookMapper.insert(book) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/advice/ExceptionHandlerAdvice.groovy: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms.advice 2 | 3 | import org.springframework.web.bind.annotation.ControllerAdvice 4 | import org.springframework.web.bind.annotation.ExceptionHandler 5 | import org.springframework.web.servlet.ModelAndView 6 | 7 | /** 8 | * Created by jack on 2017/4/16. 9 | */ 10 | @ControllerAdvice 11 | class ExceptionHandlerAdvice { 12 | 13 | //表示捕捉到所有的异常,你也可以捕捉一个你自定义的异常 14 | @ExceptionHandler(value = Exception.class) 15 | ModelAndView exception(Exception exception) { 16 | ModelAndView modelAndView = ModelAndView("error")//error页面 17 | modelAndView.addObject("errorMessage", exception.message) 18 | modelAndView.addObject("stackTrace", exception.stackTrace) 19 | 20 | modelAndView 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/mapper/BookMapper.java: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms.mapper; 2 | 3 | import java.util.List; 4 | 5 | import com.esay.springboot.bms.domain.Book; 6 | import org.apache.ibatis.annotations.Insert; 7 | import org.apache.ibatis.annotations.Mapper; 8 | import org.apache.ibatis.annotations.Options; 9 | import org.apache.ibatis.annotations.Param; 10 | import org.apache.ibatis.annotations.Select; 11 | 12 | /** 13 | * Created by jack on 2017/4/15. 14 | */ 15 | @Mapper 16 | public interface BookMapper { 17 | @Select("select * from book where state = #{state}") 18 | List findByState(@Param("state") String state); 19 | 20 | @Select("select * from book") 21 | List findAll(); 22 | 23 | @Insert({ 24 | "insert into book", 25 | "set name = #{b.name},", 26 | "author = #{b.author},", 27 | "isbn = #{b.isbn},", 28 | "inDate = #{b.inDate},", 29 | "outDate = #{b.outDate},", 30 | "press = #{b.press},", 31 | "state = #{b.state}" 32 | }) 33 | @Options(useGeneratedKeys = true, keyProperty = "id") 34 | //使用@Options注解的userGeneratedKeys 和keyProperty属性让数据库产生auto_increment(自增长)列的值,然后将生成的值设置到输入参数对象的属性中。 35 | Book insert(@Param("b") Book book) throws RuntimeException; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/config/MybatisConfig.groovy: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms.config 2 | 3 | import org.mybatis.spring.boot.autoconfigure.MybatisProperties 4 | import org.springframework.context.annotation.Bean 5 | import org.springframework.context.annotation.Configuration 6 | import org.springframework.context.annotation.Primary 7 | 8 | /** 9 | * Created by jack on 2017/4/16. 10 | */ 11 | @Configuration 12 | class MybatisConfig { 13 | @Bean 14 | @Primary 15 | MybatisProperties mybatisProperties() { 16 | MybatisProperties p = new MybatisProperties() 17 | org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration() 18 | // 开启mybatis开启数据库字段自动映射驼峰命名规则java属性 19 | config.mapUnderscoreToCamelCase = true 20 | p.configuration = config 21 | 22 | p 23 | 24 | } 25 | } 26 | 27 | /** 28 | Field properties in org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration required a single bean, but 2 were found: 29 | - mybatisProperties: defined by method 'mybatisProperties' in class path resource [com/esay/springboot/bms/config/MybatisConfig.class] 30 | - mybatis-org.mybatis.spring.boot.autoconfigure.MybatisProperties: defined in null 31 | 32 | 33 | Action: 34 | 35 | Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed 36 | */ 37 | -------------------------------------------------------------------------------- /src/main/groovy/com/esay/springboot/bms/controller/BookController.groovy: -------------------------------------------------------------------------------- 1 | package com.esay.springboot.bms.controller 2 | 3 | import com.alibaba.fastjson.JSON 4 | import com.alibaba.fastjson.serializer.SerializerFeature 5 | import com.esay.springboot.bms.domain.Book 6 | import com.esay.springboot.bms.service.BookService 7 | import groovy.json.JsonOutput 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.stereotype.Controller 10 | import org.springframework.ui.Model 11 | import org.springframework.util.StringUtils 12 | import org.springframework.web.bind.annotation.GetMapping 13 | import org.springframework.web.bind.annotation.RequestParam 14 | import org.springframework.web.bind.annotation.ResponseBody 15 | 16 | /** 17 | * Created by jack on 2017/4/15. 18 | */ 19 | @Controller 20 | class BookController { 21 | 22 | @Autowired 23 | BookService bookService; 24 | 25 | @GetMapping("/book") 26 | @ResponseBody 27 | List findByState(@RequestParam(value = "state", required = false) String state) { 28 | if (StringUtils.isEmpty(state)) { 29 | List all = bookService.findAll() 30 | println(JSON.toJSONString(all,SerializerFeature.PrettyFormat,SerializerFeature.WriteMapNullValue)) 31 | // println(new JsonOutput().toJson(all)) 32 | bookService.findAll() 33 | } else { 34 | bookService.findByState(state) 35 | } 36 | } 37 | 38 | @GetMapping("/bookPage") 39 | String findAll(Model model) { 40 | List books = bookService.findAll() 41 | model.addAttribute("books", books) 42 | "book/list" 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

    系统异常统一处理

    5 |

    ${errorMessage}

    6 |

    Debug

    7 | 8 | Google 10 | 11 | Baidu 12 | 13 | StackOverFlow 15 | 16 |

    异常堆栈跟踪日志StackTrace

    17 | 18 | <#list stackTrace as st> 19 | ${st} 20 | 21 | 22 | 23 | 24 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 第8_章: Spring Boot集成Groovy混合Java开发 2 | ==== 3 | 4 | 本章我们使用SpringBoot集成Groovy混合Java开发一个极简的RestAPI。 数据库使用mysql,ORM层使用mybatis,模板引擎使用freemarker,构建工具使用Gradle。 5 | 6 | 关于Groovy语言,我们在上一章已经简单介绍了。本章就不再多说。 7 | 8 | 9 | 10 | ##新建Gradle工程,配置build.gradle依赖 11 | 12 | 13 | ![](http://upload-images.jianshu.io/upload_images/1233356-2edee6ca1631b302.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 14 | 15 | 16 | 17 | ![](http://upload-images.jianshu.io/upload_images/1233356-5c3bf7c2c183c98b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 18 | 19 | 我们得到一个标准的gradle工程,目录如下: 20 | 21 | 22 | ![](http://upload-images.jianshu.io/upload_images/1233356-b27cee962c038487.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 23 | 24 | 25 | 由于我们勾选了Groovy支持,gradle依赖如下: 26 | 27 | ``` 28 | group 'com.easy.springboot' 29 | version '1.0-SNAPSHOT' 30 | 31 | apply plugin: 'groovy' 32 | apply plugin: 'java' 33 | 34 | sourceCompatibility = 1.8 35 | 36 | repositories { 37 | mavenCentral() 38 | } 39 | 40 | dependencies { 41 | compile 'org.codehaus.groovy:groovy-all:2.3.11' 42 | testCompile group: 'junit', name: 'junit', version: '4.11' 43 | testCompile group: 'junit', name: 'junit', version: '4.12' 44 | } 45 | 46 | ``` 47 | 48 | 49 | ##添加SpringBoot依赖 50 | 51 | 52 | boot-plugin 53 | 54 | 55 | ``` 56 | apply plugin: 'org.springframework.boot' 57 | ``` 58 | 59 | freemarker-starter 60 | 61 | 62 | ``` 63 | compile('org.springframework.boot:spring-boot-starter-web') 64 | compile('org.springframework.boot:spring-boot-starter-freemarker') 65 | ``` 66 | mybatis-spring-boot-starter 67 | 68 | ``` 69 | compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1') 70 | ``` 71 | 72 | mysql jdbc驱动 73 | 74 | 75 | ``` 76 | compile('mysql:mysql-connector-java:6.0.5') 77 | ``` 78 | 79 | 构建脚本 80 | 81 | ``` 82 | buildscript { 83 | ext { 84 | springBootVersion = '1.5.2.RELEASE' 85 | } 86 | repositories { 87 | mavenCentral() 88 | } 89 | dependencies { 90 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 91 | } 92 | } 93 | ``` 94 | 95 | 我们可以看出,在构建脚本里,dependencies里面依赖了spring-boot-gradle-plugin,其版本是我们使用的SpringBoot的版本。 96 | 97 | SpringBoot Gradle 插件是SpringBoot针对 Gradle定制的工具, 可以帮助我们打包(jar,war),运行Spring Boot 应用,进行依赖管理等。 98 | 99 | ##配置数据库DataSource 100 | 101 | 创建application.yml文件,配置数据库信息: 102 | 103 | ``` 104 | spring: 105 | datasource: 106 | url: jdbc:mysql://localhost:3306/bms?serverTimezone=UTC&useSSL=false 107 | username: root 108 | password: root 109 | driver-class-name: com.mysql.cj.jdbc.Driver 110 | ``` 111 | 112 | ##写领域模型类 113 | 114 | ``` 115 | package com.esay.springboot.bms.domain 116 | /** 117 | * Created by jack on 2017/4/15. 118 | */ 119 | class Book { 120 | Long id; 121 | String name; 122 | String isbn; 123 | String author; 124 | String press; 125 | // Date in_date; 126 | // Date out_date; 127 | Date inDate; 128 | Date outDate; 129 | String state; 130 | } 131 | 132 | ``` 133 | 134 | 我们以前使用mybatis开启数据库字段自动映射驼峰命名规则java属性,是通过下面的xml配置: 135 | ``` 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | ``` 144 | 145 | 对应的,我们使用注解的方式 146 | 147 | ``` 148 | @Configuration 149 | class MybatisConfig { 150 | @Bean 151 | @Primary 152 | MybatisProperties mybatisProperties() { 153 | MybatisProperties p = new MybatisProperties() 154 | org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration() 155 | // 开启mybatis开启数据库字段自动映射驼峰命名规则java属性 156 | config.mapUnderscoreToCamelCase = true 157 | p.configuration = config 158 | 159 | p 160 | 161 | } 162 | } 163 | ``` 164 | 165 | 其中,@Primary注解的功能:当自动装配Bean时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常。 166 | 167 | 如果不标记,会报如下错误: 168 | 169 | ``` 170 | Field properties in org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration required a single bean, but 2 were found: 171 | - mybatisProperties: defined by method 'mybatisProperties' in class path resource [com/esay/springboot/bms/config/MybatisConfig.class] 172 | - mybatis-org.mybatis.spring.boot.autoconfigure.MybatisProperties: defined in null 173 | 174 | 175 | Action: 176 | 177 | Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed 178 | ``` 179 | 180 | 通过这个错误日志,我们可以更直观的看出@Primary注解的功能。 181 | 182 | ##Mapper层代码 183 | 184 | ``` 185 | package com.esay.springboot.bms.mapper; 186 | 187 | import java.util.List; 188 | 189 | import com.esay.springboot.bms.domain.Book; 190 | import org.apache.ibatis.annotations.Insert; 191 | import org.apache.ibatis.annotations.Mapper; 192 | import org.apache.ibatis.annotations.Options; 193 | import org.apache.ibatis.annotations.Param; 194 | import org.apache.ibatis.annotations.Select; 195 | 196 | /** 197 | * Created by jack on 2017/4/15. 198 | */ 199 | @Mapper 200 | public interface BookMapper { 201 | @Select("select * from book where state = #{state}") 202 | List findByState(@Param("state") String state); 203 | 204 | @Select("select * from book") 205 | List findAll(); 206 | 207 | @Insert({ 208 | "insert into book", 209 | "set name = #{b.name},", 210 | "author = #{b.author},", 211 | "isbn = #{b.isbn},", 212 | "inDate = #{b.inDate},", 213 | "outDate = #{b.outDate},", 214 | "press = #{b.press},", 215 | "state = #{b.state}" 216 | }) 217 | @Options(useGeneratedKeys = true, keyProperty = "id") 218 | //使用@Options注解的userGeneratedKeys 和keyProperty属性让数据库产生auto_increment(自增长)列的值,然后将生成的值设置到输入参数对象的属性中。 219 | Book insert(@Param("b") Book book) throws RuntimeException; 220 | 221 | } 222 | 223 | ``` 224 | 225 | 226 | ##写控制器Controller层 227 | ``` 228 | package com.esay.springboot.bms.controller 229 | 230 | import com.alibaba.fastjson.JSON 231 | import com.alibaba.fastjson.serializer.SerializerFeature 232 | import com.esay.springboot.bms.domain.Book 233 | import com.esay.springboot.bms.service.BookService 234 | import groovy.json.JsonOutput 235 | import org.springframework.beans.factory.annotation.Autowired 236 | import org.springframework.stereotype.Controller 237 | import org.springframework.ui.Model 238 | import org.springframework.util.StringUtils 239 | import org.springframework.web.bind.annotation.GetMapping 240 | import org.springframework.web.bind.annotation.RequestParam 241 | import org.springframework.web.bind.annotation.ResponseBody 242 | 243 | /** 244 | * Created by jack on 2017/4/15. 245 | */ 246 | @Controller 247 | class BookController { 248 | 249 | @Autowired 250 | BookService bookService; 251 | 252 | @GetMapping("/book") 253 | @ResponseBody 254 | List findByState(@RequestParam(value = "state", required = false) String state) { 255 | if (StringUtils.isEmpty(state)) { 256 | List all = bookService.findAll() 257 | println(JSON.toJSONString(all,SerializerFeature.PrettyFormat,SerializerFeature.WriteMapNullValue)) 258 | // println(new JsonOutput().toJson(all)) 259 | bookService.findAll() 260 | } else { 261 | bookService.findByState(state) 262 | } 263 | } 264 | 265 | @GetMapping("/bookPage") 266 | String findAll(Model model) { 267 | List books = bookService.findAll() 268 | model.addAttribute("books", books) 269 | "book/list" 270 | } 271 | 272 | 273 | } 274 | 275 | ``` 276 | 277 | ##写视图View层 278 | ``` 279 | 280 | 281 | 282 |
    283 |
    284 | <#list books as book> 285 |

    286 |
  • 书名: ${book.name}
  • 287 |
  • 作者: ${book.author}
  • 288 |
  • 出版社: ${book.press}
  • 289 |
  • 借出时间: ${book.outDate?string('yyyy/MM/dd HH:mm:ss')}
  • 290 |
  • 还书时间: ${book.inDate?string('yyyy/MM/dd HH:mm:ss')}
  • 291 |
  • 状态: ${book.state}
  • 292 | 293 |
    294 | 295 | 296 | 297 | 298 | ``` 299 | 300 | 301 | Freemarker日期格式化使用: 302 | ``` 303 |
  • 借出时间: ${book.outDate?string('yyyy/MM/dd HH:mm:ss')}
  • 304 |
  • 还书时间: ${book.inDate?string('yyyy/MM/dd HH:mm:ss')}
  • 305 | ``` 306 | ##运行测试 307 | 308 | 命令行运行 309 | 310 | ``` 311 | gradle bootRun 312 | ``` 313 | 启动成功, 浏览器访问:http://localhost:8009/bookPage 314 | 你将看到类似如下页面: 315 | 316 | 317 | ![](http://upload-images.jianshu.io/upload_images/1233356-60934dd6d31fac18.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 318 | 319 | 320 | 321 | 访问Rest API接口:http://localhost:8009/book?state=NORMAL 322 | 我们可以看到如下输出: 323 | 324 | ``` 325 | [ 326 | { 327 | "id": 1, 328 | "name": "极简SpringBoot教程", 329 | "isbn": "88888888", 330 | "author": "陈光剑", 331 | "press": "电子工业出版社", 332 | "inDate": 1492299756000, 333 | "outDate": 1492299756000, 334 | "state": "NORMAL" 335 | } 336 | ] 337 | 338 | ``` 339 | 340 | #小结 341 | 342 | 343 | 本章工程源代码:https://github.com/EasySpringBoot/bms 344 | --------------------------------------------------------------------------------