原文地址:https://www.cloudcrossing.xyz/post/48/
最近在学习 Spring Boot 和 Kotlin,今天花了一点时间写了个小demo当作练手
以及感谢导师一直以来的帮助:)
1 框架介绍
这个Demo也可以说是一个轻量级服务器应用,其架构使用 Gradle + Kotlin + Spring Boot + Mybatis + MySQL。其中:
Gradle
Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化构建开源工具。它使用一种基于 Groovy 的特定领域语言 (DSL) 来声明项目设置,抛弃了基于 XML 的各种繁琐配置。支持maven, Ivy仓库,支持传递性依赖管理
Kotlin
Kotlin 是一个用于现代多平台应用的静态编程语言,由 JetBrains 开发。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。Kotlin 已正式成为 Android 官方支持开发语言
Spring Boot
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域 (rapid application development) 成为领导者。其主要特点有:
- 1.创建独立的Spring应用程序
- 2.嵌入的Tomcat,无需部署WAR文件
- 3.简化Maven配置
- 4.自动配置Spring
- 5.提供生产就绪型功能,如指标,健康检查和外部配置
- 6.绝对没有代码生成并且对XML也没有配置要求
Mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
MySQL
MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件
RESTful
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。即 URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作——@Ivony
2 项目结构及开发环境
Demo使用的是 IntelliJ IDEA 开发运行,该项目的文件结构大致如下:
├── build.gradle
├── gradle
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── java
├── kotlin
│ └── com
│ └── cloud
│ ├── MyApplication.kt
│ ├── controller
│ │ ├── UserController.kt
│ │ └── TestController.kt
│ ├── dao
│ │ └── UserRepository.kt
│ └── model
│ └── User.kt
├── resources
│ ├── application.yml
└── webapp
└── index.jsp
项目无需运行在 Tomcat,因为Spring Boot 自带了一个 Tomcat。main 函数入口在 MyApplication.kt 中,只需要双击该文件,然后点击 main 函数 左边的绿色右箭头,点击 Run 既可以运行项目

3 使用 IntelliJ IDEA 创建项目
3.1 创建 Kotlin 的 web 项目
第一步

第二步

第三步

