0%

SpringBoot 笔记

EasyCode插件

代码生成器

  1. 根据数据库自动生成pojo实体类
  2. 自动生成对应 controller、service、dao类
  3. 自动生成mapper文件(自动编写sql语句)

生成各层代码后,完整项目看起来就像下图

记得配置 MusicApplication ,添加注解 @MapperScan("com.music.demo.dao")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.music.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.music.demo.dao")
public class MusicApplication {

public static void main(String[] args) {
SpringApplication.run(MusicApplication.class, args);
}

}

application.properties 添加如下

1
2
3
4
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.data-password=root
spring.datasource.data-username=root
spring.datasource.url=spring.datasource.url=jdbc:mysql://localhost:3306/Music?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8

Data Sources

若连接出现时区错误,则在URL 后 添加 ?serverTimezone=Asia/Shanghai

jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai

DB First 生成各层代码

利用 EasyCode

pom.xml

1
2
3
4
5
6
<!-- 导入mybatis的依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>

Controller

取参

从url地址取出参数

1
2
@GetMapping("{name}/{id}")
@PathVariable("name")

从url query中取出参数

1
2
3
# url?name="123"
// 无需注解,直接按普通形参方式即可
query(String name)

Action 注解

1
2
3
@GetMapping("get")
等同于
@RequestMapping(value = "get", method = RequestMethod.GET)
1
2
3
4
5
6
7
8
@GetMapping("getrest/{name}/{id}")
public TbMusic getTest(@PathVariable("name") String name, @PathVariable("id") String id){
TbMusic rtn = new TbMusic();
rtn.setName(name);
rtn.setAlbumname("测试专辑名");

return rtn;
}

@PathVariable()

添加上此针对形参的注解后,来自PC和移动App都将接收匹配,而如果不加此注解(即普通方法),那么只有PC能匹配

@PathVariable() 类似 ASP.NET Core 中的 Action 注解

类比 ASP.NET Core

template 为路由规则,比如 \{:name}\{:id}

TODO: ASP.NET Core路由规则中参数有没有 : 不确定

无视下图的 [HttpGet("")],只为后图演示,其实不能这么写

建议多用 Integer 而不是 int

int 存在空指针异常,使用 包装类 Integer 即可避免

1
2
// 参数来自请求体,必须使用 json格式
@PostMapping()
1
2
3
4
@PostMapping("delete")
public String insert(@RequestBody TbMusic inputModel){
return inputModel.getName();
}

从这里来看 @RequestBody 完全就是 ASP.NET Core 中 [FromBody]

Controller 中 Action 找 视图View

1
2
3
4
@RequestMapping("login")
public String login() {
return "login";
}

注意:此处 Controller 为 @RequestMapping("tbMusic")

此时 http://localhost:8080/tbMusic/login 找到了 视图 templates/login.html

这里和 ASP.NET Core 默认找视图顺序不同

ASP.NET Core

return View("login");

应当首先去匹配当前Controller 对应文件夹下 login.cshtml

默认第一个视图引擎的工作:RazorViewEngine,它维护了一个匹配路由规则的列表

ASP.NET Core 中其实是无需注解路由的,因为这样和Controller类名 ,Action 方法名,默认匹配路由的规则已经被框架AddRoute(),添加默认路由规则所应用

默认路由规则 : {controllerName}/{actionName}/{:id}

TODO: 好像加上 :代表此参数可空,忘了,待查

post 实例

1
2
3
4
5
6
7
8
@PostMapping("post")
public TbMusic post(@RequestBody TbMusic inputModel){
// 注意:传json时,属性名大小写敏感,应对应 entity名,而不是数据库字段名,是 albumname 而不是 albumName
// ASP.NET Core 中默认模型绑定 对 属性名大小写不敏感,至少对于驼峰命名法,会自动识别
inputModel = tbMusicService.insert(inputModel);

return inputModel;
}

ASP.NET Core 中尽管有 [FromBody] ,但不是必要的,这是因为框架认为

一个 [ApiController] 就应如此,从请求体获取

Java 注解

@xxx()

C# 注解

[xxx()]

xxxAttribute : Attribute

xxxxAttribute 只是约定,不强制,若以 Attribute 结尾,则无需写最后的Attribute,VS会自动识别

若无需传参,则直接 [xxxx]

1
2
3
4
5
6
7
8
9
{
"name": "哆啦A梦",
"albumname": "专辑名",
"albumpic": "专辑图片地址",
"mp3url": "音乐地址",
"artistname": "歌者",
"mvurl": "mv链接",
"lrcurl": "歌词链接"
}

