From 3d45c851f47672d89aef4241310bdcde3fde1995 Mon Sep 17 00:00:00 2001 From: tianzhendong <1203886034@qq.com> Date: Fri, 29 Apr 2022 12:08:33 +0800 Subject: [PATCH] Auto commit. --- _config.yml | 4 +- source/_data/friends.json | 2 +- .../Git/Git-gitignore文件不生效.md | 38 + source/_posts/Git/Git.md | 468 +++ source/_posts/Java/JavaWeb.md | 3353 +++++++++++++++ source/_posts/Java/MySql.md | 2091 ++++++++++ source/_posts/Java/Mybatis.md | 1815 ++++++++ source/_posts/Java/SSM整合.md | 921 +++++ source/_posts/Java/Shiro学习.md | 1397 +++++++ source/_posts/Java/Spring5.md | 1844 +++++++++ source/_posts/Java/SpringBoot.md | 3642 +++++++++++++++++ source/_posts/Java/SpringMVC.md | 2416 +++++++++++ source/_posts/Java/VUE学习.md | 247 ++ source/_posts/Java/javaIO流.md | 775 ++++ source/_posts/Java/java一些知识点.md | 105 + source/_posts/Java/java基础.md | 1085 +++++ source/_posts/Java/java容器.md | 736 ++++ source/_posts/Java/java常用类.md | 647 +++ source/_posts/Java/剑指offer-java.md | 938 +++++ source/_posts/Java/数据结构和算法.md | 735 ++++ source/_posts/Java/网络编程.md | 535 +++ source/_posts/Java/面向接口编程.md | 332 ++ source/_posts/{ => blog}/Blog.md | 0 source/_posts/{ => blog}/BlogOpen.md | 0 source/_posts/{ => blog}/Docsify.md | 0 source/_posts/{ => blog}/Hexo-xr.md | 0 source/_posts/{ => blog}/blog-aliyun.md | 0 source/_posts/{ => 前端}/jQuery+CSS3.md | 0 source/_posts/前端/前端三件套.md | 1173 ++++++ source/_posts/{ => 效率}/Frp.md | 0 source/_posts/{ => 效率}/PicGo-GitHub.md | 0 source/_posts/{ => 效率}/markdown.md | 0 source/tools/index.html | 6 +- 33 files changed, 25299 insertions(+), 6 deletions(-) create mode 100644 source/_posts/Git/Git-gitignore文件不生效.md create mode 100644 source/_posts/Git/Git.md create mode 100644 source/_posts/Java/JavaWeb.md create mode 100644 source/_posts/Java/MySql.md create mode 100644 source/_posts/Java/Mybatis.md create mode 100644 source/_posts/Java/SSM整合.md create mode 100644 source/_posts/Java/Shiro学习.md create mode 100644 source/_posts/Java/Spring5.md create mode 100644 source/_posts/Java/SpringBoot.md create mode 100644 source/_posts/Java/SpringMVC.md create mode 100644 source/_posts/Java/VUE学习.md create mode 100644 source/_posts/Java/javaIO流.md create mode 100644 source/_posts/Java/java一些知识点.md create mode 100644 source/_posts/Java/java基础.md create mode 100644 source/_posts/Java/java容器.md create mode 100644 source/_posts/Java/java常用类.md create mode 100644 source/_posts/Java/剑指offer-java.md create mode 100644 source/_posts/Java/数据结构和算法.md create mode 100644 source/_posts/Java/网络编程.md create mode 100644 source/_posts/Java/面向接口编程.md rename source/_posts/{ => blog}/Blog.md (100%) rename source/_posts/{ => blog}/BlogOpen.md (100%) rename source/_posts/{ => blog}/Docsify.md (100%) rename source/_posts/{ => blog}/Hexo-xr.md (100%) rename source/_posts/{ => blog}/blog-aliyun.md (100%) rename source/_posts/{ => 前端}/jQuery+CSS3.md (100%) create mode 100644 source/_posts/前端/前端三件套.md rename source/_posts/{ => 效率}/Frp.md (100%) rename source/_posts/{ => 效率}/PicGo-GitHub.md (100%) rename source/_posts/{ => 效率}/markdown.md (100%) diff --git a/_config.yml b/_config.yml index 33d5866..f2fcad3 100644 --- a/_config.yml +++ b/_config.yml @@ -13,7 +13,7 @@ timezone: '' # URL ## If your site is put in a subdirectory, set url as 'http://example.com/child' and root as '/child/' -url: http://www.luckyzmj.cn +url: http://www.tianzd.cn root: / # permalink: :year/:month/:day/:title/ permalink: posts/:abbrlink.html # p 是自定义的前缀 @@ -139,7 +139,7 @@ deploy: baidu_url_submit: count: 40 # 提交最新的一个链接 host: "" # www.luckyzmj.cn # 在百度站长平台中注册的域名 - token: xxxxxxxxxxxxxxx # 请注意这是您的秘钥, 所以请不要把博客源代码发布在公众仓库里! + token: xxxxxxxxxxx # 请注意这是您的秘钥, 所以请不要把博客源代码发布在公众仓库里! path: baidu_urls.txt # 文本文档的地址, 新链接会保存在此文本文档里 # matery搜索功能 diff --git a/source/_data/friends.json b/source/_data/friends.json index 98c9afd..7198f91 100644 --- a/source/_data/friends.json +++ b/source/_data/friends.json @@ -3,7 +3,7 @@ "avatar": "https://gitee.com/tianzhendong/img/raw/master/images/TianZD22.png", "name": "docsify", "introduction": "保持一颗学习的心", - "url": "http://106.14.31.180:90", + "url": "http://tianzd.cn:90", "title": "访问主页" },{ "avatar": "https://s1.ax1x.com/2020/05/17/YRWsYT.png", diff --git a/source/_posts/Git/Git-gitignore文件不生效.md b/source/_posts/Git/Git-gitignore文件不生效.md new file mode 100644 index 0000000..45fa0a4 --- /dev/null +++ b/source/_posts/Git/Git-gitignore文件不生效.md @@ -0,0 +1,38 @@ +--- +title: Git-.gitignore文件不生效 +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: >- + .gitignore中已经标明忽略的文件目录下的文件,git push的时候还会出现在push的目录中,或者用git + status查看状态,想要忽略的文件还是显示被追踪状态。 +tags: + - Git +categories: + - Git +reprintPolicy: cc_by +abbrlink: 88176b53 +date: 2022-04-29 10:39:56 +coverImg: +img: +password: +--- + +## 原因 +在git忽略目录中,新建的文件在git中会有缓存,如果某些文件已经被纳入了版本管理中,就算是在.gitignore中已经声明了忽略路径也是不起作用的,这时候我们就应该先把本地缓存删除,然后再进行git的提交,这样就不会出现忽略的文件了。 + +需要特别注意的是: +1).gitignore只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。 +2)想要.gitignore起作用,必须要在这些文件不在暂存区中才可以,.gitignore文件只是忽略没有被staged(cached)文件, +对于已经被staged文件,加入ignore文件时一定要先从staged移除,才可以忽略。 + +## 解决 +首先清楚本地缓存,将其变为untrack状态,然后提交 +```bash +git rm -r --cached . +git add . +git commit -m 'update .gitignore' +git push -u origin master +``` \ No newline at end of file diff --git a/source/_posts/Git/Git.md b/source/_posts/Git/Git.md new file mode 100644 index 0000000..dcaeca4 --- /dev/null +++ b/source/_posts/Git/Git.md @@ -0,0 +1,468 @@ +--- +title: Git +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: 分布式版本控制工具Git介绍和一些常用操作 +tags: + - Git + - 版本控制 +categories: + - Git +reprintPolicy: cc_by +abbrlink: 69c3279c +date: 2022-04-29 10:37:33 +coverImg: +img: +password: +--- + + + +[toc] + +# Git学习 + +# 1、版本控制 + +## 版本控制工具 + +* Git +* SVN(Subversion) +* CVS(Concurrent Version System) +* VSS(Micorosoft Visual SourceSafe) +* TFS(Team Foundation Server) +* Visual Studio Online + +Git是目前世界上最先进的分布式**版本控制**系统 + +## 分类 + +### 本地版本控制 + +记录文件每次的更新,可以对每一个版本做一个快照,或是记录补丁文件,适合个人使用,如RCS + +![image-20210804192335392](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804192335392.png) + +### 集中版本控制 + +代表是SVN + +版本库是集中存放在**中央服务器**的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。**集中式版本控制系统最大的毛病就是必须联网才能工作** + +![image-20210804192519737](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804192519737.png) + +### 分布式版本控制 + +代表GIT + +每个人拥有全部代码,有安全隐患 + +分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。**集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。** + +![image-20210804192644016](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804192644016.png) + + + +# 2、Git安装 + +## Linux安装Git + +首先,你可以试着输入git,看看系统有没有安装Git: + +``` +$ git +The program 'git' is currently not installed. You can install it by typing: +sudo apt-get install git +``` + +## Macos安装Git + +直接从AppStore安装Xcode,Xcode集成了Git,不过默认没有安装,你需要运行Xcode,选择菜单“Xcode”->“Preferences”,在弹出窗口中找到“Downloads”,选择“Command Line Tools”,点“Install”就可以完成安装了。 + +## 在Windows上安装Git + +在Windows上使用Git,可以从Git官网直接下载安装程序,然后按默认选项安装即可。 +安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功。 + +## 启动Git + +**GIt Bash:**Unix与Linux风格的命令行,使用最多,推荐对坐 + +**Git CMD:**Windows风格的命令行 + +**Git GUI:**图形界面的Git,不建议使用 + +# 3、基本linux命令 + +* cd:改变目录 +* cd .. :回到上一个目录 +* pwd:查看当前目录 +* clear:清屏 +* ls(ll):列出 当前目录所有文件 +* touch:新建一个文件 +* rm:删除一个文件 +* mkdir:新建一个目录 +* rm -r:删除一个文件夹 + * rm -rf /:递归删除所有文件 +* mv:移动文件mv a.html src 把a.html移动到src文件夹下 +* reset:重新初始化终端/清平 +* history:查看命令历史 +* help:帮助 +* exit:推出 +* #:注释 + +# 4、Git配置 + +所有的配置文件都保存在本地 + +## 查看配置 + +```bash +git config -l #查看配置 +git config --system -l #查看系统配置 +git config --global -l #查看(当前用户)全局配置 +``` + +## 配置用户名和密码 + +```bash +#配置用户名、邮箱 +git config --global user.name "tianzhendong" +git config --global user.email 1203886034@qq.com +``` + +# 5、Git原理 + +## 工作区域 + +* git本地有三个工作区域: + * 工作目录(working directory):工作区域,平时存放代码的地方 + * 暂存区(stage/index):用于临时存放你的改动,事实上只是一个文件,保存即将提交到文件列表信息 + * 资源库(repository或者git directory):本地仓库,安全存放数据的位置,里面有你提交的所有版本的数据,其中HEAD指向最新放入仓库的版本 + +* 远程的git仓库(Remote directory):托管代码的服务器 + +![image-20210804204342702](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804204342702.png) + + + + + +![image-20210804205024206](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804205024206.png) + + + +## 工作流程 + +1. 在工作目录中添加、修改文件 +2. 将需要进行版本管理的文件放入暂存区 +3. 将暂存区的文件提交到git仓库 +4. 提交到远程 + +git管理的文件三种状态:已修改(modified)、已暂存(staged)、已提交(committed) + +![image-20210804205250289](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804205250289.png) + +# 6、Git使用 + +![image-20210804205421101](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804205421101.png) + +## 创建本地仓库 + +```bash +#方法1:创建全新的仓库 +git init #初始化本地库 +#方法2:克隆远程仓库到本地 +git clone [url] #克隆远程仓库到本地 +``` + +## Git文件操作 + +文件四种状态: + +* **Untracked:**未跟踪,文件在文件夹中,但是没有加入到git库 + * 通过**git add .**状态变为**staged** +* **Unmodify:**已入库,未修改 + * 如果被修改,变为**Modified** + * 如果使用**git rm**移出版本库则成为**Untracked** +* **Modified:**已修改 + * **git add**:进入暂存**staged**状态 + * **git checkout**:丢弃修改,返回到**unmodify**状态,git checkout即从库中取出文件,覆盖当前修改 +* **Staged:**暂存状态 + * **git commit**:修改同步到库中,随后文件变成**unmodify**状态 + * **git reset HEAD filename**:取消暂存,变为**Modified** + +### 基本操作 + +```bash +#查看状态 +git status +#查看指定文件状态 +git status [filename] +#添加到暂存区 +git add . +#提交暂存区内容到本地仓库 +git commit -m "注释内容" #-m表示提交信息 +``` + +### 忽略文件 + +有时候不需要把某些文件纳入版本控制中,比如数据库文件、临时文件、设计文件等 + +在主目录下建立".gitignore"文件,此文件有如下规则: + +1. 文件中的空行或以#开头的行将会被忽略 +2. 可以是用linux通配符,例如:*表示任意多个字符,?表示一个字符,[]表示可选字符范围,{}代表可选的字符串集 +3. 如果名称的最前面有一个!,表示例外规则,将不会被忽略 +4. 如果名称前面有一个/,表示要忽略的文件在此目录下,子目录下的文件不忽略 +5. 如果名称的最后面有一个/,表示要忽略的是此目录下的所有文件 + +```bash +#为注释 +*.txt #忽略所有.txt结尾的文件 +!lib.txt #lib.txt除外 +/temp #仅忽略项目根目录下的TODO文件,不包括其他目录temp +build/ #忽略build/目录下的所有文件 +doc/*.txt #忽略doc/notes.txt,但不包括doc/server/arch.txt +``` + +## 使用码云Gitee + +### 设置本机绑定SSH公钥实现免密登陆 + +```bash +# C:\users\Administrator目录 +#生成公钥 +ssh-keygen +``` + +把在.ssh目录下的id_rsa.pub文件中的内容复制到gitee公钥设置中即可 + +## IDEA中集成Git + +1. 新建项目,绑定git + 1. 将远程的git文件目录拷贝到项目中即可 +2. 修改文件,使用git + +# 7、Git分支 + +* **master**:主分支 +* **dev**:开发用 + +master主分支应该非常稳定,用来发布新版本,一般情况下不允许在上面工作,工作一般情况下在新建的dev分支上工作,工作完后,比如要分布,或者说dev分支代码稳定后可以合并到主分支master上来 + +三种分支合并情况可以见该链接:https://blog.csdn.net/qq_42780289/article/details/97945300 + +```bash +# 列出所有本地分支 +git branch +# 列出所有远程分支 +git branch -r +# 新建一个分支,但是并未切换 +git branch [branch_name] +# 新建一个分支,并切换至该分支 +git checkout -b [branch] +# 合并指定分支到当前分支 +git merge [branch] +# 删除分支 +git branch -d [branch] +# 删除远程分支 +git push origin --delete [branch] +``` + +# 8、部分指令 + +## 指令 + +```bash +git init //初始化本地库 +git add readme.txt //将文件添加到仓库 +git commit -m "first commit" //把文件提交到仓库 +git status //查看仓库当前状态 +git diff readme.txt //查看该文件的不同 +git log // 查看每次更改内容 +git reset --hard HEAD^ //回退到上一个版本,HEAD表示当前版本,HEAD^上一个版本,几个^号 表示上几个版本; +rm readme.txt //删除文件 +git rm readme.txt//从库中删除文件 +git commit -m "remove the readme.txt" +git remote add origin git@github.com:michaelliao/learngit.git //关联远程库 +git push -u origin master //将本地库的内容推送到远程 +git remote -v //查看远程库信息 +git remote rm origin //接触本地与远程的绑定关系 +git clone git@github.com:michaelliao/gitskills.git //从远程库克隆 +``` + +## 合并其他分支代码至master分支 + +下面以dev分支为例来讲解。 + +1. 当前分支所有代码提交 + 先将dev分支上所有有代码提交至git上,提交的命令一般就是这几个,先复习下: + +```bash +# 将所有代码提交 +git add . +# 编写提交备注 +git commit -m "修改bug" +# 提交代码至远程分支 +git push origin dev +``` + +2. 切换当前分支至主干(master) + +```bash +# 切换分支 +git checkout master + +# 如果多人开发建议执行如下命令,拉取最新的代码 +git pull origin master +``` + +3. 合并(merge)分支代码 + +```bash +git merge dev +# merge完成后可执行如下命令,查看是否有冲突 +git status +``` + +4. 提交代码至主干(master) + +```bash +git push origin master +``` + +5. 最后切换回原开发分支 + +```bash +git checkout dev +``` + +## 删除分支 + +```bash +// delete branch locally +git branch -d localBranchName + +// delete branch remotely +git push origin --delete remoteBranchName +``` + +## 重命名文件 + +第一种方法:使用mv命令 + +``mv readme README.md`` + +这个时候,如果使用git status查看工作区的状态,Git会提示,readme文件被删除,README.md文件未被跟踪。git add进行提交到暂存区的时候,需要把这个两个文件一起提交,即: + +``git add readme README.md`` + +第二中方法:直接使用Git的 git mv命令。 +`` +git mv readme README.md`` + +此时,我们不需要再使用git add 命令把两个文件一起提交,直接使用git commit即可。 +也就是说,git mv命令比linux的mv命令,省去了git add提交文件到暂存区这个步骤。 + + + +## 【Git】pull遇到错误:error: Your local changes to the following files would be overwritten by merge: + +首先取决于你是否想要保存本地修改。(是 /否) + +### 是 + +别急我们有如下三部曲 + +```bash +git stash +git pull origin master +git stash pop +``` + + +- git stash的时候会把你本地快照,然后git pull 就不会阻止你了,pull完之后这时你的代码并没有保留你的修改。惊了! 别急,我们之前好像做了什么? + +STASH +这时候执行git stash pop你去本地看会发现发生冲突的本地修改还在,这时候你该commit push啥的就悉听尊便了。 + +### 否 + +既然不想保留本地的修改,那好办。直接将本地的状态恢复到上一个commit id 。然后用远程的代码直接覆盖本地就好了。 + +```bash +git reset --hard +git pull origin master +``` + + + +------------------------------------------------ + +版权声明:本文为CSDN博主「转身雪人」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 +原文链接:https://blog.csdn.net/nakiri_arisu/article/details/80259531 + + + +# Git 标签 + +标签用于标记某一提交点,唯一绑定一个固定的commitId,相当于为这次提交记录指定一个别名,方便提取文件。 +可以为重要的版本打上标签,标签可以是一个对象,也可以是一个简单的指针,但是指针不会移动。 + +## 创建标签 + +`git tag ` #为当前分支指向的commit记录创建标签 + + `git tag ` #为指定的commitId创建标签 + + `git tag -a -m "msg" ` #创建标签同时添加说明信息 + +## 查看标签 + +`git tag` #查看所有标签名称 + + `git show ` #查看标签的详细信息(包含commit的信息) + + `git tag -ln [tag_name]` #显示标签名及其描述信息 + +## 远程推送标签 + +`git push ` #将标签推送到远程服务器 + + `git push --tags` #将本地的全部tag推送到远程服务器 + +## 删除标签 + +`git tag -d ` #删除本地的标签 + +`git push :refs/tags/` #删除远程标签 + +## 标签内容提取 + +``` +git archive --format=zip --output=src/xxx.zip ` #提取为zip格式,src可以是相对路径,也可以是绝对路径 + **示例:**在d盘下生成包含0.8标签内容的压缩包 + `git archive --format=zip --output=d:/v0.8.zip v0.8 +``` + +## 切换标签 + +如果我们不想直接提取出标签的代码,而是希望在指定标签下继续进行开发,此时可以切换到标签。 + +`git checkout ` #切换到指定标签 + +**示例:**切换到v0.8标签进行开发,此时提示我们处于`detached HEAD state`(分离头指针状态),即说明HEAD指针没有指向具体的分支,查看HEAD指针它直接指向了一个commit对象,此时进行开发操作没有任何意义。 + +如果想要退出`detached HEAD state`,很简单只需要切换回指定分支就可以了,如`git checkout master` + + 如果想要在当前tag下继续开发,可以新建一个分支并让HEAD指向分支就可以了。 + + + +## 强制推送 + +`git push -f origin master`强制推送到origin远程的master分支上 \ No newline at end of file diff --git a/source/_posts/Java/JavaWeb.md b/source/_posts/Java/JavaWeb.md new file mode 100644 index 0000000..5c47372 --- /dev/null +++ b/source/_posts/Java/JavaWeb.md @@ -0,0 +1,3353 @@ +--- +title: JavaWeb学习笔记 +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: JavaWeb学习笔记,粗略学了一下,没有参考价值 +tags: + - javaweb + - java + - 学习笔记 +categories: + - java +reprintPolicy: cc_by +abbrlink: b29dfb1f +date: 2022-04-29 10:48:32 +coverImg: +img: +password: +--- + + +[toc] + +# JavaWeb + +# 0、JavaEE三层架构 + +![image-20210803175103445](https://gitee.com/tianzhendong/img/raw/master//images/image-20210803175103445.png) + +# 1、概述 + +## 1.1、软件结构 + +C/S:客户端client/服务器server + +B/S:客户端采用浏览器,服务器端采用web服务器 + +## 1.2、页面组成 + +* 内容(结构):在页面中看到的数据,一般内容采用html技术 +* 表现:内容在页面上的展示形式,一般用CSS技术 +* 行为:页面中的元素与输入设备交互的响应,一般使用JavaScript技术 + +# 2、HTML + +> HTML:超文本标记语言,通过标签来标记要显示的网页中的各个部分,网页文件本身是一种文本文件,通过在文本文件中添加标记符,告诉浏览器如何显示其中的内容 + +```html + + + + + 标题 + + + tianzhendong + + +``` + +## 2.1、HTML标签介绍 + +```html +<标签名>封装的数据 +``` + +* 标签名不区分大小写 +* 标签拥有自己的属性 + * 基本属性:bgcolor = “red” 可以修改简单的样式效果 + * 事件属性:onclick= “alert(‘你好!’);” 可以设置事件响应后的代码 +* 单双标签 + * 单标签:<标签名 /> br表示换行,hr表示水平线 + * 双标签:<标签名>封装的数据 + +```html + + + + + 标题 + + + tianzhendong +
+ +
+ + + + +``` + + + +## 2.2、常用标签 + +### 字体font + +```html +我是字体标签 +``` + +### 字符实体& + +一些字符在 HTML 中拥有特殊的含义,比如小于号 (<) 用于定义 HTML 标签的开始。如果我们希望浏览器正确地显示这些字符,我们必须在 HTML 源码中插入字符实体。 + +字符实体有三部分:一个和号 (&),一个实体名称,或者 # 和一个实体编号,以及一个分号 (;)。 + +![image-20210728211011236](https://i.loli.net/2021/07/28/KObBFExtv9IMgDh.png) + +### 标题标签h1 + +支持h1-h6,align为对其属性 + +```html +

标题1

+``` + +### 超链接a + +* href属性:设置连接的地址 +* target属性:设置哪个目标进行跳转 + * _self:默认值,当前页面 + * _blank:打开新页面进行跳转 + +```html +百度 +百度 +百度 +``` + + + +### 列表标签ul/ol + +* 无序列表始于<**ul**> 标签,每个列表项始于 <**li**> +* 有序列表始于<**ol**>标签,每个列表始于<**li**> +* 属性:type=”none“,取消前面的标号或者小圆点 + +```html +
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
+``` + +### 图像标签img + +* src属性:src=“url”,url为图像的位置 +* alt属性:设置替代的文本属性 +* width属性:设置宽度 +* height属性:设置高度 +* border属性:设置边框宽度 + +```html + +找不到图片 +``` + +### 表格标签table + +表格由 <**table**> 标签来定义。每个表格均有若干行(由 <**tr**>标签定义),每行被分割为若干单元格(由 <**td**> 标签定义)。字母 td 指表格数据(table data),即数据单元格的内容。数据单元格可以包含文本、图片、列表、段落、表单、水平线、表格等等。表格的表头使用 <**th**> 标签进行定义。 + +* <**th**>:表头 +* <**tr**>:行 +* <**td**>:列 + +```html + + + + + + + + + + + + +
表头1
row 1, cell 1row 1, cell 2
row 2, cell 1row 2, cell 2
+``` + +### 内嵌窗口iframe + +在页面上开辟一个小区域,显示一个单独的页面 + +iframe和a标签组合使用: + +* 在iframe标签中使用name属性定义一个名称 +* 在a标签的target属性上设置iframe的name属性值 + +```html + +
+ +``` + +### 表单标签form + +html页面中用来收集用户信息的所有元素集合,表单元素是允许用户在表单中(比如:文本域、下拉列表、单选框、复选框等等)输入信息的元素。 + +```html + + +
+ +
+ +

用户注册