最后 Finish 即可
3.2 配置 build.gradle
增加 Spring Boot、Mybatis、MySQL、JSP、pagehelper 的依赖
其中,pagehelper 是 MyBatis 分页插件,支持任何复杂的单表、多表分页
buildscript {
ext {
kotlin_version = '1.1.2'
springBootVersion = '2.0.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
// Kotlin
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// Spring-boot
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
}
}
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
id 'war'
}
group 'com.cloud'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.11'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
// Kotlin
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
compile("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
// Spring-boot
compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
// Mybatis
compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
// 支持jsp
compile 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.7'
// jsp的jstl表达式
compile 'javax.servlet:jstl:1.2'
// mysql
compile('mysql:mysql-connector-java:5.1.13')
// pagehelper
compile group: 'com.github.pagehelper', name: 'pagehelper', version: '4.1.0'
}
3.3 创建数据库表并配置 application.yml
首先在本地创建一个 MySQL 数据库,然后新建一个 user 表,列属性为 id int,name varchar(20)
然后配置 application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/KotlinDemo
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
3.4 创建 User 类
package com.cloud.model
class User {
var id: Int = 0
var name:String? = null
}
3.5 创建应用入口 MyApplication
package com.cloud
import com.github.pagehelper.PageHelper
import org.mybatis.spring.annotation.MapperScan
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import java.util.*
@MapperScan("com.cloud.dao")
@SpringBootApplication
open class MyApplication()
fun main(args: Array<String>) {
SpringApplication.run(MyApplication::class.java, *args)
@Bean
fun pageHelper() : PageHelper {
System.out.println("=========MyBatisConfiguration.pageHelper()");
val pageHelper = PageHelper();
val p = Properties();
p.setProperty("offsetAsPageNum", "true");
p.setProperty("rowBoundsWithCount", "true");
p.setProperty("reasonable", "true");
pageHelper.setProperties(p);
return pageHelper;
}
}
- @MapperScan 用来配置扫描该包名以下的 Dao 接口
- pageHelper() 是用来设置PageHelper插件(当然,PageHelper 的设置方法不仅仅只有这个,他的功能也不仅仅如此)
3.6 创建 UserController 类
我习惯是先写控制类,然后再写 Dao 接口
PS:由于逻辑比较简单,所以省略了 Service 层,也将 SQL语句 直接以注解的形式绑定在 Dao 层
package com.cloud.controller
import com.cloud.dao.UserDao
import org.apache.ibatis.annotations.Param
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*
@RestController
class UserController {
@Autowired
lateinit var userDao: UserDao
@GetMapping("/all")
fun getAllUser() : Any {
val users = userDao.getAllUser()
return users
}
@PostMapping("/user")
fun createUser(@Param("id") id : Int, @Param("name")name : String) : Any {
userDao.createUser(id, name)
return getAllUser()
}
@DeleteMapping("user")
fun deleteUserById(@Param("id") id : Int) : Any {
userDao.deleteUserById(id)
return getAllUser()
}
@PutMapping("/user")
fun updateUserById(@Param("id") id : Int, @Param("name")name : String) : Any {
userDao.updateUserById(id, name)
return getAllUser()
}
@GetMapping("/user")
fun getUserById(id : Int) : Any {
val user = userDao.getUserById(id)
return user
}
@GetMapping("/list")
fun getUserList(@Param("currentPage") currentPage : Int, @Param("pageSize") pageSize : Int) : Any {
val currentPageNew = (currentPage - 1) * pageSize
val users = userDao.getUserList(currentPageNew, pageSize)
return users
}
}
@RestController 注解相当于 @Controller与@ResponseBody这两个注解的作用
- @RestController 注解是它继承自 @Controller 注解。4.0之前的版本,spring MVC 的组件都使用 @Controller 来标识当前类是一个控制器 servlet 。使用这个特性,我们可以开发 REST 服务的时候不需要使用 @Controller 专门的 @RestController
Spring4.3 中引进了 @GetMapping、@PostMapping、@PutMapping、@DeleteMapping 等,来帮助简化常用的 HTTP 方法的映射,并更好地表达被注解方法的语义
- 以 @GetMapping 为例,Spring官方文档说:@GetMapping 是一个组合注解,是 @RequestMapping(method = RequestMethod.GET) 的缩写。该注解将 HTTP Get 映射到特定的处理方法上
在 Mybatis 用注解来简化 xml 配置的时候, @Param 注解的作用是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入 sql 语句中
- 在方法参数的前面写上 @Param("参数名") ,表示给参数命名,名称就是括号中的内容
这里,我们在创建一个测试控制类 TestController 类,用来演示将数据返回到 jsp 页面上
package com.cloud.controller
import com.cloud.dao.UserDao
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import javax.servlet.http.HttpServletRequest
@Controller
class TestController {
@Autowired
lateinit var userDao: UserDao
@RequestMapping("/test")
fun index(request: HttpServletRequest): String {
val users = userDao.getAllUser()
request.setAttribute("users",users)
return "/index.jsp"
}
}
3.7 创建 UserDao 类
package com.cloud.dao
import com.cloud.model.User
import org.apache.ibatis.annotations.*
import org.springframework.stereotype.Repository
@Repository
interface UserDao {
@Select("select * from user")
fun getAllUser(): List<User>
@Select("select * from user where id = #{id}")
fun getUserById(@Param("id") id : Int?): User
@Insert("insert into user(id, name) values(#{id}, #{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
fun createUser(@Param("id") id : Int, @Param("name") name : String)
@Delete("delete from user where id = #{id}")
fun deleteUserById(@Param("id") id: Int)
@Update("update user set name=#{name} where id=#{id}")
fun updateUserById(@Param("id") id : Int, @Param("name") name : String)
@Select("select * from user limit #{currentPage}, #{pageSize}")
fun getUserList(@Param("currentPage") currentPage : Int, @Param("pageSize") pageSize : Int): List<User>
}
在最后一个分页查询中 getUserList() 中,我们可以看到其查询语句为:select * from user limit #{currentPage}, #{pageSize}
- 其中,limit 关键字的用法:
LIMIT [offset,] rows
。offset 指定要返回的第一行的偏移量,rows 第二个指定返回行的最大数目,且初始行的偏移量是0(不是1)
但是注意,这里的 currentPage 不是 url 中传入的 currentPage,其是经过处理的,我们可以在控制类中看到,实际上这里的 currentPage = (currentPage - 1) * pageSize
- 当你要取的是第 21 条到第 30 条的记录时,假设 pageSize 为 10,则 SQL 语句应为
select * from user limit 20, 10
,偏移量为 20 ,记录从 第 21 条开始往后数 10 条到 第30条。 - 那么这个偏移量 20 怎么来的呢?答案就是 (currentPage - 1) * pageSize。因为假设有 100 条记录,第21条到第30条的记录里所以当在第 3 页嘛。而 (currentPage - 1) 就表示将前 2 页的记录丢弃,而前 2 页的记录一共有 (currentPage - 1) * pageSize 这么多条,也即是我们查找的第 21 条到第 30 条这部分记录中第 1 条记录的偏移量啦
3.8 测试接口
创建index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<html>
<head>
<title>Demo</title>
</head>
<body>
<table border="2">
<tr>
<th>ID</th>
<th>名称</th>
</tr>
<c:forEach items="${users}" var="item" varStatus="status">
<tr>
<td>${item.id}</td>
<td>${item.name}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
在测试接口中,我们用到一个软件叫做 Postman。这是一款功能强大的网页调试与发送网页HTTP请求,它可以发送几乎所有类型的HTTP请求。界面及其使用如下:

测试 TestController 类
GET http://localhost:8080/test

测试 UserController 类

GET http://localhost:8080/list?currentPage=2&pageSize=3

其他的方法自行测试,默认增加、删除、修改之后跳转到 getAllUser()
网友评论
非常感谢~~~