启动类配置扫描dao

1
2
3
4
5
6
7
8
9
@SpringBootApplication
//扫描dao层 让mybatis框架接管到层
@MapperScan("com.qf.music.dao")
public class MusicApplication {

public static void main(String[] args) {
SpringApplication.run(MusicApplication.class, args);
}
}

mapper 文件 (xxxDao.xml)

配置扫描mapper⽂件,修改application.properties⽂件

1
2
3
4
5
6
7
8
9
10
11
# 配置数据库连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/Music?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456

# 扫描mapper⽂件让dao层和mapper⽂件进⾏关联
mybatis.mapper-locations=classpath:mapper/*.xml

# 数据库连接池配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

select resultMap namespace 详解

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
<!--命名空间-->
<mapper namespace="com.qf.music.dao.TbMusicDao">
<!-- 解决数据库字段和实体类字段不⼀样产⽣的映射问题-->
<resultMap type="com.qf.music.entity.TbMusic" id="TbMusicMap">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="albumname" column="albumName" jdbcType="VARCHAR"/>
<result property="albumpic" column="albumPic" jdbcType="VARCHAR"/>
<result property="mp3url" column="mp3url" jdbcType="VARCHAR"/>
<result property="artistname" column="artistName" jdbcType="VARCHAR"/>
<result property="mvurl" column="mvurl" jdbcType="VARCHAR"/>
<result property="lrcurl" column="lrcurl" jdbcType="VARCHAR"/>
</resultMap>
<!--
查询单个 id必须唯⼀ 和dao中的函数名关联
parameterType="" 表示约束传⼊参数的类型--如果参数类型过多,可以不⽤写
resultType="" 表示返回值的类型(必须是实体类和数据库字段⼀致的情况下使⽤)
#{}接受传⼊的参数
#号防⽌sql注⼊-->
<select id="queryById" resultMap="TbMusicMap">
select
id, name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl
from Music.tb_music
where id = #{id}
</select>
1
2
3
4
5
6
<select id="queryById" resultMap="TbMusicMap">
select
id, name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl
from music.tb_music
where id = #{id}
</select>

#{id}

防止注入

keyProperty="id" useGeneratedKeys="true"

1
2
3
4
5
<!--新增所有列-->
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into music.tb_music(name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl)
values (#{name}, #{albumname}, #{albumpic}, #{mp3url}, #{artistname}, #{mvurl}, #{lrcurl})
</insert>

数据库 id 是 自增类型,

映射到 entity 的 id

这样插入时无需赋值id,当插入后,框架会将插入后数据库此行id带回来赋值给原对象.id,这样你就可以继续使用此对象获取到id,

和 EF中的状态跟踪类似,也是带回id,赋值给原对象,其实EF中就是每条SQL中跟上了一句取最新操作得到的行

MS SQLServer

insert into temp value();select @@IDENTITY;

dao传多个参数

若仅有一参数,就可以不加 @Param()

1
2
// xxDao
interface TbMusicDao
1
2
// 注意:当参数大于等于2个时,一定要加上 @Param("xxx"),这样在 dao.xml中才能通过名字识别到 xxx,并赋予传过来的对应值
TbMusic queryById(@Param("id") Integer id, @Param("name") String name)
1
2
3
4
5
6
<select id="queryById" resultMap="TbMusicMap">
select
id, name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl
from music.tb_music
where id = #{id} and name = #{name}
</select>
1
2
3
4
5
6
7
8
/** 
* 查询指定⾏数据
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return 对象列表
*/
List<TbUser> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);

程序启动需要导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 添加了mybatis的依赖--> 
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

属性注入服务

1
2
@Autowired
private TbMusicService tbMusicService;
1
2
@Resource
private TbMusicService tbMusicService;

Q: @Autowired@Resource 有何区别?

A:

@Autowired

根据类型进行搜索,注入

@Resource

根据名称进行搜索,注入

@Autowired 自动装配

Mybatis 的动态SQL

MyBatis的映射⽂件中⽀持在基础SQL上添加⼀些逻辑操作,并动态拼接成完整的SQL之后再执 ⾏,以达到SQL复⽤、简化编程 的效果。

1. SQL 片段

将一些经常使用的定义成一个片段,要使用的地方直接引用此片段