+ + 用户名称:
+ + 用户密码:
+ 确认密码:
+ + 性别:男 +
+ + 兴趣爱好:java + c + c++
+ + 国籍:
+ + 自我评价:
+ +
+ +
+ +
+ +
+
+ +``` + +一般把表单放在一个表格table中 + +# 3、CSS + +CSS:层叠样式菜单,用于增强控制网页样式并允许将样式信息与网页内容分离的一种标记性语言 + +## 3.1、语法规则 + +* 选择器:浏览器根据“选择器”决定受CSS样式影响的HTML元素(标签) +* 属性:要改变的样式名,并且每个属性有一个值,属性和值由:分开,并且由花括号包围 +* 值 + +```html +body {color: blue} +``` + +将 body 元素内的文字颜色定义为蓝色。在上述例子中,body 是选择器,而包括在花括号内的的部分是声明。声明依次由两部分构成:属性和值,color 为属性,blue 为值。 + +注意: + +* 如果值为若干单词,则要给值加引号 + +```html +p {font-family: "sans serif";} +``` + +* 如果要定义不止一个声明,则需要用分号将每个声明分开 + +```html +p { +text-align: center; +color: black; +font-family: arial; +} +``` + +## 3.2、CSS和HTML结合方式 + +### 方法1 + +```html + + + + + CSS-HTML + + + + + tianzhendong + + +``` + +缺点: + +* 只能在同一页面内复用代码,不能再多个页面中复用CSS代码 +* 维护起来不方便,实际的项目中会有成千上万的页面,要到每个页面中修改,工作量太大 + +### 方法2 + +把CSS样式写成一个单独的CSS文件,通过link标签引入即可复用 + +```css +font{ + border : 1px solid red; +} +``` + +```html + + + + + CSS-HTML + + + + + tianzhendong + + +``` + +## 3.3、常用选择器 + +### 标签名选择器 + +可以决定哪些标签被动的使用这个样式 + +格式: + +```css +标签名{ + 属性:值; +} +``` + +```html + + + + + CSS-HTML + + + + + + tianzhendong
+ tianzhendong
+ + +``` + +### id选择器 + +可以通过id属性选择性的去使用这个样式 + +```html +#id属性值{ + 属性:值; +} +``` + +```html + + + + + CSS-HTML + + + + + + tianzhendong
+ tianzhendong
+ + +``` + +id不能为数字,需要字母开头 + +### Class(类)选择器 + +```html +.class属性值{ + 属性:值; +} +``` + +```html + + + + + CSS-HTML + + + + + + tianzhendong
+ tianzhendong
+ + +``` + +### 组合选择器 + +```html +选择器1,选择器2,选择器n{ + 属性:值; +} +``` + +## 3.4、常用样式 + +```html +color: red; /*修改字体颜色*/ +width: 300px; /*宽度*/ +height: 300px; /*高度*/ +background-color: aqua; /*背景颜色*/ +font-size: 400px; /*字体大小*/ +border: 1px solid red; /*红色的1像素的边框*/ +margin-left: auto; /*DIV居中*/ +margin-right: auto; /*DIV居中*/ +text-align: center; /*文本居中*/ +text-decoration: none; /*超链接去下划线*/ +/*表格细线*/ +border: 1px solid black; /*设置边框*/ +border-collapse: collapse;/*将边框合并*/ +list-style: none;/*列表去除修饰*/ +``` + +# 4、JavaScript + +## 4.1、概述 + +主要用于完成页面的数据验证,运行在客户端,需要运行浏览器来解析执行JavaScript代码 + +> **JS是弱类型(类型可变),java是强类型(定义变量时类型已定,不能改变)** + +**特点:** + +* 交互性(它可以做的就是信息的动态交互) +* 安全性(不允许直接访问本地硬盘) +* 跨平台性(只要是可以解释JS的浏览器都可以执行,和平台无关) + +## 4.2、JavaScript和html结合的方式 + +### 方式1 + +在head标签中或者在body标签中,使用script标签来书写JavaScript代码 + +```html + + + + + JavascriptDemo + + + + + + + +``` + +### 方式2 + +使用Script标签引入单独的JavaScript代码文件 + +```js +alert("hello"); +``` + +```html + + + + + JavascriptDemo + + + + + + + +``` + +## 4.3、变量 + + + +| 变量类型 | 表达式 | +| :--------: | -------- | +| 数值类型 | number | +| 字符串类型 | string | +| 对象类型 | object | +| 布尔类型 | boolean | +| 函数类型 | function | + +特殊的值 + +* undefined:未定义,所有js变量未赋值时,都是undefined +* null:空值 +* NAN:not a number 非数字 + +```html + + + + + JavascriptDemo + + + + + +``` + +## 4.4、关系运算 + +* 等于:==,简单的字面值比较 +* 全等于:===,除了字面值比较外,还会做类型比较 + +```html + + + + + JavascriptDemo + + + + + +``` + +## 4.5、逻辑运算 + +* 且运算:&& + * 当表达式全为真,返回最后一个表达式的值 + * 当表达式中有一个为假,返回第一个为假的表达式的值 +* 或运算:|| + * 表达式全为假,返回最后要给表达式的值 + * 有一个表达式为真,返回第一个为真的表达式的值 +* 取反运算:! + +在JavaScript中,所有的变量,都可以作为一个boolean类型的变量去使用,0、null、undefined、‘’‘’(空串)都可以认为是false + +```html + + + + + JavascriptDemo + + + + + +``` + +## 4.6、数组 + + + +```js +var 数组名 =[];//空数组 +var 数组名 =[1,'a',true]//定义数组同时赋值元素 +``` + +JavaScript中数组会自动扩容 + +```html + + + + + JavascriptDemo + + + + + +``` + +## 4.7、函数 + +js中函数重载会覆盖上面的定义,不允许重载函数 + +### 方式1:使用function关键字定义 + +```html + + + + + JavascriptDemo + + + + + +``` + +### 方式2:var fun=function(){} + +```html + + + + + JavascriptDemo + + + + + +``` + +### 隐形参数 + +```html + + + + + JavascriptDemo + + + + + +``` + +## 4.8、js中的事件 + +> 事件是由电脑输入设备与页面进行交互的响应 + +### 常用事件: + +| 事件名称 | 功能 | +| ------------------------ | -------------------------------------------- | +| onload加载完成事件 | 页面加载完成后,常用于做页面js代码初始化操作 | +| onclick单机事件 | 用于按钮的点击响应事件 | +| onblur失去焦点事件 | 用于输入框失去焦点后验证其输入内容是否合法 | +| onchange内容发生改变事件 | 用于下拉列表和输入框内容发生改变后操作 | +| onsubmint表单提交事件 | 用于表单提交前,验证所有表单项是否合法 | + +### 注册事件 + +> 告诉浏览器,事件响应后要执行哪些操作代码,叫事件注册或事件绑定 + +* 静态注册:通过html标签的事件属性直接赋予事件响应后的代码 +* 动态注册:先通过js代码得到标签的dom对象,再通过dom对象.事件名 = function(){}形式赋予事件响应后的代码.动态注册基本步骤: + * 获取标签对象 + * 标签对象.事件名= function(){} + +#### onload事件注册 + +onload加载完成事件 :页面加载完成后,常用于做页面js代码初始化操作 + +```html + + + + + JavascriptDemo + + + + + + +``` + +```HTML + + + + + JavascriptDemo + + + + + + +``` + +#### onclick事件注册 + +onclick单机事件:用于按钮的点击响应事件 + +```html + + + + + JavascriptDemo + + + + + + + + +``` + +#### onblur事件注册 + +onblur失去焦点事件:用于输入框失去焦点后验证其输入内容是否合法 + +```html + + + + + JavascriptDemo + + + + + + + + + + + + + + +
+ 用户注册 +
+ 用户名: + + +
+ 密码: + + +
+ + +``` + +#### onchange事件注册 + +onchange内容发生改变事件:用于下拉列表和输入框内容发生改变后操作 + +```html + + + + + JavascriptDemo + + + + + + + + + + + + + + +
+ 用户注册 +
+ 选择1: + + + +
+ 选择2: + + + +
+ + +``` + +#### onsubmit事件注册 + +onsubmint表单提交事件 :用于表单提交前,验证所有表单项是否合法 + +```html + + + + + JavascriptDemo + + + + + + + + + + + +
+ + +``` + +## 4.9、DOM模型 + +Document Object Model文档对象模型:把文档中的标签、属性、文本转化为对象进行管理 + +![image-20210730210623985](https://i.loli.net/2021/07/30/5vLdeYW4cHU1gqR.png) + + + +### 方法 + +| close() | 关闭用 document.open() 方法打开的输出流,并显示选定的数据。 | +| ---------------------- | ------------------------------------------------------------ | +| getElementById() | 返回对拥有指定 id 的第一个对象的引用。 | +| getElementsByName() | 返回带有指定名称的对象集合。 | +| getElementsByTagName() | 返回带有指定标签名的对象集合。 | +| open() | 打开一个流,以收集来自任何 document.write() 或 document.writeln() 方法的输出。 | +| write() | 向文档写 HTML 表达式 或 JavaScript 代码。 | +| writeln() | 等同于 write() 方法,不同的是在每个表达式之后写一个换行符。 | + +三种查询方法: + +* 优先使用getElementById() +* 其次getElementsByName() +* 最后getElementsByTagName() + +越往后,查询范围越大 + +#### getElementById() + +返回对拥有指定 id 的第一个对象的引用。 + +```html + + + + + JavascriptDemo + + + + + 密码:
+ + + + +``` + +![image-20210730221657830](https://i.loli.net/2021/07/30/nyg56iMB8HtTwsc.png) + +#### getElementsByName() + +返回带有指定名称的对象集合 + +```html + + + + + JavascriptDemo + + + + + 兴趣爱好: + C; + Java; + Python; +
+ + + + + +``` + +#### getElementsByTagName() + + 返回带有指定标签名的对象集合。 + +和getElementsByName() 类似,不过是使用标签名进行查询 + +### 节点(标签对象)的常用属性和方法 + +节点就是标签对象 + +#### 方法 + +| 方法 | 功能 | +| ------------------------- | -------------------------------- | +| getElementsByTagName() | 获取当前节点的指定标签名孩子节点 | +| appendChild(oChildNode) | 添加一个子节点 | + +#### 属性 + +| 属性 | 功能 | +| --------------- | ------------------------------------------ | +| childNodes | 获取当前节点的**所有子节点** | +| firstChild | 获取当前节点的**第一个**子节点 | +| lastChild | 获取当前节点的**最后一个**子节点 | +| parentNode | 获取当前节点的父节点 | +| nextSibling | **下一个**节点 | +| previousSibling | **上一个**节点 | +| className | 用于获取或者设置标签的**class属性值** | +| innerHTML | 获取或者设置起始标签和结束标签**中的内容** | +| innerText | 获取或者设置起始标签和结束标签**中的文本** | + + + +# 5、jQuery + +## 5.1、介绍 + +**概述:**jQuery就是JavaScript和查询Query,辅助JavaScript开发的js类库 + +**核心思想:**写的更少,做的更多,所以它实现了很多浏览器的兼容问题 + +**优势:**免费、开源,语法设计可以使开发更加便捷 + +## 5.2、安装 + +### 从网页添加 + +可以通过多种方法在网页中添加 jQuery。 您可以使用以下方法: + +* 从 [jquery.com](http://jquery.com/download/) 下载 jQuery 库 +* 从 CDN 中载入 jQuery, 如从 Google 中加载 jQuery + +### 下载 jQuery + +有两个版本的 jQuery 可供下载: + +* Production version - 用于实际的网站中,已被精简和压缩。 +* Development version - 用于测试和开发(未压缩,是可读的代码) + +以上两个版本都可以从 [jquery.com](http://jquery.com/download/) 中下载。 + +jQuery 库是一个 JavaScript 文件,您可以使用 HTML 的 <**script**> 标签引用它: + +```html + + + +``` + +**提示:** 将下载的文件放在网页的同一目录下,就可以使用jQuery。 + +### 替代方案 + +如果您不希望下载并存放 jQuery,那么也可以通过 CDN(内容分发网络) 引用它。 + +```html + + + + + +``` + +## 5.3、初体验 + +```html + + + + + jQueryDemo + + + + + + + +``` + +## 5.4、jQuery核心函数 + +**$**是jQuery的核心函数,能完成很多功能,**$**()就是调用这个函数: + +1. 传入参数为函数时:表示页面加载完成以后,相当于window.onload = function () +2. 传入html字符串时:创建html标签对象 +3. 传入选择器字符串时: + 1. **$**(“#id属性值”),根据id查询标签对象。 + 2. **$**(“标签名”),根据标签名查询标签对象。 + 3. **$**(“.class”),根据class查询标签对象 +4. 传入DOM对象时:把DOM对象转换为jQuery对象 + +## 5.5、jQuery语法 + +jQuery 语法是通过选取 HTML 元素,并对选取的元素执行某些操作。 + +#### 基础语法: **$(selector\).action\()** + +* 美元符号定义 jQuery +* 选择符(selector)"查询"和"查找" HTML 元素 +* jQuery 的 action() 执行对元素的操作 + +#### 实例: + +* $(this).hide() - 隐藏当前元素 +* $("p").hide() - 隐藏所有 <**p**> 元素 +* $("p.test").hide() - 隐藏所有 class="test" 的 <**p**> 元素 +* $("#test").hide() - 隐藏 id="test" 的元素 + +#### 文档就绪事件 + +```html +$(document).ready(function(){ + + // 开始写 jQuery 代码... + +}); +``` + +这是为了防止文档在完全加载(就绪)之前运行 jQuery 代码,即在 DOM 加载完成后才可以对 DOM 进行操作。 + +**提示:**简洁写法(与以上写法效果相同): + +```html +$(function(){ + + // 开始写 jQuery 代码... + +}); +``` + +## 5.6、jQuery选择器 + +jQuery 选择器允许您对 HTML 元素组或单个元素进行操作。 + +jQuery 选择器基于元素的 id、类、类型、属性、属性值等"查找"(或选择)HTML 元素。 它基于已经存在的 CSS 选择器,除此之外,它还有一些自定义的选择器。 + +jQuery 中所有选择器都以美元符号开头:$()。 + +**实例** + +```html + + + + + jQueryDemo + + + + + +

1级标题

+

这是一个标题

+

这是一个段落。

+

这是另一个段落。

+ + + + +``` + +## 5.7、jQuery事件 + +jQuery 是为事件处理特别设计的。 + +页面对不同访问者的响应叫做事件。 + +事件处理程序指的是当 HTML 中发生某些事件时所调用的方法。 + +实例: + +* 在元素上移动鼠标。 +* 选取单选按钮 +* 点击元素 + +### jQuery 事件方法语法 + +```html +$("p").click(function(){//定义了一个事件 + // 动作触发后执行的代码!! +}); +``` + +### 常用事件 + +| 事件名 | 功能 | +| ------------ | ------------------------------------------------------------ | +| click() | click() 方法是当按钮点击事件,被触发时会调用一个函数。 | +| dblclick() | 当双击元素时,会发生 dblclick 事件。 | +| mouseenter() | 当鼠标指针穿过元素时,会发生 mouseenter 事件。 | +| mouseleave() | 当鼠标指针离开元素时,会发生 mouseleave 事件。 | +| mousedown() | 当鼠标指针移动到元素上方,并按下鼠标按键时,会发生 mousedown 事件。 | +| mouseup() | 当在元素上松开鼠标按钮时,会发生 mouseup 事件。 | +| hover() | hover()方法用于模拟光标悬停事件。当鼠标移动到元素上时,会触发指定的第一个函数(mouseenter);当鼠标移出这个元素时,会触发指定的第二个函数(mouseleave)。 | +| focus() | 当元素获得焦点时,发生 focus 事件。当通过鼠标点击选中元素或通过 tab 键定位到元素时,该元素就会获得焦点。 | +| blur() | 当元素失去焦点时,发生 blur 事件。 | +| keydown() | 当键盘键被按下时发生 keydown 事件。 | +| keypress() | 键按下的过程 | +| keyup() | 当键盘键被松开时发生 keyup 事件。 | +| submit() | 当提交表单时,会发生 submit 事件。 | +| change() | 当元素的值改变时发生 change 事件(仅适用于表单字段 | + +```html + + + + + jQueryDemo + + + + + + + +
+ mouseenter + + +``` + +## 5.8、jQuery效果 + +### 显示/隐藏 + +#### jQuery hide() 和 show() + +通过 jQuery,您可以使用 hide() 和 show() 方法来隐藏和显示 HTML 元素: + +参数: + +* speed 参数规定隐藏/显示的速度,可以取以下值:"slow"、"fast" 或毫秒。 +* callback 参数是隐藏或显示完成后所执行的函数名称。 + +```html + + + + + jQueryDemo + + + + +

如果你点击“隐藏” 按钮,我将会消失。

+ + + + +``` + +#### toggle() + +可以使用 toggle() 方法来切换 hide() 和 show() 方法。显示被隐藏的元素,并隐藏已显示的元素: + +```html + + + + + jQueryDemo + + + + +

如果你点击按钮,我将会消失/显示。

+ + + +``` + +### 淡入淡出 + +通过 jQuery,您可以实现元素的淡入淡出效果。 + +jQuery 拥有下面四种 fade 方法: + +| 方法 | 用途 | +| ------------ | ------------------------------------------------ | +| fadeIn() | 用于淡入已隐藏的元素 | +| fadeOut() | 用于淡出可见元素。 | +| fadeToggle() | 在 fadeIn() 与 fadeOut() 方法之间进行切换。 | +| fadeTo() | 允许渐变为给定的不透明度(值介于 0 与 1 之间)。 | + +* 可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。. + +* 可选的 callback 参数是 fading 完成后所执行的函数名称。 + +### 滑动 + +jQuery 拥有以下滑动方法: + +* slideDown():向下滑动元素 +* slideUp():用于向上滑动元素。 +* slideToggle():在 slideDown() 与 slideUp() 方法之间进行切换。 + +### 动画 + +jQuery animate() 方法用于创建自定义动画。 + +```html + + + + + + + + + + +

默认情况下,所有的 HTML 元素有一个静态的位置,且是不可移动的。 +如果需要改变,我们需要将元素的 position 属性设置为 relative, fixed, 或 absolute!

+
+
+ + + +``` + +* 必需的 params 参数定义形成动画的 CSS 属性。 +* 可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。 +* 可选的 callback 参数是动画完成后所执行的函数名称。 + +#### 使用相对值 + +```html + + + + + + + + + + +

默认情况下,所有的 HTML 元素有一个静态的位置,且是不可移动的。 +如果需要改变为,我们需要将元素的 position 属性设置为 relative, fixed, 或 absolute!

+
+
+ + + +``` + +#### 使用队列功能 + +```html + + + + + + + + + + +

默认情况下,所有的 HTML 元素有一个静态的位置,且是不可移动的。 +如果需要改变为,我们需要将元素的 position 属性设置为 relative, fixed, 或 absolute!

+
+
+ + + +``` + +#### 停止动画 + +jQuery stop() 方法用于停止动画或效果,在它们完成之前。 + +stop() 方法适用于所有 jQuery 效果函数,包括滑动、淡入淡出和自定义动画。 + +```html + + + + + + + + + + + + +
点我向下滑动面板
+
Hello world!
+ + + +``` + +## 5.9、链(Chaining) + +Chaining 允许我们在一条语句中运行多个 jQuery 方法(在相同的元素上) + +```html + + + + + + + + + +

菜鸟教程!!

+ + + + +``` + +## 5.10、获取内容和属性 + +### 获得内容 - text()、html() 以及 val() + +三个简单实用的用于 DOM 操作的 jQuery 方法: + +* text() - 设置或返回所选元素的文本内容 +* html() - 设置或返回所选元素的内容(包括 HTML 标记) +* val() - 设置或返回表单字段的值 + +### 获取属性 - attr() + +jQuery attr() 方法用于获取属性值。 + +下面的例子演示如何获得链接中 href 属性的值: + +## 5.12、添加元素 + +* append() - 在被选元素的结尾插入内容 +* prepend() - 在被选元素的开头插入内容 +* after() - 在被选元素之后插入内容 +* before() - 在被选元素之前插入内容 + +```html + + + + + + + + + +

这是一个段落。

+

这是另外一个段落。

+
    +
  1. List item 1
  2. +
  3. List item 2
  4. +
  5. List item 3
  6. +
+ + + + +``` + + + +## 5.13、删除元素 + +* remove() - 删除被选元素(及其子元素) +* empty() - 从被选元素中删除子元素 + +```html + + + + + + + + + +
+ +这是 div 中的一些文本。 +

这是在 div 中的一个段落。

+

这是在 div 中的另外一个段落。

+ +
+
+ + + + +``` + +## 5.14、获取并设置 CSS 类 + +* addClass() - 向被选元素添加一个或多个类 +* removeClass() - 从被选元素删除一个或多个类 +* toggleClass() - 对被选元素进行添加/删除类的切换操作 +* css() - 设置或返回样式属性 + +```html + + + + + + + + + + +

标题 1

+

标题 2

+

这是一个段落。

+

这是另外一个段落。

+
这是一些重要的文本!
+
+ + + + +``` + +## 5.15、css() 方法 + +设置或返回被选元素的一个或多个样式属性。 + +```html + + + + + + + + + +

这是一个标题

+

这是一个段落。

+

这是一个段落。

+

这是一个段落。

+ + + +``` + + + + + +# 6、XML + +XML 指可扩展标记语言(e**X**tensible **M**arkup **L**anguage)。被设计用来**传输和存储数据**。 + +HTML 被设计用来显示数据。 + +XML 不是 HTML 的替代。 + +XML 和 HTML 为不同的目的而设计: + +* XML 被设计用来传输和存储数据,其焦点是数据的内容。 +* HTML 被设计用来显示数据,其焦点是数据的外观。 + +### 6.1、用途 + +XML 应用于 Web 开发的许多方面,常用于简化数据的存储和共享。 + +### 6.2、XML 语法规则 + +1. **XML 文档必须有根元素,它是所有其他元素的父元素,比如以下实例中 root 就是根元素** + +```xml + + + ..... + + +``` + +2. **XML 声明文件的可选部分,如果存在需要放在文档的第一行** + +```xml + +``` + +3. **在 XML 中,省略关闭标签是非法的。所有元素都必须有关闭标签:** + +```xml +

This is a paragraph.

+
+``` + +声明不是 XML 文档本身的一部分,它没有关闭标签。 + +4. **XML 标签对大小写敏感。标签 <**Letter**> 与标签 <**letter**> 是不同的。** + +5. **在 XML 中,所有元素都必须彼此正确地嵌套:** + +```xml +This text is bold and italic +``` + +6. **在 XML 中,XML 的属性值必须加引号。** + +在 XML 中,一些字符拥有特殊的意义。如果您把字符 "<" 放在 XML 元素中,会发生错误,这是因为解析器会把它当作新元素的开始。为了避免这个错误,请用**实体引用**来代替特殊字符: + +| < | < | less than | +| ------ | ---- | -------------- | +| > | > | greater than | +| & | & | ampersand | +| ' | ' | apostrophe | +| " | " | quotation mark | + +7. **在 XML 中编写注释的语法与 HTML 的语法很相似。** + +8. **在 XML 中,文档中的空格不会被删减。** + +HTML 会把多个连续的空格字符裁减(合并)为一个,在 XML 中,文档中的空格不会被删减。 + +9. **XML 以 LF 存储换行。** + +在 Windows 应用程序中,换行通常以一对字符来存储:回车符(CR)和换行符(LF)。 + +在 Unix 和 Mac OSX 中,使用 LF 来存储新行。 + +在旧的 Mac 系统中,使用 CR 来存储新行。 + +XML 以 LF 存储换行。 + +### 6.3、XML元素 + +#### 概述 + +XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。 + +一个元素可以包含: + +* 其他元素 +* 文本 +* 属性 +* 或混合以上所有... + +#### 元素命名规则 + +XML 元素必须遵循以下命名规则: + +* 名称可以包含字母、数字以及其他的字符 +* 名称不能以数字或者标点符号开始 +* 名称不能以字母 xml(或者 XML、Xml 等等)开始 +* 名称不能包含空格 + +可使用任何名称,没有保留的字词。 + +### 6.4、XML解析 + +。。。。 + +# 7、Tomcat + +## 7.1、JavaWeb概述 + +### 概念 + +javaweb是指所有通过java语言编写的可以通过浏览器访问的程序的总称,叫javaweb + +javaweb是基于请求和响应来开发的 + +* 请求:客户端给服务器发送数据,叫请求request +* 响应:服务器给客户端回传数据,叫响应response + +请求和响应是成对出现的,有请求就有响应 + +![image-20210731113642368](https://i.loli.net/2021/07/31/9mdOKJ47HFMA3p8.png) + +### Web资源分类 + +根据实现的技术和呈现的效果不同,分为静态资源和动态资源 + +* 静态:html、css、js、txt、MP4视频、jpg图片 +* 动态:jsp页面、Servlet程序 + +### web服务器 + +其他常用的web服务器: + +1. Tomcat +2. Jboss + +3. GlassFish + +4. Resin + +5. Weblogic + +## 7.2、Tomcat概述 + +由Apache组织提出的一种web服务器,**提供对jsp和Servlet的支持**,是一种**轻量级**的javaweb容器(服务器),也是当前应用最广的javaweb服务器,并且**免费** + +> 与servlet版本对应关系 + +![image-20210731114737054](https://i.loli.net/2021/07/31/ZLPvEwz2aJoKVrR.png) + +Servlet程序从2.5版本是现在市面使用最多的版本(xml配置) + +Servlet3.0以后,就是注解的版本 + +## 7.3、Tomcat使用 + +### 安装 + +官网链接:http://tomcat.apache.org/ +选择download,找到自己所要的版本,下载对应版本的Tomcat。 + +有zip和exe两种格式的,zip(64-bit Windows zip(pgp,md5,sha1))是免安装版的,exe(32-bit/64-bit Windows Service installer(pgp,md5,sha1))是安装版。同时观察自己的电脑是64位系统还是32位系统。 + +![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/12dcf4419fef39c06c0fb63464792779.png) + +### 安装目录介绍 + +![image-20210731123357654](https://i.loli.net/2021/07/31/WLstoRXBVlYF6y9.png) + +### 启动 + +1. 方法1:双击bin目录下的startup.bat + +2. 方法2:在bin目录下打开命令行,输入catalina run命令 + +### 测试 + +打开浏览器,键入 http://localhost:8080 进入tomcat页面则表示安装成功 + +### 停止 + +双击bin目录下的shutdown.bat + +### 启动界面中文乱码 + +修改C:\apache-tomcat-10.0.8\conf\logging.properties文件中的java.util.logging.ConsoleHandler.encoding = UTF-8为java.util.logging.ConsoleHandler.encoding = GBK + +### 修改Tomcat端口号 + +mysql默认端口号3306 + +Tomcat默认端口号8080 + +修改C:\apache-tomcat-10.0.8\conf\server.xml + +修改 +``` + +启动时会根据path加载abc配置文件,根据dacBase在E盘找到book工程目录 + +访问: + +浏览器输入 http://localhost:8080/abc/index.html + +### 手拖浏览器页面打开和浏览器地址打开页面的区别 + +手拖使用的是file://协议 + +浏览器地址访问使用的是:http协议 + +### 默认工程访问 + +浏览器地址:http:/ip:port/ 没有工程名的时候,默认访问root工程 + +浏览器地址:http:/ip:port/工程名/ 没有资源名的时候,默认访问index.html页面 + +### IDEA配置Tmocat + +… + +# 8、Servlet + +## 8.1、概述 + +JavaWeb三大组件: + +1. Servlet程序 +2. Fliter过滤器 +3. Listener监听器 + +servlet是javaEE规范之一,规范就是接口 + +Servlet是运行在服务器上的一个java小程序,通过HTTP接收和响应客户端发送过来的请求 + +## 8.2、手动实现Servlet程序 + +1. 编写一个类去实现servlet接口 +2. 实现service方法,处理请求,并响应数据 +3. 到web.xml中去配置servlet程序的访问地址 + +web.xml配置文件: + +```xml + + + + + + Hello + + com.example.demo.Hello + + + + + Hello + + /h + + +``` + + + +### Tomcat 10.0.4 构建类servlet报错:类HelloServlet不是Servlet + +tomcat10有个最大的变动就是包名javax.servlet改成了"jakarta.servlet + +## 8.3、Servlet声明周期 + +1. 执行servlet构造器方法 +2. init方法 +3. service方法 +4. destory方法 + +1和2在第一次访问的时候创建servlet程序会调用 + +3是每次访问都会调用 + +4在web工程停止时调用 + +## 8.4、Get、Post请求分发处理 + +servelet接口实现类: + +```java +package com.example.demo; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; + +import java.io.IOException; + +public class Hello implements Servlet { + public Hello() { + System.out.println("构造函数"); + } + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + System.out.println("init"); + } + + @Override + public ServletConfig getServletConfig() { + return null; + } + + @Override + public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { +// 获取servletRequest的子类型,因为子类型中有getmethd方法 + HttpServletRequest hsr = (HttpServletRequest) servletRequest; + //获取请求的方式 + String method = hsr.getMethod(); + //输出请求方式是get还是post,根据不同的请求做不同的事情 + if ("GET".equals(method)) { + doGet(); + } else if ("POST".equals(method)) { + doPost(); + } + } + //post方法 + private void doPost() { + System.out.println("POST请求"); + } + //get方法 + private void doGet() { + System.out.println("get请求"); + } + + @Override + public String getServletInfo() { + return null; + } + + @Override + public void destroy() { + System.out.println("destory"); + + } +} +``` + +```html + + + + + Title + + +
+ +
+ + +``` + +```xml + + + + + + Hello + + com.example.demo.Hello + + + + + Hello + + /h + + +``` + +## 8.5、HttpServlet类 + +一般在实际开发项目中,都是使用继承httpservlet类的方式实现servlet程序 + +httpservlet简单,已经实现了get和post的分发处理 + +1. 继承httpservlet类 +2. 根据业务需要重写doGet或者doPost方法 + +```java +package com.example.demo; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class Hello2 extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("doget"); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("dopost"); + } +} +``` + +3. web.xml中配置servlet程序的访问地址 + +```xml + + + + + + hello2 + com.example.demo.Hello2 + + + hello2 + /h2 + + +``` + +4. 编写html + +```html + + + + + Title + + +
+ +
+ + +``` + +## 8.6、Servlet类继承体系 + +![image-20210731211509936](https://i.loli.net/2021/07/31/et1soMiyUY8fFPR.png) + +## 8.7、ServletConfig类 + +Servlet程序的配置信息类 + +作用: + +1. 获取Servlet程序的别名Servlet-name值 +2. 初始化init-param +3. 获取servletcontext对象 + +```java +public class Hello implements Servlet { + public Hello() { + System.out.println("构造函数"); + } + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + System.out.println("init"); + //1. 获取Servlet程序的别名Servlet-name值 + System.out.println("别名是:"+servletConfig.getServletName());//别名是:hello + //2. 初始化init-param + System.out.println("init-parm:"+servletConfig.getInitParameter("name1"));//init-parm:tian + //3. 获取ServletContext对象 + System.out.println(servletConfig.getServletContext()); + } + //此处略去其他的代码 +} +``` + +## 8.8、ServletContext类 + +* 是一个接口,表示Servlet上下文对象 +* 一个web工程只有一个servletcontext对象实例 +* servletcontext对象是一个域对象 + +### 域对象 + +域对象是可以像map一样存取数据的对象,域i指的是存取数据的操作范围 + +### 作用 + +1. 获取web.xml中配置的 上下文参数context-param +2. 获取当前的工程路径,格式:/工程路径 +3. 获取工程部署后在服务器硬盘上的绝对路径 +4. 像map一样存取数据 + +```java +package com.example.demo; + +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class Hello2 extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("doget"); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("dopost"); + //获取servletcontext对象 + ServletContext servletContext = getServletConfig().getServletContext(); + //获取上下文参数context-param + System.out.println(servletContext.getInitParameter("namespace"));; + //获取当前的工程路径,格式:/工程路径 + System.out.println(servletContext.getContextPath()); + //获取工程部署后在服务器硬盘上的绝对路径 + System.out.println(servletContext.getRealPath("/")); + } +} +``` + +## 8.9、HTTP协议 + +### http协议概述 + +客户端和服务器之间通信时,发送的数据,需要遵循的规则,叫HTTP协议 + +HTTP协议中的数据又叫报文 + +**HTTP协议格式** + +* 请求:客户端给服务器发送数据 + * get请求 + * post请求 +* 响应:服务器给客户端回传数据 + +### 请求的HTTP协议格式: + +#### GET请求 + +* 请求行 + * 请求的方式:GET + * 请求的资源路径[+?+请求参数] + * 请求的协议版本号 HTTP/1.1 +* 请求头:key:value组成,不同的键值对表示不同的涵义 + +![image-20210731222752388](https://i.loli.net/2021/07/31/VHux2IzkNrqaPv9.png) + +#### Post请求 + +* 请求行 + * 请求的方式:POST + * 请求的资源路径[+?+请求参数] + * 请求的协议版本号 HTTP/1.1 +* 请求头 + * key:value组成,不同的键值对表示不同的涵义 +* 空行 +* 请求体:发送给服务器的数据 + +![image-20210731223112742](https://i.loli.net/2021/07/31/86rKuwIFHBvNRsy.png) + +#### 常用请求头说明 + +![image-20210731223237993](https://i.loli.net/2021/07/31/YIEvoh4SmXNpiJA.png) + +#### 请求区分 + +**GET请求:** + +* form标签 method=get +* a标签 +* link标签引入css +* Script标签引入js文件 +* img标签引入图片 +* iframe引入html页面 +* 在浏览器地址栏输入地址后敲回车 + +**POST请求** + +* form标签 method=post + +### 响应的HTTP协议格式 + +* 响应行 + * 响应的协议和版本号 + * 响应状态码 + * 响应状态描述符 +* 响应头 + * key:value 不同的响应头有不同的涵义 +* 空行 +* 响应体: 回传给客户端的数据 + +![image-20210731224119102](https://i.loli.net/2021/07/31/coN1dhXbDsuBmw8.png) + +#### 常见的响应码说明 + +* 200:表示请求成功 +* 302:表示请求重定向 +* 404:表示请求服务器已经收到了,但是要的数据不存在(请求地址错误) +* 500:表示服务器已经收到请求,但是服务器内部错误(代码错误) + +## 8.10、HttpServletRequest类 + +### 作用 + +每次只要有请求进入tomcat服务器,服务器就会把请求过来的HTTP协议信息解析好封装到Request对象中,然后传递到service方法(doGet和doPost)中给我们使用,可以通过HttpServletRequest对象获取到所有请求的信息 + +### 常用方法 + +| 方法 | 功能 | +| ------------------- | ------------------------------------ | +| req.getRequestURI() | 获取请求的资源路径 | +| req.getRequestURL() | 获取请求的统一资源定位符(绝对路径) | +| req.getRemoteHost() | 获取客户端的ip地址 | +| req.getHeader() | 获取请求头 | +| req.getMethod() | 获取请求的方式GET或者POST | +| req.getParameter() | 获取请求参数 | + +```java +package com.example.servletdemo; + + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class RequestServletDemo extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + //i.getRequestURI() 获取请求的资源路径 + System.out.println(req.getRequestURI()); // /Request_servlet/r + //i.getRequestURL() 获取请求的统一资源定位符(绝对路径) + System.out.println(req.getRequestURL()); // http://localhost:8080/Request_servlet/r + //i.getRemoteHost() 获取客户端的ip地址 + System.out.println(req.getRemoteHost()); + //i.getHeader() 获取请求头 + System.out.println(req.getHeader("USER-Agent")); + //i.getMethod() 获取请求的方式GET或者POST + System.out.println(req.getMethod()); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + super.doPost(req, resp); + } +} +``` + +#### req.getParameter() + +```java +package com.example.servletdemo; + + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class RequestServletDemo extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String username = req.getParameter("username"); + String password = req.getParameter("password"); +// String hobby = req.getParameter("hobby"); //适用于单个值 + String[] hobbies = req.getParameterValues("hobby"); //适用于多个值 + System.out.println(username); + System.out.println(password); + for (String hobby : hobbies) { + System.out.println(hobby); + } + } +} +``` + +```html + + + + + Title + + +
+ 用户名:
+ 密码:
+ 兴趣爱好:C++ + C + Java
+ +
+ + +``` + +### post请求中文乱码问题 + +在dopost函数中设置请求体的字符集为UTF-8 + +```java +protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + //设置设置请求体的字符集为UTF-8,解决post请求的中文乱码问题 + req.setCharacterEncoding("UTF-8"); + //省略其他 +} +``` + +### 请求转发 + +服务器收到请求后从一个资源跳转到另一个资源 + +![image-20210803152530155](https://gitee.com/tianzhendong/img/raw/master//images/image-20210803152530155.png) + +特点: + +1. 浏览器地址栏没有发生变化 +2. 是一次请求 +3. 共享request域中的数据 +4. 可以转发到web-inf目录下 +5. 不可以访问工程以外的目录 + +Servlet1: + +```java +package com.example.javaWeb; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author TianZhendong + * @date 2021/8/3 + */ +public class Servlet1 extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // 获取请求的参数,(办事的材料)查看 + String username = req.getParameter("username"); + System.out.println("请求的参数为:" + username); + // 给材料盖章 + req.setAttribute("key1", "servlet1"); + // 获取转发的地址 + /* + * 请求转发必须以斜杠开头 + * / 表示http://ip:port/工程名/ + * */ + RequestDispatcher requestDispatcher = req.getRequestDispatcher("/s2"); + // 转向servlet2 + requestDispatcher.forward(req, resp); + } +} +``` + +servlet2: + +```java +package com.example.javaWeb; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author TianZhendong + * @date 2021/8/3 + */ +public class Servlet2 extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + // 获取请求参数 + String username = req.getParameter("username"); + System.out.println("请求参数为:" + username); + // 查看柜台1的章 + Object key1 = req.getAttribute("key1"); + System.out.println("是否有柜台1的章:" + key1); + // 处理请求 + System.out.println("处理业务,这里省略"); + } +} +``` + +web.xml + +```xml + + servlet1 + com.example.javaWeb.Servlet1 + + + servlet2 + com.example.javaWeb.Servlet2 + + + servlet1 + /s1 + + + servlet2 + /s2 + +``` + +### base标签的作用 + +设置页面相对路径工作时参照的地址,href属性就是参数的地址值 + +```html + + + +``` + +### web中/意义 + +在web中/是一种绝对路径 + +* 被浏览器解析得到:http://ip:port/ + +```html +斜杠 +``` + +* 被服务器解析得到:http://ip:port/工程路径 + +```xml +/s +``` + +```java +servletContext.getRealPath("/"); +``` + +## 8.11、HttpServletResponse类 + +### 概述 + +和HttpServletRequest一样,每次请求进来,tomcat服务器都会创建一个response对象传递给servlet程序使用,HttpServletRequest表示请求过来的信息,HttpServletResponse表示相应的信息 + +### 两个输出流 + +* 字节流:getOutputStream();常用有下载(传递二进制数据) +* 字符流:getWriter()常用于回传字符串(常用) + +两个流同时只能使用一个 + +### 往客户端回传数据 + +```java +package com.example.javaWeb; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @author TianZhendong + * @date 2021/8/3 + */ +public class ResponseIoServlet extends HttpServlet { + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + // 设置服务器和客户端使用UTF-8字符集,解决乱码问题 + response.setContentType("text/html; charset=UTF-8"); + System.out.println(response.getCharacterEncoding()); + + // 回传数据 + PrintWriter writer = response.getWriter(); + writer.write("田真帅"); + } +} +``` + +### 请求重定向 + +#### 概念 + +客户端给服务器发送请求,服务器给客户端一些地址,让客户端去新地址访问(之前的地址可能被废弃) + +![image-20210803170323153](https://gitee.com/tianzhendong/img/raw/master//images/image-20210803170323153.png) + +#### 特点 + +1. 浏览器地址栏有变化 +2. 两次请求 +3. 不共享resquest域中的数据 +4. 不能访问web-inf下的资源 +5. 可以访问工程以外的数据,如百度 + +#### 使用 + +resonse1 + +```java +package com.example.javaWeb; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author TianZhendong + * @date 2021/8/3 + */ +public class Response1 extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("访问了源地址response1"); + // 设置相应码的状态,表示重定向 + resp.setStatus(302); + // 设置响应头,说明新的地址在哪里 + resp.setHeader("Location", "http://localhost:8080/demo1/response2"); + } +} +``` + +response2 + +```java +package com.example.javaWeb; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author TianZhendong + * @date 2021/8/3 + */ +public class Response1 extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("访问了源地址response1"); + // 设置相应码的状态,表示重定向 + resp.setStatus(302); + // 设置响应头,说明新的地址在哪里 + // 可以访问 + resp.setHeader("Location", "http://www.baidu.com"); + // resp.setHeader("Location", "http://localhost:8080/demo1/response2"); + } +} +``` + +#### 简单写法sendRedirect() + +```java +package com.example.javaWeb; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author TianZhendong + * @date 2021/8/3 + */ +public class Response1 extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("访问了源地址response1"); + resp.sendRedirect("http://www.baidu.com"); + } +} +``` + +# 9、jsp + +## 9.1、概述 + +全称是java serverpages:java服务器页面,用于动态web + +作用:代替servlet程序回传html页面的数据 + +已逐渐被淘汰 + +> 特点 + +* jsp页面可以嵌入java代码,为用户提供动态数据 +* html只提供静态 + +> 原理 + +* 浏览器向服务器发送请求,不管访问什么资源,都是在访问servlet +* jsp最终会被转换成一个java类 +* JSP本质上就是一个servlet +* jsp页面中的java代码会原封不动的输出,html代码会被转换为`out.write("\r\n")` + +> 依赖 + +```xml + + + + javax.servlet + servlet-api + 2.5 + + + + javax.servlet.jsp + javax.servlet.jsp-api + 2.3.3 + + + + javax.servlet.jsp.jstl + jstl-api + 1.2 + + + + taglibs + standard + 1.1.2 + + +``` + +## 9.2、JSP语法 + +> JSP表达式 + +```jsp +<%= 表达式 %> +``` + +```jsp +

+ 今天的日期是: <%= (new java.util.Date()).toLocaleString()%> +

+``` + +> JSP注释 + +```jsp +<%-- 该部分注释在网页中不会被显示--%> +``` + +> JSP脚本程序 + +脚本程序可以包含任意量的Java语句、变量、方法或表达式,只要它们在脚本语言中是有效的。 + +脚本程序的语法格式: + +```jsp +<% 代码片段 %> +``` + +```jsp +<% +out.println("Your IP address is " + request.getRemoteAddr()); +%> +``` + +> JSP声明 + +一个声明语句可以声明一个或多个变量、方法,供后面的Java代码使用。在JSP文件中,您必须先声明这些变量和方法然后才能使用它们。 + +JSP声明的语法格式: + +```jsp +<%! declaration; [ declaration; ]+ ... %> +``` + +```jsp +<%! int i = 0; %> +<%! int a, b, c; %> +<%! Circle a = new Circle(2.0); %> +``` + + + + + + + + + + + + + + + + + + + + + + + +## 9.2、EL表达式 + +expression language表达式语言 + +作用:代替jsp页面中的表达式脚本在jsp页面中进行数据的输出 + +## 9.3、jstl标签库 + +jsp标准标签库,替换jsp中的代码脚本,使得jsp页面变得更加简洁 + +# 10、 文件的上传和下载 + +## 10.1、文件的上传 + +1. 有一个form标签,method=post请求 +2. form标签的encType属性值必须为multipart/form-data值 +3. 在form标签中使用input type=file添加上传的文件 +4. 编写服务器代码接收,处理上传的数据 + +## 10.2、文件的下载 + + + +# 11、Cookie和Session + +## 11.1、Cookie + +### 概述 + +服务器通知客户端保存键值对的一种技术 + +客户端有了cookie后,每次请求都发送给服务器 + +每个Cookie的大小不能超过4kb + +### 使用 + + + +## 11.2、Session + +- Session就一个接口(HttpSession) +- Session就是会话,用来伟华客户端和服务器之间关联的一种技术 +- 每个客户端都有自己的一个Session会话 +- Session会话中,经常用来保存用户登陆之后的信息 + +# 12、Filter过滤器 + +## 概述 + +* javaweb三个组件之一,servlet、listener、filter +* filter过滤器是javaee的规范,也就是接口 +* 作用:拦截请求,过滤响应 + +# 13、json + +## 13.1、概述 + +JSON(javascript object notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。JSON采用完全独立于语言的文本格式,很多语言都提供了对json的支持,使得json成为理想的数据交换格式 + +轻量级指的是跟xml作比较 + +数据交换指的是客户端和服务器i之间业务数据的传输格式 + +## 13.2、JSON语法 + +JSON 语法是 JavaScript 语法的子集。 + +### JSON 语法规则 + +JSON 语法是 JavaScript 对象表示语法的子集。 + +- 数据在名称/值对中 +- 数据由逗号分隔 +- 大括号 **{}** 保存对象 +- 中括号 **[]** 保存数组,数组可以包含多个对象 + +### JSON 值 + +JSON 值可以是: + +- 数字(整数或浮点数) +- 字符串(在双引号中) +- 逻辑值(true 或 false) +- 数组(在中括号中) +- 对象(在大括号中) +- null + +```html + + + + + JSON + + +

+ 姓名:
+ 年龄:
+ 学校:
+

+ + + +``` + +![image-20210804154554069](https://gitee.com/tianzhendong/img/raw/master//images/image-20210804154554069.png) + +## 13.3、JSON vs XML + +JSON 和 XML 都用于接收 web 服务端的数据。 + +JSON 和 XML在写法上有所不同,如下所示: + +```json +{ + "sites": [ + { "name":"菜鸟教程" , "url":"www.runoob.com" }, + { "name":"google" , "url":"www.google.com" }, + { "name":"微博" , "url":"www.weibo.com" } + ] +} +``` + +```xml + + + 菜鸟教程 www.runoob.com + + + google www.google.com + + + 微博 www.weibo.com + + +``` + +## 13.4、JSON.parse() + +JSON字符串转换为JSON对象 + +JSON 通常用于与服务端交换数据。 + +在接收服务器数据时一般是字符串。 + +我们可以使用 JSON.parse() 方法将数据转换为 JavaScript 对象。 + +```json +JSON.parse(text[, reviver]) +``` + +**参数说明:** + +- **text:**必需, 一个有效的 JSON 字符串。 +- **reviver:** 可选,一个转换结果的函数, 将为对象的每个成员调用此函数。 + +```js +var obj = JSON.parse('{ "name":"runoob", "alexa":10000, "site":"www.runoob.com" }'); +``` + +## 13.5、JSON.stringify() + +JSON对象转换为JSON字符串 + +例如我们向服务器发送以下数据 + +```js +var obj = { "name":"runoob", "alexa":10000, "site":"www.runoob.com"}; +var myJSON = JSON.stringify(obj); +document.getElementById("demo").innerHTML = myJSON; +``` + +## 13.6、JSON使用 + +```html + + + + +菜鸟教程(runoob.com) + + +

从 JSON 字符串中创建对象

+

+网站名:
+网站地址:
+

+ + + +``` + +## 13.7、JSON在java中使用 + +### JavaBean和JSON转换 + +* toJson()方法可以把java对象转换成json字符串 + +* fromJson()相反 + +```java +Person person = new Person(1, "测试"); +//创建GSON对象实例 +Gson gson = new Gson(); +//java对象转换成json字符串 +String s = gson.toJson(person); +//json字符串转换为java对象 +Person person1 = gson.fronJson(s,Person.class); +``` + +# 14、AJAX请求 + +## 14.1、概述 + +AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。 + +AJAX 不是新的编程语言,而是一种使用现有标准的新方法。 + +AJAX 最大的优点是在**不重新加载整个页面的情况下**,可以与服务器交换数据并更新部分网页内容。 + +AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。 + +**浏览器通过js异步发起请求,局部更新页面的技术** + +## 14.2、使用 + +* 第一步:创建 XMLHttpRequest 对象 + +XMLHttpRequest 用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。 + +```js +var xmlhttp; +if (window.XMLHttpRequest) +{ + // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码 + xmlhttp=new XMLHttpRequest(); +} +else +{ + // IE6, IE5 浏览器执行代码 + xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); +} +``` + +* 第二步:向服务器发送请求 + +XMLHttpRequest 对象用于和服务器交换数据。 + +如需将请求发送到服务器,我们使用 **XMLHttpRequest** 对象的 **open()** 和 **send()** 方法: + +```js +xmlhttp.open("GET","ajax_info.txt",true); +xmlhttp.send(); +``` + +| 方法 | 描述 | +| :--------------------------- | :----------------------------------------------------------- | +| open(*method*,*url*,*async*) | 规定请求的类型、URL 以及是否异步处理请求。*method*:请求的类型;GET 或 POST*url*:文件在服务器上的位置*async*:true(异步)或 false(同步) | +| send(*string*) | 将请求发送到服务器。*string*:仅用于 POST 请求 | + +如果来自服务器的响应并非 XML,请使用 responseText 属性。 + +responseText 属性返回字符串形式的响应,因此您可以这样使用: + +```js +document.getElementById("myDiv").innerHTML=xmlhttp.responseText; +``` + +如果来自服务器的响应是 XML,而且需要作为 XML 对象进行解析,请使用 responseXML 属性: + +```js +xmlDoc=xmlhttp.responseXML; +txt=""; +x=xmlDoc.getElementsByTagName("ARTIST"); +for (i=0;i"; +} +document.getElementById("myDiv").innerHTML=txt; +``` + diff --git a/source/_posts/Java/MySql.md b/source/_posts/Java/MySql.md new file mode 100644 index 0000000..e4dc01c --- /dev/null +++ b/source/_posts/Java/MySql.md @@ -0,0 +1,2091 @@ +--- +title: MySql +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: 《MySQL》必知必会学习笔记 +tags: + - 数据库 + - MySql + - 学习笔记 +categories: + - 数据库 +reprintPolicy: cc_by +abbrlink: 6cac71de +date: 2022-04-29 10:45:40 +coverImg: +img: +password: +--- + + + +[toc] + +# 1、了解MySQL + +## 数据库(DB,DataBase) + +保存有组织的数据的容器(通常是一个文件或者一组文件) + +注:数据库不同于数据库软件,数据库软件应成为DBMS(数据库管理系统) + +数据库分类: + +* 关系型数据库SQL:通过表和表之间,行和列之间的关系进行数据的存储 + * MySQL + * Oracle + * Sql Server + * DB2 + * SQLlite +* 非关系型数据库NoSQL(Not Only SQL):对象存储,通过对象的自身的属性决定 + * Redis + * MongDB + +### 表 + +某种特定类型数据的结构化清单 + +### 列和数据类型 + +列:表中的一个字段。所有表都是由一个或多个列组成的。 + +数据类型(datatype) 所容许的数据的类型。每个表列都有相应的数据类型,它限制(或容许)该列中存储的数据。 + +### 行 + +行(row) 表中的一个记录。 + +### 主键 + +主键(primary key) 一列(或一组列),其值能够唯一区分表中每个行。 + +## SQL + +### SQL简介 + +SQL(发音为字母S-Q-L或sequel)是结构化查询语言(Structured QueryLanguage)的缩写。 SQL是一种专门用来与数据库通信的语言 + +数据的所有存储、检索、管理和处理实际上是由数据库软件——DBMS( 数据库管理系统)完成的。 MySQL是一种DBMS,即它是一种数据库软件。 + +### 客户机-服务器软件 + +DBMS可分为两类:一类为基于共享文件系统的DBMS,另一类为基于客户机—服务器的DBMS。 前者(包括诸如Microsoft Access和FileMaker)用于桌面用途,通常不用于高端或更关键的应用。 + +MySQL、 Oracle以及Microsoft SQL Server等数据库是基于客户机—服务器的数据库。客户机—服务器应用分为两个不同的部分。 服务器部分是负责所有数据访问和处理的一个软件。这个软件运行在称为数据库服务器的计算机上。 +与数据文件打交道的只有服务器软件。关于数据、数据添加、删除和数据更新的所有请求都由服务器软件完成。这些请求或更改来自运行客户机软件的计算机。 客户机是与用户打交道的软件。例如,如果你请求一个按字母顺序列出的产品表,则客户机软件通过网络提交该请求给服务器软件。服务器软件处理这个请求,根据需要过滤、丢弃和排序数据;然后把结果送回到你的客户机软件。 + +客户机和服务器软件可能安装在两台计算机或一台计算机上。不管它们在不在相同的计算机上,为进行所有数据库交互,客户机软件都要与服务器软件进行通信 + +## MySQL + +MySQL是一个关系型数据库管理系统,属于Oracle旗下产品,关系数据库将数据保存在不同的表中 + +* 是最好的RDBMS(关系数据库管理系统)应用软件之一 +* 开源 +* 体积小、速度快,总体拥有成本低 + +# 2、使用MySQL + +为了连接到MySQL,需要以下信息: + +* 主机名(计算机名)——如果连接到本地MySQL服务器, 为localhost; +* 端口(如果使用默认端口3306之外的端口); +* 一个合法的用户名; +* 用户口令(如果需要)。 + + +```sql +--使用root登录 +mysql -u root -p; + +--显示可用的数据库列表 +SHOW databases; + +--选择数据库 +USE crashcourse; + +--获得一个数据库内的表的列表 +SHOW tables; + +--显示数据库中所有表的信息 +describe student; + +--创建一个数据库 +create databases school; + +--显示表列 +SHOW columns from customers; + +--显示广泛的服务器状态信息 +SHOW status; + +--分别用来显示创建特定数据库或表的MySQL语句 +SHOW create database; +SHOW create table; + +--显示授予用户(所有用户或特定用户)的安全权限 +SHOW Grants; + +--显示服务器错误或警告消息 +SHOW errors; +SHOW warnings; + +--推出连接 +exit; +--表示单行注释 +/* +多行注释 +*/ +``` + +# 3、操作数据库 + +操作数据库——>操作数据库中的表——>操作数据库中表的数据 + +## 操作数据库 + +```sql +--增加数据库school1(在不存在时 +CREATE DATABASES IF NOT EXISTS school1; + +--删除数据库 +DROP DATABASE IF EXISTS school1; + +--使用数据库 +USE school; + +--查看所有的数据库 +SHOW DATABASES; +``` + +## 操作数据库表 + +### 创建数据库表 + +```sql +--创建数据库表 +CREATE TABLE [IF NOT EXISTS] `表名`( + `字段名` 列类型 [属性] [索引] [注释], + `字段名` 列类型 [属性] [索引] [注释], + `字段名` 列类型 [属性] [索引] [注释] +)[表类型] [字符集设置] [注释] +--实例 +CREATE TABLE `student` ( + `id` int NOT NULL COMMENT '学员id', + `name` varchar(100) NOT NULL COMMENT '学员姓名', + `age` int NOT NULL COMMENT '学员年龄', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 + +--查看创建数据表table的语句 +SHOW CREATE TABLE student; + +--显示表的结构 +DESC student; +``` + +ENGINE=InnoDB 表示采用的数据库引擎 + +数据库引擎主要有: + +* INNODB:安全性高,支持事务处理和多表多用户操作 +* MYISAM:节约空间,速度快 + +| | MYISAM | INNODB | +| ---------- | ------ | ------------- | +| 事务支持 | 不支持 | 支持 | +| 数据行锁定 | 不支持 | 支持 | +| 外键 | 不支持 | 支持 | +| 全文索引 | 支持 | 不支持 | +| 表空间大小 | 较小 | 较大,约为2倍 | + +所有的数据库文件都储存在data目录下,本质还是文件的存储 + +### 修改删除表 + +```sql +--修改表名 +ALTER TABLE teacher RENAME AS teacher1; + +--增加表的字段 表名 ADD 字段名 列属性 +ALTER TABLE teacher1 ADD age INT(11); + +--修改表的字段,重命名,修改约束 +ALTER TABLE teacher1 MODIFY age VARCHAR(11); --修改约束:列属性 +ALTER TABLE teacher1 CHANGE age age1 INT(1); --修改约束:重命名 + +--删除表的字段 +ALTER TABLE teacher1 DROP age1; + +--删除表 +DROP TABLE IF EXISTS teacher1; +``` + +MODIFY和CHANGE区别: + +* change用来字段重命名 +* modify不用来字段重命名,只能修改字段类型和约束 + +# 4、MySQL数据管理 + +## 外键 + +### 物理外键 + +数据库级别的外键,不建议使用,数据库之间有关联,容易导致混乱 + +1. 创建表时添加外键 + +```sql +CREATE TABLE `student` ( + `id` int NOT NULL COMMENT '学员id', + `name` varchar(100) NOT NULL COMMENT '学员姓名', + `age` int NOT NULL COMMENT '学员年龄', + `gradeid` int NOT NULL COMMENT `年级id` --年级表的主键 + PRIMARY KEY (`id`), --主键 + KEY `FK_gradeid` (`gradeid`), --外键 + CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`) --给外键添加约束 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 +``` + +2. 创建后添加外键 + +```sql +ALTER TABLE `student` +ADD CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`) +``` + +### 最佳实践 + +* 数据库就是单纯的表,只用来存储数据,只有行(数据)和列(字段) +* 使用程序去实现外键功能 + +# 5、DML语言 + +数据库操作语言 + +* insert添加 +* update修改 +* delete删除 + +## insert添加 + +```sql +--插入语句 +--insert into `表名` (`字段`) values('value1'),('value2')... +INSERT INTO `student`('name') +VALUES ('zhansan'), +('lisi'); + +INSERT INTO `student`('name','sex') +VALUES ('zhansan','男'), +('lisi','男'); +``` + +字段可以省略 + +### 插入完整的行 + +```sql +INSERT INTO customers(cust_name, + cust_contact, + cust_email, + cust_address, + cust_city, + cust_state, + cust_zip, + cust_country) +VALUES('Pep E.LaPew', + NULL, + NULL, + '100 Main Street', + 'Los Angeles', + 'CA', + '90046', + 'USA'); +``` + +### 插入多个列 + +如果插入的顺序相同,那么可以在VALUES后再加一个括号,把另外的数据加上 + +### 插入检索出的数据 + +INSERT SELECT + +```sql +--使用INSERT SELECT从custnew中将所有数据导入customers +INSERT INTO customers(cust_name, + cust_contact, + cust_email, + cust_address, + cust_city, + cust_state, + cust_zip, + cust_country) +SELECT cust_name, + cust_contact, + cust_email, + cust_address, + cust_city, + cust_state, + cust_zip, + cust_country +FROM custnew; +``` + +## update修改 + +```sql +--修改单个属性 +UPDATE `student` --修改的表 +SET `name`='tianzhendong' --修改的内容 +WHERE id=1; --修改的具体行,不指定条件会修改所有的行 + +--修改多个属性 +UPDATE `student` +SET `name`='tianzhendong',`email`='xxxx@qq.com' --修改的内容 +WHERE id=1; + +--更新10005客户的邮件地址 +UPDATE customers +SET cust_email='elmer@fudd.com' +WHERE cust_id = 10005; + +--更新多个列 +UPDATE customers +SET cust_email='120@fudd.com', + cust_name = 'tian' +WHERE cust_id = 10005; + +--删除某个列的值,可设置其为NULL +UPDATE customers +SET cust_email=NULL +WHERE cust_id = 10005; +``` + +## 删除 + +### delete + +```sql +--删除数据(避免这样写,会全部删除) +DELETE FROM `student`; + +--删除指定数据 +DELETE FROM `student` +WHERE id = 1; +``` + +### truncate + +可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更快( TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。 + +```sql +--清空student表,表的结构和索引约束不会变 +TRUNCATE `student`; +``` + +### 区别 + +* 相同点:都能删除数据,都不会删除表结构 + +* 不同: + * truncate**重新设置自增列,计数器归零**,delete不会影响自增 + * truncate不会影响事务 + +# 6、DQL查询数据 + +## 检索数据 + +```sql +--检索单个列 +SELECT prod_name +FROM products; + +--检索多个列 +SELECT prod_id, prod_name, prod_price +FROM products; + +--检索所有列 +SELECT * +FROM products; + +--只返回列中不同(唯一)的vend_id行 +SELECT DISTINCT vend_id +FROM products; + +--返回第一行或前几行 +SELECT prod_name +FROM products +LIMIT 5; + +--返回从行5开始的5行,检索出来的第一行为行0而不是行1。因此, LIMIT 1, 1将检索出第二行而不是第一行。如果没有足够的行只返回它能返回的那么多行。 +SELECT prod_name +FROM products +LIMIT 5,5; + +--使用完全限定的名字来引用列(同时使用表名和列字) +SELECT products.prod_name +FROM crashcourse.products; +``` + +多条SQL语句必须以分号(;)分隔。 MySQL如同多数DBMS一样,不需要在单条SQL语句后加分号。但特定的DBMS可能必须在单条SQL语句后加上分号。当然,如果愿意可以总是加上分号。事实上,即使不一定需要,但加上分号肯定没有坏处。如果你使用的是mysql命令行,必须加上分号来结束SQL语句。 + +SQL语句不区分大小写,因此SELECT与select是相同的。同样,写成Select也没有关系。 +许多SQL开发人员喜欢对所有SQL关键字使用大写,而对所有列和表名使用小写,这样做使代码更易于阅读和调试。 + +在处理SQL语句时,其中所有空格都被忽略。 SQL语句可以在一行上给出,也可以分成许多行。多数SQL开发人 +员认为将SQL语句分成多行更容易阅读和调试 + +## 排序检索数据 + +子句(clause) SQL语句由子句构成,有些子句是必需的,而有的是可选的。一个子句通常由一个关键字和所提供的数据组成。子句的例子有SELECT语句的FROM子句。 + +为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。 +ORDER BY子句取一个或多个列的名字,据此对输出进行排序。 + +```java +--指示MySQL对prod_name列以字母顺序排序数据 +SELECT prod_name +FROM products +ORDER BY prod_name; + +--检索3个列,并按其中两个列对结果进行排序——首先按价格,然后再按名称排序 +SELECT prod_id, prod_price, prod_name +FROM products +ORDER BY prod_price, prod_name;--仅在多个行具有相同的prod_price值时才对产品按prod_name进行排序 + +--按价格以降序排序产品(最贵的排在最前面) +SELECT prod_id, prod_price, prod_name +FROM products +ORDER BY prod_price DESC; + +--DESC关键字只应用到直接位于其前面的列名。 +SELECT prod_id, prod_price, prod_name +FROM products +ORDER BY prod_price DESC, prod_name; +--在上例中,只对prod_price列指定DESC,对prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(在每个价格内)仍然按标准的升序排序 + +--使用ORDER BY和LIMIT的组合,能够找出一个列中最高或最低的值 +SELECT prod_id, prod_price, prod_name +FROM products +ORDER BY prod_price DESC +LIMIT 1; +``` + +与DESC相反的关键字是ASC(ASCENDING), 在升序排序时可以指定它。 +但实际上, ASC没有多大用处,因为升序是默认的(如果既不指定ASC也不指定DESC,则假定为ASC) + +在字典( dictionary)排序顺序中,A被视为与a相同,这是MySQL(和大多数数据库管理系统)的默认行为。但是,许多数据库管理员能够在需要时改变这种行为(如果你的数据库包含大量外语字符,可能必须这样做)。 + +## 过滤数据 + +在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后, 否则将会产生错误 + +```JAVA +--在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。 +SELECT prod_price, prod_name +FROM products +WHERE prod_price = 2.50; + +--在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。 +SELECT prod_price, prod_name +FROM products +WHERE prod_price != 2.50;--等同于WHERE prod_price <> 2.50; + +--检索价格在5美元和10美元之间的所有产品 +SELECT prod_price, prod_name +FROM products +WHERE prod_price BETWEEN 5 AND 10; + +--返回没有价格(空prod_price字段,不是价格为0)的所有产品 +SELECT prod_price, prod_name +FROM products +WHERE prod_price BETWEEN IS NULL; +``` + +## 组合WHERE子句 + +```java +--AND OR IN +--检索由供应商1003制造且价格小于等于10美元的所有产品的名称和价格 +SELECT prod_id, prod_price, prod_name +FROM products +WHERE vend_id = 1003 AND prod_id <=10; + +--检索由任一个指定供应商制造的所有产品的产品名和价格 +SELECT prod_id, prod_price, prod_name +FROM products +WHERE vend_id = 1003 OR vend_id = 1002; + +--IN和OR具有相同的功能 +--检索供应商1002和1003制造的所有产品 +SELECT prod_id, prod_price, prod_name +FROM products +WHERE vend_id in(1002,1003) +ORDER BY prod_name; + +-- SQL(像多数语言一样)在处理OR操作符前,优先处理AND操作符。组合使用时应使用圆括号明确地分组相应的操作符 +SELECT prod_name, prod_price +FROM products +WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price>=10; + +--NOT NOT否定跟在它之后的条件, +-- 匹 配 1002 和 1003 之 外 供 应 商 的vend_id +--和!= <>好像没有区别,只是not适用于复杂的语句 +SELECT prod_id, prod_price, prod_name +FROM products +WHERE vend_id NOT IN (1002,1003) +ORDER BY prod_name; +``` + +## 用通配符进行过滤 + +前面介绍的所有操作符都是针对已知值进行过滤的。不管是匹配一个还是多个值,测试大于还是小于已知值,或者检查某个范围的值,共同点是过滤中使用的值都是已知的。但是,这种过滤方法并不是任何时候都好用。例如,怎样搜索产品名中包含文本anvil的所有产品?用简单的比较操作符肯定不行,必须使用通配符。利用通配符可创建比较特定数据的搜索模式。在这个例子中,如果你想找出名称包含anvil的所有产品,可构造一个通配符搜索模式,找出产品名中任何位置出现anvil的产品。 + +通配符:(wildcard) 用来匹配值的一部分的特殊字符。 + +搜索模式(search pattern)由字面值、通配符或两者组合构成的搜索条件。 + +为在搜索子句中使用通配符,必须使用LIKE操作符。 LIKE指示MySQL,后跟的搜索模式利用通配符匹配而不是直接相等匹配进行比较。 + +谓词 操作符何时不是操作符?答案是在它作为谓词( predicate)时。从技术上说, LIKE是谓词而不是操作符。 + +```java +--百分号( %)通配符 +--在搜索串中, %表示任何字符出现任意次数。 +--检索任意以jet起头的词。 %告诉MySQL接受jet之后的任意字符,不管它有多少字符。 +SELECT prod_id, prod_name +FROM products +WHERE prod_name LIKE 'jet%'; + +--匹配任何位置包含文本anvil的值,而不论它之前或之后出现什么字符。 +SELECT prod_id, prod_name +FROM products +WHERE prod_name LIKE '%anvil%'; + +--找出以s起头以e结尾的所有产品 +SELECT prod_id, prod_name +FROM products +WHERE prod_name LIKE 's%e'; + +--下划线( _)通配符 +--下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。_总是匹配一个字符,不能多也不能少 +``` + +注意:通配 +符搜索的处理一般要比前面讨论的其他搜索所花时间更长。不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。 + +* 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。 +* 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。 + +## 正则表达式 + +MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大 +小写,可使用BINARY关键字,如WHERE prod_name REGEXPBINARY 'JetPack .000'。 + +LIKE匹配整个列。如果被匹配的文本在列值中出现, LIKE将不会找到它,相应的行也不被返回(除非使用 +通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现, REGEXP将会找到它,相应的行将被返回。这是一个非常重要的差别。 + +```java +--REGEXP +--检索列prod_name包含文本1000的所有行 +SELECT prod_id, prod_name +FROM products +WHERE prod_name REGEXP '1000'; + +--检索列prod_name包含文本000的所有行, .是正则表达式语言中一个特殊的字符。它表示匹配任意一个字符,因此, 1000和2000都匹配 +且返回 +SELECT prod_id, prod_name +FROM products +WHERE prod_name REGEXP '.000'; + +--搜索两个串之一(或者为这个串,或者为另一个串),使用| +SELECT prod_id, prod_name +FROM products +WHERE prod_name REGEXP '1000|2000'; + +--匹配特定的字符 +--正则表达式[123] Ton为[1|2|3] Ton的缩写,也可以使用后者。但是,需要用[]来定义OR语句查找什么。 +-- /使用了正则表达式[123] Ton。 [123]定义一组字符,它的意思是匹配1或2或3,因此, 1 ton和2 ton都匹配且返回(没有3 ton) +SELECT prod_id, prod_name +FROM products +WHERE prod_name REGEXP '[123] Ton'; + +--集合可用来定义要匹配的一个或多个字符 +SELECT prod_id, prod_name +FROM products +WHERE prod_name REGEXP '[1-3] Ton'; + +--为了匹配特殊字符,必须用\\为前导。 \\-表示查找-, \\.表示查找. +SELECT prod_id, prod_name +FROM products +WHERE prod_name REGEXP '\\.'; +``` + +### 匹配字符类: + +| 类 | 说明 | +| ---------- | ------------------------------------------------- | +| [:alnum:] | 任意字母和数字(同[a-zA-Z0-9]) | +| [:alpha:] | 任意字符(同[a-zA-Z]) | +| [:blank:] | 空格和制表(同[\\t]) | +| [:cntrl:] | ASCII控制字符( ASCII 0到31和127) | +| [:digit:] | 任意数字(同[0-9]) | +| [:graph:] | 与[:print:]相同,但不包括空格 | +| [:lower:] | 任意小写字母(同[a-z]) | +| [:print:] | 任意可打印字符 | +| [:punct:] | 既不在[:alnum:]又不在[:cntrl:]中的任意字符 | +| [:space:] | 包括空格在内的任意空白字符(同[\\f\\n\\r\\t\\v]) | +| [:upper:] | 任意大写字母(同[A-Z]) | +| [:xdigit:] | 任意十六进制数字(同[a-fA-F0-9]) | + +### 匹配多个示例 + +可以用正则表达式重复元字符来完成 + +| 元字符 | 说明 | +| ------ | ----------------------------- | +| * | 0个或多个匹配 | +| + | 1个或多个匹配(等于{1,}) | +| ? | 0个或1个匹配(等于{0,1}) | +| {n} | 指定数目的匹配 | +| {n,} | 不少于指定数目的匹配 | +| {n,m} | 匹配数目的范围( m不超过255) | + +```java +SELECT prod_name +FROM products +WHERE prod_name REGEXP '\\([0-9] sticks?\\)'; +-- \\(\\)使用\\匹配特殊字符 +--sticks后跟?,使的s可有可无,可以出现0次或者1次,因此stick也会被匹配出来。 + +--匹配连在一起的4位数字 +SELECT prod_name +FROM products +WHERE prod_name REGEXP '[[:digit:]]{4}'; +--[:digit:]匹配任意数字,因而它为数字的一个集合。 {4}确切地要求它前面的字符(任意数字)出现4次,所以[[:digit:]]{4}匹配连在一起的任意4位数字。 +--等同于下面: +SELECT prod_name +FROM products +WHERE prod_name REGEXP '[0-9][0-9][0-9][0-9]'; +``` + +### 定位符 + +特定位置的文本需要使用定位符 + +| 元 字 符 | 说 明 | +| -------- | ---------- | +| ^ | 文本的开始 | +| $ | 文本的结尾 | +| [[:<:]] | 词的开始 | +| [[:>:]] | 词的结尾 | + + LIKE和REGEXP的不同在于, LIKE匹配整个串而REGEXP匹配子串。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使REGEXP的作用与LIKE一样。 + +```java +--找出以一个数(包括以小数点开始的数)开始的所有产品 +SELECT prod_name +FROM products +WHERE prod_name REGEXP '^[0-9\\.]'; +--^匹配串的开始。因此, ^[0-9\\.]只在.或任意数字为串中第一个字符时才匹配它们 +``` + + +## 创建计算字段 + +存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。 + +>字段(field) 基本上与列( column) 的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常用在计算字段的连接上。 + +### 拼接字段 + +拼接(concatenate) 将值联结到一起构成单个值。在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。 + +>多数DBMS使用+或||来实现拼接,MySQL则使用Concat()函数来实现。当把SQL语句转换成MySQL语句时一定要把这个区别铭记在心。 + +```java +--Concat()拼接串,即把多个串连接起来形成一个较长的串。Concat()需要一个或多个指定的串,各个串之间用逗号分隔。 +SELECT Concat(vend_name, '(', vend_country, ')') +FROM vendors +ORDER BY vend_name; +``` + +输出: + +![image-20210726210622468](https:--gitee.com/tianzhendong/img/raw/master--images/image-20210726210622468.png) + + +```java +--删除数据右侧多余的空格来整理数据,这可以使用MySQL的RTrim()函数来完成 +SELECT Concat(Rtrim(vend_name), '(', Rtrim(vend_country), ')') +FROM vendors +ORDER BY vend_name; +``` + +>Trim函数 MySQL除了支持RTrim()(正如刚才所见,它去掉串右边的空格),还支持LTrim()(去掉串左边的空格)以及Trim()(去掉串左右两边的空格) + +### 别名 + +上述拼接过后实际上它没有名字,它只是一个值。如果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户机应用中,因为客户机没有办法引用它。 + +为了解决这个问题, SQL支持列别名。 别名( alias) 是一个字段或值的替换名。别名用AS关键字赋予。 + +```java +SELECT Concat(Rtrim(vend_name), '(', Rtrim(vend_country), ')') AS vend_title +FROM vendors +ORDER BY vend_name; +``` + +### 执行算数计算 + +```java +--汇总物品的价格(单价乘以订购数量) +SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price +FROM orderitems +where order_num = 20005; +``` + +输出: + +![image-20210726210649949](https:--gitee.com/tianzhendong/img/raw/master--images/image-20210726210649949.png) + +## 使用数据处理函数 + +与其他大多数计算机语言一样, SQL支持利用函数来处理数据。函数一般是在数据上执行的,它给数据的转换和处理提供了方便。 + +### 使用函数 + +大多数SQL实现支持以下类型的函数。 + +* 用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数。 +* 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。 +* 用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。 +* 返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。 + +### 文本处理函数 + +| 函 数 | 说 明 | +| ----------- | ----------------- | +| Left() | 返回串左边的字符 | +| Length() | 返回串的长度 | +| Locate() | 找出串的一个子串 | +| Lower() | 将串转换为小写 | +| LTrim() | 去掉串左边的空格 | +| Right() | 返回串右边的字符 | +| RTrim(a) | 去掉串右边的空格 | +| Soundex() | 返回串的SOUNDEX值 | +| SubString() | 返回子串的字符 | +| Upper(a) | 将串转换为大写 | + +SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。 SOUNDEX考虑了类似的发音字符和音节,使得能对串进行发音比较而不是字母比较。 + +### 日期和时间处理函数 + +日期和时间采用相应的数据类型和特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。 + +一般,应用程序不使用用来存储日期和时间的格式,因此日期和时间函数总是被用来读取、统计和处理这些值。 + +| 函 数 | 说 明 | +| ------------- | ------------------------------ | +| AddDate() | 增加一个日期(天、周等) | +| AddTime() | 增加一个时间(时、分等) | +| CurDate() | 返回当前日期 | +| CurTime() | 返回当前时间 | +| Date() | 返回日期时间的日期部分 | +| DateDiff() | 计算两个日期之差 | +| Date_Add() | 高度灵活的日期运算函数 | +| Date_Format() | 返回一个格式化的日期或时间串 | +| Day() | 返回一个日期的天数部分 | +| DayOfWeek() | 对于一个日期,返回对应的星期几 | +| Hour() | 返回一个时间的小时部分 | +| Minute() | 返回一个时间的分钟部分 | +| Month() | 返回一个日期的月份部分 | +| Now() | 返回当前日期和时间 | +| Second() | 返回一个时间的秒部分 | +| Time() | 返回一个日期时间的时间部分 | +| Year() | 返回一个日期的年份部分 | + + +无论你什么时候指定一个日期,不管是插入或更新表值还是用WHERE子句进行过滤,日期必须为格式yyyy-mm-dd。因此, 2005年9月1日,给出为2005-09-01。 + +```java +SELECT cust_id ,order_num +FROM orders +WHERE DATE(order_date) ='2005-09-01'; + +--检索出2005年9月下的所有订单 +--方法1 +SELECT cust_id ,order_num +FROM orders +WHERE DATE(order_date) BETWEEN '2005-09-01' AND '2005-09-30'; +--方法2 +SELECT cust_id ,order_num +FROM orders +WHERE Year(order_date)=2005 AND MONTH(order_date)=9; +``` + +### 数值处理函数 + +数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此没有串或日期—时间处理函数的使用那么频繁。 + +| 函 数 | 说 明 | +| ------ | ------------------ | +| Abs() | 返回一个数的绝对值 | +| Cos() | 返回一个角度的余弦 | +| Exp() | 返回一个数的指数值 | +| Mod() | 返回除操作的余数 | +| Pi() | 返回圆周率 | +| Rand() | 返回一个随机数 | +| Sin() | 返回一个角度的正弦 | +| Sqrt() | 返回一个数的平方根 | +| Tan() | 返回一个角度的正切 | + +## 汇总数据 + +### 聚集函数 + +* 确定表中行数(或者满足某个条件或包含某个特定值的行数)。 +* 获得表中行组的和。 +* 找出表列(或所有行或某些特定的行)的最大值、最小值和平均值 + +聚集函数( aggregate function) 运行在行组上,计算和返回单个值的函数。 + +| 函 数 | 说 明 | +| ------- | ---------------- | +| AVG() | 返回某列的平均值 | +| COUNT() | 返回某列的行数 | +| MAX() | 返回某列的最大值 | +| MIN() | 返回某列的最小值 | +| SUM() | 返回某列值之和 | + +#### AVG() + +AVG()只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。 + + +```java +--AVG()通过对表中行数计数并计算特定列值之和,求得该列的平均值。 AVG()可用来返回所有列的平均值,也可以用来返回特定列或行的平均值 +--返回products表中所有产品的平均价格 +SELECT AVG(prod_price) AS avg_price +FROM products; +--确定特定列或行的平均值。 下面的例子返回特定供应商所提供产品的平均价格 +SELECT AVG(prod_price) AS avg_price +FROM products +WHERE vend_id = 1003; +``` + +#### COUNT() + +COUNT()函数进行计数。 可利用COUNT()确定表中行的数目或符合特定条件的行的数目。 + +COUNT()函数有两种使用方式。 + +* 使用COUNT(*)对表中行的数目进行计数, 不管表列中包含的是空值( NULL)还是非空值。 +* 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值 + +如果指定列名,则指定列的值为空的行被COUNT()函数忽略,但如果COUNT()函数中用的是星号( *),则不忽 +略。 + +```JAVA +--返回customers表中客户的总数 +SELECT COUNT(*) AS num_cust +FROM customers; + +--对具有电子邮件地址的客户计数 +SELECT COUNT(cust_email) AS num_cust +FROM customers; +``` + +#### MAX()函数\MIN()函数 + +MAX()返回指定列中的最大值。 MAX()要求指定列名 + +MIN()的功能正好与MAX()功能相反,它返回指定列的最小值 + +>对非数值数据使用MAX()函数\MIN()函数,虽然MAX()函数\MIN()函数一般用来找出最大最小的数值或日期值,但MySQL允许将它用来返回任意列中的最大最小值,包括返回文本列中的最大最小值。在用于文本数据时,如果数据按相应的列排序,则MAX()函数\MIN()函数返回最后一行\第一行。 + +MAX()函数\MIN()函数忽略列值为NULL的行。 + +```java +--返回products表中最贵的物品的价格 +SELECT MAX(prod_price) AS max_price +FROM products; + +--返回products表中最便宜物品的价格 +SELECT MIN(prod_price) AS min_price +FROM products; +``` + +#### SUM()函数 + +SUM()用来返回指定列值的和(总计)。\ + +```java +--检索所订购物品的总数(所有quantity值之和) +SELECT SUM(quantity) AS items_ordered +FROM orderitems +WHERE order_num = 20005; + +--返回订单中所有物品价钱之和 +SELECT SUM(quantity*item_price) AS total_price +FROM orderitems +WHERE order_num = 20005; +``` + +### 聚集不同的值 + +以上5个聚集函数都可以如下使用: + +* 对所有的行执行计算,指定ALL参数或不给参数(因为ALL是默认行为); +* 只包含不同的值,指定DISTINCT参数 + +>如果指定列名,则DISTINCT只能用于COUNT()。DISTINCT不能用于COUNT(*),因此不允许使用COUNT( DISTINCT),否则会产生错误。类似地, DISTINCT必须使用列名,不能用于计算或表达式 + +```java +--返回特定供应商1003提供的产品的平均价格(相同的产品只会计算一次) +SELECT AVG( DISTINCT prod_price) AS avg_price +FROM products +WHERE vend_id = 1003; +``` + +### 组合聚集函数 + +目前为止的所有聚集函数例子都只涉及单个函数。但实际上SELECT语句可根据需要包含多个聚集函数。 + +```JAVA +SELECT COUNT(*) AS num_items, + MIN(prod_price) AS price_min, + Max(prod_price) AS price_max, + AVG(prod_price) AS price_avg, + SUM(prod_price) AS price_sum +FROM products; +``` + +## 分组数据 + +```java +--返回vend_id=1005的数量 +SELECT COUNT(*) AS num_prods +FROM products +WHERE vend_id = 1005; + +--创建数据分组,GROUP BY子句指示MySQL按vend_id排序并分组数据。 +SELECT vend_id, COUNT(*) AS num_prods +FROM products +GROUP BY vend_id; + +--过滤数量大于等于3的。 +--where只能过滤行,不能过滤分组、 +--HAVING 用于过滤分组 +SELECT vend_id, COUNT(*) AS num_prods +FROM products +GROUP BY vend_id +HAVING COUNT(*)>=3; + +--分组和排序 +--GROUP BY子句用来按订单号(order_num列)分组数据,以便SUM(*)函数能够返回总计订单价格。 HAVING子句过滤数据,使得只返回总计订单价格大于等于50的订单。最后, 用ORDERBY子句排序输出 +SELECT order_num, SUM(quantity*item_price) AS ordertotal +FROM orderitems +GROUP BY order_num +HAVING SUM(quantity*item_price)>=50 +ORDER BY ordertotal; +``` + +## 使用子查询 + +### 子查询 + +查询(query) 任何SQL语句都是查询。但此术语一般指SELECT语句 + +SELECT语句是SQL的查询。迄今为止我们所看到的所有SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。 + +SQL还允许创建子查询( subquery) ,即嵌套在其他查询中的查询。 + +### 利用子查询进行过滤 + +```JAVA +--检索包含物品TNT2的所有订单的编号 +SELECT order_num +FROM orderitems +WHERE prod_id = 'TNT2'; +--检索具有前一步骤列出的订单编号的所有客户的ID。 +SELECT cust_id +FROM orders +WHERE order_num IN(20005,20007); + +--把第一个查询(返回订单号的那一个)变为子查询组合两个查询。 +SELECT cust_id +FROM orders +WHERE order_num IN(SELECT order_num + FROM orderitems + WHERE prod_id = 'TNT2'); + +``` + +## 联结表 + +外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。 + +可伸缩性(scale) 能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好( scale well) 。 + +联结是一种机制,用来在一条SELECT语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返 +回一组输出,联结在运行时关联表中正确的行。 + +### 创建联结 + +联结的创建非常简单,规定要联结的所有表以及它们如何关联即可。 + +```JAVA +--使用where联结 +SELECT vend_name , prod_name, prod_price +FROM vendors, products +WHERE vendors.vend_id = products.vend_id +ORDER BY vend_name, prod_name; + +--使用INNER JOIN联结,推荐 +SELECT vend_name , prod_name, prod_price +FROM vendors INNER JOIN products +ON vendors.vend_id = products.vend_id +ORDER BY vend_name, prod_name; +``` + +### 联结多个表 + +SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系 + +```JAVA +SELECT prod_name, vend_name , prod_price, quantity +FROM orderitems,products,vendors, +WHERE products.vend_id=vendors.vend_id +AND orderitems.prod_id = products.prod_id +AND order_num =20005 +ORDER BY vend_name, prod_name; +``` + +## 创建高级联结 + +### 使用别名 + +```JAVA +-- 使用别名 +SELECT cust_name, cust_contact +FROM customers AS c,orders AS o, orderitems AS oi +WHERE c.cust_id = o.cust_id + AND oi.order_num = o.order_num + AND prod_id = 'TNT2'; + +--自联结 +--在同一个表中搜索 +SELECT p1.prod_id, p1.prod_name +FROM products AS p1, products AS p2 +WHERE p1.vend_id = p2.vend_id + AND p2.prod_id = 'DTNTR'; +``` + + +## 组合查询 + +```java +SELECT vend_id, prod_id ,prod_price +FROM products +WHERE prod_price<=5 +UNION +SELECT vend_id, prod_id ,prod_price +FROM products +WHERE vend_id IN(1001,1002); + +--等同于 +SELECT vend_id, prod_id ,prod_price +FROM products +WHERE prod_price<=5 OR vend_id IN (1001,1002); +``` + +UNION从查询结果集中自动去除了重复的行(换句话说,它的行为与单条SELECT语句中使用多个WHERE子句条件一样)。 + +使用UNION ALL, MySQL不取消重复的行。 + +在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。 + +## 全文本搜索 + +### 启用全文本搜索支持 + +一般在创建表时启用全文本搜索。 CREATE TABLE语句(第21章中介绍)接受FULLTEXT子句,它给出被索引列的一个逗号分隔的列表。 + +### 进行全文本搜索 + +使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列, Against()指定要使用的搜索表达式。 + +```java +SELECT note_text +FROM productnotes +WHERE Match(note_text) Against('rabbit'); + +--等同于 +SELECT note_text +FROM productnotes +WHERE note_text LIKE '%rabbit%'; + +SELECT note_text +FROM productnotes +WHERE note_text REGEXP 'rabbit'; +``` + +区别: + +全文本搜索会根据匹配程度进行自动排序 + +### 使用查询扩展 + +找出所有提到anvils的注释。只有一个注释包含词anvils,但你还想找出可能与你的搜索有关的所有其他行,即使它们不包含词anvils。 + +在使用查询扩展时, MySQL对数据和索引进行两遍扫描来完成搜索: + +```java +SELECT note_text +FROM productnotes +WHERE Match(note_text) Against('rabbit' WITH QUERY EXPANSION); +``` + +### 布尔文本搜索 + +```java +--查询有heavy但没有rope的行 +SELECT note_text +FROM productnotes +WHERE Match(note_text) Against('heavy -rope*' IN BOOLEAN MODE); +``` + +# 7、事务 + +要么都成功,要么都失败 + +A给B转账,步骤1:A钱减少500,步骤2:B钱增加500 + +## ACID + +参考:https://blog.csdn.net/dengjili/article/details/82468576 + +ACID,数据库事务正确执行的四个基本要素的缩写,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性: + +* **原子性**(atomicity,或称不可分割性):两个步骤1和2要么都成功,要么都失败, +* **一致性**(consistency):最终一致性,事务前后的数据完整性要保证一致,转账前后A和B最终总钱数不变 +* **隔离性**(isolation,又称独立性):针对多个用户同时操作,排除其他事务对本次事务的影响 + * 同时C在给B转账,之间不会有影响 +* **持久性**(durability):事务一旦提交则不可逆,被持久化到数据库中,事务结束后的数据不随着外界原因导致数据丢失 + * 转账事务提交前断电,重启后,恢复到原状,数据状态为转账前的 + * 转账事务提交后断电,重启后,数据状态为转账后的 + +隔离性导致的问题:**事务的隔离级别:** + +* **脏读:**一个事务读取了另外一个事务未提交的数据 +* **不可重复读:**在一个事务内读取表中的某一行数据,多次读取结果不同。(这个不一定是错误,只是某些场合不对) +* **幻读(虚读):**是指在一个事务内读取到了别的事务插入的数据,导致前后读取数量总量不一致。(一般是行影响,如下图所示:多了一行) + +## 执行事务 + +mysql是默认开启事务自动提交的 + +```sql +--mysql是默认开启事务自动提交的 +SET autocommit =0; --关闭 +SET autocommit =1; --开启 +``` + +事务的完整过程: + +```sql +--1.关闭自动提交 +SET autocommit =0; --关闭自动提交 + +--2.标记一个事务的开始,从这个开始后的sql都在同一个事务内 +START TRANSACTION; +INSERT XX + +--3.1提交:持久化(成功!) +COMMIT; + +--3.2回滚:回到原来的样子(失败) +ROLLBACK; + +--4.事务结束 +SET autocommit =1; --开启自动提交 + +--了解 +SAVEPOINT 保存点名 --设置一个事务的保存点 +ROLLBACK TO SAVEPOINT 保存点名 --回滚到保存点 +RELEASE SAVEPOINT 保存点名 --撤销保存点 +``` + +# 8、索引 + +> 索引index是帮助MySQL高效获取数据的数据结构 +> +> 提取句子主干,就可以得到索引的本质:索引是数据结构,MySQL中常用的索引结构(索引底层的数据结构)有:B-TREE ,B+TREE ,HASH 等 + +**优点**: + +- 索引大大减小了服务器需要扫描的数据量 + +- 索引可以帮助服务器避免排序和临时表 + +- 索引可以将随机IO变成顺序IO + +- 索引对于InnoDB(对索引支持行级锁)非常重要,因为它可以让查询锁更少的元组。在MySQL5.1和更新的版本中,InnoDB可以在服务器端过滤掉行后就释放锁,但在早期的MySQL版本中,InnoDB直到事务提交时才会解锁。对不需要的元组的加锁,会增加锁的开销,降低并发性。 InnoDB仅对需要访问的元组加锁,而索引能够减少InnoDB访问的元组数。但是只有在存储引擎层过滤掉那些不需要的数据才能达到这种目的。一旦索引不允许InnoDB那样做(即索引达不到过滤的目的),MySQL服务器只能对InnoDB返回的数据进行WHERE操作,此时,已经无法避免对那些元组加锁了。如果查询不能使用索引,MySQL会进行全表扫描,并锁住每一个元组,不管是否真正需要。 + +- - 关于InnoDB、索引和锁:InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写锁) + + + +**缺点** + +- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存索引文件。 +- 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。 +- 如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。 +- 对于非常小的表,大部分情况下简单的全表扫描更高效; + +索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。 + +因此应该只为最经常查询和最经常排序的数据列建立索引。 + +MySQL里同一个数据表里的索引总数限制为16个。 + +## 索引的分类 + +```sql +--显示所有索引信息 +SHOW INDEX FROM student; +``` + + + +* 主键索引*PRIMARY KEY* + +唯一的标识,一张表只能有一个主键索引,不允许重复、不允许为 NULL; + +```sql + ALTER TABLE TableName ADD PRIMARY KEY(column_list); +``` + +* 唯一索引*UNIQUE KEY + +避免重复的列出现,允许为 NULL 值,一张表可有多个唯一索引,索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一 + +```sql +ALTER TABLE TableName ADD UNIQUE (column_list); +``` + +* 常规索引*KEY/INDEX* + +一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许 NULL 值插入; + +```sql +ALTER TABLE TableName ADD INDEX IndexName(`字段名`(length)); +``` + +* 全文索引*FullText* + * 在特定的数据库引擎下才有,MylSAIM + * 快速定位数据,它查找的是文本中的关键词,主要用于全文检索。 + +## 索引原则 + +* 索引不是越多越好 +* 不要对进程变动数据加索引 +* 小数据量的表不需要加索引 +* 索引一般加在常用来查询的字段上 + +# 9、权限管理 + +## 用户管理 + +```sql +--创建用户 +CREATE USER username IDENTIFIED BY 'password'; + +--修改用户密码 +USE mysql --使用mysql数据库 +ALTER USER 'test'@'localhost' IDENTIFIED WITH MYSQL_NATIVE_PASSWORD BY '新密码'; --%代替localhost表示所有ip + +--用户名重命名 +RENAME USER oldname TO newname; + +--用户授权 +GRANT ALL PRIVILEGES ON *.* TO username;--全部的表,所有权限,除了给别人授权 + +--查看指定用户权限 +SHOW GRANTS FOR username; + +--撤销权限 revoke哪些权限, 在哪个表, 给谁 +REVOKE ALL PRIVILEGES ON *.* FROM username; + +--删除用户 +DROP USER username; +``` + +# 10、数据库备份 + +* 直接拷贝物理文件 +* 在可视化工具中导出 +* 使用命令行mysqludmp + +**导出** + +```bash +mysqldump -h 主机 -u 用户名 -p 密码 数据库 表1 表2 表3 >物理磁盘位置/文件名 +mysqldump -hlocalhost -uroot -p123456 school student >D:/a.sql +``` + +**导入** + +```sql +--首先登陆数据库 +source 物理磁盘位置/文件名.sql +``` + +# 11、规范数据库设计 + +## 为什么需要设计 + +当数据库比较复杂时,需要进行数据库设计 + +糟糕的设计: + +* 数据冗余,浪费空间 +* 数据增删麻烦,会产生异常 +* 程序性能差 + + + +良好的设计: + +* 节省内存空间 +* 保证数据库的完整性 +* 方便开发系统 +* 避免使用物理外键 + + + +软件开发中,关于数据库的设计: + +* 分析需求:分析业务和需要处理的数据库需求 +* 概要设计:设计关系图E-R图 + + + +设计数据库步骤(个人博客): + +1. 收集信息,分析需求 + 1. 用户表(用户登陆注销、个人信息,写博客,创建分类) + 2. 分类表(文章分类,谁创建的) + 3. 文章表(文章的信息) + 4. 评论表(评论信息) + 5. 友链表(友情链接信息) + 6. 自定义表(可以不需要)(系统信息,某个关键的字,或者一些主题)key:value +2. 标识实体(把需求落实到每个字段) +3. 标识实体之间的关系 + 1. 写博客:user->blog + 2. 创建分类:user->category + 3. 关注:user->user + 4. 评论:user->user->blog + +## 三大范式 + +为什么需要数据规范化: + +* 信息重复 +* 更新异常 +* 插入异常 + * 无法正常显示信息 +* 删除异常 + * 丢失有效的信息 + +**三大范式:**规范数据库的设计 + +* **第一范式(1NF)** + * 要求数据库表中的每一列都是不可分割的原子数据项 +* **第二范式(2NF)** + * 前提:满足第一范式 + * 每张表只描述一件事情 +* **第三范式(3NF)** + * 前提:满足第一范式和第二范式 + * 确保数据表中的每一列数据都和主键直接相关,而不能间接相关 + +**规范性和性能的问题**: + +* 关联查询的表不能超过三个 +* 考虑商业化的需求和目标,数据库的性能更加重要 +* 在规范性能的问题的时候,需要适当的考虑一下规范性 +* 故意给某些表增加一些冗余字段(从多表查询变为单表查询) +* 故意增加一些计算列(从大数据量降低为小数据量的查询) + +# 12、JDBC + +## 概述 + +### 数据库驱动 + +应用程序通过数据库驱动和数据库链接、打交道。不同的数据库有不同的驱动 + +**应用程序——>MySQL驱动、Oracle驱动——>数据库** + +### JDBC + +> 架构中,没有什么是加一层解决不了的 + +SUN公司为了简化开发人员的(对数据库的统一)操作,提供了一个java操作数据库的规范,即JDBC + +这些规范的实现由具体的厂商去做,对于开发人员,只需要掌握JDBC接口的操作即可 + +**应用程序——>JDBC——>MySQL驱动、Oracle驱动——>数据库** + +## 第一个JDBC程序 + +1. **maven引入jdbc驱动,配置pom.xml** + +数据库版本为8.0.26 + +```xml + + mysql + mysql-connector-java + 8.0.26 + +``` + +2. **编写java程序** + 1. 加载驱动 + 2. 连接数据库Drivermanager + 3. 获得执行sql对象Statement + 4. 获得返回的结果集ResultSet + 5. 释放连接 + +```java +public class JdbcTest { + public static void main(String[] args) throws ClassNotFoundException, SQLException { + //1.加载驱动 固定写法 + Class.forName("com.mysql.cj.jdbc.Driver"); + + //2.用户信息和url + /*school:连接的数据库 + useUnicode=true&characterEncoding=utf8&useSSL=true:支持中文编码&字符集utf8&使用安全连接*/ + String url = "jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=true"; + String username = "root"; + String password = "123456"; + + //3.连接成功,数据库对象Connection代表数据库对象 + Connection connection = DriverManager.getConnection(url, username, password); + + //4.执行sql对象 statement执行sql的对象 + Statement statement = connection.createStatement(); + + //5.执行sql对象去执行sql,可能存在结果,查看连接结果 + //sql语句 + String sql = "SELECT * FROM student"; + //返回的结果集,结果集中封装了查询出来的全部结果 + ResultSet resultSet = statement.executeQuery(sql); + //输出结果 + while (resultSet.next()) { + System.out.println("id = " + resultSet.getObject("id")); + System.out.println("name = " + resultSet.getObject("name")); + System.out.println("age = " + resultSet.getObject("age")); + System.out.println("=============="); + } + + //6.释放连接 + resultSet.close(); + statement.close(); + connection.close(); + } +} +``` + +## JDBC对象解释 + +### Connection + +```java +//3.连接成功,数据库对象Connection代表数据库对象 +Connection connection = DriverManager.getConnection(url, username, password); +connection.rollback(); //事务回滚 +connection.commit(); //事务提交 +connection.setAutoCommit(true); //设置事务自动提交 +``` + + + +### Statement + +执行sql的对象的类 + +```java +Statement statement = connection.createStatement(); //获取statement对象 +ResultSet resultSet = statement.executeQuery(sql); //查询操作,返回ResultSet结果集 +boolean execute = statement.execute(sql);//执行任何sql +int i = statement.executeUpdate(sql);//更新、插入、删除,返回一个受影响的行数 +``` + +### ResultSet + +查询的结果集 + +```java +ResultSet resultSet = statement.executeQuery(sql); //查询操作,返回ResultSet结果集 + +resultSet.getObject(); //返回结果,在不知道列的类型的情况下使用 +resultSet.getString(); +resultSet.getInt(); +.... +``` + +遍历操作 + +```java +resultSet.beforeFirst(); //移动到最前面 +resultSet.afterLast(); //移动到最后面 +resultSet.next(); //移动到下一个 +resultSet.previous(); //移动到前一个 +resultSet.absolute(row); //移动到指定行 +``` + +### 释放资源 + +```java +//6.释放连接 +resultSet.close(); +statement.close(); +connection.close(); +``` + +## 采用配置文件方式 + +**新增db.properties文件** + +```properties +driver = com.mysql.cj.jdbc.Driver +url = jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=true +username = root +password = 123456 +``` + +**jdbc配置类:** + +```java +package com.jdbc; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.*; +import java.util.Properties; + +/** + * @author TianZhendong + * @date 2021/8/5 + */ + +public class JdbcUtils { + private static String driver=null; + private static String url=null; + private static String username=null; + private static String password=null; + static{ + try{ + //db.properties"为配置文件名 + InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties"); + Properties properties = new Properties(); + properties.load(in); + driver=properties.getProperty("driver"); + url=properties.getProperty("url"); + username=properties.getProperty("username"); + password=properties.getProperty("password"); + //驱动加载 + Class.forName(driver); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + + /** + * @Description: 获取连接 + * @Author: TianZD + * @Date: 2021/8/5 19:15 + * @Param: [] + * @Return: java.sql.Connection + */ + public static Connection getConnection() throws SQLException { + return DriverManager.getConnection(url, username, password); + } + + /** + * @Description: 释放资源 + * @Author: TianZD + * @Date: 2021/8/5 19:14 + * @Param: [conn, st, rs] + * @Return: void + */ + public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException { + if(rs!=null){ + rs.close(); + } + if(st!=null){ + st.close(); + } + if(conn!=null){ + conn.close(); + } + } +} +``` + +java: + +```java +package com.jdbc; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import com.jdbc.JdbcUtils; + +/** + * @program: JDBCDemo + * @description: test + * @author: TianZD + * @create: 2021-08-05 19:22 + **/ +public class InsertTest { + public static void main(String[] args) { + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + try { + //获取数据库连接 + connection = JdbcUtils.getConnection(); + //获得sql的执行对象 + statement = connection.createStatement(); + String sql = "INSERT INTO student(id, name, age)" + + " VALUES(3, 'stu3', '25')"; + int i = statement.executeUpdate(sql); + if (i > 0) { + System.out.println("插入成功!"); + } + } catch (SQLException throwable) { + throwable.printStackTrace(); + } finally { + JdbcUtils.release(connection,statement, null); + } + + } +} +``` + +## SQL注入 + +SQL 注入(SQL Injection)是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。主要原因是**程序对用户输入数据的合法性没有判断和处理**,导致攻击者可以在 Web 应用程序中事先定义好的 **SQL 语句中添加额外的 SQL 语句**,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。 + + + +简而言之,**SQL 注入就是在用户输入的字符串中加入 SQL 语句**,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令或访问未被授权的数据。 + +## PreparedStatement对象 + +可以防止SQL注入 + +本质是:把传递进来的参数当作字符 + +具体实现见DBCP部分代码 + +## 数据库连接池 + +数据库连接——执行完毕——释放 + +连接——释放很耗费资源 + +**池化技术:准备一些预先的资源,过来就连接预先准备好的** + +* 最小连接数:根据常用连接数确定 + +* 最大连接数:业务最高承载上限 + +* 排队等待 + +* 等待超时 + +编写连接池,实现一个接口DataSource + +> 开源数据源实现 + +* DBCP +* C3P0 +* Druid阿里巴巴 + +使用这些数据库连接池后,在项目开发中就不需要编写连接数据库代码了 + +### DBCP + +maven引入依赖: + +```xml + + + mysql + mysql-connector-java + 8.0.26 + + + commons-dbcp + commons-dbcp + 1.4 + + +``` + +编写dbcp.properties + +```properties +#连接设置 该部分格式不能变 +driverClassName=com.mysql.cj.jdbc.Driver +url=jdbc:mysql://localhost:3306/school +username=root +password=123456 + +#下面的都可以不要 +# +initialSize=10 + +#最大连接数量 +maxActive=50 + +# +maxIdle=20 + +# +minIdle=5 + +# +maxWait=60000 + + +#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] +#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 +connectionProperties=useUnicode=true;characterEncoding=gbk + +#指定由连接池所创建的连接的自动提交(auto-commit)状态。 +defaultAutoCommit=true + +#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 +#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE +defaultTransactionIsolation=READ_UNCOMMITTED +``` + +编写配置类 + +```java +package com.jdbc; + +import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp.BasicDataSourceFactory; + +import javax.sql.DataSource; +import java.io.InputStream; +import java.sql.*; +import java.util.Properties; + +/** + * @program: JDBCDemo + * @description: test + * @author: TianZD + * @create: 2021-08-05 23:17 + **/ +public class JdbcUtils_Dbcp { + private static DataSource dataSource = null; + static{ + try{ + //dbcp.properties为配置文件名 + InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("dbcp.properties"); + Properties properties = new Properties(); + properties.load(in); + + //创建数据源 工厂模式 + dataSource = BasicDataSourceFactory.createDataSource(properties); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * @Description: 从数据源中获取连接 + * @Author: TianZD + * @Date: 2021/8/5 19:15 + * @Param: [] + * @Return: java.sql.Connection + */ + public static Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + /** + * @Description: 释放资源 + * @Author: TianZD + * @Date: 2021/8/5 19:14 + * @Param: [conn, st, rs] + * @Return: void + */ + public static void release(Connection conn, Statement st, ResultSet rs) { + if(rs!=null){ + try { + rs.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + if(st!=null){ + try { + st.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + if(conn!=null){ + try { + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } +} +``` + +测试 + +```java +package com.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @program: JDBCDemo + * @description: + * @author: TianZD + * @create: 2021-08-05 23:23 + **/ +public class TestDbcp { + public static void main(String[] args) { + Connection connection = null; + PreparedStatement preparedStatement = null; + try { + connection = JdbcUtils_Dbcp.getConnection(); + //sql语句,其中value值先不赋予 + + String sql = "insert into student(id, `name`, `age`) values(?,?,?)"; + + //预先编译sql,先写sql,但不执行 + preparedStatement = connection.prepareStatement(sql); + + //手动value赋值 + preparedStatement.setInt(1, 3); + preparedStatement.setString(2, "tian"); + preparedStatement.setInt(3, 25); + + //执行 + int i = preparedStatement.executeUpdate(); + if (i > 0) { + System.out.println("插入成功!"); + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } finally { + JdbcUtils_Dbcp.release(connection, preparedStatement, null); + } + } +} +``` + +### C3P0 + +maven引入依赖 + +```xml + + + mysql + mysql-connector-java + 8.0.26 + + + com.mchange + c3p0 + 0.9.5.2 + + +``` + +**配置文件,名字必须为c3p0-config** + +```xml + + + + + 10 + 30 + 100 + 10 + 200 + + + + + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/school + root + 123456 + 10 + 30 + 100 + 10 + 200 + + + +``` + +配置类 + +```java +package com.jdbc; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import org.apache.commons.dbcp.BasicDataSourceFactory; + +import javax.sql.DataSource; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +/** + * @program: JDBCDemo + * @description: + * @author: TianZD + * @create: 2021-08-05 23:41 + **/ +public class JdbcUtils_C3P0 { + private static DataSource dataSource = null; + static{ + try{ + //创建数据源 工厂模式 + dataSource = new ComboPooledDataSource("MySQL"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * @Description: 从数据源中获取连接 + * @Author: TianZD + * @Date: 2021/8/5 19:15 + * @Param: [] + * @Return: java.sql.Connection + */ + public static Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + /** + * @Description: 释放资源 + * @Author: TianZD + * @Date: 2021/8/5 19:14 + * @Param: [conn, st, rs] + * @Return: void + */ + public static void release(Connection conn, Statement st, ResultSet rs) { + if(rs!=null){ + try { + rs.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + if(st!=null){ + try { + st.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + if(conn!=null){ + try { + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } +} +``` + +测试 + +```java +package com.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @program: JDBCDemo + * @description: + * @author: TianZD + * @create: 2021-08-05 23:45 + **/ +public class TestC3P0 { + public static void main(String[] args) { + Connection connection = null; + PreparedStatement preparedStatement = null; + try { + connection = JdbcUtils_C3P0.getConnection(); + //sql语句,其中value值先不赋予 + + String sql = "insert into student(id, `name`, `age`) values(?,?,?)"; + + //预先编译sql,先写sql,但不执行 + preparedStatement = connection.prepareStatement(sql); + + //手动value赋值 + preparedStatement.setInt(1, 4); + preparedStatement.setString(2, "tianzhendong"); + preparedStatement.setInt(3, 28); + + //执行 + int i = preparedStatement.executeUpdate(); + if (i > 0) { + System.out.println("插入成功!"); + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } finally { + JdbcUtils_C3P0.release(connection, preparedStatement, null); + } + } +} +``` + + + + + + + diff --git a/source/_posts/Java/Mybatis.md b/source/_posts/Java/Mybatis.md new file mode 100644 index 0000000..5b13bb7 --- /dev/null +++ b/source/_posts/Java/Mybatis.md @@ -0,0 +1,1815 @@ +--- +title: Mybatis +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: Mybatis框架学习笔记,粗略学了一下,没有参考价值 +tags: + - Mybatis + - Java + - 数据库 + - 学习笔记 +categories: + - java +reprintPolicy: cc_by +abbrlink: da3e1361 +date: 2022-04-29 11:05:46 +coverImg: +img: +password: +--- + + +[toc] + +# Mybatis + +# 1、简介 + +> **Mybatis** + +* MyBatis 是一款优秀的**持久层**框架 +* 它支持自定义 SQL、存储过程以及高级映射 +* **MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作** +* MyBatis 可以通过简单的 **XML 或注解**来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 + +获取mybatis + +```xml + + org.mybatis + mybatis + x.x.x + +``` + +> **持久化** + +数据持久化 + +* 持久化就是将程序的数据在持久状态和瞬时状态转化的过程 +* 内存:断电即失 +* 数据库、io文件可以进行持久化 + +为什么需要持久化:有一些对象不能丢失 + +> **持久层** + +Dao层、Service层、Controller层 + +# 2、入门 + +## 第一个Mybatis程序 + +**思路:**搭建环境==》导入Mybatis==》编写代码==》测试 + +> **搭建环境** + +1. **搭建数据库** + +```sql +CREATE DATABASE mybatis; +CREATE TABLE `user`( + `id` INT(20) NOT NULL, + `name` VARCHAR(30) DEFAULT NULL, + `pwd` VARCHAR(30) DEFAULT NULL, + PRIMARY KEY(`id`) +)ENGINE=INNODB DEFAULT CHARSET=UTF8; +``` + +2. **新建maven项目** + 1. 普通maven项目 + 2. 删除src目录,使其变为父工程 + 3. 导入依赖 + +```xml + + + + + mysql + mysql-connector-java + 8.0.25 + + + + org.mybatis + mybatis + 3.5.7 + + + + junit + junit + 3.8.2 + test + + +``` + +> **创建模块** + +由于在上述步骤中配置了父工程,并且父工程已经导入了依赖,因此所有的子模块不需要再次引入依赖 + +子模块的xml配置文件中多了<**parent**> + +```xml + + MybatisStudy + org.example + 1.0-SNAPSHOT + +``` + +> **编写mybatis核心配置文件,在resource目录下创建mybaits-config.xml** + +XML 配置文件中包含了**对 MyBatis 系统的核心设置**,包括**获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器**(TransactionManager)。 + +```xml + + + + + + + + + + + + + + + + + + +``` + +> **编写mybatis工具类** + +* **从xml配置中获取 SqlSessionFactory 实例** + +* **从 SqlSessionFactory 中获取 SqlSession** + +每个基于 MyBatis 的应用都是以一个 **SqlSessionFactory 的实例为核心**的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则**可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例**。 + +从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。 + + **SqlSession中包括了操作数据库的方法** + +```java +public class MybatisUtils { + private static SqlSessionFactory sqlSessionFactory = null; + static { + try { + /*以下三句话是固定的 + 用来从xml配置中获取sqlSessionFactory对象 + * 1. 加载配置文件,maven中可以直接读取到resource中的配置文件 + * 2. 获取输入流实例 + * 3. 从XML配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例*/ + String resource = "mybatis-config.xml"; + InputStream inputStream = Resources.getResourceAsStream(resource); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * @Description: 从 SqlSessionFactory对象 中获取 SqlSession实例 + * @Author: TianZD + * @Date: 2021/8/6 22:01 + * @Param: [] + * @Return: org.apache.ibatis.session.SqlSession + */ + public static SqlSession getSqlSession() { + return sqlSessionFactory.openSession(); + } + +} +``` + +> **编写代码** + +* **实体类** + +```java +package com.tian.pojo; + +/** + * @program: MybatisStudy + * @description: 实体类 + * @author: TianZD + * @create: 2021-08-06 22:04 + **/ +public class User { + private int id; + private String name; + private String pwd; + + public User() { + } + + public User(int id, String name, String pwd) { + this.id = id; + this.name = name; + this.pwd = pwd; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPwd() { + return pwd; + } + + public void setPwd(String pwd) { + this.pwd = pwd; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", name='" + name + '\'' + + ", pwd='" + pwd + '\'' + + '}'; + } +} +``` + +* **Dao接口** + + + +```java +package com.tian.dao; + +import com.tian.pojo.User; + +import java.util.List; + +/** + * @program: MybatisStudy + * @description: Dao接口,后面用mapper代替,两者等价 + * @author: TianZD + * @create: 2021-08-06 22:07 + **/ +public interface UserDao { + //查询全部 + List getUserList(); + + //根据id查询 + User getUserById(int id); + + //insert插入 + int addUser(User user); + + //update修改 + int updateUser(User user); + + //删除用户 + int deleteUser(int id); +} +``` + +* **接口实现类Mapper** + +以前采用的方式是创建一个**接口实现类,现在采用xml**配置文件的方式 + +```xml + + + + + + + + + + + + + insert into mybatis.user (id, name, pwd) + values (#{id}, #{name}, #{pwd}); + + + + + update mybatis.user + set name = #{name}, + pwd = #{pwd} + where id = #{id}; + + + + + delete + from mybatis.user + where id = #{id}; + + + +``` + +* **Junit测试** + + + +```java +package com.tian.dao; + +import com.tian.pojo.User; +import com.tian.utils.MybatisUtils; +import org.apache.ibatis.session.SqlSession; +import org.junit.Test; + +import java.util.List; + +/** + * @program: MybatisStudy + * @description: 测试 + * @author: TianZD + * @create: 2021-08-06 22:56 + **/ +public class UserDaoTest { + @Test + public void test(){ + //1. 获得SqlSession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + //2. 执行sql、获取结果、输出 + //方式1,getMapper + UserDao userDao = sqlSession.getMapper(UserDao.class); + List userList = userDao.getUserList(); + for (User user : userList) { + System.out.println(user); + } + + //3. 关闭SqlSession + sqlSession.close(); + } + + @Test + public void getUserById(){ + // 1. 获得SqlSession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + // 2. 执行sql + UserDao mapper = sqlSession.getMapper(UserDao.class); + User userById = mapper.getUserById(2); + System.out.println(userById); + // 3. 关闭 + sqlSession.close(); + } + + @Test + public void addUserTest(){ + // 1.从配置类中获得SqlSession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + // 2.sql语句 + UserDao mapper = sqlSession.getMapper(UserDao.class); + int tian4 = mapper.addUser(new User(4, "tian4", "123456")); + if (tian4 > 0) { + System.out.println("插入成功"); + } + + // 增删改需要提交事务 + sqlSession.commit(); + // 3. 关闭 + sqlSession.close(); + } + + @Test + public void updateUserTest() { + // 1.获取sqlsession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + // 2.sql + UserDao mapper = sqlSession.getMapper(UserDao.class); + int tian4 = mapper.updateUser(new User(4, "tian4", "123123")); + // 增删改需要提交事务 + sqlSession.commit(); + // 3.关闭 + sqlSession.close(); + } + + @Test + public void deleteUserTest() { + // 1.获取sqlsession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + // 2.sql + UserDao mapper = sqlSession.getMapper(UserDao.class); + int i = mapper.deleteUser(4); + // 增删改需要提交事务 + sqlSession.commit(); + // 3.关闭 + sqlSession.close(); + } +} +``` + +> **总结** + +在写完上述以后,后续使用步骤: + +1. 在UserDao接口中增加相应的方法 +2. 在UserMapper.xml中增加相应的sql语句 +3. 在测试方法中增加相应的测试方法 + +**注意**:增删改需要在关闭之前提交事务 + +```java +sqlSession.commit(); +``` + +> **可能错误:** + +* org.apache.ibatis.binding.BindingException: Type interface com.tian.dao.UserDao is not known to the MapperRegistry. + +在mybatis.config.xml文件中没有配置mapper.xml + +增加如下: + +**注意,路径用斜杠隔开** + +```xml + + + +``` + + + +* 错误2: + +Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/tian/dao/UserMapper.xml + +资源过滤原因 + +maven由于约定大于配置,可能遇到配置文件无法被导出或者生效的问题,解决方案: + +在父工程pop.xml中配置resource,防治资源导出失败 + +```xml + + + + src/main/resources + + **/*.properties + **/*.xml + + false + + + src/main/java + + **/*.properties + **/*.xml + + false + + + +``` + + + +## 生命周期和作用域 + +作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的**并发问题** + +![image-20210807175425111](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807175425111.png) + +> **SqlSessionFactoryBuilder** + +* **一旦创建了 SqlSessionFactory,就不再需要它了** +* **最佳作用域是方法作用域(也就是局部方法变量)** + +这个类可以被实例化、使用和丢弃,**一旦创建了 SqlSessionFactory,就不再需要它了**。 因此 SqlSessionFactoryBuilder 实例的**最佳作用域是方法作用域(**也就是**局部方法变量**)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。 + +> **SqlSessionFactory** + +* **说白了可以想象为:数据库连接池** +* **一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例** +* **最佳作用域是应用作用域** +* **最简单的就是使用单例模式或者静态单例模式** + +SqlSessionFactory **一旦被创建就应该在应用的运行期间一直存在**,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的**最佳作用域是应用作用域**。 有很多方法可以做到,**最简单的就是使用单例模式或者静态单例模式。** + +> **SqlSession** + +* **连接到连接池的一个请求** +* **最佳的作用域是请求或方法作用域** +* **用完之后需要关闭,否则造成资源被占用** + +每个线程都应该有它自己的 SqlSession 实例。**SqlSession 的实例不是线程安全的,因此是不能被共享的**,所以它的**最佳的作用域是请求或方法作用域**。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,**每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它**。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式: + +```java +try (SqlSession session = sqlSessionFactory.openSession()) { + // 你的应用逻辑代码 +} +``` + +![image-20210807175744673](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807175744673.png) + +每一个Mapper代表一个具体的业务 + + + +## CRUD + +> **namespace** + +namespace中的包名要和Dao/Mapper接口的包名保持一致 + +```xml + +``` + +绑定时的包名用.不能用/ + +> **select** + +选择、查询语句 + +1. 编写接口 + +```java + //查询全部 + List getUserList(); + + //根据id查询 + User getUserById(int id); +``` + +2. 编写对应的mapper中的sql语句 + +```xml + + + + + +``` + +3. 编写测试 + +```java +@Test +public void test(){ + //1. 获得SqlSession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + //2. 执行sql、获取结果、输出 + //方式1,getMapper + UserDao userDao = sqlSession.getMapper(UserDao.class); + List userList = userDao.getUserList(); + for (User user : userList) { + System.out.println(user); + } + + //3. 关闭SqlSession + sqlSession.close(); +} + +@Test +public void getUserById(){ +// 1. 获得SqlSession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); +// 2. 执行sql + UserDao mapper = sqlSession.getMapper(UserDao.class); + User userById = mapper.getUserById(2); + System.out.println(userById); +// 3. 关闭 + sqlSession.close(); +} +``` + +> **insert** + +增删改需要提交事务 + +1. 编写接口 +2. mapper中的sql语句 + +```xml + + + insert into mybatis.user (id, name, pwd) + values (#{id}, #{name}, #{pwd}); + +``` + +3. 测试:增删改需要提交事务 + +```java +// 增删改需要提交事务 + sqlSession.commit(); +``` + +> **update** + +1. 编写接口 +2. sql语句 + +```xml + + + update mybatis.user + set name = #{name}, + pwd = #{pwd} + where id = #{id}; + +``` + +3. 测试:需要提交事务 + +> **Delete** + +1. 接口 +2. sql语句 + +```xml + + + delete + from mybatis.user + where id = #{id}; + +``` + +3. 测试:需要提交事务 + + + +## 使用Map传参 + +假如我们的实体类或者数据库中的表、字段或者参数过多,我们应当考虑使用Map + +使用User对象时,假如需要修改密码,当字段过多时,sql语句中还需要把其他字段给加上,很麻烦 + +使用map时: + +1. 接口 + +```java +int updateUser2(Map map); +``` + +2. sql语句 + +sql语句中传入的参数类型为map + +具体传入的参数不需要和数据库以及实体类中的对应,在map.put()中进行对应即可 + +```xml + + + update mybatis.user + set pwd = #{userPwd} + where id = #{userId}; + +``` + +3. 测试 + +```java +@Test +public void updateUser2Test() { + // 1.获取sqlsession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + // 2.sql + UserDao mapper = sqlSession.getMapper(UserDao.class); + + // map + Map map = new HashMap<>(); + map.put("userId", 3); + map.put("userPwd", "000000"); + + int i = mapper.updateUser2(map); + + // 增删改需要提交事务 + sqlSession.commit(); + // 3.关闭 + sqlSession.close(); +} +``` + +## 模糊查询 + +模糊查询需要防止sql注入 + +1. java代码执行的时候,传入通配符% % + +```java +List u = mapper.getUserLike("%tian%"); +``` + +2. mapper中的sql语句使用where 字段 like 加%,会导致sql注入 + +```xml +select * from mybatis.user where name like "%"#{value}"%" +``` + + + +# 3、配置解析 + +## 核心配置文件mybatis-config.xml + +配置文件包含了会深深影响Mybatis行为的设置和属性信息 + +* **属性properties** +* **设置settings** +* **类型别名typeAliases** +* **环境配置environments** + * environment(环境变量) + - transactionManager(事务管理器) + - dataSource(数据源) +* **映射器mappers** +* 了解 + * 类型处理器typeHandlers + * 对象工厂objectFactory + * 插件plugins + * 数据库厂商标识databaseProvider + +## 环境配置environments + +```xml + + + + + + + + + + + + + +``` + +**MyBatis 可以配置成适应多种环境,通过id选择使用哪一个** + +**每个 SqlSessionFactory 实例只能选择一种环境** + + + +* **事务管理器** + +在 MyBatis 中有两种类型的事务管理器(也就是 type="[**JDBC|MANAGED**]"),**默认使用JDBC:** + +* **数据源** + +有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"),**默认使用pooled** + +**UNPOOLED**– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 + +**POOLED**– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。 + +连接数据库,数据库连接池:dbcp、c3p0、druid,用完会回收 + +**JNDI** – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用 + +## 属性properties + +**通过properties属性涉嫌引用配置文件** + +属性都是可以外部配置且可以动态替换的,既可以在典型的java属性文件中配置,也可可以通过properties元素的子元素来传递,【db.properties】 + +**通过外部配置db.properties** + +1. 编写配置文件:db.properties: + +```properties +driver=com.mysql.cj.jdbc.Driver +url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8 +username=root +password=123456 +``` + +2. xml中引入配置文件 + +在xml中,所有的标签都可以规定其顺序,properties=》settings=》typerAliases。。properties只能放在最上面 + +* 中间可以设置属性,设置用户名和密码 +* 如果两个设置的属性中和db.properties中有同一个字段,优先使用外部的db.properties + +```xml + + + +``` + +3. 在整个配置文件中用来替换需要动态配置的属性值 + +```xml + + + + + + +``` + +## 类型别名typerAliases + +类型别名可为 Java 类型设置一个**缩写名字**。 它仅用于 XML 配置,意在**降低冗余的全限定类名**书写。 + +```xml + + + + +``` + +也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如: + +在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 `domain.blog.Author` 的别名为 `author` + +```xml + + + +``` + +使用`com.tian.pojo.User`时只需要用`user`即可 + +**在实体类比较少的时候,使用第一种,实体类多的时候使用第二种,第一种可以自定义别名,第二种可以通过在实体类上增加注解来自定义别名,如下:** + +```java +@Alias("newName") +public class User{ + ... +} +``` + +## 设置settings + +这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为 + + + +| cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true \| false | true | +| :----------------: | ------------------------------------------------------------ | ------------------------------------------------------------ | ------ | +| lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 `fetchType` 属性来覆盖该项的开关状态。 | true \| false | false | +| logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J \| LOG4J \| LOG4J2 \| JDK_LOGGING \| COMMONS_LOGGING \| STDOUT_LOGGING \| NO_LOGGING | 未设置 | + +一个配置完整的 settings 元素的示例如下: + +```xml + + + + + + + + + + + + + + + + + +``` + +## 其他配置 + +* 类型处理器typeHandlers +* 对象工厂objectFactory +* 插件plugins + * mybatis-generator-core + * mybatis-plus:一个增强工具,简化mybatis + * 通用mapper +* 数据库厂商标识databaseProvider + +## 映射器mappers + +MapperRegistry:注册绑定我们的Mapper文件 + +MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们**需要告诉 MyBatis 到哪里去找到这些语句**。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 `file:///` 形式的 URL),或类名和包名等。 + +**maven中resources文件夹下的文件在编译后,都放在了根目录下** + +> **方式1 :推荐使用** + +```xml + + + + +``` + +> **方式2:使用class文件** + +**注意**: + +* 接口和他的Mapper配置文件必须同名 +* 接口和他的Mapper配置文件必须在同一个包下(Mapper配置文件可以在maven的resources目录下) + +```xml + + + + +``` + +> **方式3**:使用扫描包进行绑定注入 + +**注意:** + +* 接口和他的Mapper配置文件必须同名 +* 接口和他的Mapper配置文件必须在同一个包下 + +```xml + + + + +``` + +# 4、解决属性名和字段名不一致 + +> **问题** + +如:数据库中的字段名为pwd,实体类中的属性名为password + +User: + +```java +public class User { + private int id; + private String name; + private String password; + ... +} +``` + +Mapper: + +```xml + +``` + +test: + +```java +@Test +public void getUserByIdTes() { + SqlSession sqlSession = MybatisUtils.getSqlSession(); + UserMapper mapper = sqlSession.getMapper(UserMapper.class); + User userById = mapper.getUserById(2); + System.out.println(userById); +} +``` + +查询得到输出: + +`User{id=2, name='tian2', password='null'}` + +分析: + +由于数据库中的字段为pwd,在测试查询时,传入的参数为password,导致查询不到 + +> **解决方法:** + +* 起别名 + +在mapper中修改sql语句: + +```xml + +``` + +* resultMap + +## resultMap + +> 结果集映射 + +* `resultMap`元素是Mybatis中最重要最强大的元素 +* 设计思想是:对于简单的语句根本不需要配置显式的结果集映射,对于复杂的语句,只需要描述他们的关系就可以了 + +> 简单的结果集映射 + +```xml + + + + + + + +``` + +> 复杂 + +一对多,多对一 + + + +# 5、日志 + +## 日志工厂 + +> **介绍** + +如果一个数据库操作出现了异常,需要排错,需要日志 + +曾经用:sout、debug + +现在:日志工厂 + +mybatis提供的: + +* SLF4J + +* **LOG4J** +* LOG4J2 +* JDK_LOGGING +* COMMONS_LOGGING +* **STDOUT_LOGGING** +* NO_LOGGING + +具体使用哪一个,在mybatis-config.xml中的settings中设置,默认不使用 + +![image-20210807195224236](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807195224236.png) + +> **标准的日志工厂实现** + +使用`STDOUT_LOGGING`不需要导包 + +* 在mybatis-config.xml中配置日志 + +```xml + + + + + +``` + +* 配置后输出: + +```bash +Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter. +Class not found: org.jboss.vfs.VFS +JBoss 6 VFS API is not available in this environment. +Class not found: org.jboss.vfs.VirtualFile +VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment. +Using VFS adapter org.apache.ibatis.io.DefaultVFS +Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo +Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo +Reader entry: User.class +Listing file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo +Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo/User.class +Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-02/target/classes/com/tian/pojo/User.class +Reader entry: ���� < : +Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo +Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo +Reader entry: User.class +Listing file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo +Find JAR URL: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo/User.class +Not a JAR: file:/C:/javaCode/MybatisStudy/Mybatis-01/target/classes/com/tian/pojo/User.class +Reader entry: ���� < : +Checking to see if class com.tian.pojo.User matches criteria [is assignable to Object] +Checking to see if class com.tian.pojo.User matches criteria [is assignable to Object] +PooledDataSource forcefully closed/removed all connections. +PooledDataSource forcefully closed/removed all connections. +PooledDataSource forcefully closed/removed all connections. +PooledDataSource forcefully closed/removed all connections. +Opening JDBC Connection +Created connection 1446983876. +Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@563f38c4] +==> Preparing: select * from mybatis.user where id = ? +==> Parameters: 2(Integer) +<== Columns: id, name, pwd +<== Row: 2, tian2, 1234567 +<== Total: 1 +User{id=2, name='tian2', password='1234567'} +``` + + + +## Log4j + +**注**:需要导包 + +> **什么是log4j** + +* Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件 +* 我们也可以控制每一条日志的输出格式 +* 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程 +* 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。 + +> **使用log4j** + +1. 导包 + +```xml + + log4j + log4j + 1.2.17 + +``` + +2. 新增配置文件`log4j.properties` + +网上找即可 + +```properties +#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 +log4j.rootLogger=DEBUG,console,file + +#控制台输出的相关设置 +log4j.appender.console = org.apache.log4j.ConsoleAppender +log4j.appender.console.Target = System.out +log4j.appender.console.Threshold=DEBUG +log4j.appender.console.layout = org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=[%c]-%m%n + +#文件输出的相关设置 +log4j.appender.file = org.apache.log4j.RollingFileAppender +log4j.appender.file.File=./log/tian.log +log4j.appender.file.MaxFileSize=10mb +log4j.appender.file.Threshold=DEBUG +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n + +#日志输出级别 +log4j.logger.org.mybatis=DEBUG +log4j.logger.java.sql=DEBUG +log4j.logger.java.sql.Statement=DEBUG +log4j.logger.java.sql.ResultSet=DEBUG +log4j.logger.java.sql.PreparedStatement=DEBUG +``` + +3. 配置log4j为日志的实现 + +```XML + + + + +``` + +4. **log4j使用** + +* 在要使用log4j的类中,导入包`import org.apache.log4j.Logger` +* 获取日志对象,参数为当前类的class + +```java +static Logger logger = Logger.getLogger(UserDaoTest.class); +``` + +* 编写测试代码 + +日志级别:info\debug\error + +```java +package com.tian.dao; + +import org.apache.log4j.Logger; +import org.junit.Test; + +/** + * @program: MybatisStudy + * @description: 测试 + * @author: TianZD + * @create: 2021-08-06 22:56 + **/ +public class UserDaoTest { + static Logger logger = Logger.getLogger(UserDaoTest.class); + @Test + public void testLog4j() { + //不同的级别 + logger.info("info:进入了testLog4j方法"); + logger.debug("debug:进入了testLog4j方法"); + logger.error("error:进入了testLog4j方法"); + } +} +``` + +* 输出 + +在控制台和配置的输出文件中会输出以下: + +```bash +[com.tian.dao.UserDaoTest]-info:进入了testLog4j方法 +[com.tian.dao.UserDaoTest]-debug:进入了testLog4j方法 +[com.tian.dao.UserDaoTest]-error:进入了testLog4j方法 +``` + +# 6、分页 + +> **为什么要分页** + +减少数据的处理量 + +> **limit分页** + +```sql +SELECT * FROM user LIMIT startIndex,pageSize; +``` + +> 使用mybatis分页 + +核心sql + +1. 接口 + +```java +// 分页查询 +List getUserByLimit(Map map); +``` + +2. Mapper.xml + +```xml + + + + + + + +``` + +3. 测试 + +```java +package com.tian.dao; + +import com.tian.pojo.User; +import com.tian.utils.MybatisUtils; +import org.apache.ibatis.session.SqlSession; +import org.apache.log4j.Logger; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @program: MybatisStudy + * @description: 测试 + * @author: TianZD + * @create: 2021-08-06 22:56 + **/ +public class UserDaoTest { + static Logger logger = Logger.getLogger(UserDaoTest.class); + @Test + public void TestLimit() { + //1. 获取sqlsession对象 + SqlSession sqlSession = MybatisUtils.getSqlSession(); + //2. sql, 获取mapper + UserMapper mapper = sqlSession.getMapper(UserMapper.class); + //2.1 map + Map map = new HashMap<>(); + map.put("startIndex", 1); + map.put("pageSize", 2); + //2.2 sql + List userByLimit = mapper.getUserByLimit(map); + for (User user : userByLimit) { + logger.info(user); + } + //3. 关闭 + sqlSession.close(); + } +} +``` + +# 7、使用注解开发(mybatis不推荐) + +## 面向接口编程 + +> **三个面向** + +**面向过程**:考虑问题时,以一个具体的流程为单位,考虑它的实现 + +**面向对象**:考虑问题时,以对象为单位,考虑他的属性和方法 + +**面向接口**:更多体现的是对系统整体的架构 + +> **理解** + +**接口**:应是定义(规范、约束)与实现(名实分离的原则)的分离,本身反映了系统设计人员对系统的抽象理解 + +**面向过程编程(`Procedure Oriented`、简称`PO`)** 和 **面向对象编程(`Object Oriented`、简称`OO`)** 我们一定听过,然而实际企业级开发里受用更多的一种编程思想那就是:**面向接口编程(`Interface-Oriented`)**! + +接口这个概念我们一定不陌生,实际生活中**最常见的例子就是**:插座! + +我们只需要事先定义好插座的**接口标准**,各大插座厂商只要按这个接口标准生产,管你什么牌子、内部什么电路结构,这些均和用户无关,用户拿来就可以用;而且即使插座坏了,只要换一个符合接口标准的新插座,一切照样工作! + +同理,实际代码设计也是这样! + +我们在设计一个软件的代码架构时,我们都希望**事先约定**好各个功能的**接口**(即:约定好接口签名和方法),实际开发时我们只需要实现这个接口就能完成具体的功能!后续即使项目变化、功能升级,程序员只需要按照接口约定重新实现一下,就可以达到系统升级和扩展的目的! + +正好,Java中天生就有`interface`这个语法,这简直是为面向接口编程而生的! + +> **优点** + +* 代码的灵活解耦 +* 代码的扩展性 +* 提高复用 +* 分层开发中,上层不用管具体实现,大家遵守共同的标准,使得开发变得容易,规范性更好 + +## mybatis中的注解开发 + +**mybatis中不推荐使用注解**,其他框架推荐 + +使用注解来映射简单语句会使**代码显得更加简洁**,但对于**稍微复杂一点的语句,Java 注解不仅力不从心**,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。 + +用来替代mapper.xml配置文件 + +1. **mybatis.config.xml**中绑定接口 + +```xml + + + + +``` + +2. **UserMapper.interface** + +```java +public interface UserMapper { + //注解 + @Select("select id,name,pwd as password from mybatis.user") + List getUsers(); +} +``` + +3. **test.java, 没有变化** + +```java +public class UserDaoTest { + @Test + public void test() { + SqlSession sqlSession = MybatisUtils.getSqlSession(); + UserMapper mapper = sqlSession.getMapper(UserMapper.class); + List users = mapper.getUsers(); + for (User user : users) { + System.out.println(user); + } + sqlSession.close(); + } +} +``` + +> **本质** + +**反射机制实现** + +> **底层** + +**动态代理** + +# 8、Mybatis执行流程 + +![image-20210807224915524](https://gitee.com/tianzhendong/img/raw/master//images/image-20210807224915524.png) + +# 9、多对一、一对多 + +**关联**:多个学生,关联一个老师,多对一 + +**集合**:一个老师,有很多学生,一对多 + +。。。 + +# 10、动态SQL + +> **理解** + +Mybatis的强大特性之一就是动态sql,使用动态sql可以拜托不同条件下拼接sql语句的痛苦 + +**动态sql就是根据不同的条件,生成不同的sql语句** + +如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。 + +- if +- choose (when, otherwise) +- trim (where, set) +- foreach + + + +## 搭建环境 + +> **创建sql表** + +```sql +CREATE TABLE `blog`( + `id` varchar(50) NOT NULL COMMENT '博客id', + `title` varchar(100) not null comment '博客标题', + `author` varchar(30) not null comment '博客作者', + `create_time` datetime not null comment '创建时间', + `views` int(30) not null comment '浏览量' +)engine = InnoDB default charset=utf8 +``` + +> **创建一个基础工程** + +1. 导包 + +2. 编写配置文件mybatis-config.xml和db.properties + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +**mybatisUtils类省略** + +**编写IDUtils类,用于生成随机id** + +```java +import java.util.UUID; +public class IDUtils { + public static String getId() { + return UUID.randomUUID().toString().replaceAll("-", ""); + } +} +``` + + + + + + + +3. 编写实体类 + +```java +@Data +public class Blog { + private String id; + private String title; + private String author; + //data的属性名和字段名不一致,字段名为create_time + private Date createTime; + private int views; +} +``` + +4. 编写实体类对应Mapper接口和Mapper.xml文件 + +```java +public interface BlogMapper { + // 插入数据 + int addBlog(Blog blog); +} +``` + +```xml + + + insert into mybatis.blog(id, title, author, create_time, views) + values (#{id},#{title},#{author},#{createTime},#{views}); + + +``` + +**生成的数据库如下:** + +![image-20210808003003987](https://gitee.com/tianzhendong/img/raw/master//images/image-20210808003003987.png) + + + +## 动态sql-IF + +1. **BlogMapper.interface** + +```java + //查询博客 + List queryBlogIF(Map map); +``` + +2. **BlogMapper.xml** + +```xml + +``` + +where属性:*where* 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,*where* 元素也会将它们去除。 + +```xml +select * from mybatis.blog + + + and title = #{title} + + + and author = #{author} + + +``` + +3. **test** + +**传入空值,查询全部** + +```java +@Test +public void queryBlogIFTest() { + SqlSession sqlSession = MybatisUtils.getSqlSession(); + BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); + HashMap map = new HashMap(); + //传入空值,查询全部 + List blogs = mapper.queryBlogIF(map); + for (Blog blog : blogs) { + System.out.println(blog); + } + sqlSession.close(); +} +``` + +**传入值,过滤查询** + +```java +public void queryBlogIFTest() { + SqlSession sqlSession = MybatisUtils.getSqlSession(); + BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); + + HashMap map = new HashMap(); + //赋值,过滤 + map.put("title", "blog1"); + List blogs = mapper.queryBlogIF(map); + for (Blog blog : blogs) { + System.out.println(blog); + } + sqlSession.close(); +} +``` + +## 动态sql-choose(when,otherwise) + +从多个条件中选择一个使用,有点像 Java 中的 switch 语句 + +传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就根据views进行查找 + +```xml + +``` + + + +## set元素 + +用于动态更新语句的类似解决方案叫做 *set*。*set* 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如 + +```xml + + update Author + + username=#{username}, + password=#{password}, + email=#{email}, + bio=#{bio} + + where id=#{id} + +``` + +*set* 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。 + + + +## sql片段 + +抽取sql部分片段,方便重复代码复用 + +```xml + + + + and title = #{title} + + + and author = #{author} + + + + + SELECT * + FROM mybatis.blog + WHERE ID in + + + id = #{item} + + + +``` + +# 11、缓存 + +## 简介 + +> **什么是缓存** + +放在内存中的临时数据 + +一次查询的结果暂时放在内存中,再次查询相同数据的时候直接从缓存取,不用再走数据库了,从而提高查询雄安率,解决了高并发系统的性能问题 + +> **为什么使用缓存** + +减少和数据库的交互次数,减小系统开销,提高系统效率 + +> **什么样的数据能使用缓存** + +经常查询并且不经常改变的数据 + + + +## Mybatis缓存 + +> mybatis缓存介绍 + +mybatis默认定义了两级缓存:一级缓存和二级缓存 + +* 默认下,只有一级缓存开启,(sqlsession级别的缓存,也叫本地缓存,在sqlsession创建和关闭之间的部分) +* 二级需要手动开启和配置,基于namespace级别的缓存 +* 为了提高扩展性,mybatis定义了缓存接口cache,可通过实现cache接口自定义二级缓存 + + + +> **一级缓存** + +默认开启,本地缓存 + +sqlsession级别的缓存,在sqlsession创建和关闭之间,使用代码查询统一数据多次,只会和数据库交互一次 + +> **二级缓存** + +* 全局缓存 +* 基于namespace级别的缓存,一个名称空间,对应一个二级缓存 +* 工作机制 + * 一个会话查询一条数据,数据被放在当前会话的一级缓存中 + * 会话关闭后,对应的一级缓存就没了,但是我们想要的是会话关闭了,一级缓存中的数据保存到二级缓存中 + * 新的会话查询数据,可以从二级缓存中获取内容 + * 不同的mapper查出的数据会放在自己对应的缓存中 + + + +**步骤**: + +1. 开启全局缓存 + +mybatis-config.xml + +```xml + + + + +``` + +2. 在mapper.xml中加入``标签 + +使用:在mapper.xml中加标签 + +```xml + +``` + +高级配置: + +```xml + +``` + +这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。 + +## Mybatis缓存原理 + +![](https://gitee.com/tianzhendong/img/raw/master//images/image-20210808041200380.png) + diff --git a/source/_posts/Java/SSM整合.md b/source/_posts/Java/SSM整合.md new file mode 100644 index 0000000..5157586 --- /dev/null +++ b/source/_posts/Java/SSM整合.md @@ -0,0 +1,921 @@ +--- +title: SSM整合 +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: SSM整合学习笔记,粗略学了一下,没有参考价值 +tags: + - SSM + - Java + - 学习笔记 +categories: + - java +reprintPolicy: cc_by +abbrlink: 254393f0 +date: 2022-04-29 11:10:43 +coverImg: +img: +password: +--- + + + +[toc] + + + +# 1、SpringMVC-SSM整合 + +## 1.1、环境 + +> 环境 + +* IDE:IDEA 2021 1.1 +* 项目管理:Maven ,apache-maven-3.8.1 +* JAVA:JDK 16.01 +* Web服务器:apache-tomcat-9.0.50 +* 数据库:MySQL 8.0.26 +* 数据库管理工具:Navicat premium 15 + +> 数据库环境 + +```sql +CREATE DATABASE `ssmbuild`; +USE `ssmbuild`; +DROP TABLE IF EXISTS `books`; +CREATE TABLE `books` ( + `bookID` INT(10) NOT NULL AUTO_INCREMENT COMMENT '书id', + `bookName` VARCHAR(100) NOT NULL COMMENT '书名', + `bookCounts` INT(11) NOT NULL COMMENT '数量', + `detail` VARCHAR(200) NOT NULL COMMENT '描述', + KEY `bookID` (`bookID`) +)ENGINE = INNODB DEFAULT CHARSET = utf8; + +INSERT INTO `books`(`bookID`,`bookName`,`bookCounts`,`detail`) +VALUES (1,'java',1,'从入门到放弃'), + (2,'MySQL',10,'从删库到跑路'), + (3,'Linux',5,'从进门到坐牢'); +``` + +> 基本环境搭建 + +* 新建maven工程 +* 配置pom.xml + * 导入依赖 + * 配置静态资源 + +```xml + + + + + junit + junit + 4.12 + test + + + + mysql + mysql-connector-java + 8.0.25 + + + + com.mchange + c3p0 + 0.9.5.2 + + + + javax.servlet + servlet-api + 2.5 + + + javax.servlet.jsp + jsp-api + 2.2 + + + javax.servlet + jstl + 1.2 + + + + org.mybatis + mybatis + 3.5.7 + + + + org.mybatis + mybatis-spring + 2.0.2 + + + + org.springframework + spring-webmvc + 5.3.9 + + + org.springframework + spring-jdbc + 5.3.9 + + + + + log4j + log4j + 1.2.17 + + + + + + + + + src/main/java + + **/*.properties + **/*.xml + + false + + + src/main/resources + + **/*.properties + **/*.xml + + false + + + +``` + +> IDEA连接数据库 + +![image-20210815222657080](https://gitee.com/tianzhendong/img/raw/master//images/image-20210815222657080.png) + +> 建立项目包结构 + +* dao +* pojo +* controller +* service + +> 建立核心配置文件 + +* spring:applicationContext.xml + +```xml + + + + +``` + +* mybatis:mybatis-config.xml和database.properties + +mybatis-config.xml + +```xml + + + + + + + + + + + + + + + + +``` + +database.properties + +```properties +jdbc.driver=com.mysql.cj.jdbc.Driver +#如果使用mysql8.0以上,需要增加时区设置 +jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai +jdbc.username=root +jdbc.password=123456 +``` + +* log4j.properties + +```properties +#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 +log4j.rootLogger=DEBUG,console,file + +#控制台输出的相关设置 +log4j.appender.console = org.apache.log4j.ConsoleAppender +log4j.appender.console.Target = System.out +log4j.appender.console.Threshold=DEBUG +log4j.appender.console.layout = org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=[%c]-%m%n + +#文件输出的相关设置 +log4j.appender.file = org.apache.log4j.RollingFileAppender +log4j.appender.file.File=./log/tian.log +log4j.appender.file.MaxFileSize=10mb +log4j.appender.file.Threshold=DEBUG +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n + +#日志输出级别 +log4j.logger.org.mybatis=DEBUG +log4j.logger.java.sql=DEBUG +log4j.logger.java.sql.Statement=DEBUG +log4j.logger.java.sql.ResultSet=DEBUG +log4j.logger.java.sql.PreparedStatement=DEBUG +``` + +## 1.2、Mybatis层 + +主要是dao层和service层,底层相关,MVC的Model层,数据和业务 + + + +> pojo层 + +```java +public class Books { + private int bookID; + private String bookName; + private int bookCounts; + private String detail; +//get、set、toString、construct +} +``` + +> dao层 + +* 接口 + +```java +public interface BookMapper { + //add + int addBook(Books books); + + //delete + int deleteBook(@Param("bookID") int id); + + //update + int updateBook(Books books); + + //select one + Books selectBookById(@Param("bookID") int id); + + //select all + List selectBookAll(); +} +``` + +* Mapper.xml + +```xml + + + + + insert into ssmbuild.books (bookName, bookCounts, detail) + values (#{bookName},#{bookCounts},#{detail}); + + + + delete + from ssmbuild.books + where bookID = #{bookID}; + + + + update ssmbuild.books + set bookName = #{bookName}, bookCounts = #{bookCounts}, detail = #{detail} + where bookID = #{bookID}; + + + + + + + +``` + +* 绑定mapper.xml到mybatis-config.xml配置文件中 + +```xml + + + + + +``` + +> service层 + +* BookService接口 + +```java +public interface BookService { + //add + int addBook(Books books); + + //delete + int deleteBook(int id); + + //update + int updateBook(Books books); + + //select one + Books selectBookById(int id); + + //select all + List selectBookAll(); +} +``` + + + +* 接口实现类 + +```java +public class BookServiceImpl implements BookService{ + //业务层调用dao层:组合dao层 + private BookMapper bookMapper; + + public void setBookMapper(BookMapper bookMapper) { + this.bookMapper = bookMapper; + } + + @Override + public int addBook(Books books) { + return bookMapper.addBook(books); + } + + @Override + public int deleteBook(int id) { + return bookMapper.deleteBook(id); + } + + @Override + public int updateBook(Books books) { + return bookMapper.updateBook(books); + } + + @Override + public Books selectBookById(int id) { + return bookMapper.selectBookById(id); + } + + @Override + public List selectBookAll() { + return bookMapper.selectBookAll(); + } +} +``` + +## 1.3、Spring层 + +> dao层 + +spring-dao.xml + +* 关联数据库配置文件 +* 连接池 +* sqlSessionFactory +* sqlSession + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +> service + +spring-service.xml + +* 扫描service下的包 +* 将业务类注入到spring,可以通过配置或者注解实现 +* 声明式事务配置 +* AOP事务支持 + +```xml + + + + + + + + + + + + + + + + + + + +``` + +## 1.4、SpringMVC层 + +> 增加web支持 + +> web.xml + +* dispatchservlet +* 乱码过滤 + +```xml + + + + + + dispatcherServlet + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + classpath:springmvc-servlet.xml + + + 1 + + + dispatcherServlet + / + + + + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + utf-8 + + + + encodingFilter + /* + + + + + 15 + + + +``` + +> springmvc-servlet.xml + +```xml + + + + + + + + + + + + + + + + + + +``` + +## 1.5、配置文件整合 + +applicationContext.xml + +```xml + + + + + + + +``` + +# 2、实际业务实现 + +## 2.1、查询书籍功能 + +### 查询 + +将controller和web交互 + +> controller + +*BookController.class* + +```java +@Controller +@RequestMapping("/book") +public class BookController { + //controller层调用service层 + @Autowired + @Qualifier("BookServiceImpl") + private BookService bookService; + + //查询全部书籍,并返回书籍展示页面 + public String selectAllBook(Model model) { + List books = bookService.selectBookAll(); + model.addAttribute("list", books); + return "allBook"; + } + +} +``` + +> jsp + +* allBook.jsp +* index.jsp,设置由首页跳转 + +```jsp +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + 书籍展示 + + +

书籍展示

+ + +``` + +```jsp +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + $Title$ + + + $END$ +

+ 进入书籍展示页面 +

+ + +``` + +### 错误 + +```bash +org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.tian.service.BookService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier("BookServiceImpl")} +``` + +bean不存在 + +> 解决 + +web.xml中需要引入applicationContxt.xml而不是springmvc-config.xml + +### 美化 + +> 首页 + +```jsp +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + 首页 + + + +

+ 进入书籍展示页面 +

+ + +``` + +![](https://gitee.com/tianzhendong/img/raw/master//images/image-20210816033535794.png) + +> 查询页 + +```jsp +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + 书籍展示 + + + + + + + + + + + + + + + + + + + + +
书籍列表
书籍ID书籍名称书籍数量书籍描述
${books.bookID}${books.bookName}${books.bookCounts}${books.detail}
+ + +``` + +![image-20210816201951940](https://gitee.com/tianzhendong/img/raw/master//images/image-20210816201951940.png) + +## 2.2、添加书籍功能 + +> 待跳转页面 + +addBook.jsp + +```jsp +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + 添加书籍页面 + + +
+ + + + + + + + + + + + + + + + + +
书籍名称:
书籍数量:
书籍描述:
+
+ + +``` + +![image-20210816205546620](https://gitee.com/tianzhendong/img/raw/master//images/image-20210816205546620.png) + +> controller + +```java +// 跳转到添加书籍界面 +@RequestMapping("/toAddBook") +public String toAddBook() { + return "addBook"; +} + +//添加书籍请求 +@RequestMapping("/addBook") +public String addBook(Books book) { + bookService.addBook(book); + return "redirect:/book/allBook"; +} +``` + +> 跳转按钮 + +allBook.jsp + +```xml + + + + 添加书籍 + +``` + +![image-20210816210610577](https://gitee.com/tianzhendong/img/raw/master//images/image-20210816210610577.png) + +## 2.3、修改、删除书籍 + +> 按钮 + +allBook.jsp + +```jsp + +  修改 + | + 删除  + +``` + +![image-20210816221451118](https://gitee.com/tianzhendong/img/raw/master//images/image-20210816221451118.png) + +> controller + +```java + //跳转到修改请求页面 + @RequestMapping("/toUpdateBook/{bookId}") + public String toUpdateBook(@PathVariable("bookId") int id, Model model) { + Books books = bookService.selectBookById(id); + model.addAttribute("bookSelected", books); + return "updateBook"; + } + + //修改书籍 + @RequestMapping("/updateBook") + public String updateBook(Books books) { + bookService.updateBook(books); + return "redirect:/book/allBook"; + } + + //删除书籍 + @RequestMapping("/deleteBook/{bookId}") + public String deleteBook(@PathVariable("bookId") int id) { + bookService.deleteBook(id); + return "redirect:/book/allBook"; + } +} +``` + +> 待跳转页面 + +updateBook.jsp + +```jsp +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + 修改书籍 + + +
+ <%--隐藏于传递不需要用户修改的bookID--%> + + + + + + + + + + + + + + + + + + +
书籍名称:
书籍数量:
书籍描述:
+
+ + +``` + +![image-20210816223906448](https://gitee.com/tianzhendong/img/raw/master//images/image-20210816223906448.png) + + + diff --git a/source/_posts/Java/Shiro学习.md b/source/_posts/Java/Shiro学习.md new file mode 100644 index 0000000..cc2415b --- /dev/null +++ b/source/_posts/Java/Shiro学习.md @@ -0,0 +1,1397 @@ +--- +title: Shiro学习 +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: Shiro学习笔记,粗略学了一下,没有参考价值 +tags: + - Shiro + - java + - 学习笔记 +categories: + - java +reprintPolicy: cc_by +abbrlink: c0a48af9 +date: 2022-04-29 10:50:23 +coverImg: +img: +password: +--- + +--- +title: shiro +tags: java +notebook: JAVA + +--- + +# 1、Shiro简介 + +## 1.1、Shiro 是什么? + +* Apache Shiro 是 Java 的一个安全(权限)框架。 +* Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。 +* Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。 +* 下载地址 + * 官网:http://shiro.apache.org/ + * github:https://github.com/apache/shiro + +## 1.2、有哪些功能? + +[![image-20200729114647110](https://camo.githubusercontent.com/39792d7499fb7656be26a3573d232e7932aa10e43d2950cc47f3d1f53de77554/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131343634373131302e706e67)](https://camo.githubusercontent.com/39792d7499fb7656be26a3573d232e7932aa10e43d2950cc47f3d1f53de77554/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131343634373131302e706e67) + +* Authentication:身份认证/登录,验证用户是不是拥有相应的身份 +* Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限 +* Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的 +* Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储 +* Web Support:Web 支持,可以非常容易的集成到Web 环境 +* Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率 +* Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去 +* Testing:提供测试支持 +* "Run As":允许一个用户假装为另一个用户(如果他们允许)的身份进行访问 +* Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了 + +## 1.3、Shiro架构(外部) + +从外部来看Shiro,即从应用程序角度的来观察如何使用Shiro完成工作 + +[![image-20200729114702566](https://camo.githubusercontent.com/e34dd392bfaaa048ccc5967d91a82e0eb538cdec6fdbfd602817f3a9e890fc70/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131343730323536362e706e67)](https://camo.githubusercontent.com/e34dd392bfaaa048ccc5967d91a82e0eb538cdec6fdbfd602817f3a9e890fc70/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131343730323536362e706e67) + +* Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API 核心就是Subject。Subject 代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;与Subject 的所有交互都会委托给SecurityManager;Subject 其实是一个门面,SecurityManager才是实际的执行者 +* SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且其管理着所有Subject;可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色 +* Realm:Shiro从Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm 看成DataSource + +## 1.4、Shiro架构(内部) + +[![image-20200729114720578](https://camo.githubusercontent.com/3c0eb32912aa456b96172a990316de48563666257f2b5bcb7889df7af26e4419/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131343732303537382e706e67)](https://camo.githubusercontent.com/3c0eb32912aa456b96172a990316de48563666257f2b5bcb7889df7af26e4419/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131343732303537382e706e67) + +* Subject:任何可以与应用交互的“用户”; +* SecurityManager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证、授权、会话及缓存的管理。 +* Authenticator:负责Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了; +* Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能; +* Realm:可以有1 个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的Realm; +* SessionManager:管理Session 生命周期的组件;而Shiro并不仅仅可以用在Web 环境,也可以用在如普通的JavaSE环境 CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能 +* Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密。 + +# 2、Hello World + +## 2.1、快速实践 + +* 查看官方文档:http://shiro.apache.org/tutorial.html + +* 官方的quickstart : https://github.com/apache/shiro/tree/master/samples/quickstart/ + + [![image-20200729115148574](https://camo.githubusercontent.com/f8297239e27c6e9dac7cba9c91042bd0276075bc593e48a36dcaf90e85cf49ef/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131353134383537342e706e67)](https://camo.githubusercontent.com/f8297239e27c6e9dac7cba9c91042bd0276075bc593e48a36dcaf90e85cf49ef/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393131353134383537342e706e67) + +1. 创建一个maven父工程,用来学习Shiro,删掉不必要的部分 + +2. 创建一个普通的Maven子工程:hell-shiro + + [![image-20200729120114648](https://camo.githubusercontent.com/304172a69b541dc5d1019428828b8549f1b9f21d1911640cc6552ab0f96b9481/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393132303131343634382e706e67)](https://camo.githubusercontent.com/304172a69b541dc5d1019428828b8549f1b9f21d1911640cc6552ab0f96b9481/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393132303131343634382e706e67) + +3. 根据[官方文档](https://github.com/apache/shiro/blob/master/samples/quickstart/pom.xml),我们导入Shiro的依赖 + + [![image-20200729120207730](https://camo.githubusercontent.com/0465f0a1e9d59c306b118c89f971feac1805f79ac5d6349a4f7e16e8883493e7/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393132303230373733302e706e67)](https://camo.githubusercontent.com/0465f0a1e9d59c306b118c89f971feac1805f79ac5d6349a4f7e16e8883493e7/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393132303230373733302e706e67) + + [版本号点击这里](https://mvnrepository.com/artifact/org.apache.shiro/shiro-core) + + ``` + + + org.apache.shiro + shiro-core + 1.5.3 + + + + + org.slf4j + jcl-over-slf4j + 1.7.26 + + + org.slf4j + slf4j-log4j12 + 1.7.26 + + + log4j + log4j + 1.2.17 + + + ``` + +4. 相关配置文件 + + * log4j.properties——[官网](https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/log4j.properties) + + ``` + log4j.rootLogger=INFO, stdout + + log4j.appender.stdout=org.apache.log4j.ConsoleAppender + log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n + + # General Apache libraries + log4j.logger.org.apache=WARN + + # Spring + log4j.logger.org.springframework=WARN + + # Default Shiro logging + log4j.logger.org.apache.shiro=INFO + + # Disable verbose logging + log4j.logger.org.apache.shiro.util.ThreadContext=WARN + log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN + ``` + + * shiro.ini——[官网](https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/shiro.ini) + + ``` + [users] + # user 'root' with password 'secret' and the 'admin' role + root = secret, admin + # user 'guest' with the password 'guest' and the 'guest' role + guest = guest, guest + # user 'presidentskroob' with password '12345' ("That's the same combination on + # my luggage!!!" ;)), and role 'president' + presidentskroob = 12345, president + # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz' + darkhelmet = ludicrousspeed, darklord, schwartz + # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz' + lonestarr = vespa, goodguy, schwartz + + # ----------------------------------------------------------------------------- + # Roles with assigned permissions + # + # Each line conforms to the format defined in the + # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc + # ----------------------------------------------------------------------------- + [roles] + # 'admin' role has all permissions, indicated by the wildcard '*' + admin = * + # The 'schwartz' role can do anything (*) with any lightsaber: + schwartz = lightsaber:* + # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with + # license plate 'eagle5' (instance specific id) + goodguy = winnebago:drive:eagle5 + ``` + + * 启动类 Quickstart——[官网](https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/java/Quickstart.java) + + ``` + /* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + import org.apache.shiro.SecurityUtils; + import org.apache.shiro.authc.*; + import org.apache.shiro.config.IniSecurityManagerFactory; + import org.apache.shiro.mgt.SecurityManager; + import org.apache.shiro.session.Session; + import org.apache.shiro.subject.Subject; + import org.apache.shiro.util.Factory; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + + /** + * Simple Quickstart application showing how to use Shiro's API. + * 简单入门Shiro使用API + * + * @since 0.9 RC2 + */ + public class Quickstart { + + private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); + + + public static void main(String[] args) { + + // The easiest way to create a Shiro SecurityManager with configured + // realms, users, roles and permissions is to use the simple INI config. + // We'll do that by using a factory that can ingest a .ini file and + // return a SecurityManager instance: + + // Use the shiro.ini file at the root of the classpath + // (file: and url: prefixes load from files and urls respectively): + // 读取配置文件: + Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); + SecurityManager securityManager = factory.getInstance(); + + // for this simple example quickstart, make the SecurityManager + // accessible as a JVM singleton. Most applications wouldn't do this + // and instead rely on their container configuration or web.xml for + // webapps. That is outside the scope of this simple quickstart, so + // we'll just do the bare minimum so you can continue to get a feel + // for things. + SecurityUtils.setSecurityManager(securityManager); + + // Now that a simple Shiro environment is set up, let's see what you can do: + + // get the currently executing user: + // 获取当前的用户对象 Subject + Subject currentUser = SecurityUtils.getSubject(); + + // Do some stuff with a Session (no need for a web or EJB container!!!) + //通过当前用户拿到Shiro的Session 可以脱离web存值取值 + Session session = currentUser.getSession(); + session.setAttribute("someKey", "aValue"); + String value = (String) session.getAttribute("someKey"); + if (value.equals("aValue")) { + log.info("Retrieved the correct value! [" + value + "]"); + } + + // let's login the current user so we can check against roles and permissions: + //判断当前的用户是否被认证 + if (!currentUser.isAuthenticated()) { + //Token 令牌 + UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); + //设置记住我 + token.setRememberMe(true); + try { + //执行登录操作 + currentUser.login(token); + } catch (UnknownAccountException uae) { + log.info("There is no user with username of " + token.getPrincipal()); + } catch (IncorrectCredentialsException ice) { + log.info("Password for account " + token.getPrincipal() + " was incorrect!"); + } catch (LockedAccountException lae) { + log.info("The account for username " + token.getPrincipal() + " is locked. " + + "Please contact your administrator to unlock it."); + } + // ... catch more exceptions here (maybe custom ones specific to your application? + catch (AuthenticationException ae) { + //unexpected condition? error? + } + } + + //say who they are: + //print their identifying principal (in this case, a username): + log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); + + //test a role: + // 检查角色 + if (currentUser.hasRole("schwartz")) { + log.info("May the Schwartz be with you!"); + } else { + log.info("Hello, mere mortal."); + } + + //test a typed permission (not instance-level) + //粗粒度 + if (currentUser.isPermitted("lightsaber:wield")) { + log.info("You may use a lightsaber ring. Use it wisely."); + } else { + log.info("Sorry, lightsaber rings are for schwartz masters only."); + } + + //a (very powerful) Instance Level permission: + //细粒度 + if (currentUser.isPermitted("winnebago:drive:eagle5")) { + log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + + "Here are the keys - have fun!"); + } else { + log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); + } + + //all done - log out! + //注销 + currentUser.logout(); + + //结束 + System.exit(0); + } + } + ``` + + [![image-20200729130649625](https://camo.githubusercontent.com/3e9d7c61a23719bee5050d36fe1b7b78b367c725c78cceb7e3257223cec104d8/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393133303634393632352e706e67)](https://camo.githubusercontent.com/3e9d7c61a23719bee5050d36fe1b7b78b367c725c78cceb7e3257223cec104d8/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393133303634393632352e706e67) + + * Spring Secutrry都有~(只是换了个名字) + + ``` + // 获取当前的用户对象 Subject + Subject currentUser = SecurityUtils.getSubject(); + Session session = currentUser.getSession(); + currentUser.isAuthenticated() + currentUser.getPrincipal() + currentUser.hasRole("schwartz") + currentUser.isPermitted("lightsaber:wield") + currentUser.logout(); + ``` + +# 3、SpringBoot集成 + +## 3.1、SpringBoot整合Shiro环境搭建 + +1. 新建一个项目或模块,勾选依赖 + + [![image-20200729174715011](https://camo.githubusercontent.com/98ca792576cb060f2f278b236c3887867ea62c4c8fbff32d7fe51db60841b562/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393137343731353031312e706e67)](https://camo.githubusercontent.com/98ca792576cb060f2f278b236c3887867ea62c4c8fbff32d7fe51db60841b562/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393137343731353031312e706e67) + + pom.xml + + ``` + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + ``` + +2. 测试环境是否正常 + + * 新建一个controller页面 + + ``` + @Controller + public class MyController { + + @RequestMapping({"/","/index"}) + public String toIndex(Model model) { + model.addAttribute("msg","hello,Shiro"); + return "index"; + } + + @RequestMapping("/user/add") + public String add() { + return "user/add"; + } + + @RequestMapping("/user/update") + public String update() { + return "user/update"; + } + } + ``` + + * 新建一个index.html页面 + + ``` + + + + + 首页 + + +
+

首页

+

+ +
+ add | update +
+ + + ``` + + * 新建一个add.html页面 + + ``` + + + + + Title + + +

add

+ + + ``` + + * 新建一个update.html页面 + + ``` + + + + + Title + + +

update

+ + + ``` + + * 项目结构 + + [![image-20200729190325307](https://camo.githubusercontent.com/8d47214d44b9e5d644acf03a1a8ee7f14bcf49be620a6124baab3e84371e36b9/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139303332353330372e706e67)](https://camo.githubusercontent.com/8d47214d44b9e5d644acf03a1a8ee7f14bcf49be620a6124baab3e84371e36b9/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139303332353330372e706e67) + + * 运行截图 + + [![image-20200729190548307](https://camo.githubusercontent.com/0690397defb63342a011bbe18b61a2b53786dc731053d47db48fb5a97e3d95fe/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139303534383330372e706e67)](https://camo.githubusercontent.com/0690397defb63342a011bbe18b61a2b53786dc731053d47db48fb5a97e3d95fe/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139303534383330372e706e67) + +3. 导入shiro整合spring的包——[官网](https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring),查看最新版本 + + ``` + + + + + org.apache.shiro + shiro-spring + 1.5.3 + + ``` + +4. 编写导入配置类 + + * 编写一个自定义类UserRealm + + ``` + //自定义的UserRealm + public class UserRealm extends AuthorizingRealm { + //授权 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + System.out.println("执行了=>授权doGetAuthorizationInfo"); + return null; + } + + //认证 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { + System.out.println("执行了=>认证doGetAuthorizationInfo"); + return null; + } + } + ``` + + * 编写配置ShiroConfig + + * 创建realm对象,需要自定义类 + * DefaultWebSecurityManager + * ShiroFilterFactoryBean + + ``` + @Configuration + public class ShiroConfig { + + //3. shiroFilterFactoryBean + + @Bean + public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + // 设置安全管理器 + bean.setSecurityManager(defaultWebSecurityManager); + + return bean; + } + + //2. DefaultWebSecurityManager + + @Bean + public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + + // 关联userRealm + securityManager.setRealm(userRealm); + return securityManager; + } + //1. 创建realm对象,需要自定义类 + + @Bean + public UserRealm userRealm() { + return new UserRealm(); + } + } + ``` + +## 3.2、Shiro实现登录拦截 + +* 在`ShiroConfig`中的`getShiroFilterFactoryBean`方法中添加如下配置 + + * anon: 无需认证就可以访问 + * authc: 必须认证了才能访问 + * user: 必须拥有记住我功能才能用 + * perms: 拥有对某个资源的权限才能访问 + * role: 拥有某个角色权限 + + ``` + Map filterMap = new LinkedHashMap<>(); + filterMap.put("/user/add","authc"); + filterMap.put("/user/update","authc"); + bean.setFilterChainDefinitionMap(filterMap); + ``` + +* 点击首页的add或者update之后 + + [![image-20200729191619576](https://camo.githubusercontent.com/d148dd9cf41446cddf6f2f595b498350d7fbd40bd59407f39e99353d2fdefdd8/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139313631393537362e706e67)](https://camo.githubusercontent.com/d148dd9cf41446cddf6f2f595b498350d7fbd40bd59407f39e99353d2fdefdd8/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139313631393537362e706e67) + +* 添加拦截成功页面 + + * 登录页面login.html + + ``` + + + + + 登录页面 + + +

登录

+
+ +
+

用户名:

+

密码:

+

密码:

+
+ + + ``` + + * 在MyConfig中添加 + + ``` + @RequestMapping("/toLogin") + public String toLogin() { + return "login"; + } + ``` + + * 在`ShiroConfig`中的`getShiroFilterFactoryBean`方法中添加如下配置 + + ``` + //设置登录的请求 + bean.setLoginUrl("/toLogin"); + ``` + +* 拦截成功页面 + + [![image-20200729192409085](https://camo.githubusercontent.com/c30215e3b1528739cffad8d0497103cb9bac7220047ea6aabf7a5cf3ccfed6c6/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139323430393038352e706e67)](https://camo.githubusercontent.com/c30215e3b1528739cffad8d0497103cb9bac7220047ea6aabf7a5cf3ccfed6c6/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393139323430393038352e706e67) + +## 3.3、Shiro实现用户认证 + +1. 在`MyController`中编写用户提交表单之后处理 + + ``` + @RequestMapping("/login") + public String login(String username, String password, Model model) { + //获取一个用户 + Subject subject = SecurityUtils.getSubject(); + // 封装用户的登录数据 + UsernamePasswordToken token = new UsernamePasswordToken(username, password); + + try { + subject.login(token);//执行登录的方法,如果没有异常就说明ok了 + return "index"; + } catch (UnknownAccountException e) {//用户名不存在 + model.addAttribute("msg","用户名错误"); + return "login"; + } catch (IncorrectCredentialsException e) {//密码不存在 + model.addAttribute("msg","密码错误"); + return "login"; + } + + } + ``` + +2. login.html的修改 + + ``` + + + + + 登录页面 + + +

登录

+
+ +

+
+

用户名:

+

密码:

+

密码:

+
+ + + ``` + +3. 用户输入登录信息 + + * 页面 + + [![image-20200729220647520](https://camo.githubusercontent.com/f18382061e7338a2ff3d1646e4d43f604d506897f62ed318b466f910a04fb2bc/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393232303634373532302e706e67)](https://camo.githubusercontent.com/f18382061e7338a2ff3d1646e4d43f604d506897f62ed318b466f910a04fb2bc/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393232303634373532302e706e67) + + * 控制台 + + [![image-20200729220926500](https://camo.githubusercontent.com/490b413c12125467e4f8916eea52ca4f8b1e1534217a97cc95ee336119aff81f/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393232303932363530302e706e67)](https://camo.githubusercontent.com/490b413c12125467e4f8916eea52ca4f8b1e1534217a97cc95ee336119aff81f/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303732393232303932363530302e706e67) + +4. 用户认证编写`UserRealm`中的认证(doGetAuthenticationInfo) + + ``` + //认证 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + System.out.println("执行了=>认证doGetAuthorizationInfo"); + // 用户名、密码, 数据中取 + String name = "root"; + String password = "123456"; + + UsernamePasswordToken userToken = (UsernamePasswordToken) token; + + if (!userToken.getUsername().equals(name)) { + return null;//抛出异常 UnknownAccountException + } + + // 密码认证,shiro做 + return new SimpleAuthenticationInfo("",password,""); + } + ``` + +## 3.4、Shiro整合Mybatis + +1. 导入依赖 + + ``` + + org.projectlombok + lombok + + + mysql + mysql-connector-java + + + + log4j + log4j + 1.2.17 + + + + com.alibaba + druid + 1.1.23 + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.1.3 + + ``` + +2. 配置文件application.yml的编写 + + ``` + spring: + datasource: + username: root + password: admin + #?serverTimezone=UTC解决时区的报错 + url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 + driver-class-name: com.mysql.cj.jdbc.Driver + type: com.alibaba.druid.pool.DruidDataSource + + #Spring Boot 默认是不注入这些属性值的,需要自己绑定 + #druid 数据源专有配置 + initialSize: 5 + minIdle: 5 + maxActive: 20 + maxWait: 60000 + timeBetweenEvictionRunsMillis: 60000 + minEvictableIdleTimeMillis: 300000 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + poolPreparedStatements: true + + #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 + #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority + #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j + filters: stat,wall,log4j + maxPoolPreparedStatementPerConnectionSize: 20 + useGlobalDataSourceStat: true + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 + + mybatis: + type-aliases-package: nuc.ss.pojo + mapper-locations: classpath:mapper/*.xml + ``` + +3. User类的编写 + + ``` + @Data + @AllArgsConstructor + @NoArgsConstructor + public class User { + private int id; + private String name; + private String pwd; + } + ``` + +4. UserMapper.xml映射 + + ``` + + + + + + + + + + + insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd}); + + + + update mybatis.user set name=#{name},pwd = #{pwd} where id = #{id}; + + + + delete from mybatis.user where id = #{id} + + + ``` + +5. UserService接口实现 + + ``` + public interface UserService { + + public User queryUserByName(String name); + } + ``` + +6. UserServiceImpl业务逻辑 + + ``` + @Service + public class UserServiceImpl implements UserService { + + @Autowired + UserMapper userMapper; + @Override + public User queryUserByName(String name) { + return userMapper.queryUserByName(name); + } + } + ``` + +7. 测试环境 + + ``` + @SpringBootTest + class ShiroSpringbootApplicationTests { + + @Autowired + UserService userService; + @Test + void contextLoads() { + System.out.println(userService.queryUserByName("狂神")); + } + + } + ``` + + [![image-20200730121720922](https://camo.githubusercontent.com/308c5e1c04ff9875ffcc4e2179d20eb918ce323646e2f6208a0d849aae6a262b/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303132313732303932322e706e67)](https://camo.githubusercontent.com/308c5e1c04ff9875ffcc4e2179d20eb918ce323646e2f6208a0d849aae6a262b/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303132313732303932322e706e67) + +8. `UserRealm`连接真实数据库 + + ``` + //认证 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + System.out.println("执行了=>认证doGetAuthorizationInfo"); + + UsernamePasswordToken userToken = (UsernamePasswordToken) token; + + // 真实数据库 用户名、密码, 数据中取 + User user = userService.queryUserByName(userToken.getUsername()); + + if (user == null) {//没有这个人 + return null; + } + + // 密码认证,shiro做 + return new SimpleAuthenticationInfo("",user.getPwd(),""); + } + ``` + + [![image-20200730180019861](https://camo.githubusercontent.com/c9af4c0276c84116535b93571ccafcaa1b84dd7ffeb1eb9ecd885c31d5738144/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138303031393836312e706e67)](https://camo.githubusercontent.com/c9af4c0276c84116535b93571ccafcaa1b84dd7ffeb1eb9ecd885c31d5738144/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138303031393836312e706e67) + +9. 断点测试密码加密类型 + + * 打断点Debug + + [![image-20200730182621912](https://camo.githubusercontent.com/fff18c314ac225e18601b0ab9d48e3d87da840cbd7a919ce32e66d8966b96b11/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138323632313931322e706e67)](https://camo.githubusercontent.com/fff18c314ac225e18601b0ab9d48e3d87da840cbd7a919ce32e66d8966b96b11/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138323632313931322e706e67) + + * 默认是`SimpleCredentialsMatcher`加密 + + [![image-20200730181814293](https://camo.githubusercontent.com/1213c4a155a6cbf83c20593b4334363db71cc351482d4d87ae86f2e231ba204c/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138313831343239332e706e67)](https://camo.githubusercontent.com/1213c4a155a6cbf83c20593b4334363db71cc351482d4d87ae86f2e231ba204c/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138313831343239332e706e67) + + * MD5加密——[测试](http://tool.chinaz.com/tools/md5.aspx) + + 123456——E10ADC3949BA59ABBE56E057F20F883E + + * MD5盐值加密 + + * 所有加密 + + [![image-20200730181944253](https://camo.githubusercontent.com/8cf8498ea81bdcff2fb9917cfdd05525c2e65814464c181b5e636d1e9c1e24a1/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138313934343235332e706e67)](https://camo.githubusercontent.com/8cf8498ea81bdcff2fb9917cfdd05525c2e65814464c181b5e636d1e9c1e24a1/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303138313934343235332e706e67) + +## 3.5、Shiro实现用户授权 + +1. `ShiroConfig`中的`getShiroFilterFactoryBean`方法添加认证代码 + + ``` + //授权,正常情况下,没有授权会跳转到为授权页面 + filterMap.put("/user/add","perms[user:add]"); + filterMap.put("/user/update","perms[user:update]"); + ``` + +2. 登录之后点击add按钮会弹出如下页面 + + [![image-20200730195133631](https://camo.githubusercontent.com/81387d3d3a6a48219f33ce3f53d6880f65cd73c05324208568f22b61c5d2f111/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303139353133333633312e706e67)](https://camo.githubusercontent.com/81387d3d3a6a48219f33ce3f53d6880f65cd73c05324208568f22b61c5d2f111/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303139353133333633312e706e67) + +3. 添加为授权页面 + + * MyController + + ``` + @RequestMapping("/noauto") + @ResponseBody + public String unauthorized() { + return "未经授权,无法访问此页面"; + } + ``` + + * `ShiroConfig`中的`getShiroFilterFactoryBean`方法中添加 + + ``` + //为授权页面 + bean.setUnauthorizedUrl("/noauto"); + ``` + +4. 再次测试 + + [![image-20200730195807437](https://camo.githubusercontent.com/416afcb2900fd3fdd2564dfb3cdff907d7b67a112ef0ab2c1889d1886fc1b42b/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303139353830373433372e706e67)](https://camo.githubusercontent.com/416afcb2900fd3fdd2564dfb3cdff907d7b67a112ef0ab2c1889d1886fc1b42b/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303139353830373433372e706e67) + + [![image-20200730195946692](https://camo.githubusercontent.com/7be97fcb647f93b4eee73a38f2a54888a5f95953f429981bbdbcfc142e473df6/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303139353934363639322e706e67)](https://camo.githubusercontent.com/7be97fcb647f93b4eee73a38f2a54888a5f95953f429981bbdbcfc142e473df6/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303139353934363639322e706e67) + + 所以需要在UserRealm中为用户进行真正授权 + +5. UserRealm类的修改 + + ``` + //自定义的UserRealm + public class UserRealm extends AuthorizingRealm { + + @Autowired + UserService userService; + //授权 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + System.out.println("执行了=>授权doGetAuthorizationInfo"); + + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + + //拿到当前登录的这个对象 + Subject subject = SecurityUtils.getSubject(); + User currentUser = (User)subject.getPrincipal();//拿到user对象 + + //设置当前用户的权限 + info.addStringPermission(currentUser.getPerms()); + + return info; + } + + //认证 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + ...... + // 密码认证,shiro做 + return new SimpleAuthenticationInfo(user,user.getPwd(),""); + } + } + ``` + +6. 再次测试 + + [![image-20200730202810034](https://camo.githubusercontent.com/31ac9e7acaee627de7b5b29aa307991405d5a80d7ed15a291955b34ddd6cc955/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303230323831303033342e706e67)](https://camo.githubusercontent.com/31ac9e7acaee627de7b5b29aa307991405d5a80d7ed15a291955b34ddd6cc955/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303230323831303033342e706e67) + +## 3.6、Shiro整合Thymeleaf + +1. shiro-thymeleaf整合包导入——[官网](https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro) + + ``` + + + com.github.theborakompanioni + thymeleaf-extras-shiro + 2.0.0 + + ``` + +2. 在ShiroConfig中整合ShiroDialect + + ``` + // 整合ShiroDialect: 用来整合 Shiro thymeleaf + @Bean + public ShiroDialect getShiroDialect() { + return new ShiroDialect(); + } + ``` + +3. index.html页面 + + ``` + + + + + 首页 + + + +
+

首页

+

+ + + + +
+ 登录 +
+ +
+ +
+ add +
+ +
+ update +
+ +
+ + + ``` + +4. 页面显示 + + [![image-20200730205736153](https://camo.githubusercontent.com/802d55dc3d0600bf012f27959d4d743aaf19bbdead74f083c9797d14e1b57c58/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303230353733363135332e706e67)](https://camo.githubusercontent.com/802d55dc3d0600bf012f27959d4d743aaf19bbdead74f083c9797d14e1b57c58/68747470733a2f2f67697465652e636f6d2f6c7a685f67697465652f737072696e67626f6f745f696d6167652f7261772f6d61737465722f696d672f696d6167652d32303230303733303230353733363135332e706e67) + +## 3.7、所有代码 + +* ShiroConfig + + ``` + package nuc.ss.config; + + import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; + import org.apache.shiro.spring.web.ShiroFilterFactoryBean; + import org.apache.shiro.web.mgt.DefaultWebSecurityManager; + import org.springframework.beans.factory.annotation.Qualifier; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + import java.util.LinkedHashMap; + import java.util.Map; + + @Configuration + public class ShiroConfig { + + //shiroFilterFactoryBean + + @Bean + public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + // 设置安全管理器 + bean.setSecurityManager(defaultWebSecurityManager); + + // 添加shiro的内置过滤器 + /* + anon: 无需认证就可以访问 + authc: 必须认证了才能访问 + user: 必须拥有记住我功能才能用 + perms: 拥有对某个资源的权限才能访问 + role: 拥有某个角色权限 + */ + + //拦截 + Map filterMap = new LinkedHashMap<>(); + //filterMap.put("/user/add","authc"); + //filterMap.put("/user/update","authc"); + + + //授权,正常情况下,没有授权会跳转到为授权页面 + filterMap.put("/user/add","perms[user:add]"); + filterMap.put("/user/update","perms[user:update]"); + + filterMap.put("/user/*","authc"); + + bean.setFilterChainDefinitionMap(filterMap); + + //设置登录的请求 + bean.setLoginUrl("/toLogin"); + + //为授权页面 + bean.setUnauthorizedUrl("/noauto"); + + return bean; + } + + //DefaultWebSecurityManager + + @Bean + public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + + // 关联userRealm + securityManager.setRealm(userRealm); + return securityManager; + } + //创建realm对象,需要自定义类 + + @Bean + public UserRealm userRealm() { + return new UserRealm(); + } + + // 整合ShiroDialect: 用来整合 Shiro thymeleaf + @Bean + public ShiroDialect getShiroDialect() { + return new ShiroDialect(); + } + } + ``` + +* UserRealm + + ``` + package nuc.ss.config; + + import nuc.ss.pojo.User; + import nuc.ss.service.UserService; + import org.apache.shiro.SecurityUtils; + import org.apache.shiro.authc.*; + import org.apache.shiro.authz.AuthorizationInfo; + import org.apache.shiro.authz.SimpleAuthorizationInfo; + import org.apache.shiro.realm.AuthorizingRealm; + import org.apache.shiro.session.Session; + import org.apache.shiro.subject.PrincipalCollection; + import org.apache.shiro.subject.Subject; + import org.springframework.beans.factory.annotation.Autowired; + + //自定义的UserRealm + public class UserRealm extends AuthorizingRealm { + + @Autowired + UserService userService; + //授权 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { + System.out.println("执行了=>授权doGetAuthorizationInfo"); + + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + + //info.addStringPermission("user:add"); + + //拿到当前登录的这个对象 + Subject subject = SecurityUtils.getSubject(); + User currentUser = (User)subject.getPrincipal();//拿到user对象 + + //设置当前用户的权限 + info.addStringPermission(currentUser.getPerms()); + + return info; + } + + //认证 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + System.out.println("执行了=>认证doGetAuthorizationInfo"); + + UsernamePasswordToken userToken = (UsernamePasswordToken) token; + + // 虚拟用户 + //String name = "root"; + //String password = "123456"; + //if (!userToken.getUsername().equals(name)) { + // return null;//抛出异常 UnknownAccountException + //} + + // 真实数据库 用户名、密码, 数据中取 + User user = userService.queryUserByName(userToken.getUsername()); + + if (user == null) {//没有这个人 + return null; + } + + //首页 + //Subject currentSubject = SecurityUtils.getSubject(); + //Session session = currentSubject.getSession(); + //session.setAttribute("loginUser",user); + + + // 密码认证,shiro做 + return new SimpleAuthenticationInfo(user,user.getPwd(),""); + } + } + ``` + +* MyController + + ``` + package nuc.ss.controller; + + import org.apache.shiro.SecurityUtils; + import org.apache.shiro.authc.IncorrectCredentialsException; + import org.apache.shiro.authc.UnknownAccountException; + import org.apache.shiro.authc.UsernamePasswordToken; + import org.apache.shiro.subject.Subject; + import org.springframework.stereotype.Controller; + import org.springframework.ui.Model; + import org.springframework.web.bind.annotation.RequestMapping; + import org.springframework.web.bind.annotation.ResponseBody; + + @Controller + public class MyController { + + @RequestMapping({"/","/index"}) + public String toIndex(Model model) { + model.addAttribute("msg","hello,Shiro"); + return "index"; + } + + @RequestMapping("/user/add") + public String add() { + return "user/add"; + } + + @RequestMapping("/user/update") + public String update() { + return "user/update"; + } + + @RequestMapping("/toLogin") + public String toLogin() { + return "login"; + } + + @RequestMapping("/login") + public String login(String username, String password, Model model) { + //获取一个用户 + Subject subject = SecurityUtils.getSubject(); + // 封装用户的登录数据 + UsernamePasswordToken token = new UsernamePasswordToken(username, password); + + try { + subject.login(token);//执行登录的方法,如果没有异常就说明ok了 + return "index"; + } catch (UnknownAccountException e) {//用户名不存在 + model.addAttribute("msg","用户名错误"); + return "login"; + } catch (IncorrectCredentialsException e) {//密码不存在 + model.addAttribute("msg","密码错误"); + return "login"; + } + } + + @RequestMapping("/noauto") + @ResponseBody + public String unauthorized() { + return "未经授权,无法访问此页面"; + } + } + ``` + +* pom依赖 + + ``` + + + 4.0.0 + nuc.ss + shiro-springboot + 0.0.1-SNAPSHOT + shiro-springboot + Demo project for Spring Boot + + + 1.8 + UTF-8 + UTF-8 + 2.3.0.RELEASE + + + + + + + + + + com.github.theborakompanioni + thymeleaf-extras-shiro + 2.0.0 + + + + org.projectlombok + lombok + + + mysql + mysql-connector-java + + + + log4j + log4j + 1.2.17 + + + + com.alibaba + druid + 1.1.23 + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.1.3 + + + + + org.apache.shiro + shiro-spring + 1.5.3 + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + ``` + + + +# 4、完美的解释 + +[让 Apache Shiro 保护你的应用](https://www.infoq.cn/article/apache-shiro/?itm_source=infoq_en&itm_medium=link_on_en_item&itm_campaign=item_in_other_langs) \ No newline at end of file diff --git a/source/_posts/Java/Spring5.md b/source/_posts/Java/Spring5.md new file mode 100644 index 0000000..ca00241 --- /dev/null +++ b/source/_posts/Java/Spring5.md @@ -0,0 +1,1844 @@ +--- +title: Spring5 +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: SSM中的Spring5框架学习笔记,粗略学了一下,没有参考价值 +tags: + - Spring + - java + - 学习笔记 +categories: + - java +reprintPolicy: cc_by +abbrlink: 7f09f5da +date: 2022-04-29 11:06:51 +coverImg: +img: +password: +--- + + + +[toc] + +# Spring + +# 1、概述 + +> 概述 + +Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,**是针对bean的生命周期进行管理的轻量级容器**(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大**IOC、AOP及Web MVC**等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。 + +> 理念 + +使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架 + +> SSH和SSM + +SSH:Struct2+Spring+Hibernate + +SSM:SpringMVC+Spring+Mybatis + +> 优点 + +* **开放源**代码的**免费**的J2EE应用程序框架(容器) +* **轻量级、非入侵式**的框架 +* 是针对bean的生命周期进行管理的**轻量级**容器 +* 提供了功能强大**IOC、AOP及Web MVC**等功能 +* **支持事务的处理** +* Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架**组合使用** + +**总结**:Spring就是一个**轻量级**的控制反转**IOC**和面向切面编程**AOP**的框架 + +> 缺点 + +* 配置十分繁琐,人称“配置地狱” + +> 组成 + +![查看源图像](https://gitee.com/tianzhendong/img/raw/master//images/403a0003482fc287bac5) + +Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。 + +> 扩展 + +现代化的java开发,就是基于spring的开发 + +![image-20210808065217942](https://gitee.com/tianzhendong/img/raw/master//images/image-20210808065217942.png) + +* Spring Boot + +快速开发的脚手架,基于springboot可以快速开发单个微服务 + +约定大于配置 + +* Spring Cloud + +基于Spring Boot实现 + +现在大多数公司都在使用springboot进行快速开发 + +学习springboot需要完全掌握spring 和springmvc + +# 2、IOC理论推导 + +## 原来方式 + +> 基础代码 + +1. 编写Dao层UserDao接口 + +```java +public interface UserDao { + void getUser(); +} +``` + +2. 编写Dao层UserDaoImpl实现类和UserDaoMysqlImpl实现类 + +```java +public class UserDaoImpl implements UserDao { + @Override + public void getUser() { + System.out.println("默认获取用户数据"); + } +} +``` + +```java +public class UserDaoMysqlImpl implements UserDao{ + @Override + public void getUser() { + System.out.println("Mysql获取用户数据"); + } +} +``` + +3. 编写Service层UserService接口 + +```java +public interface UserService { + void getUser(); +} +``` + +4. 编写Service层UserServiceImpl实现类 + +```java +public class UserServiceImpl implements UserService { + //创建dao层对应接口的实现类对象,从而调用dao层的方法 + //弊端:新增或者改变需求时,dao层实现类增加或改变,需要更改这里的代码 + private UserDao userDao = new UserDaoImpl(); + @Override + public void getUser() { + //调用dao层对应方法 + userDao.getUser(); + } +} +``` + +5. 编写用户代码MyTest类 + +```java +public class MyTest { + public static void main(String[] args) { + //用户实际调用的是业务层,不接触dao层 + UserService userService = new UserServiceImpl(); + userService.getUser(); + } +} +``` + +> 分析 + +用户实际调用的是业务层,不接触dao层,通过创建`userservice`接口引用指向一个`UserServiceImpl`实现类对象,调用`UserServiceImpl`中的`getUser()`方法。由于`UserServiceImpl`中创建dao层对应接口的实现类对象,从而调用dao层的`getUser()`方法 + +**弊端**: + +在我们之前的业务中,用户的需求可能会影响我们原来的代码,需要根据用户的需求去修改源代码,如果程序代码量大,修改的成本十分昂贵 + +* 新增或者改变需求时,dao层实现类增加或改变,需要更改这里的代码,比如要调用`UserDaoMysqlImpl`中的方法,需要改变UserServiceImpl中的代码 + + + +## 改进-IOC原型 + +> 代码 + +1. 改变Service层UserServiceImpl实现类 + +```java +public class UserServiceImpl implements UserService { + private UserDao userDao; + //通过set进行动态实现值的注入 + public void setUserDao(UserDao userDao) { + this.userDao = userDao; + } + @Override + public void getUser() { + //调用dao层对应方法 + userDao.getUser(); + } +} +``` + +2. 改变用户代码MyTest类 + +```java +public class MyTest { + public static void main(String[] args) { + //用户实际调用的是业务层,不接触dao层 + UserService userService = new UserServiceImpl(); + // 实现UserDaoImpl类中的方法 + ((UserServiceImpl) userService).setUserDao(new UserDaoImpl()); + userService.getUser(); + //实现UserDaoMysqlImpl方法 + ((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl()); + userService.getUser(); + } +} +``` + +> 分析 + +在新增或者改变需求时,只需要更改MyTest类中的代码 + +使用一个set接口实现,实现了革命性的变化 + +```java + private UserDao userDao; + //通过set进行动态实现值的注入 + public void setUserDao(UserDao userDao) { + this.userDao = userDao; + } +``` + +* 之前程序是主动创建对象,控制权在程序员手上,用户访问业务层,由程序员代码决定用户调用什么 +* 使用**set注入**后,程序不具有主动性,通过把**接口给用户**,用户访问业务层,主动权在用户,由用户选择调用什么,程序变成了**被动接收对象**(来自用户) + +**这种思想即控制反转IOC的原型,从本质上解决了问题,程序员不需要再管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上** + +## IOC本质 + +**控制反转是一种通过描述(XML或者注解)并通过第三方去生产或获取特定对象的方式,在Spring中实现控制反转的是IOC容器,实现方式是依赖注入** + +* 控制反转IOC(inversion of control)是一种设计思想,DI(依赖注入)是实现IOC的一种方式。 + +* 在没有IOC的程序中,使用面向对象编程,对象的创建和对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制 + +* 控制反转后,对象的创建转移给了第三方 +* 控制反转就是获得依赖对象的方式反转了 + +IOC是Spring框架的核心内容,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。采用XML方式配置Bean的时候,Bean的的定义信息和实现是分离的,采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的 + +**Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建于组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象** + +# 3、HelloSpring + +## 代码 + +> 创建实体类Hello.class + +该类为后续要生成的对象的类,**必须要有set方法(依赖注入要用)** + +```java +public class Hello { + private String str; + + public String getStr() { + return str; + } + + public void setStr(String str) { + this.str = str; + } + + public Hello() { + } + + public Hello(String str) { + this.str = str; + } + + public void helloSpring() { + System.out.println("hello " + str); + } +} +``` + +> Spring配置文件Beans.xml + +```xml + + + + + + + + + + +``` + +> 业务代码MyTest.class + +```java +import com.tian.pojo.Hello; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class MyTest { + public static void main(String[] args) { + //获取Spring的上下文对象,固定语句 + ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); + //对象都在Spring中进行管理,要使用的化直接去里面取 + Hello hello = (Hello) context.getBean("hello"); + hello.helloSpring(); + } +} +``` + +## 分析 + +对象由spring进行创建,对象的属性由Spring容器设置,这个过程就叫控制反转IOC,一句话说就是对象由Spring来创建、管理、装配 + +* **控制**:传统应用程序的对象由程序本身控制创建,使用SPring后,对象由Spring进行创建 + +* **反转**:程序本身不创建对象,变为被动的接收对象 + +* **依赖注入**:利用set方法进行注入,对象必须要有set方法 + + + +# 4、IOC创建对象的方式 + +在xml中使用<**bean**>后,不管有没有使用,都会创建 + +> 使用无参构造创建对象,**默认** + +必须有无参构造函数 + +```xml + + + +``` + + + +> 有参构造函数——参数名 + +**重点** + +```xml + + + +``` + + + +> 通过有参构造函数——参数下标 + +```xml + + + +``` + +> 有参构造函数——参数类型 + +不建议使用:**当类中有两个或以上属性类型一样时,会导致错误** + +```xml + + + +``` + +> 总结 + +**在配置文件加载的时候,容器中管理的对象就已经初始化了** + + + +# 5、Spring配置 + +> 别名alias + +起一个小名,两个名字hello和hello2都能用来创建对象 + +```xml + +``` + +> Bean的配置 + +```xml + + + + +``` + +> import + +一般用于多个团队开发,可以将多个配置文件导入合成一个 + +项目中有多个人开发,三个人负责不同的类的开发,不同的类需要注册在不同的bean中,可以利用import将所有的bean.xml合成一个总的。 + +```xml + +``` + + + +# 6、DI依赖注入 + +## 构造器注入 + +见`4、IOC创建对象的方式` + +## set方式注入【重点】 + +> 依赖注入:set注入 + +* 依赖:bean对象的创建依赖于容器 +* 注入:bean对象中的所有属性由容器注入 + +> 环境搭建 + +1. 复杂类型 + +```java +package com.tian.pojo; +public class Address { + private String address; +//get set tostring省略 +} +``` + +2. 真实类型 + +```java +public class Student { + private String name; + private Address address; + private String[] books; + private List hobbies; + private Map card; + private Set games; + private String wife; + private Properties info; + //省略了get 和set tostring +} +``` + +3. 配置文件 + +```xml + + + + +``` + + + +3. 测试代码 + +```java +public class MyTest { + public static void main(String[] args) { + ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); + Student student = (Student) context.getBean("student"); + System.out.println(student.getName()); + } +} +``` + +> 注入方式 + +* 普通值注入value + +* bean注入ref +* 数组注入 +* list +* map +* properties +* null + +```xml + + + + + + + + + + + + book1 + book2 + book3 + + + + + + 听歌 + 电影 + + + + + + + + + + + + + LOL + COD + + + + + + + + + + com.mysql.driver + ual + root + 123456 + + + +``` + +## 扩展C\P命名空间注入 + +这个的使用要在bean.xml文件的头目录里面加上两行语句 + +```xml +xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" + + + + + +``` + + + +# 7、Bean的作用域scope + +> 单例模式`scope="singleton"` + +默认就是单例,也可以手动设置 + +```xml + + + +``` + +> 原型模式`scope="prototype"` + +每次从容器中get的时候,都会产生一个新对象 + +```xml + + + +``` + +> 其余的request、session、application + +只能在web开发中使用 + +# 8、Bean的自动装配 + +自动装配:Spring会在上下文中自动寻找、装配属性 + +装配方式: + +* 在xml中显式的配置 +* 在java中显式的配置 +* 隐式的自动装配Bean【重要】 + +## 搭建环境 + +一个人有两个从宠物,dog和cat + +xml配置 + +```xml + + + + + + + +``` + +## byName、byType自动装配 + +> byName + +byname:会自动在容器上下文中查找和自己对象中set方法后面中的值对应的**bean id**,如果dog改成dog222,则不会成功 + +```xml + + + + + + +``` + +> byType + +byType:会自动在容器上下文中查找和自己对象属性**类型相同**的bean + +如果dog改成dog222 ,依然会成功,甚至注册dog时不需要id属性`` + +**注意,如果dog注册了多个对象,即同一个类型有两个对象,则bytype不可以使用** + +```xml + + + + + + +``` + +> 总结 + +* byname:需要保证所有bean的id唯一,并且bean需要和自动注入的属性的set方法的值一致 +* byType:需要保证所有bean的class唯一,并且bean需要和自动注入的属性类型一致 + +## 使用注解实现自动装配 + +Spring2.5开始支持注解 + +> 使用注解: + +* 导入约束:context约束`xmlns:context="http://www.springframework.org/schema/context"` +* 配置注解的支持:` ` + +```xml + + + + + + +``` + +* xml配置 + +```xml + + + +``` + +> @Autowired + +* 默认为**byType**方式 + +* 直接在属性上使用或者在set方法上使用 +* 可以不需要set方法 + +```java +public class Person { + @Autowired + private Cat cat; + @Autowired + private Dog dog; + private String name; +} +``` + +* 扩展:autowired有一个属性,required,默认为true,如果显式的定义了其为false,说明这个对象允许为null + +```java + @Autowired(required = false) +``` + +> @Qualifier + +Autowired默认为byType方式,如果IOC容器中同一个类型注册了多个对象,那么会出现问题,需要配置Qualifier使用,指定具体的对象 + +```xml + + +``` + + + +```java +public class Person { + @Autowired + @Qualifier(value = "cat1") + private Cat cat; +} +``` + +> @Resource + +Resource是java自带的注解 + +通过**byName方式** + +```java +public class Person { + //@Autowired + //@Qualifier(value = "cat1") + @Resource(name="cat1") + private Cat cat; +} +``` + +# 9、使用注解开发 + +## Bean + +1. 导包,必须有aop的包,`pop.xml` + +```xml + + + org.springframework + spring-webmvc + 5.3.9 + + +``` + +2. 导入context约束,增加注解支持,`applicationContext.xml` + +```xml + + + + + + + +``` + +3. 实体类@Component + +**在实体类上使用`@Component`注解等价于``,id默认为类的名字小写** + +```java +//等价于 +// id默认为类的名字小写 +@Component +public class User { + private String name; + //get set +} + +``` + +## 属性的注入@Value + +适用于简单的,复杂的还是通过配置文件 + +```java +//等价于 +// id默认为类的名字小写 +@Component +public class User { + @Value("tianzd") + private String name; + //get set +} +``` + +## 衍生的注解 + +@Component有几个衍生注解,在web开发中会按照MVC三层架构开发 + +* @Component 用于pojo层 +* @Service 用于service业务层 +* @Controller 用于Controller层 +* @Repository 用于Dao层 + +四个功能一样,都是代表将某个类注册到spring中,装配Bean + +## 自动装配注解 + +@Autowired + +@Resource + +## 作用域注解@Scope + +在某个类上标注,修改称单例或者多例模式 + +## 小结 + +> xml与注解 + +* xml更加万能,适用于任何场景,维护简单方便 + +* 注解不是自己的类使用不了,维护复杂 + +> 最佳实践 + +* xml用来管理Bean +* 注解只负责完成属性注入@Value + + + +# 10、使用java的方式配置Spring + +舍弃xml配置文件,使用config类实现 + +> 实体类 + +```java +package com.tian.pojo; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +//这个注解的意思就是说明这个类被注册到了容器中 +@Component +public class User { + private String name; + + public String getName() { + return name; + } + @Value("tianzhendong") + public void setName(String name) { + this.name = name; + } +} +``` + +> 配置类 + +```java +package com.tian.config; + +import com.tian.pojo.User; +import org.springframework.context.annotation.*; +//使其成为配置类,同beans.xml +//这个也会被spring容器托管,注册到容器中,因为他也是一个@component +@Configuration +@ComponentScan("com.tian") +//@Import(MyConfig1.class) 合并配置类,相当于xml中的import +public class MyConfig { + //注册一个bean,相当于一个bean标签 + //方法的名字相当于id属性 + //返回值类型,相当于class属性 + //返回值就是要注入到bean中的对象 + @Bean(name = "user1") + @Scope(value = "singleton") + public User getUser() { + return new User(); + } +} +``` + +> 测试类 + +```java +import com.tian.config.MyConfig; +import com.tian.pojo.User; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class MyTest { + public static void main(String[] args) { + ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); + User user = context.getBean("user1", User.class); + System.out.println(user.getName()); + } +} +``` + +# 11、代理模式 + +**中介** + +> 为什么学习代理模式 + +![image-20210808200209343](https://gitee.com/tianzhendong/img/raw/master//images/image-20210808200209343.png) + +在业务增删改时,为了避免更改底层的代码 + +代理模式是SpringAop的底层 + +**SpringAOP的底层,面试重点** + +> 分类 + +* 静态代理 +* 动态代理 + + + +## 静态代理 + +租房:中介,房东,客户,出租房接口(中介和房东的) + +> 角色分析 + +* 抽象角色:一般会使用接口或者抽象类解决 +* 真实角色:被代理的角色,房东 +* 代理角色:代理真实角色,中介,代理真实角色后,一般会做一些附加操作 +* 客户:访问代理对象的角色 + +> 抽象接口 + +```java +package com.tian.demo1; + +public interface Rent { + public void rent(); +} +``` + +> 实现类 + +```java +package com.tian.demo1; +public class LandLord implements Rent{ + @Override + public void rent() { + System.out.println("房子租出去了"); + } +} +``` + +> 代理 + +```java +package com.tian.demo1; +public class Proxy implements Rent{ + private Rent landLord; + + public Proxy(LandLord landLord) { + this.landLord = landLord; + } + + public Proxy() { + } + + @Override + public void rent() { + landLord.rent(); + } +} +``` + +> 客户端 + +```java +package com.tian.demo1; +public class Client { + public static void main(String[] args) { + Proxy proxy = new Proxy(new LandLord()); + proxy.rent(); + } +} +``` + +> 优点 + +* 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务 +* 公共业务交给代理,实现了业务的分工 +* 公共业务发生扩展时,方便集中管理 + +> 缺点 + +* *代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。* +* *代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。* + +## 动态代理 + +每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,为了弥补静态代理的缺点:一个真实角色就会产生一个代理,代码量翻倍,开发效率降低 + +> 概述 + +动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象 + +* 角色和静态代理一样 +* 动态代理的代理类是动态生成的,不是直接写好的 + +**分类** + +* 基于接口的动态代理——JDK动态代理,这里使用这种 +* 基于类——cglib +* java字节码——javasist + +> JDK动态代理 + +在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持 + +* Proxy类:生成动态代理实例 +* InvocationHandler接口:调用处理程序并返回一个结果 + +> InvocationHandler接口 + +每一个代理实例都有一个关联的调用处理程序,InvocationHandler是由代理实例的调用处理程序实现的接口 + +方法:invoke(object, object[]) + +```java +//Object proxy:被代理的对象 +//Method method:要调用的方法 +//Object[] args:方法调用时所需要参数 +public interface InvocationHandler { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; +} +``` + + + +> Proxy类 + +有个静态方法,可以创建动态代理类的实例 + +```java +//CLassLoader loader:类的加载器 +//Class interfaces:得到全部的接口 +//InvocationHandler h:得到InvocationHandler接口的子类的实例 +public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException +``` + +> 动态创建代理对象的类 + +* 动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法 +* invoke方法就是**调用被代理接口的所有方法时需要调用的**,该invoke方法返回的值是被代理接口的一个实现类 + +```java +//动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。 +//invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类 +public class LogHandler implements InvocationHandler { + // 目标对象 + private Object targetObject; + //绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。 + public Object newProxyInstance(Object targetObject){ + this.targetObject=targetObject; + //该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 + //第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器 + //第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口 + //第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法 + //根据传入的目标返回一个代理对象 + return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), + targetObject.getClass().getInterfaces(),this); + } + @Override + //关联的这个实现类的方法被调用时将被执行 + /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/ + //invoke方法中实现了代理类要扩展的公共功能。 + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ + //代理扩展逻辑,实现代理类要扩展的公共功能 + System.out.println("proxy do"); + + return method.invoke(targetObject, args); + } +} +``` + +> 优点 + +* 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务 +* 公共业务交给代理,实现了业务的分工 +* 公共业务发生扩展时,方便集中管理 +* 一个动态代理类代理的是一个接口,一般就是对应一个业务 +* 一个动态代理类可以代理多个类,只要是实现了同一个接口即可 + +# 12、AOP + +## 简介 + +> 什么是AOP + +**面向切面编程**,AOP是能够让我们在不影响原有功能的前提下,为软件**横向扩展**功能。 那么横向扩展怎么理解呢,我们在WEB项目开发中,通常都遵守三层原则,包括控制层(Controller)->业务层(Service)->数据层(dao),那么从这个结构下来的为纵向,它具**体的某一层就是我们所说的横向**。我们的AOP就是可以作用于这某一个横向模块当中的所有方法。 + +![](https://gitee.com/tianzhendong/img/raw/master//images/20210808215619.png) + + + +> 和oop区别 + +AOP和OOP的区别:AOP是OOP的补充,当我们需要为多个对象引入一个公共行为,比如日志,操作记录等,就需要在每个对象中引用公共行为,这样程序就产生了大量的重复代码,使用AOP可以完美解决这个问题。 + +> AOP在Spring中的作用 + +**提供声明式事务,允许用户自定义切面** + +- 横切关注点:跨越应用程序多个模块的方法或功能,即:与我们业务逻辑无关的,但我们需要关注的部分,如日志,安全,缓存,事务等 +- 切面:拦截器类,其中会定义切点以及通知 +- 切点:具体拦截的某个业务点 +- 通知:切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下: + - 前置通知:@Before 在目标业务方法执行之前执行 + - 后置通知:@After 在目标业务方法执行之后执行 + - 返回通知:@AfterReturning 在目标业务方法返回结果之后执行 + - 异常通知:@AfterThrowing 在目标业务方法抛出异常之后 + - 环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行 + +## 方式1:使用Spring的API接口实现AOP + +> 导包:注意 + +```xml + + + org.aspectj + aspectjweaver + 1.9.7 + + +``` + +> 接口 + +```java +public interface UserService { + public void add(); + public void delete(); + public void update(); + public void selete(); +} +``` + +> 接口实现类 + +```java +public class UserServiceImpl implements UserService{ + @Override + public void add() { + System.out.println("add"); + } + + @Override + public void delete() { + System.out.println("delete"); + } + + @Override + public void update() { + System.out.println("update"); + } + + @Override + public void selete() { + System.out.println("selete"); + } +} +``` + +> 增强方法log:注意 + +```java +package com.tian.log; + +import org.springframework.aop.MethodBeforeAdvice; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +@Component +public class Log implements MethodBeforeAdvice { + + @Override + //method:要执行的目标对象的方法 + //objects: 参数 + //target:目标对象 + public void before(Method method, Object[] args, Object target) throws Throwable { + System.out.println("目标对象为:" + target.getClass().getName()); + System.out.println("执行的方法为:" + method.getName()); + for (int i = 0; i < args.length; i++) { + System.out.println("第"+i+"个参数为:"+args[i]); + } + } +} +``` + +> xml配置:注意 + +```xml + + + + + + + + + + + +``` + +> 测试:注意 + +```java +import com.tian.service.UserService; +import com.tian.service.UserServiceImpl; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +public class MyTest { + public static void main(String[] args) { + ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); + //动态代理代理的是接口 + UserService userService = context.getBean("userService", UserService.class); + userService.add(); + } +} +``` + +## 方式2:自定义切入类 + +> 切入类 + +```java +public class DiyPointCut { + + public void before() { + System.out.println("---------方法执行前---------"); + } + + public void after() { + System.out.println("---------方法执行后---------"); + } +} +``` + +> 配置 + +```xml + + + + + + + + + + +``` + +## 方式3:注解 + +> 增强类 + +```java +//声明该类是一个切面 +@Aspect +public class AnnotationPointCut { + + //声明前置方法 + @Before("execution(* com.service.UserServiceImpl.*(..))") + public void before() { + System.out.println("这是使用注解的前置增强"); + } + + //声明后置方法 + @After("execution(* com.service.UserServiceImpl.*(..))") + public void after() { + System.out.println("使用注解的后置增强"); + } + + + //环绕增强的优先级更高 + @Around("execution(* com.service.UserServiceImpl.*(..))") + public void around(ProceedingJoinPoint jp) throws Throwable { + System.out.println("环绕前"); + System.out.println("签名:" + jp.getSignature()); + //执行目标方法proceed + Object proceed = jp.proceed(); + System.out.println("环绕后"); + System.out.println("proceed对象:"+proceed); + } +} + +``` + +> 配置 + +```xml + + + +``` + +# 13、整合Mybatis + +## 步骤: + +1. 导入jar包 + - junit单元测试 + - mybatis + - mysql + - spring + - aop + - mybatis-spring(新包,用于整合) + +```xml + + + junit + junit + 4.12 + + + mysql + mysql-connector-java + 8.0.25 + + + org.mybatis + mybatis + 3.5.7 + + + org.springframework + spring-webmvc + 5.3.9 + + + + org.springframework + spring-jdbc + 5.3.9 + + + org.aspectj + aspectjweaver + 1.9.7 + + + org.mybatis + mybatis-spring + 2.0.2 + + +``` + +2. 编写配置文件 + +3. 测试 + +## 回忆mybatis + +数据: + +![image-20210810222253418](https://gitee.com/tianzhendong/img/raw/master//images/image-20210810222253418.png) + +1. 编写实体类 + +```java +package com.tian.pojo; + +/** + * @program: SpringStudy + * @description: + * @author: TianZD + * @create: 2021-08-10 21:00 + **/ +public class User { + private int id; + private String name; + private String password; + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", name='" + name + '\'' + + ", password='" + password + '\'' + + '}'; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public User() { + } + + public User(int id, String name, String password) { + this.id = id; + this.name = name; + this.password = password; + } +} +``` + +2. 编写核心配置文件 + +```xml + + + + + + + + + + + + + + + + + + + + + + +``` + +3. 编写核心配置类 + +```java +package com.tian.utils; + +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import java.io.IOException; +import java.io.InputStream; + +/** + * @program: SpringStudy + * @description: + * @author: TianZD + * @create: 2021-08-10 21:07 + **/ +public class MybatisUtils { + public static SqlSessionFactory sqlSessionFactory; + static { + try { + String resources = "mybatis-config.xml"; + InputStream inputStream = Resources.getResourceAsStream(resources); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } + } + public static SqlSession getSqlSession() { + return sqlSessionFactory.openSession(); + } +} +``` + +4. 编写接口 + +```java +package com.tian.dao; + +import com.tian.pojo.User; + +import javax.sound.midi.VoiceStatus; +import java.util.List; +import java.util.Map; + +/** + * @program: SpringStudy + * @description: + * @author: TianZD + * @create: 2021-08-10 21:18 + **/ +public interface UserMapper { + + public int insert(User user); + + public int delete(int id); + + public int update(User user); + + public User select(int id); +} +``` + +5. 编写Mapper.xml + +```xml + + + + + + + + + + + + delete + from mybatis.user + where id = #{id}; + + + + update mybatis.user + set name = #{name},pwd = #{password} + where id =#{id}; + + + + insert into mybatis.user (id, name, pwd) + values (#{id},#{name},#{password}); + + + + + + +``` + +6. 测试 + +## Mybatis-Spring + +http://mybatis.org/spring/zh/ + +* 不变内容: + +实体类User.class + +UserMapper.interface接口 + +* 编写数据源 + +```xml + + + + + + + +``` + +* SqlSessionFactory + +```xml + + + + + + + + +``` + +* SqlSessionTemplate + +```xml + + + + + +``` + +* 给接口加实现类 + +```java +package com.tian.dao; + +import com.tian.pojo.User; +import org.mybatis.spring.SqlSessionTemplate; + +/** + * @program: SpringStudy + * @description: + * @author: TianZD + * @create: 2021-08-10 22:44 + **/ +public class UserMapperImpl implements UserMapper{ + UserMapper userMapper = null; + private SqlSessionTemplate sqlSessionTemplate; + public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { + this.sqlSessionTemplate = sqlSessionTemplate; + } + + + @Override + public int insert(User user) { + userMapper = sqlSessionTemplate.getMapper(UserMapper.class); + return userMapper.insert(user); + } + + @Override + public int delete(int id) { + userMapper = sqlSessionTemplate.getMapper(UserMapper.class); + return userMapper.delete(id); + } + + @Override + public int update(User user) { + userMapper = sqlSessionTemplate.getMapper(UserMapper.class); + return userMapper.update(user); + } + + @Override + public User select(int id) { + userMapper = sqlSessionTemplate.getMapper(UserMapper.class); + return userMapper.select(id); + } +} +``` + +* 将实现类注入到spring中 + +```xml + + + +``` + +* 测试 + +```xml +import com.tian.dao.UserMapper; +import com.tian.pojo.User; +import com.tian.utils.MybatisUtils; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @program: SpringStudy + * @description: + * @author: TianZD + * @create: 2021-08-10 21:33 + **/ +public class MyTest { + @Test + public void userMapperTest() { + ApplicationContext context = new ClassPathXmlApplicationContext("spring-mybatis.xml"); + UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class); + System.out.println(userMapper.select(2)); + } +} +``` + + + + + +## 进一步简化:SqlSessionDaoSupport + + + +`SqlSessionDaoSupport` 是一个抽象的支持类,用来为你提供 `SqlSession`。调用 `getSqlSession()` 方法你会得到一个 `SqlSessionTemplate`,之后可以用于执行 SQL 方法 + +```java +package com.tian.dao; + +import com.tian.pojo.User; +import org.apache.ibatis.session.SqlSession; +import org.mybatis.spring.support.SqlSessionDaoSupport; + +/** + * @program: SpringStudy + * @description: + * @author: TianZD + * @create: 2021-08-10 23:17 + **/ +public class UserMapperImpl_2 extends SqlSessionDaoSupport implements UserMapper{ + SqlSession sqlSession = getSqlSession(); + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + + @Override + public int insert(User user) { + return userMapper.insert(user); + } + + @Override + public int delete(int id) { + return userMapper.delete(id); + } + + @Override + public int update(User user) { + return userMapper.update(user); + } + + @Override + public User select(int id) { + return userMapper.select(id); + } + +} +``` + +注册:略去了sqlsessiontemplate注册 + +需要注册userMapperimpl2,SqlSessionDaoSupport` 需要通过属性设置一个 `sqlSessionFactory` 或 `SqlSessionTemplate + +```xml + + + + + + + + + + + + + + + + + + + +``` + + + +# 14、声明式事务 + +事务分为: + +* 声明式事务:AOP实现 +* 编程式事务 + + + +## 使用声明式事务 + +> 配置声明式事务 + +要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 `DataSourceTransactionManager` 对象: + +```xml + + + +``` + + + +> aop实现事务 + +```xml + + + + + + + + + + + + + + + + + + +``` + +> 事务的传播特性 + +事务传播特性,就是多个事务方法调用时如何定义方法间事务的传播。Spring 定义了 7 种传播行为: + +- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。 +- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。 +- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。 +- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。 +- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 +- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。 +- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。 + diff --git a/source/_posts/Java/SpringBoot.md b/source/_posts/Java/SpringBoot.md new file mode 100644 index 0000000..469da0b --- /dev/null +++ b/source/_posts/Java/SpringBoot.md @@ -0,0 +1,3642 @@ +--- +title: SpringBoot +author: TianZD +top: true +cover: true +toc: true +mathjax: false +summary: SpringBoot框架学习笔记,粗略学了一下,没有参考价值 +tags: + - SpringBoot + - 学习笔记 +categories: + - java +reprintPolicy: cc_by +abbrlink: 6f2612a2 +date: 2022-04-29 11:07:58 +coverImg: +img: +password: +--- + + + +[toc] + +# SpringBoot + +# 1、概述 + +## 1.1、学习总结 + +* JavaSE:oop +* mysql:持久化 +* html+css+js+jquery+框架:视图 +* javaWeb:独立开发MVC三层架构的网站 +* ssm:框架:简化了开发的流程,配置繁琐 + +war包:tomcat运行 + +* spring简化:SpringBoot,jar包,内嵌tomcat,微服务架构 +* springcloud:管理微服务 + +## 1.2、spring + +**Spring理念、目的** + +spring是**为了解决企业级应用开发的复杂性**创建的,**简化开发** + +**Spring如何简化开发** + +1. 基于pojo的轻量级和最小入侵编程 +2. 通过IOC,依赖注入(DI)和面向接口实现松耦合 +3. 基于切面(AOP)和惯例进行声明式编程 +4. 通过切面和模板(template)减少样式代码 + + + +> 区别 + +程序=数据结构+算法:程序员 + +程序=面向对象+框架:码农 + +## 1.3、微服务 + +> 什么是微服务 + +微服务是一种**架构风格**,要求在开发一个应用的时候,应用必须构建成一系列小服务的组合,可以通过http的方式通信 + +> 单体运行架构 + +all in one,所用的应用服务都封装在一个应用中 + +> 微服务架构 + +打破all in one的架构方式,把每个功能元素苏里出来,把苏里出来的功能元素动态组合,需要的功能元素才去拿来组合,需要多一时可以整合多个功能元素,所以微服务架构是对功能元素进行复制,没有对整个应用进行复制 + +* 节省了调用资源 +* 每个功能元素的服务都是一个可替换的,可独立升级的软件代码 + +## 1.4、SpringBoot + +> 概述 + +Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。用我的话来理解,就是 Spring Boot 其实不是什么新的框架,它默认配置了很多框架的使用方式,就像 Maven 整合了所有的 Jar 包,Spring Boot 整合了所有的框架。 + +> 优势 + +其实就是简单、快速、方便!平时如果我们需要搭建一个 Spring Web 项目的时候需要怎么做呢? + +* 1)配置 web.xml,加载 Spring 和 Spring mvc +* 2)配置数据库连接、配置 Spring 事务 +* 3)配置加载配置文件的读取,开启注解 +* 4)配置日志文件 +* … +* 配置完成之后部署 Tomcat 调试 +* … + +现在非常流行微服务,如果我这个项目仅仅只是需要发送一个邮件,如果我的项目仅仅是生产一个积分;我都需要这样折腾一遍! + +但是如果使用 Spring Boot 呢? +很简单,我仅仅只需要非常少的几个配置就可以迅速方便的搭建起来一套 Web 项目或者是构建一个微服务! + +## 1.5、Spring Boot、Spring MVC 和 Spring + +分别描述各自的特征: + +* Spring 框架就像一个**家族**,有众多衍生产品例如 boot、security、jpa等等;但他们的基础都是Spring 的**ioc和 aop**,**ioc 提供了依赖注入的容器**, **aop解决了面向切面编程**,然后在此两者的基础上实现了其他延伸产品的高级功能。 + +* Spring MVC提供了一种**轻度耦合的方式来开发web应用**;它是**Spring的一个模块**,是**一个web框架**;通过**DispatcherServlet,** ModelAndView 和 View Resolver,开发web应用变得很容易;解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。 + +* Spring Boot实现了auto-configuration**自动配置**(另外三大神器actuator监控,cli命令行接口,starter依赖),降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具;同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box)。 + +所以,用最简练的语言概括就是: + +* Spring 是一个“引擎”; + +* Spring MVC 是基于Spring的一个 MVC 框架; + +* Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。 + +# 2、SpringBoot程序 + +## 2.1、初始化 + +> 创建: + +* IDEA新建项目,选择SpringInitializr + +![image-20210822110418427](https://i.loli.net/2021/08/22/8Tph41FdNJseuf9.png) + +* 勾选SpringWeb + +![image-20210822110442198](https://i.loli.net/2021/08/22/5FcsUVz69rJuNRa.png) + +* 新建之后,删除多余文件 + +![image-20210822112126048](https://i.loli.net/2021/08/22/Xt3I1BbJvcAu9MQ.png) + +> 分析 + +默认自动生成一下文件 + +* springboot启动文件,启动器 + +```java +package com.tian.springboot01helloworld; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Springboot01HelloworldApplication { + + public static void main(String[] args) { + SpringApplication.run(Springboot01HelloworldApplication.class, args); + } + +} +``` + +* application.properties,默认为空 +* pop.xml 依赖 + +```xml + + + + + + + + 4.0.0 + org.springframework.boot + spring-boot-starter-web + 2.5.4 + spring-boot-starter-web + Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container + https://spring.io/projects/spring-boot + + Pivotal Software, Inc. + https://spring.io + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + + + + Pivotal + info@pivotal.io + Pivotal Software, Inc. + https://www.spring.io + + + + scm:git:git://github.com/spring-projects/spring-boot.git + scm:git:ssh://git@github.com/spring-projects/spring-boot.git + https://github.com/spring-projects/spring-boot + + + GitHub + https://github.com/spring-projects/spring-boot/issues + + + + org.springframework.boot + spring-boot-starter + 2.5.4 + compile + + + org.springframework.boot + spring-boot-starter-json + 2.5.4 + compile + + + org.springframework.boot + spring-boot-starter-tomcat + 2.5.4 + compile + + + org.springframework + spring-web + 5.3.9 + compile + + + org.springframework + spring-webmvc + 5.3.9 + compile + + + +``` + +* 测试文件 + +```java +package com.tian.springboot01helloworld; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Springboot01HelloworldApplicationTests { + + @Test + void contextLoads() { + } + +} +``` + +> 打jar包 + +* 在右侧maven中,双击package,自动打jar包 + +![image-20210822113501487](https://i.loli.net/2021/08/22/CFH5hvoyu1OsqE3.png) + +* 在target目录下,生成一个jar包 + +![image-20210822113642680](https://i.loli.net/2021/08/22/LaDJh6kZs1glytP.png) + +* 在命令行进入jar包所在的目录 +* 执行`java -jar .\springboot-01-helloworld-0.0.1-SNAPSHOT.jar`![image-20210822114010844](https://i.loli.net/2021/08/22/WiYaEIflxM93GtZ.png) + +> 新建目录结构 + +在Springboot启动程序的同级目录下新建controller、dao、pojo、service + + + +![image-20210822112537801](https://i.loli.net/2021/08/22/P3STa8FxzvYUDp1.png) + +> 新建简单接口 + +```java +package com.tian.springboot01helloworld.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + @RequestMapping("/hello") + public String hello() { + // 调用业务,接收前端参数 + return "hello,world!"; + } +} +``` + +`@RestController` 的意思就是 Controller 里面的方法都以 json 格式输出,不用再写什么 jackjson 配置的了! + +> 浏览器访问 + + + +## 1.2、配置 + +> 更改项目端口号 + +application.properties + +```properties +# 更改端口号 +server.port=8081 +``` + +> 更改banner + +* 百度搜索springboot banner在线生成,并复制 + +* 在resources下创建`banner.txt`,并粘贴进去 + +![image-20210822115259816](https://i.loli.net/2021/08/22/5C9B4gzlGaODU7p.png) + +> 开发环境的热启动 + +热启动在正常开发项目中已经很常见了吧,虽然平时开发web项目过程中,改动项目启重启总是报错;但springBoot对调试支持很好,修改之后可以实时生效,需要添加以下的配置: + +```xml + + + org.springframework.boot + spring-boot-devtools + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + +``` + +该模块在完整的打包环境下运行的时候会被禁用。如果你使用 `java -jar`启动应用或者用一个特定的 classloader 启动,它会认为这是一个“生产环境”。 + +# 3、原理 + +## 3.1、启动执行流程 + +![img](https://i.loli.net/2021/08/22/tBX3JQApne9H8Mb.png) + +上图为SpringBoot启动结构图,我们发现启动流程主要分为三个部分: + +* 第一部分进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器; +* 第二部分实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块; +* 第三部分是自动化配置模块,该模块作为springboot自动配置核心,在后面的分析中会详细讨论。在下面的启动程序中我们会串联起结构中的主要功能。 + +## 3.2、入口 + +springboot有自己独立的启动类(独立程序) + +```java +@SpringBootApplication +public class Springboot01HelloworldApplication { + + public static void main(String[] args) { + SpringApplication.run(Springboot01HelloworldApplication.class, args); + } + +} +``` + +主要包括: + +* Annotation定义(@SpringBootApplication),是Spring Boot的核心注解,它其实是一个组合注解 +* 类定义(SpringApplication.run):启动整个spring-boot程序,该方法所在类需要使用 + + + +## 3.3、自动配置 + +![preview](https://i.loli.net/2021/08/22/AW2O3vsFH4CQGuM.png) + +### 3.3.1、@SpringBootApplication + +```java +@Target``(ElementType.TYPE) ``// 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明 +@Retention``(RetentionPolicy.RUNTIME) ``// 注解的生命周期,保留到class文件中(三个生命周期) +@Documented` `// 表明这个注解应该被javadoc记录 +@Inherited` `// 子类可以继承该注解 +@SpringBootConfiguration` `// 继承了Configuration,表示当前是注解类 +@EnableAutoConfiguration` `// 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助 +@ComponentScan``(excludeFilters = { ``// 扫描路径设置 +@Filter``(type = FilterType.CUSTOM, classes = TypeExcludeFilter.``class``), +@Filter``(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.``class``) }) +public` `@interface` `SpringBootApplication { +... +}   +``` + +![img](https://i.loli.net/2021/08/22/XyWueDcvs4SgZVa.png) + +在其中比较重要的有三个注解,分别是: + +* @SpringBootConfiguration // 继承了Configuration,表示当前是注解类 +* @EnableAutoConfiguration // 开启自动配置 +* @ComponentScan(excludeFilters = { // 扫描路径设置(具体使用待确认)扫描主类所在的同级包以及下级包里的Bean + +接下来对三个注解一一详解,增加对springbootApplication的理解: + +#### **@SpringBootConfiguration** + +在springboot中我们大多用配置类来解决配置问题 + +@SpringBootConfiguration继承了Configuration,表示当前是注解类 + +@Configuration实际上就是一个@Component,表示一个受Spring管理的组件 + +#### **@ComponentScan** + +* ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,将这些bean定义加载到**IoC**容器中 + +* 我们可以通过basePackages等属性来**细粒度**的定制@ComponentScan自动扫描的范围,如果不指定,则**默认**Spring框架实现会从声明@ComponentScan所在类的package进行扫描。 + +#### **@EnableAutoConfiguration**:自动配置核心 + +此注解顾名思义是可以**自动配置**,所以应该是springboot中**最为重要的注解**。 + +```java +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@AutoConfigurationPackage +@Import({AutoConfigurationImportSelector.class}) +``` + +其中最重要的两个注解:1、@AutoConfigurationPackage【重点注解】2、@Import(AutoConfigurationImportSelector.class)【重点注解】 + +> `@AutoConfigurationPackage` + +自动配置包。内部是采用了@Import,来给容器导入一个Registrar组件,**程序运行到这里,会去加载启动类所在包下面的所有类** + +`@Import`:将一个组件注入容器中 + +1. **`@Import({Registrar.class})`** + +```java +static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { + Registrar() { + } + + public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { + AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); + } + + public Set determineImports(AnnotationMetadata metadata) { + return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata)); + } +} +``` + +该类有一个主要方法是**registerBeanDefinitions()**,该方法对容器中进行了批量注册 + +* AnnotationMetadata(注解元数据类):该类存储了注解相关的位置、属性值等信息 +* PackageImports(metadata).getPackageNames():该方法用于返回该注解所在位置的包名信息。(由此可以得到,该位置包名本质其实就是主程序所在的位置): + +进入到**register()**方法的底层,发现其功能是将该包名位置下的所有组件添加到容器当中去。 + +由此可以得到,该注解@AutoConfigurationPackage本质上就是通过导入Register类,将@SpringBootApplication核心注解(即主程序入口)所在位置同层级包下的所有组件扫描注册到Spring容器当中去。这也就解释了为什么在建立dao、service等目录结构的时候,应该在启动程序同级目录下新建 + +![image-20210822133236798](https://i.loli.net/2021/08/22/c1SkgxVbsMWRQAn.png) + +> `@Import(AutoConfigurationImportSelector.class)` + +![img](https://i.loli.net/2021/08/22/rZwnhQWpfHlFgVR.png) + +* 通过@Import往容器中导入注册了一个特殊类**AutoConfigurationImportSelector**。该类中有一个主要方法是**selectImports()** +* selectImports()方法主要利用**getAutoConfigurationEntry**(annotationMetadata)给容器中批量导入了一些组件 +* getAutoConfigurationEntry方法的主要作用是调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取了所有的131个初始候选配置类,并将他们通过一系列筛选过滤后返回上层,最后进一步批量注册到容器中发挥作用。 +* 利用工厂加载 Map **loadSpringFactories**(@Nullable ClassLoader classLoader),得到所有的组件 +* 从**META-INF/spring.factories**位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件。 + +我们发现在项目初始导入的每个Jar包下,都可能包含META-INF/spring.factories这个文件,文件中配置声明了一系列系统类的全类名。而其中比较核心的是spring-boot-autoconfigure-2.5.3.jar这个自动配置包,该文件中使用autoconfigure.EnableAutoConfiguration=声明了131个初始配置类 + +**总结**: + +该注解实际上是通过导入了AutoConfigurationImportSelector这个类,初始化来自动扫描加载所有META-INF/spring.factories这个文件下写死的资源(SpringBoot帮我们配置好了),其中比较核心的是spring-boot-autoconfigure-2.5.3.jar这个自动配置包,该包帮我们初始化加载了131个初始配置类,帮助我们进一步完成SpringBoot的启动和配置功能! + +> 总结 + +![在这里插入图片描述](https://i.loli.net/2021/08/22/7QDXaMqZxFvNAmb.png) + +### 3.3.2、自动配置流程总结 + +![在这里插入图片描述](https://i.loli.net/2021/08/22/Ge8bvq3DUyktcg2.png) + + + +### 3.3.3、修改默认配置 + +由上可以知道,自动配置会从META-INF/spring.factories加载初始配置类,初始配置类以XXXAutoConfiguration结尾,如org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration, + +![image-20210822174506126](https://i.loli.net/2021/08/22/bxo7Se3w1ZQDFa9.png) + +点进去是`WebMvcAutoConfiguration.java`配置类 + +修改其默认配置可以通过`application.yaml`修改`WebMvcAutoConfiguration.java`配置类中对应的参数 + +如: + +```yaml +spring: + mvc: + static-path-pattern: /** +``` + +### 3.3.4、总结 + +**一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!** + +* 一但这个配置类生效;这个配置类就会给容器中添加各种组件; +* 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的; +* 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着; +* 配置文件能配置什么就可以参照某个功能对应的这个属性类 + +> 精髓 + +1、SpringBoot启动会加载大量的自动配置类 + +2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中; + +3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了) + +4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可; + +**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件 + +**xxxxProperties:封装配置文件中相关属性;** + +> 了解:@Conditional + +了解完自动装配的原理后,我们来关注一个细节问题,**自动配置类必须在一定的条件下才能生效;** + +**@Conditional派生注解(Spring注解版原生的@Conditional作用)** + +作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效; + +![图片](https://mmbiz.qpic.cn/mmbiz_png/uJDAUKrGC7IPEXZtUAUBhnSZvUmrPzbDGcJRvdK3PtqHPAWYBBmpe1XBVjQJeiatU4vasEaxckHlOga1BV9RPaw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +**那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。** + +我们怎么知道哪些自动配置类生效? + +**我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;** + +``` +#开启springboot的调试类 +debug=true +``` + +**Positive matches:(自动配置类启用的:正匹配)** + +**Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)** + +**Unconditional classes: (没有条件的类)** + + + +## 3.4、依赖管理 + +> 父工程 + +* springboot的父工程中,进行了**资源过滤**、**核心依赖**导入 +* 在写或者引入依赖的时候,不需要指定版本 + +```xml + + + org.springframework.boot + spring-boot-starter-parent + 2.5.4 + + +``` + +点进去,发现其父项目为:spring-boot-dependencies + +```xml + + org.springframework.boot + spring-boot-dependencies + 2.5.4 + +``` + +spring-boot-dependencies声明了很多开发中常用的jar包 + +![image-20210822150254879](https://i.loli.net/2021/08/22/NSDrvB8fOjHwYGF.png) + +所以在你pom.xml文件中引入jar的时候,如果该jar在`spring-boot-dependencies`中定义了版本号,那么你可以不写。如果你想使用其他的版本号,那么也可以在pom.xml中定义version,遵循就近原则。比如你想使用自定义版本号的mysql驱动,只需在pom.xml中进行定义 + +```xml + + 5.1.43 + +``` + + + +> 启动器 + +在SpringBoot项目中,我们只需要引入`spring-boot-starter-web`包就可以写接口并且进行访问,因为在这个starter中整合了我们之前写Spring项目时引入的`spring-aop`、`spring-context`、`spring-webmvc`等jar包,包括tomcat,所以SpringBoot项目不需要外部的tomcat,只需要启动application类使用内置的tomcat服务器即可。 + +```xml + + + + org.springframework.boot + spring-boot-starter-web + +``` + +```xml + + + org.springframework.boot + spring-boot-starter + 2.5.4 + compile + + + org.springframework.boot + spring-boot-starter-json + 2.5.4 + compile + + + org.springframework.boot + spring-boot-starter-tomcat + 2.5.4 + compile + + + org.springframework + spring-web + 5.3.9 + compile + + + org.springframework + spring-webmvc + 5.3.9 + compile + + +``` + +在SpringBoot项目中,根据官方文档,有各种场景的`spring-boot-starter-*`可以使用,只要引入了starter,这个场景所有常规需要的依赖就会自动引入。 + +所有场景启动器最底层的依赖就是`spring-boot-starter`,该jar包是核心启动包,包含了自动配置的支持,日志以及YAML。Core starter, including auto-configuration support, logging and YAML,这是官方对它的描述。 + +```xml + + + org.springframework.boot + spring-boot + 2.5.4 + compile + + + org.springframework.boot + spring-boot-autoconfigure + 2.5.4 + compile + + + org.springframework.boot + spring-boot-starter-logging + 2.5.4 + compile + + + jakarta.annotation + jakarta.annotation-api + 1.3.5 + compile + + + org.springframework + spring-core + 5.3.9 + compile + + + org.yaml + snakeyaml + 1.28 + compile + + +``` + +# 4、yaml + +## 4.1、配置文件 + +SpringBoot使用一个全局的配置文件,配置文件的名称是固定的 + +* application.properties + * 语法:`key=value` +* application.yml + * 语法:`key: 空格 value` + +配置文件的作用:修改SpringBoot自动配置的默认值 + +## 4.2、yaml + +以前的配置文件都是用xml配置 + +xml配置: + +```xml + + 8081 + +``` + +yaml配置 + +```yaml +server: + port: 8081 +``` + +> 基本语法规则 + +**同一级的字段要对齐,冒号后面要带上空格。** + +* 大小写敏感 +* 使用缩进表示层级关系 +* **缩进时不允许使用Tab键,只允许使用空格**。 +* 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可 +* **#** 表示注释,从这个字符一直到行尾,都会被解析器忽略。 + +YAML 支持的数据结构有三种。 + +* 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary) +* 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list) +* 纯量(scalars):单个的、不可再分的值 + +```yaml +# 普通的key-value +name: tian +# 对象 +student: + name: chen + age: 18 + sex: 男 + class: 11 +student: {name: tian,age: 18} +# 数组 +# 一维数组 +list: + - A + - B + - C +list: [A,B,C] +``` + +## 4.3、给属性赋值 + +> 实体类 + +Dog.class + +```java +@Component +public class Dog { + private String name; + private Integer age; + // get,set,tostring,constuctor +} +``` + +Person.class + +```java +@Component +public class Person { + private String name; + private Integer age; + private boolean happy; + private Date birth; + private Map map; + private List list; + private Dog dog; + // get,set,tostring,constuctor +} +``` + +> spring赋值 + +@Value(“value”) + +@Autowired + +```java +public class Dog { + @Value("wangzai") + private String name; + @Value("1") + private Integer age; +} +``` + +```java +@SpringBootTest +class Springboot02AddvalueApplicationTests { + @Autowired + private Dog dog; + @Test + void contextLoads() { + System.out.println(dog); + } +} +``` + +> yaml赋值 + +application.yaml + +```yaml +person: + name: tian + age: 2 + happy: false + birth: 2020-01-01 + map: {k1: v1,k2: v2} + list: + - code + - jf + - fas + dog: + name: wangcai + age: 1 +``` + +person.class + +```java +@Component +@ConfigurationProperties(prefix = "person") +public class Person { + ... +} +``` + +> Spring Boot Configuration Annotation Processor not configured 问题解决 + +![image-20210822163721616](https://i.loli.net/2021/08/22/QIUvbnGBXkZWFzM.png) + +* 分析 + +它的意思是“Spring Boot配置注解执行器没有配置”,配置注解执行器的好处是什么。 + +配置注解执行器配置完成后,当执行类中已经定义了对象和该对象的字段后,在配置文件中对该类赋值时,便会非常方便的弹出提示信息。 + +* 解决 + +在pom.xml文件中引入依赖 + +```xml + + org.springframework.boot + spring-boot-configuration-processor + true + +``` + +> 松散绑定 + +在yaml中写的是`last-name`,等同于`lastName` + +> JSR303数据校验@Validated + +可以在字段增加一层过滤器验证,保证数据的合法性 + +* 导入依赖 + +pom.xml + +```xml + + org.springframework.boot + spring-boot-starter-validation + +``` + +* 添加注解 + +```java +@Validated +public class Person { + // message为数据不合法时候的提示信息 + @Email(message = "邮箱格式错误") + private String email; +} +``` + +* 常见参数 + +![在这里插入图片描述](https://i.loli.net/2021/08/22/q89vAVmwOb1frNI.png) + + + +* 附加 + +![在这里插入图片描述](https://i.loli.net/2021/08/22/wgMAXsk58H4mpWB.png) + +```java +@NotNull(message="名字不能为空") +private String userName; +@Max(value=120,message="年龄最大不能查过120") +private int age; +@Email(message="邮箱格式错误") +private String email; + +空检查 +@Null 验证对象是否为null +@NotNull 验证对象是否不为null, 无法查检长度为0的字符串 +@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. +@NotEmpty 检查约束元素是否为NULL或者是EMPTY. + +Booelan检查 +@AssertTrue 验证 Boolean 对象是否为 true +@AssertFalse 验证 Boolean 对象是否为 false + +长度检查 +@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 +@Length(min=, max=) string is between min and max included. + +日期检查 +@Past 验证 Date 和 Calendar 对象是否在当前时间之前 +@Future 验证 Date 和 Calendar 对象是否在当前时间之后 +@Pattern(value) 验证 String 对象是否符合正则表达式的规则 +``` + +# 5、配置文件 + +配置文件必须以`application`开头,`application`后面可以有其他东西,`application-test` + +## 5.1、配置文件位置 + +配置文件可以放的位置:从上到下,优先级降低 + +1. `file:./config/` +2. `file:./` +3. `classpath:/config/` + +4. `classpath:/` + +`file:./`为项目根目录,即`pom.xml`所在的同级目录 + +`classpath:/`为`src/main/resources`下的目录 + +## 5.2、多环境配置 + +一个项目在开发环境dev、测试环境test、生产环境prod、预发布环境uat,不同的环境会有不同的配置,比如数据库的配置就不同 + +### 方法1:修改配置文件 + + + +![img](https://i.loli.net/2021/08/22/6GBxLqInEfD8umt.png) + +这种方式的好处就是可以把公共的变量配置在application.properties文件中,不同环境需要的变量配置在不同的文件中,比如数据库信息,线程池的大小,redis信息等。 + +**修改application.yml配置文件,具体内容如下图:** + +![img](https://i.loli.net/2021/08/22/m1WZKIfgTzoQAMD.png) + +在启动服务时,服务器就会通过application.yml文件去调用application-dev.yml文件 + +### 方法2:命令启动服务,命令中带参数方式(此方式可以没有application.yml文件) + +第一步:进入到项目目录下,先用maven对项目进行打包,会在target目录下生成项目的jar包 + +第二步:进入target目录,执行命令:java -jar 生成的jar包 --spring.profiles.active=prod + +项目就会调用application-prod.yml配置文件,即以生产环境的配置要求启动服务。同理,若是开发环境,只需将prod改为dev即可。 + +# 6、SpringBoot Web开发 + +自动配置会从`META-INF/spring.factories`加载初始配置类,初始配置类以`XXXAutoConfiguration`结尾 + +springboot中webMVC开发的自动配置为`org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration`,配置类为:`WebMvcAutoConfiguration.java` + +## 6.1、静态资源路径 + +静态资源路径是指系统可以直接访问的路径,且路径下的所有文件均可被用户通过浏览器直接读取。 + +> 默认配置 + +`WebMvcAutoConfiguration.java`配置类中的静态资源路径设置: + +```java +@Override +public void addResourceHandlers(ResourceHandlerRegistry registry) { + //用户自定义后,会覆盖默认的 + if (!this.resourceProperties.isAddMappings()) { + logger.debug("Default resource handling disabled"); + return; + } + //默认路径 + addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); + addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { + registration.addResourceLocations(this.resourceProperties.getStaticLocations()); + if (this.servletContext != null) { + ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); + registration.addResourceLocations(resource); + } + }); +} +``` + +Spring Boot 对静态资源映射提供了默认配置 + +* **自定义**:自定义后,会把下面两个默认的覆盖 +* **webjars(不建议使用)**:`localhost:8080/webjars/` +* **classpath**:`localhost:8080/` + * `classpath:/static` + * `classpath:/public` + * `classpath:/resources` + * `classpath:/META-INF/resources` + +![image-20210822181213875](https://i.loli.net/2021/08/22/c76gZ3X1kUfJVS5.png) + +优先级:resources>**static**>public + +**注意:templates不是静态资源路径** + +在src/main/resources目录下新建 public、resources、static 三个目录,并分别放入 1.jpg 2.jpg 3.jpg 三张图片。然后通过浏览器分别访问: + +``` +http://localhost:8080/1.jpg +http://localhost:8080/2.jpg +http://localhost:8080/3.jpg +``` + +地址均可以正常访问,Spring Boot 默认会从 public resources static 三个目录里面查找是否存在相应的资源。 + +> 自定义资源路径 + +`properties.yaml` + +```yaml +spring: + mvc: + static-path-pattern: /upload/** +``` + +通过spring.mvc.static-path-pattern这种方式配置,会使Spring Boot的默认配置失效,也就是说,/public , /resources 等默认配置不能使用。 +配置中配置了静态模式为/upload/**,访问时候就只能通过/upload/xx 来访问。 + +``` +http://localhost:8080/upload/1.jpg +``` + +## 6.2、首页定制 + +首页可以放在public、resources、static目录下,通过`localhost:8080`直接访问 + +![image-20210822182234904](https://i.loli.net/2021/08/22/hNDoU21k9ZXFMYO.png) + +在templates下的只能通过controller进行访问 + +## 6.3、Thymeleaf模板引擎 + +### 模板 + +模板的诞生是为了将显示与数据分离,模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的HTML代码。 + +![ZWPF0M5W_CR_NEUY6H46__W](https://oss-cn-hangzhou.aliyuncs.com/yqfiles/6c258773c5fc6fca7229b978a7a27b8ff1b326f0.png) + +模板技术并不是什么神秘技术,干的是拼接字符串的体力活。模板引擎就是利用正则表达式识别模板标识,并利用数据替换其中的标识符。比如: + +```js +Hello, <%= name%> +``` + +数据是`{name: '木的树'}`,那么通过模板引擎解析后,我们希望得到`Hello, 木的树`。模板的前半部分是普通字符串,后半部分是模板标识,我们需要将其中的标识符替换为表达式。模板的渲染过程如下: +![7J8ICGIRY_4PH_0N_6COAXO](https://oss-cn-hangzhou.aliyuncs.com/yqfiles/e437978f5ecc9f4046d63de6cdcce42bdc308ea2.png) + +### 模板引擎 + +> 什么是模板引擎 + +**模板引擎**(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的html文档。从字面上理解模板引擎,最重要的就是模板二字,这个意思就是做好一个模板后套入对应位置的数据,最终以html的格式展示出来,这就是模板引擎的作用。 + +如:**Thymeleaf**,FreeMarker,Enjoy,Velocity,**JSP** + +> JSP + +虽然是一款功能比较强大的模板引擎,并被广大开发者熟悉,但它前后端耦合比较高。比如说前端的html页面还要手动修改成jsp页面,大大加重了工作量,而且动态和静态资源也是耦合性太高。 + +其次是JSP页面的效率没有HTML高,因为JSP是同步加载。而且JSP需要tomcat,但又不支持nginx等,已经跟不上时代的潮流。 + +综上:目前开发中已经很少用JSP了,只是我们很多时候会在碰到一些以前的框架里有用到JSP技术,但是技多不压身,推荐还是学一下(如果工作不需要,可以不学)。 + +### Thymeleaf介绍 + +[Thymeleaf 官网](https://www.thymeleaf.org/) + +> 特点 + +Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下三个极吸引人的特点: + +* Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。 + +* Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。 + +* Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。 + +> **动静分离**: + +浏览器无法直接识别`.jsp`文件,需要借助网络(服务端)才能进行访问;而Thymeleaf用html做模板可以直接在浏览器中打开。开发者充分考虑html页面特性,将Thymeleaf的语法通过html的标签属性来定义完成,这些标签属性不会影响html页面的完整性和显示。如果通过后台服务端访问页面服务端会寻找这些标签将服务端对应的数据替换到相应位置实现动态页面!大体区别可以参照下图: + +![在这里插入图片描述](https://i.loli.net/2021/08/22/wQybk5IetuFZx1M.png) + +上图的意思就是如果直接打开这个html那么浏览器会对`th`等标签忽视而显示原始的内容。如果通过服务端访问那么服务端将先寻找`th`标签将服务端储存的数据替换到对应位置。具体效果可以参照下图,下图即为一个动静结合的实例。 + +### 使用Thymeleaf + +对于构建一个完整程序,创建第一个Thymeleaf程序需要以下几个步骤: + +1. 创建程序,添加依赖 +2. 编写Controller +3. 编写Thymeleaf页面:需要放在`templates`目录下 +4. 访问页面 + +> 创建程序,添加依赖 + +* 方法1:在创建程序的时候,勾选其中Web 模块的Spring web依赖以及Template 模块的Thymeleaf依赖 + +![image-20210822201028792](https://i.loli.net/2021/08/22/loQriWv6zZKFAmR.png) + +* 方法2:在pom.xml中添加以下依赖 + +```xml + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + +``` + +> 编写controller + +```java +@Controller +public class ThymeleafController { + @RequestMapping("/test") + public String test1(Model model) { + model.addAttribute("msg", "hello,thymeleaf"); + return "test"; + } +} +``` + +> 编写Thymeleaf页面 + +咱们在项目的resources目录下的templates文件夹下面创建一个叫index.html的文件,咱们在这个html文件中的``标签修改为``这样在Thymeleaf中就可以使用Thymeleaf的语法和规范啦。 + +`test.html` + +```html + + + + + Title + + +

tian

+ + +``` + +> 浏览器测试 + +* 直接打开页面 + +![image-20210822204040165](https://i.loli.net/2021/08/22/Z7koXvl2EKx4SNt.png) + +* 通过服务端访问 + +会进行动态替换 + +![image-20210822204025137](https://i.loli.net/2021/08/22/CeE5Xuxaf7mKt1o.png) + + + +### thymeleaf语法 + +> 标签 + +Thymeleaf通过特殊的标签来寻找属于Thymeleaf的部分,并渲染该部分内容。Thymeleaf也主要通过标签来识别替换对应位置内容,Thymeleaf标签有很多很多,功能也很丰富,这里列举一些比较常用的标签如下: + +| 标签 | 作用 | 示例 | +| :-------- | :----------------- | :----------------------------------------------------------- | +| th:id | 替换id | `` | +| th:text | 文本替换 | `

bigsai

` | +| th:utext | 支持html的文本替换 | `

content

` | +| th:object | 替换对象 | `
` | +| th:value | 替换值 | `` | +| th:each | 迭代 | `` | +| th:href | 替换超链接 | `超链接` | +| th:src | 替换资源 | `` | + +> 链接表达式@{…} + +上面我们已经学习到Thymeleaf是一个基于html的模板引擎,但是我们还是需要加入特定标签来声明和使用Thymeleaf的语法。我们需要在Thymeleaf的头部加Thymeleaf标识: + +```html + +``` + +在Thymeleaf 中,如果想引入链接比如link,href,src,需要使用`@{资源地址}`引入资源。其中资源地址可以static目录下的静态资源,也可以是互联网中的绝对资源。 + +**引入css** + +```html + +``` + +**引入JavaScript:** + +```html + +``` + +**超链接:** + +```html +超链接 +``` + +> 变量表达式: ${...} + +在Thymeleaf中可以通过${…}进行取值,这点和ONGL表达式语法一致。 + +**取普通字符串:** +如果在controller中的Model直接存储某字符串,我们可以直接`${对象名}`进行取值。完整代码如下: + +```html +

普通字符串

+ + + + +
+``` + +**取JavaBean对象:** +取JavaBean对象也很容易,因为JavaBean自身有一些其他属性,所以咱们就可以使用`${对象名.对象属性}`或者`${对象名['对象属性']}`来取值,这和JavaScript语法是不是很相似呢!除此之外,如果该JavaBean如果写了get方法,咱们也可以通过get方法取值例如`${对象.get方法名}`完整代码如下: + +```html +

JavaBean对象

+ + + + + + + + + + + + + +
介绍
年龄
介绍
+``` + +**取List集合(each):** +因为List集合是个有序列表,里面内容可能不止一个,你需要遍历List对其中对象取值,而遍历需要用到标签:`th:each`,具体使用为` `,其中item就相当于遍历每一次的对象名,在下面的作用域可以直接使用,而userlist就是你在Model中储存的List的名称。完整的代码为: + +```html +

List取值

+ + + + +
+``` + +**直接取Map:** +很多时候我们不存JavaBean而是将一些值放入Map中,再将Map存在Model中,我们就需要对Map取值,对于Map取值你可以`${Map名['key']}`来进行取值。也可以通过`${Map名.key}`取值,当然你也可以使用`${map.get('key')}`(java语法)来取值,完整代码如下: + +```html +

Map取值

+ + + + + + + + + +
place:
feeling:
+``` + +**遍历Map:** +如果说你想遍历Map获取它的key和value那也是可以的,这里就要使用和List相似的遍历方法,使用`th:each="item:${Map名}"`进行遍历,在下面只需使用`item.key`和`item.value`即可获得值。完整代码如下: + +```html +

Map遍历

+ + + + + +
+``` + +> 选择表达式 + +* if-then:`(if) ? (then)` +* if-then-else:`(if) ? (then) : (else)` +* default : `(value) ?: (defaultvalue)` + +> 选择变量表达式: *{...} + +变量表达式不仅可以写成${...},而且还可以写成*{...}。 + +但是,有一个重要的区别:星号语法对选定对象而不是整个上下文评估表达式。也就是说,只要没有选定的对象,美元(`${…}`)和星号(`*{...}`)的语法就完全一样。 + +什么是选定对象?使用`th:object`属性的表达式的结果。就可以选定对象,具体实例如下: + +```html +
+

Name: .

+

Age: 18.

+

Detail: 好好学习.

+
+``` + +当然`*{…}`也可和`${…}`混用。上面的代码如果不使用选定对象,完全等价于: + +```html +
+

Name: .

+

Age: 18.

+

Detail: 好好学习.

+
+``` + +> 消息表达: #{...} + +文本外部化是从模板文件中提取模板代码的片段,以便可以将它们保存在单独的文件(通常是.properties文件)中,文本的外部化片段通常称为“消息”。通俗易懂的来说`#{…}`语法就是用来**读取配置文件中数据**的。在Thymeleaf你可以使用`#{...}`语法获取消息,具体实例代码如下: +首先在templates目录下建立`home.properties`中写入以下内容: + +```properties +bigsai.nane=bigsai +bigsai.age=22 +province=Jiang Su +``` + +在`application.properties`中加入以下内容: + +```properties +spring.messages.basename=templates/home +``` + +这样我们就可以在Thymeleaf中读取配置的文件了,完整代码如下: + +```html +

消息表达

+ + + + + + + + + + + + + +
name
年龄
province
+``` + +> 抽取公共页面 + +网页中很多具有公共的部分,可以把公共的部分抽取出来,放在resources/commons/commons.html下 + +* 定义公共部分 + +```html +

+ //公共部分 +

+``` + +* 使用公共部分 + +```html +
+ +或者用th:replace +``` + + + +## 6.4、扩展SpringMVC + +> 扩展 + +Springboot在自动配置很多组件的时候,先看容器中有没有用户自己配置的,如果有就用用户自己配置的,如果没有就用自动配置的,如果有些组件可以存在多个,比如视图解析器,就将用户配置的和自己默认的组合起来 + +* 编写一个**@Configuration注解类**,类型要为**WebMvcConfigurer**,还**不能标注@EnableWebMve**注解,一般放在config包下、 + +* 重写相应的方法 + +```java +@Configuration +public class MyMvcConfig implements WebMvcConfigurer { + //视图跳转 + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/tiaozhuan").setViewName("test"); + } +} +``` + +如上,重写了addViewControllers方法,当浏览器访问:`http://localhost:8080/tiaozhuan`时,会跳转到templates下的test.html页面 + + + +# 7、员工管理系统 + +## 7.1、准备工作 + +> 资源下载 + +[资源下载](https://www.kuangstudy.com/app/code) + +下载完成后放置资源 + +![image-20210822221308597](https://i.loli.net/2021/08/22/KzmHa4GXAy3PrMj.png) + +> 数据 + + + + + + + + + +## 7.2、首页 + +```html + + + + + + + + Signin Template for Bootstrap + + + + + + + + + + + + +``` + +> 首页访问 + +首页`index.html`在templates目录下,不能直接访问 + +通过下面两种方法设置后,可以通过`http://localhost:8080`和`http://localhost:8080/index.html`访问 + +* 方法1:通过增加controller + +```java +@Controller +public class IndexController { + @RequestMapping({"/","/index.html"}) + public String index() { + return "index"; + } +} +``` + + + +* 方法2:通过扩展springmvc + +```java +@Configuration +public class MyMvcCfonfig implements WebMvcConfigurer { + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("index"); + registry.addViewController("/index.html").setViewName("index"); + + } +} +``` + +![image-20210823012558877](https://i.loli.net/2021/08/23/SIHr3X4TCnJDx9L.png) + +> 首页Thymeleaf + +所有页面的静态资源都需要使用thymeleaf接管:`th:href=“@{}”` + +使用thymeleaf语法对首页中的链接进行更改 + +```html + + # 略 + + + + + # 略 + +``` + +![image-20210823014357633](https://i.loli.net/2021/08/23/MosWyf5FaOxSzHj.png) + +## 7.3、页面国际化 + +中英文语言切换 + +> 设置idea + +设置为utf-8 + +![image-20210823015246261](https://i.loli.net/2021/08/23/gsAdhG4NM157Fjc.png) + +> 编写国际化配置文件 + +![image-20210823021454836](https://i.loli.net/2021/08/23/noXT1emdif7EILB.png) + + + +* login.properties + +```properties +login.btn=登录 +login.password=密码 +login.remember=记住我 +login.tip=请登录 +login.username=用户名 +``` + +* login_en_US.properties + +```properties +login.btn=sign in +login.password=password +login.remember=remember me +login.tip=please sign in +login.username=username +``` + +* login_zh_CN.properties + +```properties +login.btn=登录 +login.password=密码 +login.remember=记住我 +login.tip=请登录 +login.username=用户名 +``` + +> 配置文件位置 + +国际化自动配置类:MessageSourceAutoConfiguration.class + +在application.properties中配置 + +```properties +# 配置g国际化文件位置 +spring.messages.basename=i18n.login +``` + +> 页面中设置国际化消息Thymeleaf + +Thymeleaf语法:`th:text=“#{}”` + +```html + +

Please sign in

+ +

Please sign in

+ +``` + +![image-20210823022834729](https://i.loli.net/2021/08/23/fk2Stqug5PNR1mO.png) + + + +> 语言切换 + +国际化语言的切换主要是因为有一个区域信息解析器在其作用。 + +是根据HttpServletRequest中的locale属性来判定启用哪个语言文件的。 + +我们的需求是通过点击链接来切换语言,那么我们可以自定义一个区域信息解析器来替代这个默认的解析器。 + +* 首先,配置类扩展mvc + +```java +@Bean +public LocaleResolver localeResolver(){ + return new NativeLocaleResolver(); +} + +protected static class NativeLocaleResolver implements LocaleResolver{ + + @Override + public Locale resolveLocale(HttpServletRequest request) { + String language = request.getParameter("language"); + Locale locale = Locale.getDefault(); + if(!StringUtils.isEmpty(language)){ + String[] split = language.split("_"); + locale = new Locale(split[0],split[1]); + } + return locale; + } + + @Override + public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { + + } +} +``` + +* 编写页面 + +```html +中文 +English +``` + +## 7.4、登录 + +> index.html + +```html +