代码生成器
生成各层代码后,完整项目看起来就像下图
记得配置 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
若连接出现时区错误,则在URL 后 添加
?serverTimezone=Asia/Shanghai
jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai
DB First 生成各层代码
利用 EasyCode
<!-- 导入mybatis的依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
@GetMapping("{name}/{id}")
@PathVariable("name")
# url?name="123"
// 无需注解,直接按普通形参方式即可
query(String name)
@GetMapping("get")
等同于
@RequestMapping(value = "get", method = RequestMethod.GET)
@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 即可避免
// 参数来自请求体,必须使用 json格式
@PostMapping()
@PostMapping("delete")
public String insert(@RequestBody TbMusic inputModel){
return inputModel.getName();
}
从这里来看
@RequestBody
完全就是 ASP.NET Core 中[FromBody]
@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: 好像加上:
代表此参数可空,忘了,待查
@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
{
"name": "哆啦A梦",
"albumname": "专辑名",
"albumpic": "专辑图片地址",
"mp3url": "音乐地址",
"artistname": "歌者",
"mvurl": "mv链接",
"lrcurl": "歌词链接"
}
@SpringBootApplication
//扫描dao层 让mybatis框架接管到层
@MapperScan("com.qf.music.dao")
public class MusicApplication {
public static void main(String[] args) {
SpringApplication.run(MusicApplication.class, args);
}
}
# 配置数据库连接信息
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
<!--命名空间-->
<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>
<select id="queryById" resultMap="TbMusicMap">
select
id, name, albumName, albumPic, mp3url, artistName, mvurl, lrcurl
from music.tb_music
where id = #{id}
</select>
#{id} 防止注入
<!--新增所有列-->
<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;
若仅有一参数,就可以不加
@Param()
// xxDao
interface TbMusicDao
// 注意:当参数大于等于2个时,一定要加上 @Param("xxx"),这样在 dao.xml中才能通过名字识别到 xxx,并赋予传过来的对应值
TbMusic queryById(@Param("id") Integer id, @Param("name") String name)
<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>
/**
* 查询指定⾏数据
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return 对象列表
*/
List<TbUser> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);
<!-- 添加了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>
@Autowired
private TbMusicService tbMusicService;
@Resource
private TbMusicService tbMusicService;
Q: @Autowired 和 @Resource 有何区别? A: @Autowired 根据类型进行搜索,注入 @Resource 根据名称进行搜索,注入 @Autowired 自动装配
MyBatis的映射⽂件中⽀持在基础SQL上添加⼀些逻辑操作,并动态拼接成完整的SQL之后再执 ⾏,以达到SQL复⽤、简化编程 的效果。
将一些经常使用的定义成一个片段,要使用的地方直接引用此片段
<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>
<where>
<!--通过实体作为筛选条件查询-->
<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,
<set>
<!--通过主键修改数据-->
<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>
<foreach>
<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>
<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片段代码执行
全局:对于新建项目
局部:当前项目
<!-- 热部署工具包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
Ctrl + Shift + Alt + /
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<html xmlns:th="http://www.thymeleaf.org">
// ognl 表达式
${}
注意:
jsp内
// el表达式
${}
<p th:text="${session.user.username}" style="display: inline-block">靓仔</p>
<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根域
<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地址必须 在 @{} 内
<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: @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: 无法连接数据库
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
sout
System.out.println();
C# VS 中
cl
Console.WriteLine()
iter
for(Object o : a) {}
fori
for(int i = 0; i < a.size(); i++)
IDEA
View -> Project
Alt + 1
// psvm
public static void main(String[] args) {
}
Ctrl + F9 强制重启,重新编译
代码 Region 折叠块
VS
#region
#endRegion
IDEA
//region
//endregion
快捷键:Ctrl+Alt+T
# 配置sql日志信息
logging.level.com.qf.music.dao=debug
// 前端传后端 约束
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
// 后端向前端
@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
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 对应
<dependency>
<groupId>it.source</groupId>
<artifactId>ValidateCode</artifactId>
<version>1.0</version>
</dependency>
/**
* 动态SQL必加标识符@Param(), 就算只有一个参数
* @param value
* @return
*/
List<TbMusic> queryLike(@Param("value") String value);
<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进行ajax请求。响应具有 Set-Cookie 标头集(我已在Chrome开发工具中验证了此标签)。但是,浏览器在收到响应后不会设置Cookie!当我导航到我的域中的另一个网页时,不发送Cookie。 (注意:我没有做任何跨域的ajax请求;请求与文档在同一个域中。)
解决:
@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;
}
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 = "/";
}
}
});