1
2
3
4
5
6
7
8
9
<mapper namespace="com.qf.mybatis.part2.dynamic.BookDao"> 
<sql id="BOOKS_FIELD"> <!-- 定义SQL⽚段 -->
SELECT id,name,author,publish,sort
</sql>
<select id="selectBookByCondition" resultType="com.qf.mybatis.part2.dynamic.Book">
<include refid="BOOKS_FIELD" /> <!-- 通过ID引⽤SQL⽚段 -->
FROM t_books
</select>
</mapper>

2. <where>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!--通过实体作为筛选条件查询-->
<select id="queryAll" resultMap="TbMusicMap">
select
id, name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl
from music.tb_music
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="albumname != null and albumname != ''">
and albumName = #{albumname}
</if>
<if test="albumpic != null and albumpic != ''">
and albumPic = #{albumpic}
</if>
<if test="mp3url != null and mp3url != ''">
and mp3url = #{mp3url}
</if>
<if test="artistname != null and artistname != ''">
and artistName = #{artistname}
</if>
<if test="mvurl != null and mvurl != ''">
and mvurl = #{mvurl}
</if>
<if test="lrcurl != null and lrcurl != ''">
and lrcurl = #{lrcurl}
</if>
</where>
</select>

是为解决 拼接SQL where条件语句时,由于参数可能存在根据条件有无 ,而出现的 and、or 关键词拼接时的错误

块 会根据内容自动判断是否添加 where,

  1. 若if一个都未成立,最后就是没有条件,那么无 where
  2. 若成立一个if 等情况,而其前无if成立,即 and artistName = #{artistname} 情况出现,那么去掉前面的 and

3. <set>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!--通过主键修改数据-->
<update id="update">
update music.tb_music
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="albumname != null and albumname != ''">
albumName = #{albumname},
</if>
<if test="albumpic != null and albumpic != ''">
albumPic = #{albumpic},
</if>
<if test="mp3url != null and mp3url != ''">
mp3url = #{mp3url},
</if>
<if test="artistname != null and artistname != ''">
artistName = #{artistname},
</if>
<if test="mvurl != null and mvurl != ''">
mvurl = #{mvurl},
</if>
<if test="lrcurl != null and lrcurl != ''">
lrcurl = #{lrcurl},
</if>
</set>
where id = #{id}
</update>

4. <foreach>

