EasyCode插件
代码生成器
- 根据数据库自动生成pojo实体类
- 自动生成对应 controller、service、dao类
- 自动生成mapper文件(自动编写sql语句)
生成各层代码后,完整项目看起来就像下图
image-20201222182737677
记得配置 MusicApplication
,添加注解 @MapperScan("com.music.demo.dao")
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
添加如下
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
image-20201222174824454
DB First 生成各层代码
利用 EasyCode
image-20201222165012573
pom.xml
代码语言:javascript复制<!-- 导入mybatis的依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
Controller
取参
从url地址取出参数
代码语言:javascript复制@GetMapping("{name}/{id}")
@PathVariable("name")
从url query中取出参数
代码语言:javascript复制# url?name="123"
// 无需注解,直接按普通形参方式即可
query(String name)
Action 注解
代码语言:javascript复制@GetMapping("get")
等同于
@RequestMapping(value = "get", method = RequestMethod.GET)
代码语言:javascript复制@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(""),只为后图演示,其实不能这么写
image-20201223154002734
image-20201223151710524
代码语言:javascript复制建议多用 Integer 而不是 int int 存在空指针异常,使用 包装类 Integer 即可避免
// 参数来自请求体,必须使用 json格式
@PostMapping()
代码语言:javascript复制@PostMapping("delete")
public String insert(@RequestBody TbMusic inputModel){
return inputModel.getName();
}
从这里来看
@RequestBody
完全就是 ASP.NET Core 中[FromBody]
Controller 中 Action 找 视图View
代码语言:javascript复制@RequestMapping("login")
public String login() {
return "login";
}
image-20201223165230962
注意:此处 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 实例
代码语言:javascript复制@PostMapping("post")
public TbMusic post(@RequestBody TbMusic inputModel){
// 注意:传json时,属性名大小写敏感,应对应 entity名,而不是数据库字段名,是 albumname 而不是 albumName
// ASP.NET Core 中默认模型绑定 对 属性名大小写不敏感,至少对于驼峰命名法,会自动识别
inputModel = tbMusicService.insert(inputModel);
return inputModel;
}
代码语言:javascript复制ASP.NET Core 中尽管有 FromBody ,但不是必要的,这是因为框架认为 一个 ApiController 就应如此,从请求体获取 Java 注解 @xxx() C# 注解 xxx() xxxAttribute : Attribute xxxxAttribute 只是约定,不强制,若以 Attribute 结尾,则无需写最后的Attribute,VS会自动识别 若无需传参,则直接 xxxx
{
"name": "哆啦A梦",
"albumname": "专辑名",
"albumpic": "专辑图片地址",
"mp3url": "音乐地址",
"artistname": "歌者",
"mvurl": "mv链接",
"lrcurl": "歌词链接"
}
启动类配置扫描dao
代码语言:javascript复制@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⽂件
代码语言:javascript复制# 配置数据库连接信息
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 详解
代码语言:javascript复制<!--命名空间-->
<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>
代码语言:javascript复制<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"
代码语言:javascript复制<!--新增所有列-->
<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传多个参数
代码语言:javascript复制若仅有一参数,就可以不加
@Param()
// xxDao
interface TbMusicDao
代码语言:javascript复制// 注意:当参数大于等于2个时,一定要加上 @Param("xxx"),这样在 dao.xml中才能通过名字识别到 xxx,并赋予传过来的对应值
TbMusic queryById(@Param("id") Integer id, @Param("name") String name)
image-20201223194743623
代码语言:javascript复制<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>
代码语言:javascript复制/**
* 查询指定⾏数据
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return 对象列表
*/
List<TbUser> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);
程序启动需要导入依赖
代码语言:javascript复制<!-- 添加了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>
属性注入服务
代码语言:javascript复制@Autowired
private TbMusicService tbMusicService;
代码语言:javascript复制@Resource
private TbMusicService tbMusicService;
Q: @Autowired 和 @Resource 有何区别? A: @Autowired 根据类型进行搜索,注入 @Resource 根据名称进行搜索,注入 @Autowired 自动装配
Mybatis 的动态SQL
MyBatis的映射⽂件中⽀持在基础SQL上添加⼀些逻辑操作,并动态拼接成完整的SQL之后再执 ⾏,以达到SQL复⽤、简化编程 的效果。
1. SQL 片段
代码语言:javascript复制将一些经常使用的定义成一个片段,要使用的地方直接引用此片段
<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>
代码语言:javascript复制<!--通过实体作为筛选条件查询-->
<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,
- 若if一个都未成立,最后就是没有条件,那么无 where
- 若成立一个if 等情况,而其前无if成立,即 and artistName = #{artistname} 情况出现,那么去掉前面的 and
3. <set>
代码语言:javascript复制<!--通过主键修改数据-->
<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>
代码语言:javascript复制<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 模糊查询
代码语言:javascript复制<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
全局:对于新建项目
image-20201228155013651
局部:当前项目
image-20201228155109786
pom.xml 依赖
代码语言:javascript复制<!-- 热部署工具包 -->
<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 /
image-20201228155245262
Thymeleaf 模板引擎
- 若需使用 Thymeleaf 模板,所有页面必须经过 SpringMVC 视图解析器解析
- 使用 thymeleaf 需导入对应依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 给相应页面添加 thymeleaf 的访问地址
<html xmlns:th="http://www.thymeleaf.org">
- thymeleaf 获取变量
// ognl 表达式
${}
注意:
jsp内
代码语言:javascript复制// el表达式
${}
代码语言:javascript复制<p th:text="${session.user.username}" style="display: inline-block">靓仔</p>
- thymeleaf 设置路径
<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根域
- thymeleaf 的 each 循环
<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地址必须 在 @{} 内
- thymeleaf 的 if 判断
<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: 无法连接数据库
代码语言:javascript复制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
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
快键键
代码语言:javascript复制sout
System.out.println();
C# VS 中
cl
Console.WriteLine()
代码语言:javascript复制iter
for(Object o : a) {}
代码语言:javascript复制fori
for(int i = 0; i < a.size(); i )
代码语言:javascript复制IDEA
View -> Project
Alt 1
代码语言:javascript复制// psvm
public static void main(String[] args) {
}
Ctrl F9 强制重启,重新编译
代码 Region 折叠块
代码语言:javascript复制代码 Region 折叠块
VS
#region
#endRegion
IDEA
//region
//endregion
快捷键:Ctrl Alt T
SQL日志
image-20201227084027306
代码语言:javascript复制# 配置sql日志信息
logging.level.com.qf.music.dao=debug
时间格式
代码语言:javascript复制// 前端传后端 约束
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
// 后端向前端
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
配置Maven环境变量
image-20201228093821956
安装jar包到Maven仓库
代码语言:javascript复制mvn install:install-file -DgroupId=it.source -DartifactId=ValidateCode -Dpackaging=jar -Dversion=1.0 -Dfile=F:ValidateCode-1.0.jar
image-20201228095458297
注意:不知道为什么,cmd成功,powershell失败
安装成功后,即可在 pom.xml 增加jar包依赖
代码语言:javascript复制注意:groupId, artifactId 对应
<dependency>
<groupId>it.source</groupId>
<artifactId>ValidateCode</artifactId>
<version>1.0</version>
</dependency>
动态SQL仅一参数也必加@Param()
代码语言:javascript复制/**
* 动态SQL必加标识符@Param(), 就算只有一个参数
* @param value
* @return
*/
List<TbMusic> queryLike(@Param("value") String value);
代码语言:javascript复制<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 默认的方式是转发 转发: 表示 一次请求 重定向: 重新发起一次请求
AJAX 请求登录后,响应 Set-Cookie ,但浏览器仍然没有设置 Cookie
参考:
- 为什么浏览器在AJAX请求返回后不会设置Cookie? - IT屋-程序员软件开发技术分享社区
我使用$ .ajax进行ajax请求。响应具有 Set-Cookie 标头集(我已在Chrome开发工具中验证了此标签)。但是,浏览器在收到响应后不会设置Cookie!当我导航到我的域中的另一个网页时,不发送Cookie。 (注意:我没有做任何跨域的ajax请求;请求与文档在同一个域中。)
解决:
代码语言:javascript复制@PostMapping("login")
public JsonResponse login(String userName, String password, HttpServletRequest request, HttpServletResponse res) {
// 验证后,生成 token
// 创建一个 cookie对象
Cookie cookie = new Cookie("token", token);
cookie.setMaxAge(7 * 24 * 60 * 60); // 7天过期
// 当从ajax请求设置cookie时,设置 Path 选项很重要
cookie.setPath("/");
cookie.setHttpOnly(true);
//将cookie对象加入response响应
res.addCookie(cookie);
return response;
}
代码语言:javascript复制set-cookie: token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MjY2MjIxMTcsInVzZXJpZCI6MSwidXNlcm5hbWUiOiJyb290In0.xrz8paGPPAGjQVmzT9TqBVE-Ef4X0NvBdqmCZkrOMHA; Max-Age=604800; Expires=Sat, 24-Jul-2021 09:28:37 GMT; Path=/; HttpOnly
发现问题,在部分浏览器(手机浏览器),使用 Session 方式,登录无效,原因:浏览器没有成功为 JSESSIONID 存到Cookie中
本人尝试过在前端手动设置 cookie,但是 xhr.getResponseHeader("Set-Cookie");
没拿到值(null
),而且对于不同版本的 Tomcat
,Set-Cookie
字段大小写不一致
$.ajax({
url: "/api/user/login",
type: "POST",
data: { "userName": userName, "password": password},
dataType: "json",
success: function (data, status, xhr) {
if (data.code == -1) {
// 账号或密码错误
alert("账号或密码错误");
} else {
// 一切正确
localStorage.setItem("token", data.data);
// console.log(data, status, xhr);
// var cookie = xhr.getResponseHeader("Set-Cookie");
// if (cookie==null || cookie == "") {
// cookie = xhr.getResponseHeader("Set-Cookie");
// }
// console.log(cookie);
// document.cookie = cookie;
// util.setCookie("token", data.data, 7); // TODO: 无效,需要 path, domain, 改为在服务端设置
// alert("登录成功: " util.getCookie("token"));
window.location.href = "/";
}
}
});
参考
- 本文作者: yiyun
- 本文链接: https://cloud.tencent.com/developer/article/1970643
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!