1
2
3
4
5
6
7
8
<insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">
insert into music.tb_music(name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl)
values
<foreach collection="entities" item="entity" separator=",">
(#{entity.name}, #{entity.albumname}, #{entity.albumpic}, #{entity.mp3url}, #{entity.artistname},
#{entity.mvurl}, #{entity.lrcurl})
</foreach>
</insert>

5. 示例:like 模糊查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="queryByIdAndNameAndArtistName" resultMap="TbMusicMap">
select
id, name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl
from music.tb_music
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="name != null and name != ''">
and name like CONCAT('%',#{name},'%')
</if>
<if test="artistname != null and artistname != ''">
and artistName like CONCAT('%',#{artistname},'%')
</if>
</where>
</select>

注意:

name != null and name != ''

中间是 and 不是 && ,说明这段不是由Java直接执行,而是框架内部自己做了解析,!= null 既不是SQL工作,and 又不是本身Java片段代码执行

热部署

设置:IDEA开启 自动 Build project

全局:对于新建项目

局部:当前项目

pom.xml 依赖

1
2
3
4
5
6
7
<!-- 热部署工具包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

IDEA 开启 compiler.automake.xxx

Ctrl + Shift + Alt + /

Thymeleaf 模板引擎

  1. 若需使用 Thymeleaf 模板,所有页面必须经过 SpringMVC 视图解析器解析

  2. 使用 thymeleaf 需导入对应依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  1. 给相应页面添加 thymeleaf 的访问地址
1
<html xmlns:th="http://www.thymeleaf.org">
  1. thymeleaf 获取变量
1
2
// ognl 表达式
${}

注意:

jsp内

1
2
// el表达式
${}
1
<p th:text="${session.user.username}" style="display: inline-block">靓仔</p>
  1. thymeleaf 设置路径
1
2
3
<link rel="stylesheet" href="../static/layui/css/layui.css" th:href="@{/layui/css/layui.css}">

<script type="text/javascript" src="../static/layui/layui.js" th:src="@{/layui/layui.js}"></script>

注意:一定要加 / ,表示根路径

其实 thymeleaf 的工作就是一个模板引擎,就是一个替换html模板中申明的变量,替换为从后端传过来的变量值

如果

th:href="@{/layui/css/layui.css}" 前不加 / ,则会从当前路径接上url,于是,若当前处于 http://localhost:8080/home/index1,则接上后变为:http://localhost:8080/home/layui/css/layui.css

注意,去掉最近index1,视 http://localhost:8080/home/ 基url

而 /layui/css/layui.css ,则一定是web根域

  1. thymeleaf 的 each 循环
1
2
3
4
5
<div th:each="music : ${session.musics}">
<p th:text="${music.name}">歌曲名称</p>
<p th:text="${music.albumname}">专辑名称</p>
<img src="" th:src="@{${music.albumpic}}" />
</div>

url地址必须 在 @{} 内

  1. thymeleaf 的 if 判断
1
2
3
4
5
6
<div th:each="music : ${session.musics}">
<div th:if="${music.name} != '小碗面'">
<p th:text="${music.name}">歌曲名称</p>
</div>
<p th:text="${music.albumname + ' - 专辑'}">专辑名称</p>
</div>

Q&A

Q: @Controller@RestController 的区别?

A: @Controller 会将方法返回值类型为String 的解析为一个路径(视图路径),这是由于SpringMvc 的原因,(拦截解析为视图路径)

在方法上注解 @ResponseBody 将返回的数据转换成 json 格式数据

当直接在controller 类上注解 @RestController 就相当于ASP.NET WebAPI 中的 ApiController ,框架认为你将使用json风格数据,使用Restful API 风格

其实与 ASP.NET MVC 与 ASP.NET WebAPI 普通 Controller 与 ApiController 的区别 类似

Q: 无法连接数据库

1
java.sql.SQLException: The server time zone value '中国标准时间' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the 'serverTimezone' configuration property) to use a more specific time zone value if you want to utilize time zone support.

A:

添加 serverTimezone=Asia/Shanghai

1
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/music?serverTimezone=Asia/Shanghai&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8

补充

各层之间用接口进行隔离,采用 依赖注入 注入对应实现

xxxDao 接口的 实现 是 mapper/xxxDao.xml

MySQL: limit n 限制条数n

MS SQLServer: top n

快键键

1
2
3
4
5
6
sout
System.out.println();

C# VS 中
cl
Console.WriteLine()
1
2
iter
for(Object o : a) {}
1
2
fori
for(int i = 0; i < a.size(); i++)
1
2
3
IDEA
View -> Project
Alt + 1
1
2
3
4
// psvm
public static void main(String[] args) {

}

Ctrl + F9 强制重启,重新编译

代码 Region 折叠块

1
2
3
4
5
6
7
8
9
10
代码 Region 折叠块

VS
#region
#endRegion

IDEA
//region
//endregion
快捷键:Ctrl+Alt+T

SQL日志

1
2
# 配置sql日志信息
logging.level.com.qf.music.dao=debug

时间格式

1
2
3
4
5
// 前端传后端 约束
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")

// 后端向前端
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")

配置Maven环境变量

安装jar包到Maven仓库

1
mvn install:install-file -DgroupId=it.source -DartifactId=ValidateCode -Dpackaging=jar -Dversion=1.0 -Dfile=F:\ValidateCode-1.0.jar

注意:不知道为什么,cmd成功,powershell失败

安装成功后,即可在 pom.xml 增加jar包依赖

注意:groupId, artifactId 对应

1
2
3
4
5
<dependency> 
<groupId>it.source</groupId>
<artifactId>ValidateCode</artifactId>
<version>1.0</version>
</dependency>

动态SQL仅一参数也必加@Param()

1
2
3
4
5
6
/**
* 动态SQL必加标识符@Param(), 就算只有一个参数
* @param value
* @return
*/
List<TbMusic> queryLike(@Param("value") String value);
1
2
3
4
5
6
7
8
9
10
11
12
<select id="queryLike" resultMap="TbMusicMap">
select
id, name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl
from music.tb_music
<where>
<if test="value != null and value != ''">
name like CONCAT('%',#{value},'%')
or artistName like CONCAT('%',#{value},'%')
or albumName like CONCAT('%',#{value},'%')
</if>
</where>
</select>

动态SQL 指的是需要使用 <if></if> 等这种标签(使得SQL语句可变),在这种标签内需要引用参数,引用参数使用 @Param("name") 中设置的name

而如果仅传一个参数,也不需要动态SQL,则直接使用 #{value} 引用此参数,也不需要 @Param() 指定参数名

注意:if 标签test内,用的 and 来表示且,看起来就像SQL,但其实 test 中并不由SQL解析,而是框架

其它

SpringMVC 默认的方式是转发

转发: 表示 一次请求

重定向: 重新发起一次请求

参考