LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

P-luminary

Linux,Redis,Jedis

2023/11/15

Linux基础入门

Linux操作系统

  • 介绍Linux与CentOS
  • 讲解Linux基础
  • Linux文本工具与命令
  • yum应用安装与卸载
  • CentOS的权限与系统安全
  • 部署OA项目至Linux服务器

主流操作系统

不同领域的主流操作系统,主要分为以下这么几类: 桌面操作系统、服务器操作系统、移动设备操作系统、嵌入式操作系统。接下来,这几个领域中,代表性的操作系统是那些?

1). 桌面操作系统

操作系统 特点
Windows 用户数量最多
MacOS 操作体验好,办公人士首选
Linux 用户数量少(桌面操作系统,Linux使用较少)

2). 服务器操作系统

操作系统 特点
Unix 安全、稳定、付费
Linux 安全、稳定、免费、占有率高
Windows Server 付费、占有率低

3). 移动设备操作系统

操作系统 特点
Android 基于 Linux 、开源,主要用于智能手机、平板电脑和智能电视
IOS 苹果公司开发、不开源,用于苹果公司的产品,例如:iPhone、 iPad

4). 嵌入式操作系统

操作系统 特点
Linux 机顶盒、路由器、交换机

2.2 Linux系统版本

Linux系统的版本分为两种,分别是: 内核版 和 发行版。

1). 内核版

  • 由Linus Torvalds及其团队开发、维护

  • 免费、开源

  • 负责控制硬件

2). 发行版

  • 基于Linux内核版进行扩展

  • 由各个Linux厂商开发、维护

  • 有收费版本和免费版本

我们使用Linux操作系统,实际上选择的是Linux的发行版本。在linux系统中,有各种各样的发行版本,具体如下:

发行版本 Logo 特点
Ubuntu image-20210809001838861 以桌面应用为主
RedHat image-20210809001731378 应用最广泛、收费
CentOS image-20210809001741238 RedHat的社区版、免费
openSUSE image-20210809001750999 对个人完全免费、图形界面华丽
Fedora image-20210809001800676 功能完备、快速更新、免费
红旗Linux image-20210809001814942 北京中科红旗软件技术有限公司开发

除了上述罗列出来的发行版,还有很多Linux发行版,这里,我们就不再一一列举了。

什么是操作系统

  • 操作系统(Operating System)是应用程序运行的基础支撑环境
  • 操作系统作用是管理和控制计算机系统的硬件与软件资源
  • Intel x86架构上常见的操作系统:Windows、Linux、Unix…

Linux操作系统

  • Linux是开源的基于Inter x86架构的类Unix多用户操作系统
  • 支持多任务、多用户、多CPU
  • 高效而灵活
  • 兼容任意x86架构计算机
  • 强大易用的系统命令
  • 完整的应用软件生态

Linux发行版本

  • Linux系统内核(kernel)提供了Linux操作系统的核心功能
  • 不同开发商在内核基础上扩展封装,形成了不同发行版本
  • 常见发行版:Red Hat Linux、CentOS、Ubuntu、SUSE…

Linux发行版选择建议

  • 桌面系统:Ubuntu
  • 服务器操作系统:**CentOS(免费)**、Red Hat Linux(收费)
  • 特定需求:Debian(稳定性)、Fedoras(新特性)、麒麟Linux(国产)

CentOS - 社区企业操作系统

  • 基于Red Hat Enterprice Linux的开源企业级Linux发行版本
  • 各版本CentOS都会获得十年的支持,与RHEL保持同步更新
  • CentOS采用社区支持,同步修正了RHEL许多BUG

CentOS版本选择

  • CentOS 5/6:历史淘汰版本
  • CentOS 7:主流版本,成熟稳定,大多数服务器的首先版本
  • CentOS 8:全新版本,全新内核,存在漏洞隐患

安装CentOS 7.7

vmware.com/cn.html

VMware-workstation-full-14.1.2-8497320.exe
[链接: https://pan.baidu.com/s/1EbuhZ4D4kh_NcRJqTA-vhQ
密码:oq37]

mirrors.aliyun.com/centos/7.7.1908/isos/x86_64/ 下载 Everything-1908.iso
[centos-vault-centos-7.7.1908-isos-x86_64安装包下载_开源镜像站-阿里云 (aliyun.com)]

VM典型 镜像Iso 存储地址放在最快的硬盘 将虚拟磁盘存储为单个文件 自定义(内存2GB 处理器2核 虚拟化 Intel VT-x)

Linux目录结构

根目录/ 下各个目录的作用及含义说明:

编号 目录 含义
1 /bin 存放二进制可执行文件
2 /boot 存放系统引导时使用的各种文件
3 /dev 存放设备文件
4 /etc 存放系统配置文件
5 /home 存放系统用户的文件
6 /lib 存放程序运行所需的共享库和内核模块
7 /opt 额外安装的可选应用程序包所放置的位置
8 /root 超级用户目录
9 /sbin 存放二进制可执行文件,只有root用户才能访问
10 /tmp 存放临时文件
11 /usr 存放系统应用程序
12 /var 存放运行时需要改变数据的文件,例如日志文件

Linux命令格式

命令 [参数选项] [文件或路径]
Linux文件操作核心命令
命令 用途
cd 切换目录
pwd 查看当前目录
ls、ll(详细列表) 显示目录内容
mkdir 创建目录
cp 复制文件与目录
mv 移动或重命名文件
rm 删除文件或目录
find 查找目录或文件

Xshell连接虚拟机详细教程-CSDN博客

按下Tab可以自动提示
输入su 密码root 进入root最高权限

cd ..                 返回上级目录
cd ./local             ./代表当前目录
cd local            到当前目录    

mkdir ./imooc
mkdir -p ./imooc/sample/demo       -p是连续创建多级目录

mkdir -p ./imooc1/sample1
mkdir -p -v./imooc1/sample1     -v是显示执行过程
mkdir -p -v./imooc1/sample1/demo1
=
mkdir -pv ./imooc1/sample1
    
cp Xftp-7.0.014lp.exe ./imooc/sample/demo 文件复制到指定目录
cp -r imooc/sample/demo imooc1/sample1/demo1 复制整个文件夹

cd imooc/sample/demo
mv Xftp-7.0.014lp.exe xftp.exe 重命名
ls

mv xftp.exe /imooc1/sample1/demo1 移动到其他目录
mv demo imooc1/sample1/demo1 移动文件夹到其他目录

cd game/share
rm -r music  y y y    依次删除文件夹中的文件
rm -f music 强制直接删除, 不经过询问
rm -rf music 强制迭代删除            【不要轻易使用】
【ex】 rm -rf / imooc/sample/demo 中间不小心加了空格 就把/后面的整个都删除了

find / -name *.exe 按指定的格式去搜索文件
cd ..
[root@imooc home] find / -name *

vim文本编辑器

远程在线文本编辑器
  • vi是linux重要的文字编辑工具,vim是增强版
  • vim用于在远程环境下用命令形式对文本进行在线编辑
  • 用法格式:vim [选项] [文件]

vim三种模式

  • 普通模式:默认模式,文本只读,不可编辑
  • 编辑模式:编辑文本模式,普通模式按i键进入,ESC键退出
  • 命令模式:执行保存、搜索、退出等操作

vim重要快捷键

i进入编辑模式

命令 用途
delete或x 删除单个字符
dd 删除整行
/str 全文查找str字符串,n下一个,N前一个
:% s/old/new/g 替换文件内所有old字符串为new
u 撤销最近一次操作
:wq或者**:wq!** 退出并保存,只读文件要格外加!
:q! 强制退出放弃保存
vim server.xml         修改文件
按i进入编辑模式
Home跳转行首
End跳转行尾
Esc退出编辑模式

命令只允许在普通模式下运行
/8080                     查找端口(高亮显示)
查找到后按n是查找下一个 按N是查找上一个
u  撤销最近一次操作
:% s/8080/8900/g        进行端口的全局替换
:% s/"80"/8900/g         想要把80改成8900 且不影响其他数值[把引号也加入其中]

Linux文本工具

常用文本工具
命令 用途
echo 屏幕打印与文本输出 后面要加引号”hello”
cat 合并文件或查看文件内容
tail 显示文件内容尾部
grep 文本过滤工具
echo "hello" > hello.txt 将左侧命令的结果重写到右侧文件
vim hello.txt
:q        退出

echo "hello" >> hello.txt 内容的追加

cat hello.txt 直接打印到控制台
cat -n hello.txt  -n是在每一行前加行号
cat -nE hello.txt  结尾的时候显示结束符$

echo "hello" > hello.txt
cat hello.txt  => hello
echo "my name is lili" > myname.txt
cat hello.txt myname.txt >> full.txt    合并文件内容为新的文件    
vim full.txt
echo 和 cat 在实际开发中 打印日志 最后用cat拼接日志

cat > test.txt << EOF   在当前的输入环境中产生输入流 输入的全输入到test.txt 输入流结束字符串是EOF
> Welcome to imooc.com
> I like linux
> bye!
> EOF

tail处理日志很方便 bug日志
tail full.txt => hello       my name is lili
tail -n 1 full.txt => my name is lili   "1"代表显示文本最后一行
tail -f full.txt      对文件进行监听 一旦产生变化就输出控制台【适合用在服务器】
ctrl + c 退出

#cat test.txt
=> welcome to imooc.com
=> I like linux
=> bye!
#grep l test.txt
we"l"come to imooc.com
I "l"ike "l"inux

#grep l test.txt > out.txt            符合条件的可以输出到out.txt文件中
#grep -v l test.txt                    -v 挑选出不包含l的test.txt文件
#grep i...c test.txt                "."代表匹配任意字符

#ll | grep .txt                     通道特殊使用方法:筛选出后缀是.txt的 
前面ll命令输出的结果会作为后面gerp命令的依据   通过|进行分割 

#ll | grep -E "log[0-9]{1,5}.txt"    通道特殊使用方法:扩展正则表达式
-rw-r--r--. 1 root root 3 12月 23 13:10 log1.txt
-rw-r--r--. 1 root root 3 12月 23 13:10 log2.txt
-rw-r--r--. 1 root root 3 12月 23 13:10 log3.txt

文件打包[文件组织]与压缩[磁盘节省]

Linux压缩程序-gzip
  • gzip是Linux系统的文件压缩程序
  • gzip压缩包文件扩展名 .gz
  • 大流量的网站默认都在使用 gzip 进行数据压缩传输
tar打包与压缩
  • tar是linux系统将多个文件打包和压缩的工具
  • tar本质的打包软件,扩展名是 **.tar **
  • tar可结合gzip或其他压缩工具实现打包压缩,扩展名 .tar.gz
  • 压缩命令:tar zcvf tomcat.tar.gz game/src
  • 解压缩命令:tar zxvf tomcat.tar.gz -C game/src

tar常用可选项

选项 用途
z 通过gzip压缩或解压
c 创建新的tar.gz文件
v 显示执行过程
f 指定压缩文件名称
x 解压缩tar.gz文件
-C 指定解压缩目录

安装与卸载应用程序

为CentOS安装应用程序
  • 在CentOS中安装第三方应用程序包含两种方式
    • rpm: Red Hat 软件包管理器, 相当于应用程序安装文件的执行者
    • 编译安装:用户自己从网站下载程序源码进行编译安装

yum与rpm的关系

  • rpm安装过程中, 需要用户自己解决依赖问题
  • yum通过引入软件仓库,联网下载rpm包及依赖,并依次自动安装
  • yum是rpm的前端程序,其目的就是简化rpm的安装过程
yum常用命令
  • yum search 应用名 #在仓库中查询是否存在指定应用
  • yum instal -y 应用名 #全自动下载安装应用及依赖
  • yum info 应用名 #查看应用详细信息
  • yum list installed 应用名 #查看已安装的应用程序
  • rpm -ql 应用名 #查看安装后输出的文件清单
  • yum remove -y 应用名 #全自动卸载指定应用
① 利用yum安装tree
yum search tree
yum install tree.x86_64 安装tree
yum install -y tree.x86_64 遇到所有的默认y进行安装
tree -d 或 tree                查看文件树型结构
which tree                     查看刚刚tree安装的目录
rpm -ql tree.x86_64         查看刚刚tree安装了哪些文件
yum list installed          查看已经安装的应用
yum list installed *tree*   查看已安装的应用其中有tree的
    
yum remove tree.x86_64  卸载程序
② 利用编译方式安装应用程序
  • 如yum仓库未提供rpm,往往需要采用编译安装方式
  • 编译安装是指从应用网站下载源码后,对源码进行编译后使用
  • 编译命令:**make #**使用对应编译器对源码编译生成可执行文件
yum与编译安装的比较
yum安装 编译安装
兼容性 差,每种发行版都要准备 好,全平台适用
复杂度 简单 复杂
安装速度
来源 应用仓库 官网下载
应用场景 日常系统软件 开源软件,最新版本

编译安装Redis[非关系型数据库]

# make
# yum install -y gcc
# cd ..
# clear
# rm -rf ./redis-4.0.14
# tar zxvf redis-4.0.14.tar,gz
# cd redis-4.0.14/
# clear
# make

[root@imooc redis-4.0.14]# ./src/redis-server redis.conf

Linux系统管理命令

使用 ifconfig 查看网卡ip

netstat 查看网络端口号

  • netstat -tulpn 或者 netstat -ano

    • netstat 常用选项

      选项 用途
      t 显示tcp传输协议的连接状况
      u 显示udp传输协议的连接状况
      l 显示处于监听状态的网络连接
      p 显示应用PID和程序名称
      n 显示ip地址
      a 显示所有连接
      o 显示计时器

查看进程 & 杀掉进程

  • ps -ef
    # ps -ef | grep vim ‘|’代表通道
  • **kill -9 PID ** 专门按照pid杀死指定进程(强制删除)
#ps -ef | grep redis
#netstart -tulpn | grep 6379

应用服务化

  • 应用服务化是指让应用程序以服务方式在系统后台运行
  • Linux系统对服务化应用进行统一管理
  • 服务管理命令:systemctl
指令 用途
start 启动服务
stop 停止服务
restart 重启服务
enable 设置开机启动
disable 禁止开机启动
status 查看服务状态
daemon-reload 重载服务配置文件
list-unit-files 列出所有服务
启动redis
./src/redis-server redis.conf  进入到redis安装目录下执行
ctrl + z 
#ps -ef | grep redis

#find / -name *.pid

cd.. cd.. cd.. 
cd usr/lib/systemd/system 
#pwd
=>/usr/lib/systemd/system 
#vim redis.service 按i进入编写模式
=>
<==========================================================================>
[Unit]
Description=Redis
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking                    #描述服务类型:后台运行
PIDFile=/run/redis_6379.pid     #指向刚刚继承编号的pid
ExecStart=/usr/local/redis-4.0.14/src/redis-server /usr/local/redis-4.0.14/redis.conf                                 #服务启动时使用什么命令 调用redis-server
ExecStop=/bin/kill -s QUIT $MAINPID 
                                #对指定的命令关闭 $后自动带入pidfile  -s quit是按正常流程关闭
PrivateTmp=true
            
[Install]
WantedBy=multi-user.target         #将radis分配到multi-user.target服务组上[随系统自动启动]
<==========================================================================>
[root@imooc system]#systemctl daemon-reload   对所有redis进行重载

[root@imooc system]#ps -ef | grep redis  
[root@imooc system]#kill -s QUIT #杀死所有redis进程测试能否实现自启动
[root@imooc system]#systemctl start redis
[root@imooc system]#systemctl status redis

https://www.cnblogs.com/niway/p/15346572.html

[root@imooc system]#systemctl stop redis  #停止服务
[root@imooc system]#systemctl enable redis #随着系统启动
[root@imooc system]#systemctl list-unit-files #查看系统中每一个服务命令
[root@imooc system]#systemctl list-unit-files | grep enabled #查看自启动的命令

[root@imooc system]#shutdown -r now   #断开连接

Linux用户与权限

用户
  • Linux是多用户多任务系统,包含两个概念:用户用户组
  • 用户与账户是同一概念,用于登录系统与区分资源权限
  • 用户让系统变的更安全,同时也保护了用户的个人数字资产
用户组
  • 用户组就是将用户分组,隶属用户自动拥有组权限
  • 一个用户可隶属于多个组,用户可任意切换当前组
  • 用户组的出现让用户权限管理变更轻松
用户与用户组的常用命令
命令 用途
useradd 创建新用户
passwd 修改密码
usermod 修改用户信息/分配组 (覆盖原组)
groupadd 创建新的用户组
chown 更改文件的属主或属组
chmod 更改文件的访问权限
newgrp 切换用户当前组

项目内部文件权限管理实践

3员工(2个程序员 1个测试员)
[root@imooc imooc]# adduser d1
[root@imooc imooc]# adduser d2
[root@imooc imooc]# adduser t1
[root@imooc imooc]# passwd d1 => shuangyu1
[root@imooc imooc]# passwd d2 => shuangyu2
[root@imooc imooc]# passwd t1 => shuangyu3
[root@imooc imooc]# groupadd developer
[root@imooc imooc]# groupadd testor
[root@imooc imooc]# usermod -g developer d1
[root@imooc imooc]# usermod -g developer d2
[root@imooc imooc]# usermod -g testor t1
在上方点击打开 并且复制3个Centos7 分别改用户名(d1 d2 t1)和密码(shuangyu1/2/3) 
[root@imooc imooc]# cd
[root@imooc ~]# cd /usr/local/share
[root@imooc share]# mkdir dev-document
[root@imooc share]# ll
drwxr-xr-x.  2 root root  28 11月 15 18:37 applications
drwxr-xr-x.  2 root root   6 12月  3 20:36 dev-document
drwxr-xr-x.  2 root root   6 4月  11 2018 info
drwxr-xr-x. 21 root root 243 11月 15 18:25 man
【前方的drwxr-xr-x 含义见图】

[root@imooc share]# chown d1:developer dev-document  更改对应目录文件的属主(由超级管理员交給研发组) d1可以对developer拥有完整的操作权限 developer以外的用户有读取执行权 其他用户 只有执行权
drwxr-xr-x.  2 root root       28 11月 15 18:37 applications
drwxr-xr-x.  2 d1   developer   6 12月  3 20:36 dev-document
drwxr-xr-x.  2 root root        6 4月  11 2018 info
drwxr-xr-x. 21 root root      243 11月 15 18:25 man

[root@imooc share]# chmod 750 dev-document/ 其他用户不允许任何权限
[root@imooc share]# ll
总用量 0
drwxr-xr-x.  2 root root       28 11月 15 18:37 applications
drwxr-x---.  2 d1   developer   6 12月  3 20:36 dev-document
drwxr-xr-x.  2 root root        6 4月  11 2018 info
drwxr-xr-x. 21 root root      243 11月 15 18:25 man

返回到Centos 7 - t1
[t1@imooc ~]$ cd /usr/local/share/dev-document/
-bash: cd: /usr/local/share/dev-document/: 权限不够
返回到Centos 7 - d2
[d2@imooc ~]$ cd /usr/local/share
[d2@imooc share]$ mv dev-document/ doc
mv: 无法将"dev-document/" 移动至"doc": 权限不够


上方的chmod 750 的意思是 对应下方的表 第一个7是第一个rwx相加之和4+2+1
====================chmod命令====================
★ chmod 750:组用户可读写,其他用户不允许访问 ★        
★ chmod 777:所有用户拥有完整权限 ★
★ chmod 700:只有属主拥有完整权限 ★
====================++++++++====================
d1弄个文档整个公司的所有人都可以使用
[d1@imooc ~]$ cd /usr/local/share/dev-document/
[d1@imooc dev-document]$ vim code.md
[d1@imooc dev-document]$ ll
-rw-r--r--. 1 d1 developer 13 12月  3 20:59 code.md
第一个是d则是文件夹 -则是文件 属主可以对文件读写。组仅仅可以读取。其他人仅仅可以读取
[d1@imooc dev-document]$ chmod 770 code.md
[d1@imooc dev-document]$ ll
总用量 4
-rwxrwx---. 1 d1 developer 13 12月  3 20:59 code.md
d2即可读写
[d2@imooc dev-document]$ vim code.md

如何让d1同时拥有两个组
[root@imooc share]# usermod -G developer,testor d1
[root@imooc share]# groups d1
d1: developer testor

将当前组切换到新的用户组上
[d1@imooc ~]$ newgrp testor
[d1@imooc ~]$ groups
d1:testor developer

sudo获取超级管理员权限

  • sudo可以让普通用户拥有超级管理员的执行权限
  • 普通用户要经过超级管理员授权才能使用
  • 授权命令:visudo
在root端输入 visudo 并且输入100gg 快速定位到100行
[root@imooc ~]#visudo
## The COMMANDS section may have other options added to it.
##
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
d1        ALL=(ALL)        ALL     #按i进入编辑模式且增加此行
        ↑:ALL任意电脑可以连接    后ALL可以切换其他用户执行命令   后后ALL允许哪些命令
ESC退出模式后输入
[root@imooc imooc]# visudo -c
/etc/sudoers:解析正确


[d1@imooc ~]$ useradd d3    #目前还没有权限
useradd: Permission denied.
useradd:无法锁定 /etc/passwd,请稍后再试。
[d1@imooc ~]$ sudo useradd d3     #以d1的身份給d3一个超级管理员的权限
我们信任您已经从系统管理员那里了解了日常注意事项。
总结起来无外乎这三点:

    #1) 尊重别人的隐私。
    #2) 输入前要先考虑(后果和风险)。
    #3) 权力越大,责任越大。

[sudo] d1 的密码:=》 shuangyu1
[d1@imooc ~]$ sudo passwd d3 
更改用户 d3 的密码 =》 shuangyu33

回到主Centos 7
[root@imooc ~]#visudo
## Allow root to run any commands anywhere
root    ALL=(ALL)       ALL
d1      ALL=(ALL)       NOPASSWD:ALL
NOPASSWD:ALL意味着做任何命令之前不用输入密码

[d1@imooc ~]$ sudo useradd d4
[d1@imooc ~]$                     #直接不需要输入密码了

CentOS7防火墙firewall

防火墙
  • 防火墙是借助硬件和软件对内外部网络环境的保护措施
  • CentOS 7基于firewall实现应用层防火墙,CentOS6基于iptables
  • firewall-cmd是firewall的核心命令
对外开放Tomcat
把apache-tomcat.tar.gz放入/usr/local中 
[root@imooc local]# tar zxvf apache-tomcat-9.0.34.tar.gz
[root@imooc local]# ll
[root@imooc local]# cd apache-tomcat-9.0.34/
[root@imooc apache-tomcat-9.0.34]# cd bin
[root@imooc bin]# ./startup.sh
[root@imooc bin]# ./startup.sh
Using CATALINA_BASE:   /usr/local/apache-tomcat-9.0.34
Using CATALINA_HOME:   /usr/local/apache-tomcat-9.0.34
Using CATALINA_TMPDIR: /usr/local/apache-tomcat-9.0.34/temp
Using JRE_HOME:        /usr
Using CLASSPATH:       /usr/local/apache-tomcat-9.0.34/bin/bootstrap.jar:/usr/local/apache-tomcat-9.0.34/bin/tomcat-juli.jar
Tomcat started.
[root@imooc bin]# netstat -tulpn|grep 8080  #查看端口是否开启
tcp6    0    0 :::8080        :::*         LISTEN      4302/java  
在虚拟机内部去fox浏览器搜索 localhost:8080  就会看到可爱的绿色小猫咪
    
若想在计算机中访问端口 192.168.170.129:8080 则需要关闭虚拟机的防火墙
[root@imooc bin]# firewall-cmd --state   #查看防火墙状态
running
[root@imooc bin]# firewall-cmd --list-ports #查看防火墙放行的端口(空代表无任何放行端口)
[root@imooc bin]# firewall-cmd --zone=public --permanent --add-port=8080/tcp
#### zone防火墙定义的规则区域、产生永久变更、增加端口8080、tcp形式
[root@imooc bin]# firewall-cmd --reload  #进行配置重载

若不需要开放8080端口则需要
[root@imooc bin]# firewall-cmd --zone=public --permanent --remove-port=8080/tcp
[root@imooc bin]# firewall-cmd --reload  #进行配置重载

[root@imooc bin]# firewall-cmd --zone=public --permanent --add-port=8000-9000/tcp 
###放行区域端口
[root@imooc bin]# firewall-cmd --reload  #进行配置重载

Bash Shell

  • Shell是一个用c语言编写的脚本解释器,是用户通过代码操作Linux的桥梁
  • Shell脚本描述要执行的任务,完成系列复杂操作,文件通常以**.sh**后缀
  • Shell脚本通过Shell解释器执行,按解释器分类分为多种类型

Linux Shell分类

Shell种类 Shell解释器
Bourne Shell /usr/bin/sh 或 /bin/sh
Boourne Again Shell /bin/bash(默认)
C Shell /usr/bin/csh
K Shell /usr/bin/ksh
Shell for Root /sbin/sh
一键发布Tomcat应用程序
编写shall脚本
[root@imooc local]# vim deploy_tomcat.sh
======================================================================
echo "准备下载Tomcat9"
wget https://mirror.bit.edu.cn/apache/tomcat/tomcat-9/v9.0.34/bin/apache-tomcat-9.0.34.tar.gz
echo "正在解压缩Tomcat9"
tar zxf apache-tomcat-9.0.34.tar,gz
echo "防火墙开放8080端口"
firewall-cmd --zone=public --permanent --add-port=8080/tcp
firewall-cmd --reload
echo "启动Tomcat"
cd ./apache-tomcat-9.0.34/bin
./startup.sh
======================================================================
[root@imooc local]# ./deploy_tomcat.sh        开启文件

综合训练:Linux部署慕课网办公OA

部署架构:Chrome + Tomcat Web服务器 + MySQL服务器 【中间通过网络通信】
重新安装CentOS 7-DB【最小值安装】
[root@localhost ~]# yum install -y net-tools
CentOS 7-DB        ifconfig ==》 192.168.170.131    账户:centos-db 密码:panchunyao123
CentOS 7-WEB    ifconfig ==》 192.168.170.133    账户:centos-web 密码:panchunyao123
DB按照MySQL
WEB按照Tomcat

CentOS安装MySQL 8

查找有没有mysql安装包
[centos-db@localhost ~]$ yum search mysql-community
去官网找到Linux版的 右键复制链接地址
[root@localhost ~]# wget https://dev.mysql.com/get/mysql80-community-release-el7-11.noarch.rpm
bash: wget: command not found
卧槽 wegt不存在,那就按装wget
[root@localhost ~]# yum install -y wget
[root@localhost mysql]# ll
total 16
-rw-r--r--. 1 root root 14064 Oct 24 07:44 mysql80-community-release-el7-11.noarch.rpm

[root@localhost mysql]# yum localinstall -y mysql80-community-release-el7- 11.noarch.rpm     #自动安装mysql源
[root@localhost mysql]# yum search mysql-comm #此时查找一下就会出现大量mysql组件
[root@localhost mysql]# yum install -y mysql-community-server #安装mysql{在欧美很慢}
ctrl+c 停止安装
[root@localhost mysql]# cd /var/cache/yum/x86_64/7/mysql80-community/packages/
#yum下载的缓存路径 在网上下载好后Linux版的MySQL后 进入

[root@localhost mysql80-community]  /var/cache/yum/x86_64/7/mysql80-community
用命令更改文件夹权限:chmod 777 test01(需要赋予权限的文件夹)
之后将解压缩的MySQL拖进去

注意:如果直接下载 yum localinstall mysql80-community-release-el7- 11.noarch.rpm  的话会有很多依赖不会被自动下载,最保准的就是去网上下载Linux版本的MySQL再利用Xftp拖进去

[root@localhost package]# yum install -y mysql-community-server 【最快最省事 自动下载&安装依赖】
[root@localhost package]# systemctl start mysqld
[root@localhost package]# netstat -tulpn
tcp6    0    0    :::3306        :::*        LISTEM        21850/mysqld
[root@localhost package]# systemctl status mysqld     #查看myql启动状态
[root@localhost package]# systemctl enable mysqld     #设置开机自启动

初始化MySQL[CentOS 7-DB]

查看mysql日志寻找mysql密码
[root@localhost package]# vi /var/log/mysqld.log
root@localhost: Y,#)foTQ,7js
[root@localhost package]# mysql -uroot -p
修改密码!
mysql> alter user 'root'@'localhost' identified with mysql_native_password by 'Panchunyao123!'
mysql> use mysql
mysql> select host,user from user;
mysql> update user set host='%' where user='root'; #任意设备都可使用%连接到mysql服务器
host: %            user: root
mysql> flush privileges; #使修改的权限数据生效
mysql> exit

下一步是放行防火墙3306端口
[root@localhost package]# firewall-cmd --zone=public --permanent --add-port=3306/tcp
[root@localhost package]# firewall-cmd --reload

去电脑端的Navicat Premium 连接新数据库
MySQL-新建连接
连接名:centos-db
主机:192.168.170.131
端口:3306
用户名:root
密码:Panchunyao123!

新建数据库 imooc-oa  utf8mb4 执行sql文件 imooc-oa.sql

部署配置Web应用服务器

CentOS 7-DB        ifconfig ==》 192.168.170.131    账户:centos-db 密码:panchunyao123
CentOS 7-WEB    ifconfig ==》 192.168.170.133    账户:centos-web 密码:panchunyao123
[root@localhost ~]# yum search jdk  #查看仓库中包含哪些jdk
[root@localhost ~]# yum install -y java-1.8.0-openjdk    #安装jdk以及所有依赖
[root@localhost ~]# java -version
[root@localhost ~]# which java      #查看安装到哪个地方
/bin/java
安装tomcat
[root@localhost ~]# cd /usr
用命令更改文件夹权限:chmod 777 local
[root@localhost usr]# cd local
打开Xftp将apache-tomcat-9.0.34.tar.gz 传入到local内
[root@localhost local]# tar zxf apache-tomcat-9.0.34.tar.gz     #对压缩包进行解压
将素材资料里的imooc_oa.war拖入Xftp的local中 之后进行解压
[root@localhost local]# tar zxf imooc_oa.war 
需要将imooc_oa复制到tomcat9的webapps下才可生效
[root@localhost local]# mv imooc_oa ./apache-tomcat-9.0.34/webapps/
[root@localhost local]# cd apache-tomcat-9.0.34/webapps/
[root@localhost webapps]# vim ./imooc_oa/WEB-INF/classes/mybatis-config.xml
#远程对服务器配置连接数据库服务器
-bash: vim: 未找到命令 需要先安装vim
[root@localhost webapps]# yum install -y vim-common
[root@localhost webapps]# yum install -y vim-enhanced  #再安装一个增强的vim包
[root@localhost webapps]# vim ./imooc_oa/WEB-INF/classes/mybatis-config.xml
输入/root 进行全文查找 修改<property 中的value="jdbc:mysql:..." loacalhost改为自己网络ip:192.168.170.131
再修改一下下面的password:  Panchunyao123!
:wq
[root@localhost webapps]# cd ..
[root@localhost apache-tomcat-9.0.34]# vim ./conf/server.xml
/8080 搜索 <Connector port="80" 只保留80端口
/Host> 找到最下面 在上面一行加入
<Context path="/" docaBacs="imooc_oa"/> #将imooc_oa目录映射到根路径"/"
:wq

之后启动tomcat
[root@localhost apache-tomcat-9.0.34]# ./bin/startup.sh
[root@localhost apache-tomcat-9.0.34]# netstat -tulpn  #查看进程
tcp6        0        0 :::80        :::*        LISTEN        20303/java

用防火墙将80端口对外进行暴露
[root@localhost apache-tomcat-9.0.34]# firewall-cmd --zone=public --permanent --add-port=80/tcp
[root@localhost apache-tomcat-9.0.34]# firewall-cmd --reload  #重载

回到电脑浏览器输入:http://192.168.170.133/login.html

数据库安全【根据3306端口可以查到数据库根源】:对指定IP端口进行放行
[root@localhost ~]# firewall-cmd --zone=public --permanent --remove-port=3306/tcp
[root@localhost ~]# firewall-cmd --reload  #重载
[root@localhost ~]# firewall-cmd --permanent --zone=public --add-rich-rule="rule family="ipv4" source address="192.168.170.133" port protocol="tcp" port="3306" accept "    #-rich-rule防火墙内部规则强大的定义表达式
[root@localhost ~]# firewall-cmd --reload  #重载
[root@localhost apache-tomcat-9.0.34]# ./bin/shutdown.sh
[root@localhost apache-tomcat-9.0.34]# ./bin/startup.sh


Redis (内存[非常快]数据库、非关系型数据库)

  • Redis是Key-Value型NoSQL数据库
  • Redis将数据存储在内存(RAM)中,同时也能持久化到磁盘
  • Redis常用于缓存,利用内存的高效提高程序的处理速度
Redis特点
  • 速度快
  • 广泛的语言支持
  • 持久化
  • 多种数据结构
  • 主从复制(多台Redis可以保持数据同步)
  • 分布式与高可用(7×24小时服务 淘宝/京东) 全国各地多个主机 降低网络传输时间

Redis的安装与启动

$ wegt http://download.redis.io/releases/redis-5.0.2.tar.gz
$ tar xzf redis-5.0.2.tar.gz
$ cd redis-5.0.2
$ make
[root@imooc imooc]# cd /usr/local
[root@imooc local]# ll
[root@imooc local]# mkdir redis
[root@imooc local]# ll
[root@imooc local]# cd redic
[root@imooc redic]# yum install gcc
[root@imooc redic]# wegt http://download.redis.io/releases/redis-5.0.2.tar.gz
[root@imooc redis]# tar xzf redis-5.0.2.tar.gz
[root@imooc redis]# cd redis-5.0.2
[root@imooc redis-5.0.2]# make
Hint: It's a good idea to run 'make test' ;)   安装成功
[root@imooc redis-5.0.2]# ll  #找redis.conf
总用量 240
-rw-rw-r--.  1 root root 85327 11月 22 2018 00-RELEASENOTES
-rw-rw-r--.  1 root root    53 11月 22 2018 BUGS
-rw-rw-r--.  1 root root  1894 11月 22 2018 CONTRIBUTING
-rw-rw-r--.  1 root root  1487 11月 22 2018 COPYING
drwxrwxr-x.  6 root root   192 12月  5 11:47 deps
-rw-rw-r--.  1 root root    11 11月 22 2018 INSTALL
-rw-rw-r--.  1 root root   151 11月 22 2018 Makefile
-rw-rw-r--.  1 root root  4223 11月 22 2018 MANIFESTO
-rw-rw-r--.  1 root root 20555 11月 22 2018 README.md
-rw-rw-r--.  1 root root 62155 11月 22 2018 redis.conf
-rwxrwxr-x.  1 root root   275 11月 22 2018 runtest
-rwxrwxr-x.  1 root root   280 11月 22 2018 runtest-cluster
-rwxrwxr-x.  1 root root   281 11月 22 2018 runtest-sentinel
-rw-rw-r--.  1 root root  9710 11月 22 2018 sentinel.conf
drwxrwxr-x.  3 root root  8192 12月  5 11:49 src
drwxrwxr-x. 10 root root   167 11月 22 2018 tests
drwxrwxr-x.  8 root root  4096 11月 22 2018 utils
[root@imooc redis-5.0.2]# cd src
[root@imooc src]# ll     #找redis- server启动目录

[root@imooc src]#cd ..
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf
若端口被占用

找到redis-server的进程,然后杀死对应的进程,然后重新启动redis

>>> ps -ef | grep -i redis
root      3585 19590  0 10:36 pts/20   00:00:00 redis-server *:6379
user      3684  3663  0 10:38 pts/21   00:00:00 grep --color=auto -i redis
进程3585是redis的服务,

kill -9 3585
  • 在Windows系统安装Redis
https://github.com/microsoftarchive/redis/releases 下载后解压
打开cmd
C:\Users\Pluminary>d:
D:\>cd Redis-x64-3.0.504
D:\Redis-x64-3.0.504>dir
D:\Redis-x64-3.0.504>redis-server redis.windows.conf
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 3.0.504 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 22828
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

Redis的常用基本配置

配置项 示例 说明
daemonize daemonize yes 是否启用后台运行, 默认no
port port 6379 设置端口号, 默认6379
logfile logfile 日志文件 设置日志文件
databases databases 255 设置redis数据库总量
dir dir 数据文件目录 设置数据文件存储目录
requirepass requirepass 12345 设置使用密码
守护进程方式启动Redis
[root@imooc ~]# cd /usr/local/redis/redis-5.0.2/
[root@imooc redis-5.0.2]# vim redis.conf    #打开后台启动
136行 daemonize no 改成 daemonize yes
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf
关闭终端打开全新终端
[root@imooc ~]# netstat -tulpn
tcp        0        0        127.0.0.1:6379 ...     6338/./src/redis-se

如果关闭
kill -9 6338
redis使用
[root@imooc redis-5.0.2]# ./src/redis-cli    #执行redis内置指令
127.0.0.1:6379> ping        #启动成功
PONG
127.0.0.1:6379> exit        #退出
[root@imooc redis-5.0.2]# ./src/redis-cli shutdown  #更加安全的关闭redis

报错(添加log文件 将redis命令行的结果打印到log中)
https://blog.csdn.net/qq_46127735/article/details/113933690


为了保护安全将port从6379改为6380
[root@imooc redis-5.0.2]# vim redis.conf
/port 寻找92行
port 6380
[root@imooc redis-5.0.2]# ./src/redis-cli -p 6380
127.0.0.1:6380> select 0        #当前使用第几号数据库
127.0.0.1:6380> select 15
[root@imooc redis-5.0.2]# vim redis.conf
在186行 可以改变数据连接数量
databases 15→255
    
[root@imooc redis-5.0.2]# vim redis.conf
507行注释去掉 此行是输入密码
requirepass panchunyao123
再次登录就 
127.0.0.1:6380> auth panchunyao123

redis中有一个dump.rdb 全量备份 同时备份到磁盘中

Redis通用命令

命令 示例 说明
select select 0 选择0号数据库
set set name lily 设置key=name, value=lily
get get hello 获得key=hello结果
keys keys he* 根据Pattern表达查询符合条件的key
dbsize dbsize 返回key的总数
exists exists a 检查key=a是否存在
del del a 删除key=a的数据
expire expire hello 20 设置key=hello 20秒后过期
ttl ttl hello 查看key=a的过期剩余时间
[root@imooc redis-5.0.2]# ./src/redids-cli -p 6380   #重新连接到端口
127.0.0.1:6380> select 10
127.0.0.1:6380[10]> set name lily   #十号数据库中增加key=name value=lily
127.0.0.1:6380[10]> get name  => "lily"
127.0.0.1:6380[10]> select 9
127.0.0.1:6380[9]> get name =>(nil)
127.0.0.1:6380[9]> set name kitty
127.0.0.1:6380[9]> get name =>"kitty"
127.0.0.1:6380[9]> keys *  #列举表达式能匹配的所有key
127.0.0.1:6380[9]> set sex male
127.0.0.1:6380[9]> keys *
127.0.0.1:6380[9]> keys n* #模糊匹配表达式
127.0.0.1:6380[9]> dbsize  #显示当前数据库的总量 => 2个 => "name"  "sex"
127.0.0.1:6380[9]> del sex #删除 => 1 返回0则不存在
127.0.0.1:6380[9]> expire name 30   #生效开始之后30秒存活时间
127.0.0.1:6380[9]> ttl name #查看存活时间 时间一到自动清除     keys*中无name数据

Redis数据类型

  • String - 字符串类型 (String最大512mb 建议单个kv不超过100kb)
name Lily
counter 3321
sn 7361-7749
字符串命令
命令 示例 说明
get get hello 获取key=hello结果
set set hello world 设置key=hello, value=hello
mset mget mset hello world java best
mget hello java
一次性设置或者获取多个值
del del hello 删除key=hello
incr/decr incr count
decr count
key值自增/自减1
incrby/decrby incrby count 99
decrby count 99
自增自减指定步长
127.0.0.1:6380[9]> select 10 
127.0.0.1:6380[10]> set name lily
127.0.0.1:6380[10]> set sex 18
127.0.0.1:6380[10]> set birthday 1998-03-11
127.0.0.1:6380[10]> keys *
1) "birthday"  2)"name"  3)"sex"
127.0.0.1:6380[10]> mset name1 kitty sex1 20 birthday1 2001-03-02 #一次性设置多个键值对
127.0.0.1:6380[10]> mget name sex birthday  #一次性提取多个
127.0.0.1:6380[10]> clear  #当前屏幕清空
127.0.0.1:6380[10]> incr age  #将指定的数字自增+1
127.0.0.1:6380[10]> set age 20
127.0.0.1:6380[10]> keys age => "age"
127.0.0.1:6380[10]> get age => "20"
127.0.0.1:6380[10]> incr age => (integer) 22 #不可以自增字符串噢
127.0.0.1:6380[10]> decrby age 3 #对指定的key自减 => 20-3=17
127.0.0.1:6380[10]> del age #删除某个key
  • Hash - Hash类型
Hash类型用于存储结构化数据

↓↓↓ ↓↓↓ key = emp:1 ↓↓↓ ↓↓↓ 在value中又产生一个键值对[下面全是单个的emp:1的key值]

name smith
age 35
birthday 2001-02-02
height 178
命令 示例 说明
hget hget emp:1 age 获取hash中key=age的值
hset hset emp:1 age 23 设置hash中age=23
hmset
hmget
hgetall
hmset emp:1 age 30 name kaka
hmget emp:1 age name
hgetall emp:1
设置hash多个值
获取hash多个值
获取hash所有值
hdel hdel emp:1 age 删除emp:1的age
hexists hexists emp:1 name 检查是否存在
hlen hlen emp:1 获取指定长度
127.0.0.1:6380[1]> hset emp:1 name zhangsan       #设置某个hash值
127.0.0.1:6380[1]> hset emp:1 age 35
127.0.0.1:6380[1]> hset emp:1 birthday 2001-02-02
127.0.0.1:6380[1]> hset emp:1 height 178
127.0.0.1:6380[1]> keys * => "emp:1"
127.0.0.1:6380[1]> hget emp:1 name    #获取指定hash值 =>"zhangsan"
127.0.0.1:6380[1]> hget emp:1 age => "35"
127.0.0.1:6380[1]> hgetall emp:1 #提取所有的数据 

127.0.0.1:6380[1]> hmset emp:2 name lisa age 23 birthday 1990-05-03 height 165
127.0.0.1:6380[1]> hgetall emp:2
127.0.0.1:6380[1]> del emp:2     #删除整个对象
127.0.0.1:6380[1]> hlen emp:1 => (integer) 4     #代表在emp:1中有4个属性
127.0.0.1:6380[1]> hgetall emp:1 
127.0.0.1:6380[1]> hexists emp:1 name  #判断属性是否存在 是则返回1
  • List - 列表类型
    • List列表就是一系列字符串的”数组”, 按插入顺序排序
    • List列表最大长度为2的32次方-1, 可以包含40亿个元素
    List命令
    • rpush listkey c b a - 右侧插入 先c后b最后a
    • lpush listkey f e d - 左侧插入 先f后e最后d d e f c b a
    • rpop listkey - 右侧弹出 d e f c b
    • lpop listkey -左侧弹出 e f c b
127.0.0.1:6380[1]> rpush list c =>(integer) 1
127.0.0.1:6380[1]> lrange list 0 -1  #输出指定列表起始到结束范围内的所有元素 [开始 末尾]
127.0.0.1:6380[1]> rpush list b a =>(integer) 3
127.0.0.1:6380[1]> lrange list 0 -1 => "c" "b" "a"
127.0.0.1:6380[1]> lpush list f      #在左侧插入
127.0.0.1:6380[1]> lrange list 0 -1 => "f" "c" "b" "a"
127.0.0.1:6380[1]> lpush list b a
127.0.0.1:6380[1]> lrange list 0 -1 => "a" "b" "f" "c" "b" "a"
127.0.0.1:6380[1]> rpop list => "a"
127.0.0.1:6380[1]> lrange list 0 -1 => "a" "b" "f" "c" "b"
127.0.0.1:6380[1]> lpop list => "b" "f" "c" "b"
  • Set - 集合类型 Zset - 有序集合类型
    • Set集合是字符串的无序集合, 集合成员是唯一的
    • Zset集合是字符串的有序集合, 集合成员是唯一的
Set集合
127.0.0.1:6380[1]> sadd set1 a => 1
127.0.0.1:6380[1]> keys * => "set1" "emp:1" "list"
127.0.0.1:6380[1]> sadd set1 b => "b"
127.0.0.1:6380[1]> sadd set1 c => "c"
127.0.0.1:6380[1]> sadd set1 d => "d"
127.0.0.1:6380[1]> sadd set1 e => "e"
127.0.0.1:6380[1]> sadd set1 f => "f"
127.0.0.1:6380[1]> smembers set1 => "a" "b" "c" "d" "e" "f" "e"  #字母顺序打乱 乱序

127.0.0.1:6380[1]> sadd set2 d     #创建一个set2与set1有重叠
127.0.0.1:6380[1]> sadd set2 e
127.0.0.1:6380[1]> sadd set2 f
127.0.0.1:6380[1]> sadd set2 h
127.0.0.1:6380[1]> sadd set2 g
127.0.0.1:6380[1]> smembers set2 => "d" "h" "e" "f" "g"
127.0.0.1:6380[1]> sinter set1 set2 =>     "d" "f" "e"     #取其中的交集
127.0.0.1:6380[1]> sunion set1 set2 =>     "d" "g" "b" "c" "h" "f" "e" "a"    #取其中的并集[取并集并去除重复元素]
127.0.0.1:6380[1]> sdiff set1 set2 #寻找叉集(在set1有 在set2中没有[两个部分交集排除在外])
=> "a" "c" "b"
127.0.0.1:6380[1]> sdiff set2 set1 => "h" "g"
Zset集合
127.0.0.1:6380[1]> zadd zset1 100 a =>1
127.0.0.1:6380[1]> zadd zset1 101 b => 1
127.0.0.1:6380[1]> zrange zset1 0 -1 => "a" "b" #按照顺序排序
127.0.0.1:6380[1]> zadd zset1 99 c => 1  
127.0.0.1:6380[1]> zrange zset1 0 -1 => "c" "a" "b" #按照分数升序排列
127.0.0.1:6380[1]> zadd zset1 102 d
127.0.0.1:6380[1]> zadd zset1 103 e
127.0.0.1:6380[1]> zadd zset1 104 f
127.0.0.1:6380[1]> zrange zset1 0 -1 withscores #升序打印分数
127.0.0.1:6380[1]> zrangebyscore zset1 100 103 #符合score从100-103的名字

Java客户端-Jedis

  • Jedis是Java语言开发的Redis客户端工具包
  • Jedis只是对Redis命令的封装, 掌握Redis命令便可轻易上手
允许远程访问需要改文件
[root@imooc ~]# cd /usr/local/redis/redis-5.0.2/
[root@imooc redis-5.0.2]# vim redis.conf
第88行 protected-mode yes 将yes设置为no
第69行 bind 127.0.0.1 改为 bind 0.0.0.0     #四个0代表所有ip主机都可以访问进来【真正开发时要用特定的ip号】
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf
[root@imooc redis-5.0.2]# netstat -tulpn | grep redis
[root@imooc redis-5.0.2]# firewall-cmd --zone=public --add-port=6379/tcp --permanent
[root@imooc redis-5.0.2]# firewall-cmd --reload
[root@imooc redis-5.0.2]# ifconfig => IP地址: 192.168.170.135

[root@imooc redis-5.0.2]# ./src/redis-cli -p 6379

报错连接超时:
Java远程连接Redis时出现: java.net.SocketTimeoutException: connect timed out的解决办法-CSDN博客
Java连接Redis connection timed out 报错的解决方法_caused by: io.netty.channel.abstractchannel$annota-CSDN博客

systemctl start firewalld  #开启防火墙
systemctl enable firewalld.service  #开机自启动防火墙

systemctl stop firewalld.service #关闭防火墙
重启 Redis 服务端
ps -ef|grep redis    #查看 Redis 进程 
kill -s 9 进程号      #杀死 Redis 进程
[root@imooc redis-5.0.2]# ./src/redis-server redis.conf 
package com.imooc.jedis;

import redis.clients.jedis.Jedis;

import java.util.List;

public class JedisTestor {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            jedis.auth("root");
            jedis.select(2);
            System.out.println("Redis连接成功");
            //字符串
            jedis.set("sn", "7781-9938");
            String sn = jedis.get("sn");
            System.out.println(sn);
            jedis.mset(new String[]{"title", "婴幼儿奶粉", "num", "20"});
            List<String> goods = jedis.mget(new String[]{"sn", "title", "num"});
            System.out.println(goods);
            Long num = jedis.incr("num");
            System.out.println(num);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }
}
pom.xml【两个jar:    jedis-2.9.0.jar        fastjson-1.2.62.jar】
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Jedis_test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>
[root@imooc redis-5.0.2]# ./src/redis-cli
127.0.0.1:6379> auth root
127.0.0.1:6379> select 2
127.0.0.1:6379[2]> keys * => "sn"
127.0.0.1:6379[2]> get sn => "7781-9938"
【此处更换新的java代码再次插入 奶粉...】
------------------------------------------------------------------
jedis.mset(new String[]{"title", "婴幼儿奶粉", "num", "20"});
List<String> goods = jedis.mget(new String[]{"sn", "title", "num"});
------------------------------------------------------------------
127.0.0.1:6379[2]> keys * => "sn" "num" "title"
127.0.0.1:6379[2]> get num => 21
127.0.0.1:6379[2]> get title => \xe5\xa9\xb4\xe5\xb9\xbc\xe5...

Jedis操作Hash类型

package com.imooc.jedis;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JedisTestor {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            jedis.auth("root");
            jedis.select(2);
            System.out.println("Redis连接成功");
            //字符串
            jedis.set("sn", "7781-9938");
            String sn = jedis.get("sn");
            System.out.println(sn);
            jedis.mset(new String[]{"title", "婴幼儿奶粉", "num", "20"});
            List<String> goods = jedis.mget(new String[]{"sn", "title", "num"});
            System.out.println(goods);
            Long num = jedis.incr("num");
            System.out.println(num);

            //Hash
            jedis.hset("student:3312", "name", "张晓明");
            String name = jedis.hget("student:3312", "name");
            System.out.println(name);

            Map<String,String> studentMap = new HashMap();
            studentMap.put("name", "李兰");
            studentMap.put("age", "18"); //所有数据类型都是String
            studentMap.put("id", "3313");
            jedis.hmset("student:3313", studentMap);
            Map<String,String> smap = jedis.hgetAll("student:3313");
            System.out.println(smap);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }
}

---------------------------------------------------------------
Redis连接成功
7781-9938
[7781-9938, 婴幼儿奶粉, 20]
21
张晓明
{name=李兰, age=18, id=3313}
127.0.0.1:6380[2]> hgetall student:3313

Jedis操作List类型

package com.imooc.jedis;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JedisTestor {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try { //List
            jedis.del("letter"); //要先删除不然lpop、rpop的时候会追加数据
            jedis.rpush("letter", new String[]{"d", "e", "f"});
            jedis.lpush("letter", new String[]{"c", "b", "a"});
            List<String> letter = jedis.lrange("letter", 0, -1);
            jedis.lpop("letter");
            jedis.rpop("letter");
            letter = jedis.lrange("letter", 0, -1);
            System.out.println(letter);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }
}
----————--——----------------------------------------------------
[a, b, c, d, e, f] => [b, c, d, e]

利用Jedis缓存数据 [放在内存处理 速度快]

缓存数据:资料不要太大 信息比较稳定更新次数较低
Goods.java
public class Goods {
    private Integer goodsId;
    private String goodsName;
    private String description;
    private Float price;
    Getter + Setter + Constructor[空+全]
}
CacheSample.java
package com.imooc.jedis;

import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;

public class CacheSample {
    public CacheSample(){ //数据初始化
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            List<Goods> goodsList = new ArrayList();
            goodsList.add(new Goods(8818,"红富士苹果","",3.5f));
            goodsList.add(new Goods(8819,"赣南脐橙","",5f));
            goodsList.add(new Goods(8820,"进口香蕉","",2f));
            //javabean序列化为json字符串保存到java里
            jedis.auth("root");
            jedis.select(3);
            for (Goods goods : goodsList){
                String json = JSON.toJSONString(goods);
                System.out.println(json);
                String key = "goods:" + goods.getGoodsId();
                jedis.set(key, json); //key + value[序列化为json]
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }

    public static void main(String[] args) {
        new CacheSample();
    }
}
________________________________________________________________
{"description":"","goodsId":8818,"goodsName":"红富士苹果","price":3.5}
{"description":"","goodsId":8819,"goodsName":"赣南脐橙","price":5.0}
{"description":"","goodsId":8820,"goodsName":"进口香蕉","price":2.0}
________________________________________________________________
127.0.0.1:6379[3]> keys *
127.0.0.1:6379[3]> "goods:8820" "goods:8819" "goods:8818"
127.0.0.1:6379[3]> get goods:8820

更新
CacheSample.java
package com.imooc.jedis;

import com.alibaba.fastjson.JSON;
import redis.clients.jedis.Jedis;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CacheSample {
    public CacheSample(){ //数据初始化
        Jedis jedis = new Jedis("192.168.170.135", 6379);
        try {
            List<Goods> goodsList = new ArrayList();
            goodsList.add(new Goods(8818,"红富士苹果","",3.5f));
            goodsList.add(new Goods(8819,"赣南脐橙","",5f));
            goodsList.add(new Goods(8820,"进口香蕉","",2f));
            //javabean序列化为json字符串保存到java里
            jedis.auth("root");
            jedis.select(3);
            for (Goods goods : goodsList){
                String json = JSON.toJSONString(goods);
                System.out.println(json);
                String key = "goods:" + goods.getGoodsId();
                jedis.set(key, json); //key + value[序列化为json]
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            jedis.close();
        }
    }

    public static void main(String[] args) {
        new CacheSample();
        System.out.println("请输入要查询的商品编号:");
        String goodsId = new Scanner(System.in).next();
        Jedis jedis = new Jedis("192.168.170.135");
        jedis.auth("root");
        jedis.select(3);
        String key = "goods:" + goodsId;
        if (jedis.exists(key)){
            String json = jedis.get(key);
            System.out.println(json);
            //由json转回到java对象
            Goods g = JSON.parseObject(json, Goods.class);
            System.out.println(g.getGoodsName());
            System.out.println(g.getPrice());
        }else{
            System.out.println("您输入的商品编号不存在,请重新输入!");
        }
    }
}
阅读全文

MyBatis实现OA系统项目实战

2023/11/7

慕课网办公OA平台

课程介绍
  • 需求说明与环境准备
  • 开发基于RBAC的访问控制模块
  • 开发多级请假审批流程

办公自动化OA系统

  • 办公自动化系统(Office Automation)是替代传统办公的解决方案
  • OA系统是利用软件技术构建的单位内部办公平台,用于辅助办公
  • 利用OA系统可将办公数据数字化,可扩大提高办公流程执行效率

项目需求

  • 慕课网办公OA系统要求采用多用户B/S架构设计开发
  • HR为每一位员工分配系统账户,员工用此账户登录系统
  • 公司采用分级定岗,从1-8依次提升,不同岗位薪资水平不同
    • 6级(含)以下员工为业务岗,对应人员执行公司业务事宜
    • 7-8级为管理岗,其中7级为部门经理8级为总经理
    • 业务岗与管理岗员工可用系统功能不同,要求允许灵活配置

请假流程

  • 公司所有员工都可以使用”请假申请”功能申请休假
  • 请假时间少于72小时,部门经理审批后直接通过
  • 请假时间大于72小时,部门经理审批后还需总经理进行审批
  • 部门经理只允许批准本部门员工申请
  • 部门经理请假需直接由总经理审批
  • 总经理提起请假申请,系统自动批准通过

搭建基础架构

框架&组件
  • MySQL 8
  • Mybatis 3.5
  • Alibaba Druid
  • Servlet 3.1
  • Freemarker 2.3
  • LayUl 2.5
工程结构
imooc-oa  eclipse工程项目
 /src - java源代码目录
 /WebContent - Web资源目录
 /css - css文件目录
 /js - js文件目录
 /image - 图片资源目录
 /upload - 上传文件目录
 /WEB-INF   //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
   /jsp - jsp页面目录
   /lib - jar文件目录
   /classes - 编译后的class目录
   /web.xml web描述符文件
包结构
com.imooc-oa //逆命名法
    /controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
    /service - 存放处理逻辑类model[伪数据库] //完成业务逻辑 service与dao进行传递调用
    /dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
    /entity - 存放实体类 JavaBean java中的简单对象
    /utils - 通用工具类 底层通用的工具类或方法

环境配置

Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客

配置pom.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>imooc-oa</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>

    <dependencies>
        <!--Mybatis 框架-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--MySQL 8 JDBC驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <!--Druid数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <!--Junit4单元测试框架-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <!--只参与Maven Test,不进行发布-->
            <scope>test</scope>
        </dependency>
        <!--Logback日志输出组件-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--Freemarker依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <!--servlet-api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--依赖只参与编译测试,不进行发布-->
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
<!--用Maven时必打的代码-->
                <!--利用Maven编译插件将编译级别提高至1.8,解决lambda表达式错误-->
                <groupId>org.apache.maven.plugins</groupId>
                <!--maven-compiler-plugin是Maven自带的编译插件-->
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <!--检查源码采用1.8规则,默认为1.5-->
                    <source>1.8</source>
                    <!--按1.8规则生成字节码-->
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>
配置数据库连接池
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <environments default="dev">
        <!--开发环境配置-->
        <environment id="dev">
            <!--事务管理器采用JDBC方式-->
            <transactionManager type="JDBC"></transactionManager>
            <!--利用Mybatis自带连接池管理连接-->
            <dataSource type="POOLED">
            <!--MyBatis与Druid的整合-->
                <!--JDBC连接属性-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/test.xml"/>
    </mappers>
</configuration>

开发Mybatis

Mybatis复习_resources.getresourceasreader的读取路径-CSDN博客

test.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
    <select id="sample" resultType="string">
        select 'success'
    </select>
</mapper>
util-MybatisUtils.java
package util;

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.Reader;
import java.util.function.Function;

public class MybatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    //利用静态块在初始化类时实例化sqlSessionFactory
    static{
        Reader reader = null;
        try{
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        }catch(IOException e){
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 执行SELECT查询SQL
     * @param func 要执行查询语句的代码块
     * @return 查询结果
     */
    //用于数据的查询[极大的简化查询] mybatis执行SQL时一定要有mapper的xml
    public static Object executeQuery(Function<SqlSession,Object> func){ //函数式接口
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try{//具体查询交给Function实现 查询前完成连接的打开和关闭
            Object obj = func.apply(sqlSession);
            return obj;
        }finally {
            sqlSession.close(); //最后一步释放连接资源
        }
    }

    /**
     * 执行INSERT/UPDATE/DELETE写操作SQL
     * @param func 要执行的写操作代码块
     * @return 写操作后返回的结果
     */
    public static Object executeUpdate(Function<SqlSession,Object> func){ //函数式接口
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        try{//具体查询交给Function实现 查询前完成连接的打开和关闭
            Object obj = func.apply(sqlSession);
            sqlSession.commit();
            return obj;
        }catch (RuntimeException e){
            sqlSession.rollback();
            throw e;
        }finally {
            sqlSession.close(); //最后一步释放连接资源
        }
    }
}
MybatisUtilsTestor.java
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import util.MybatisUtils;

public class MybatisUtilsTestor {
//    @Test
//    public void testcase1(){
//        String result = (String)MybatisUtils.executeQuery(sqlSession -> {
//            String out = (String)sqlSession.selectOne("test.sample");
//            return out; //out会被retrun obj接收 返回Object
//        });
//        System.out.println(result);
//    }
    @Test
    public void testcase2(){
        String result = (String) MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
        System.out.println(result);
    }
}

MyBatis整合Druid连接池 (自定义连接池)

重新整合mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <environments default="dev">
        <!--开发环境配置-->
        <environment id="dev">
            <!--事务管理器采用JDBC方式-->
            <transactionManager type="JDBC"></transactionManager>
            <!--利用Mybatis自带连接池管理连接-->
<!--            <dataSource type="POOLED">-->
            <dataSource type="com.imooc.oa.datasource.DruidDataSourceFactory">
            <!--MyBatis与Druid的整合-->
                <!--JDBC连接属性-->
                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/imooc-oa?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
                <!--连接池初始连接数-->
                <property name="initialSize" value="10"/>
                <!--连接池最大连接数-->
                <property name="maxActive" value="20"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/test.xml"/>
    </mappers>
</configuration>
com.imooc.oa.datasource.DruidDataSourceFactory
package com.imooc.oa.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

import javax.sql.DataSource;
import java.sql.SQLException;

public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
    public DruidDataSourceFactory(){ //1.创造空的数据源对象
        // 2.调用setProperties读取xml对dataSource属性源进行设置
        this.dataSource = new DruidDataSource(); //表达数据源信息
    }
    //3.数据源需要额外设置要重写
    @Override
    public DataSource getDataSource() { //获取已经初始化的连接池进行返回
        try {
            ((DruidDataSource)this.dataSource).init(); //初始化Druid数据源
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return this.dataSource;
    }
}
Ctrl + Shift + N 文件查找对话框

整合Freemarker

pom.xml
    <!--Freemarker依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <!--servlet-api-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--依赖只参与编译测试,不进行发布-->
            <scope>provided</scope>
        </dependency>
web-WEB-INF-ftl-test.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>${result}</h1>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>freemaker</servlet-name>
        <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
        <init-param>
            <!--        定义模板的存储路径-->
            <param-name>TemplatePath</param-name>
            <param-value>/WEB-INF/ftl</param-value>
        </init-param>
        <init-param>
<!-- default_encoding用于设置读取ftl文件时采用的字符集,进而避免中文乱码的产生-->
            <param-name>default_encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>freemaker</servlet-name>
        <url-pattern>*.ftl</url-pattern>
    </servlet-mapping>
</web-app>
TestServlet.java
package com.imooc.oa.test;

import com.imooc.oa.util.MybatisUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "TestServlet", urlPatterns = "/test")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String result = (String)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("test.sample"));
        req.setAttribute("result",result);
        req.getRequestDispatcher("/test.ftl").forward(req,resp);
    }
}

RBAC(Role-Based Access Control)介绍

  • 基于**角色权限控制**(RBAC)是面向企业安全策略的访问控制方式

  • RBAC核心思想是将控制访问的资源与角色(Role)进行绑定

  • 系统的用户(User)与角色(Role)再进行绑定, 用户便拥有对应权限

一般主键cno或id都要设定字段类型为 BigInt
imooc-oa.sql

实现用户登录

基于LayUI开发登录页

LayUI前端框架

Layui - 经典开源模块化前端 UI 框架(官网文档镜像站) (layuiweb.com)

login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <link rel="stylesheet" href="/resources/layui-main/src/css/layui.css">
    <style>
        body {
            background-color: #f2f2f2;
        }

        .oa-container {
            /*background-color: white;*/
            position: absolute;
            width: 400px;
            height: 350px;
            top: 50%;
            left: 50%;
            padding: 20px;
            margin-left: -200px;
            margin-top: -175px;
        }
        #username,#password{
            /*text-align: center;*/
            /*font-size: 24px;*/
        }
    </style>
</head>
<body>
<div class="oa-container">
    <h1 style="text-align: center; margin-bottom: 20px">办公OA系统</h1>
    <form class="layui-form">
        <div class="layui-form-item">
            <input type="text" id="username" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input">
        </div>
        <div class="layui-form-item">
            <input type="password" id="password" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
        </div>
    </form>
</div>
</body>
</html>

实现用户登录-1

com.imooc.oa.entity.User
package com.imooc.oa.entity;

public class User {
    /*
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
     */
    private Long userId;
    private String username;
    private String password;
    private Long employeeId;
    Getter + Setter
}
com.imooc.oa.dao.UserDao
package com.imooc.oa.dao;

import com.imooc.oa.entity.User;
import com.imooc.oa.util.MybatisUtils;

/**
 * 用户表
 */
public class UserDao {
    /**
     * 按照用户名查询用户表
     * @param username 用户名
     * @return User对象包含对应的用户信息,null则代表对象不存在
     */
    public User selectByUsername(String username){
        User user = (User)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("usermapper.selectByUsername",username));
        return user;
    }
}
user.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="usermapper">
    <select id="selectByUsername" parameterType="String" resultType="com.imooc.oa.entity.User">
        select * from sys_user where username = #{value}
    </select>
</mapper>
mybatis-config.xml
<mappers>
    <mapper resource="mappers/test.xml"/>
    <mapper resource="mappers/user.xml"/>
</mappers>

Dao → Service

创建测试用例快捷键 Ctrl+Shift+T
UserSerive.java
package com.imooc.oa.serive;

import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.User;
import com.imooc.oa.serive.exception.BussinessException;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */ 
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        if(!password.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
}
test/serive.UserServiceTest.java
package com.imooc.oa.serive;

import junit.framework.TestCase;
import org.junit.Test;

public class UserServiceTest extends TestCase {
    private UserService userService = new UserService();

    @Test
    public void testCheckLogin1() {
        userService.checkLogin("uu","1234");
    }
    @Test
    public void testCheckLogin2() {
        userService.checkLogin("m8","1234");
    }
    @Test
    public void testCheckLogin3() {
        userService.checkLogin("uu","test");
    }
}
serive.exception.BussinessException.java
package com.imooc.oa.serive.exception;

/**
 * 业务逻辑异常
 */
public class BussinessException extends RuntimeException{
    private String code; //异常编码,异常的以为标识
    private String message; //异常的具体文本消息
    public BussinessException(String code, String msg){
        super(code + ":" + msg);
        this.code = code;
        this.message = msg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

实现用户登录-2

com.imooc.oa.controller.LoginServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
    Logger logger = LoggerFactory.getLogger(LoginServlet.class);
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接收用户输入
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Map<String, Object> result = new HashMap<>();
        try {
            //调用业务逻辑
            User user = userService.checkLogin(username, password);
            result.put("code", "0");
            result.put("message", "success");
        }catch (BussinessException ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getCode());
            result.put("message", ex.getMessage());
        }catch (Exception ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getClass().getSimpleName());
            result.put("message", ex.getMessage());
        }
        //返回对应结果
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

实现用户登录-3

pom.xml
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.62</version>
</dependency>
login.xml实现增添表单校验
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>慕课网办公OA系统</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        body{
            background-color: #F2F2F2;
        }
        .oa-container{
            /*background-color: white;*/
            position: absolute;
            width: 400px;
            height: 350px;
            top: 50%;
            left: 50%;
            padding: 20px;
            margin-left: -200px;
            margin-top: -175px;
        }
        #username,#password{
            /*text-align: center;*/
            /*font-size: 24px;*/
        }
    </style>
</head>
<body>
<div class="oa-container">
    <h1 style="text-align: center;margin-bottom: 20px">办公OA系统</h1>
    <form class="layui-form">
        <div class="layui-form-item">
            <input type="text" id="username" lay-verify="required" name="username" placeholder="请输入用户名" autocomplete="off" class="layui-input" >
        </div>

        <div class="layui-form-item">
            <input type="password" id="password" lay-verify="required" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input" >
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="login">登录</button>
        </div>
    </form>
</div>
<script src="/resources/layui/layui.all.js"></script>
<script>
    // 表单提交事件 表单输入校验 在上面加 lay-verify="requrired"
    layui.form.on("submit(login)" , function(formdata){//data参数包含了当前表单的数据
        console.log(formdata);
        //发送ajax请求进行登录校验
        layui.$.ajax({
            url : "/check_login",
            data : formdata.field, //提交表单数据
            type : "post",
            dataType : "json" ,
            success : function(json){
                console.log(json);
                if(json.code == "0"){ //登录校验成功 内置弹出层
                    layui.layer.msg("登录成功");
                }else{
                    layui.layer.msg(json.message);
                }
            }
        })
        return false;//submit提交事件返回true则表单提交,false则阻止表单提交
    })
</script>
</body>
</html>
</html>
后面通过Ajax[浏览器后台(上面return false)]请求向服务器发起异步通信获取校验是否通过

分析后台首页布局与设计

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    姓名[部门-职务]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <!--父节点-->
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">模块1</a>
                    <dl class="layui-nav-child module" data-node-id="1"></dl>
                </li>
                <!--子节点-->
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能1</a>
                </dd>
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能2</a>
                </dd>
                <dd class="function" data-parent-id="1">
                    <a href="javascript:void(0)" target="ifmMain">功能3</a>
                </dd>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">模块2</a>
                    <dl class="layui-nav-child module" data-node-id="2"></dl>
                </li>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能3</a>
                </dd>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能4</a>
                </dd>
                <dd class="function" data-parent-id="2">
                    <a href="javascript:void(0)" target="ifmMain">功能5</a>
                </dd>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

动态显示功能菜单-1 【核心:rbac.xml

通过用户找到角色sys_user 再通过角色找到节点sys_role_user 接下来通过节点编号sys_role_node去获取与之对应的节点其他信息(三表关联)

xml→Dao→UserService

resources.mappers.rbac.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="rbacmapper">
    <select id="selectNodeByUserId" parameterType="Long" resultType="com.imooc.oa.entity.Node">
        select distinct n.*
        from
            sys_role_user ru, sys_role_node rn, sys_node n
        where
            ru.role_id = rn.role_id and user_id = #{value} and rn.node_id = n.node_id
        order by n.node_code
    </select>
</mapper>
mybatis-config.xml
    <mappers>
        <mapper resource="mappers/test.xml"/>
        <mapper resource="mappers/user.xml"/>
        <mapper resource="mappers/rbac.xml"/>
    </mappers>
imooc.oa.dao.RbacDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Node;
import com.imooc.oa.util.MybatisUtils;

import java.util.List;

public class RbacDao {
    public List<Node> selectNodeByUserId(Long userId){
        return (List)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectList("rbacmapper.selectNodeByUserId", userId));
    }
}
service.UserService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;

import java.util.List;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化
    private RbacDao rbacDao = new RbacDao();

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        if(!password.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
    public List<Node> selectNodeByUserId(Long userId){
        List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
        return nodeList;
    }
}
UserServiceTest.java
@Test
    public void selectNodeByUserId(){
        List<Node> nodeList = userService.selectNodeByUserId(2l);
        System.out.println(nodeList);
    }
Node.java
public class Node {
    private Long nodeId;
    private Integer nodeType;
    private String nodeName;
    private String url;
    private Integer nodeCode;
    private Long parentId;
    Setter + Getter
}

动态显示功能菜单-2 (不同用户登录不同功能)

修改LoginServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;
import com.imooc.oa.service.exception.BussinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LoginServlet" ,urlPatterns = "/check_login")
public class LoginServlet extends HttpServlet {
    Logger logger = LoggerFactory.getLogger(LoginServlet.class);
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        //接收用户输入
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Map<String, Object> result = new HashMap<>();
        try {
            //调用业务逻辑 不能直接 request.setAttribute 选更大的对象
            User user = userService.checkLogin(username, password);
            HttpSession session = request.getSession();
            //session种存在用户信息 向session存入登录用户信息,属性名:login_user
            session.setAttribute("login_user", user);
            result.put("code", "0");
            result.put("message", "success");
            result.put("redirect_url", "/index"); //url登陆成功直接返回客户端
        }catch (BussinessException ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getCode());
            result.put("message", ex.getMessage());
        }catch (Exception ex){
            logger.error(ex.getMessage() , ex);
            result.put("code", ex.getClass().getSimpleName());
            result.put("message", ex.getMessage());
        }
        //返回对应结果
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
oa.controller.IndexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
//接收数据传入
@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("login_user");
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        request.setAttribute("node_list",nodeList);
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
将index.html 变成 index.ftl并放在Web/WEB-INF/ftl/index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    姓名[部门-职务]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

Xml配置下实现Mapper接口 (登录用户所对应员工)

接口 xml mapper增加 employeeservice

index.ftl indexServlet 改index.ftl

增加自动化部门 entity.Department dao.创建接口DepartmentDao mappers.department.xml -config.xml注册 DepartmentSerive.java
IndexServlet.java index.ftl

entity.Employee.java
public class Employee{
    private Long employeeId;
    private String name;
    private Long departmentId;
    private String title;
    private Integer level;
    Getter+Setter
}
dao.EmployeeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Department;

public interface DepartmentDao {
    public Department selectById(Long departmentId);
}
mybatis-config.xml
<mapper resource="mappers/employee.xml"/>

index.ftl
 <!--右侧当前用户信息-->
   <ul class="layui-nav layui-layout-right">
       <li class="layui-nav-item">
           <a href="javascript:void(0)">
               <!--图标-->
              <span class="layui-icon layui-icon-user" style="font-size: 20px">
               </span>
               <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
IndexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    private EmployeeService employeeService = new EmployeeService();
    private DepartmentService departmentService = new DepartmentService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        //得到当前登录用户对象
        User user = (User) session.getAttribute("login_user");
        Employee employee = employeeService.selectById(user.getEmployeeId());
        //获取登录用户可用功能模块列表
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        //获取员工对应的部门
        Department department = departmentService.selectById(employee.getDepartmentId());
        //放入请求属性 session生存时间长
        request.setAttribute("node_list",nodeList);
        session.setAttribute("current_employee",employee);
        session.setAttribute("current_department", department);
        //请求派发至ftl进行展现
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
增加自动化部门
entity.Department.java
public class Department {
    private Long departmentId;
    private String departmentName;
    Getter+Setter
}
dao.DepartmentDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Department;

public interface DepartmentDao {
    public Department selectById(Long departmentId);
}
mapper.department.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.DepartmentDao">
    <!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Department">
        select * from adm_department where department_id = #{value}
    </select>
</mapper>

mybatis-config.xml
<mapper resource="mappers/department.xml"/>
service.DepartmentServlet.java
package com.imooc.oa.service;

import com.imooc.oa.dao.DepartmentDao;
import com.imooc.oa.entity.Department;
import com.imooc.oa.util.MybatisUtils;

public class DepartmentService {
    /**
     * 按编号得到部门对象
     * @param departmentId 部门编号
     * @return 部门对象,null代表部门不存在
     */
    public Department selectById(Long departmentId){
        return (Department) MybatisUtils.executeQuery(
                sqlSession -> sqlSession.getMapper(DepartmentDao.class).selectById(departmentId));
    }
}
controller.indexServlet.java
package com.imooc.oa.controller;

import com.imooc.oa.entity.Department;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.DepartmentService;
import com.imooc.oa.service.EmployeeService;
import com.imooc.oa.service.UserService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

@WebServlet(name = "IndexServlet", urlPatterns = "/index")
public class IndexServlet extends HttpServlet {
    private UserService userService = new UserService();
    private EmployeeService employeeService = new EmployeeService();
    private DepartmentService departmentService = new DepartmentService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response){

    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        //得到当前登录用户对象
        User user = (User) session.getAttribute("login_user");
        Employee employee = employeeService.selectById(user.getEmployeeId());
        //获取登录用户可用功能模块列表
        List<Node> nodeList = userService.selectNodeByUserId(user.getUserId());
        //获取员工对应的部门
        Department department = departmentService.selectById(employee.getDepartmentId());
        //放入请求属性 session生存时间长
        request.setAttribute("node_list",nodeList);
        session.setAttribute("current_employee",employee);
        session.setAttribute("current_department", department);
        //请求派发至ftl进行展现
        request.getRequestDispatcher("/index.ftl").forward(request, response);
    }
}
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="#">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="javascript:void(0)" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>

基于MD5算法对密码加密

MD5摘要算法
  • MD5信息摘要算法广泛使用的密码散列函数
  • MD5可以产生出一个128位的散列值用于唯一标识源数据
  • 项目中通常使用MD5作为敏感数据的加密算法
MD5特点
  • 压缩性, MD5生成的摘要长度固定
  • 抗修改, 源数据哪怕有一个字节变化, MD5也会有巨大差异
  • 不可逆, 无法通过MD5反向推算源数据
Apache Commons Codec
  • Commons-Codec是Apache提供的编码/解码组件
  • 通过Commons-Codec可以轻易生成源数据的MD5摘要
  • MD5摘要方法: String md5 = DigestUtils.md5Hex (源数据)
util.MD5Utils.java
package com.imooc.oa.util;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Utils {
    public static String md5Digest(String source){
        return DigestUtils.md5Hex(source);
    }
}

敏感数据加盐混淆

md5utilstest userservice user

public class User {
    /*
    <settings>
        <!--开启驼峰命名转换 form_id -> formId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
     */
    private Long userId;
    private String username;
    private String password;
    private Long employeeId;
    private Integer salt;
    Getter + Setter
}
util.MD5Utils.java
package com.imooc.oa.util;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Utils {
    /**
     * 对数据源加盐混淆后生成MD5摘要
     * @param source 源数据
     * @return MD5摘要
     */
    public static String md5Digest(String source){
        return DigestUtils.md5Hex(source);
    }
    public static String md5Digest(String source, Integer salt){
        char[] ca = source.toCharArray(); //字符数组
        for (int i = 0; i < ca.length; i++) {
            ca[i] = (char)(ca[i] + salt);
        }
        String target = new String(ca);
//        System.out.println(target);
        String md5 = DigestUtils.md5Hex(target);
        return md5;
    }

    public static void main(String[] args) {
        System.out.println(MD5Utils.md5Digest("test", 188));
    }
}
修改UserService.java 中的密码校验
package com.imooc.oa.service;

import com.imooc.oa.dao.RbacDao;
import com.imooc.oa.dao.UserDao;
import com.imooc.oa.entity.Node;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MD5Utils;

import java.util.List;

public class UserService { //创建测试用例快捷键 Ctrl+Shift+T
    private UserDao userDao = new UserDao(); //实例化
    private RbacDao rbacDao = new RbacDao();

    /**
     * 根据前台输入进行登录校验
     * @param username 前台输入的用户名
     * @param password 前台输入的密码
     * @return 校验通过后,包含对应用户数据的User实体类
     * @throws BussinessException L001-用户名不存在,L002-密码错误
     */
    public User checkLogin(String username, String password){
        User user = userDao.selectByUsername(username);
        if (user == null){
            //抛出用户不存在异常
            throw new BussinessException("L001", "用户名不存在");
        }
        //对前台输入的密码加盐混淆后生成MD5,与保存在数据库中的MD5密码进行对比
        String md5 = MD5Utils.md5Digest(password, user.getSalt());
        if(!md5.equals(user.getPassword())){
            throw new BussinessException("L002", "密码错误");
        }
        return user;
    }
    public List<Node> selectNodeByUserId(Long userId){
        List<Node> nodeList = rbacDao.selectNodeByUserId(userId);
        return nodeList;
    }
}

实现注销功能

loginServlet保存着数据 indexservlet中的session保存着数据
清除session

oa.controller.LogoutServlet.java
package com.imooc.oa.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "LogoutServlet", urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getSession().invalidate(); //会话注销
        response.sendRedirect("/login.html"); //跳转回去
    }
}
index.ftl
<!--注销按钮-->
<li class="layui-nav-item"><a href="/logout">注销</a></li>

请假流程数据库设计

开发多级审批流程
  • 公司所有员工都可以使用”请假申请”功能申请休假
  • 请假时间少于72小时,部门经理审批后直接通过
  • 请假时间大于72小时,部门经理审批后还需总经理进行审批
  • 部门经理只允许批准本部门员工申请
  • 部门经理请假需直接由总经理审批
  • 总经理提起请假申请,系统自动批准通过

工作流程表设计

请假单表LeaveForm → 审批任务流程表ProcessFlow → 消息通知表Notice

设计约束
  • 每一个请假单对应一个审批流程

  • 请假单创建后, 按业务规则生成部门经理、总经理审批任务

  • 审批任务的经办人只能审批自己辖区内的请假申请(总裁办可以审批所有 软件只能审批软件)

  • 所有审批任务”通过”, 代表请假已经批准

  • 任意审批任务”驳回”操作, 其余审批任务取消, 请假申请驳回

  • 请假流程中注意节点产生的操作都要生成对应的系统通知

实现Dao与数据交互

entity 数据新增接口dao(依靠Mybatis) 增加mapper 创造LeaveFormDaoTest
ProcessFlowDao + Test mapper
NoticeDao

entity.LeaveForm.java
public class LeaveForm {
    private Long formId;
    private Long employeeId;
    private Integer formType;
    private Date startTime;
    private Date endTime;
    private String reason;
    private Date createTime;
    private String state;
    Getter + Setter
}
dao.LeaveForm.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;

public interface LeaveFormDao {
    public void insert(LeaveForm form);
}
mybatis-config.xml
<mapper resource="mappers/leave_form.xml"/>
test.dao.LeaveFormDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class LeaveFormDaoTest extends TestCase {
    @Test
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = new LeaveForm();
            form.setEmployeeId(4L);//员工编号
            form.setFormType(1); //事假
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date startTime = null; //起始时间
            Date endTime = null; //结束时间
            try {
                startTime = sdf.parse("2020-03-25 08:00:00");
                endTime = sdf.parse("2020-04-01 18:00:00");
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
            form.setStartTime(startTime);
            form.setEndTime(endTime);
            form.setReason("回家探亲"); //请假事由
            form.setCreateTime(new Date()); //创建时间
            form.setState("processing"); //当前状态
            dao.insert(form);
            return null;
        });
    }

}

entity.ProcessFlow.java
package com.imooc.oa.entity;

import java.util.Date;

public class ProcessFlow {
    private Long processId;
    private Long formId;
    private Long operatorId;
    private String action;
    private String result;
    private String reason;
    private Date createTime;
    private Date auditTime;
    private Integer orderNo;
    private String state;
    private Integer isLast;
    Setter + Getter
}
dao.ProcessFlowDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.ProcessFlow;

public interface ProcessFlowDao {
    public void insert(ProcessFlow processFlow);
}
mybatis-config.xml
<mapper resource="mappers/process_flow.xml"/>
test.dao.ProcessFlowDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;

import java.util.Date;

public class ProcessFlowDaoTest extends TestCase {
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            ProcessFlowDao dao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow = new ProcessFlow();
            flow.setFormId(31L);
            flow.setOperatorId(21L);
            flow.setAction("audit");
            flow.setReason("approved");
            flow.setReason("同意");
            flow.setCreateTime(new Date());
            flow.setAuditTime(new Date());
            flow.setOrderNo(1);
            flow.setState("ready");
            flow.setIsLast(1);
            dao.insert(flow);
            return null;
        });
    }
}

entity.Notice.java
public class Notice {
    private Long noticeId;
    private Long receiverId;
    private String content;
    private Date createTime;
    Getter + Setter
}
dao.NoticeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;

public interface NoticeDao {
    public void insert(Notice notice);
}
mybatis-config.xml
<mapper resource="mappers/notice.xml"/>
test.dao.NoticeDaoTest.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;
import junit.framework.TestCase;
import org.junit.Test;

import java.util.Date;

public class NoticeDaoTest extends TestCase {
    @Test
    public void testInsert(){
        MybatisUtils.executeQuery(sqlSession -> {
            NoticeDao dao = sqlSession.getMapper(NoticeDao.class);
            Notice notice = new Notice();
            notice.setReceiverId(21L);
            notice.setContent("测试消息");
            notice.setCreateTime(new Date());
            dao.insert(notice);
            return null;
        });
    }
}

实现请假申请业务逻辑-1

LeaveFormService employee.xml{动态审批sql}

employee.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名类名一致-->
<mapper namespace="com.imooc.oa.dao.EmployeeDao">
    <!--id与方法名对应 parameterType与方法参数类型对应 resultType与方法返回类型对应-->
    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.Employee">
        select * from adm_employee where employee_id = #{value}
    </select>
    <select id="selectLeader" parameterType="com.imooc.oa.entity.Employee" resultType="com.imooc.oa.entity.Employee">
        select * from adm_employee where
            <if test="emp.level &lt; 7">
                level = 7 and department_id = #{emp.departmentId}
            </if>
            <if test="emp.level == 7">
                level = 8;
            </if>
            <if test="emp.level == 8">
                employee_id = #{emp.employeeId}
            </if>
    </select>
</mapper>
service.LeaveFormService.java (重要业务走向代码不放在程序中)
package com.imooc.oa.service;

import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.util.MybatisUtils;

import java.util.Date;

/**
 * 请假单流程服务
 */
public class LeaveFormService {
    /**
     * 创建请假单
     * @param form 前端输入的请假单数据
     * @return 持久化后的请假单对象
     */
    public LeaveForm createLeaveForm(LeaveForm form){
            LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
            //1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());
            if (employee.getLevel() == 8){
                form.setState("approved");
            }else {
                form.setState("processing");
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            leaveFormDao.insert(form);
            //2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow1 = new ProcessFlow();
            flow1.setFormId(form.getFormId());
            flow1.setOperatorId(employee.getEmployeeId());
            flow1.setAction("apply");
            flow1.setCreateTime(new Date());
            flow1.setOrderNo(1);
            flow1.setState("complete");
            flow1.setIsLast(0);
            processFlowDao.insert(flow1);
            //3.分情况创建其余流程数据 employee.xml(动态sql语句)
              //3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
            if (employee.getLevel() < 7){
                //动态sql EmployeeDao
                Employee dmanage = employeeDao.selectLeader(employee);
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(dmanage.getEmployeeId());
                flow2.setAction("audit");//审批任务
                flow2.setCreateTime(new Date());
                flow2.setOrderNo(2);
                flow2.setState("process");
                long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
                float hours = diff/(1000*60*60) * 1f;
                if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
                    flow2.setIsLast(0); //最后节点
                    processFlowDao.insert(flow2);
                    Employee manager = employeeDao.selectLeader(dmanage);//总经理
                    ProcessFlow flow3 = new ProcessFlow();
                    flow3.setFormId(form.getFormId());
                    flow3.setOperatorId(manager.getEmployeeId());
                    flow3.setAction("audit");
                    flow3.setCreateTime(new Date());
                    flow3.setState("ready");
                    flow3.setOrderNo(3);
                    flow3.setIsLast(1);
                    processFlowDao.insert(flow3);
                }else {//小于3天{
                    flow2.setIsLast(1);
                    processFlowDao.insert(flow2);
                }
            } else if (employee.getLevel() == 7) {//部门经理
                //3.2 7级员工,生成总经理审批任务
                Employee manager = employeeDao.selectLeader(employee);
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(manager.getEmployeeId());
                flow.setAction("audit");
                flow.setCreateTime(new Date());
                flow.setState("process");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
            }else if (employee.getLevel() == 8){
                //3.3 8级员工,生成总经理审批任务,系统自动通过
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(employee.getEmployeeId());
                flow.setAction("audit");
                flow.setResult("自动通过");
                flow.setCreateTime(new Date());
                flow.setAuditTime(new Date());
                flow.setState("complete");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
            }
            return form;
        });
        return savedForm;
    }
} 
service.BussinessConstants.java
package com.imooc.oa.service;

public class BussinessConstants {
    public static final int MANAGER_AUDIT_HOURS = 36; //总经理请假审批时间阈值
}

ctrl+shift+t 生成测试用例

Test.service.LeaveFormServiceTest.java
package com.imooc.oa.service;

import com.imooc.oa.entity.LeaveForm;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import static org.junit.Assert.*;

public class LeaveFormServiceTest {
    LeaveFormService leaveFormService = new LeaveFormService();

    /**
     * 市场部员工请假单(72小时以上)测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm1() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(8l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1); //事假
        form.setReason("市场部员工请假单(72小时以上)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 市场部员工请假单(72小时内)测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm2() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(8l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020032718"));
        form.setFormType(1);
        form.setReason("市场部员工请假单(72小时内)");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 研发部部门经理请假单测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm3() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(2l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("研发部部门经理请假单");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }

    /**
     * 总经理请假单测试用例
     * @throws ParseException
     */
    @Test
    public void createLeaveForm4() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH");
        LeaveForm form = new LeaveForm();
        form.setEmployeeId(1l);
        form.setStartTime(sdf.parse("2020032608"));
        form.setEndTime(sdf.parse("2020040118"));
        form.setFormType(1);
        form.setReason("总经理请假单");
        form.setCreateTime(new Date());
        LeaveForm savedForm = leaveFormService.createLeaveForm(form);
        System.out.println(savedForm.getFormId());
    }
}

实现请假申请控制

Servlet 前后端整体交互(底层)

leaveformservlet

controller.LeaveFormServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.LeaveFormService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "LeaveFormServlet", urlPatterns = "/leave/*")
public class LeaveFormServlet extends HttpServlet {
    private LeaveFormService leaveFormService = new LeaveFormService();
    private Logger logger = LoggerFactory.getLogger(LoggerFactory.class);
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // http://localhost/leave/create
        String uri = request.getRequestURI();
        String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
        if (methodName.equals("create")){

        }
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        this.doPost(request,response);
    }

    /**
     * 创建请假单
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("login_user");
        String formType = request.getParameter("formType");
        String strStartTime = request.getParameter("startTime");
        String strEndTime = request.getParameter("endTime");
        String reason = request.getParameter("reason");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
        Map result = new HashMap();
        try {
            LeaveForm form = new LeaveForm();
            form.setEmployeeId(user.getEmployeeId());
            form.setStartTime(sdf.parse(strStartTime));
            form.setEndTime(sdf.parse(strEndTime));
            form.setFormType(Integer.parseInt(formType));
            form.setReason(reason);
            form.setCreateTime(new Date());
            //2.调用业务逻辑方法
            leaveFormService.createLeaveForm(form);
            result.put("code", "0");
            result.put("message", "success");
        } catch (Exception e){
            logger.error("请假申请异常",e);
            result.put("code", e.getClass().getSimpleName());
            result.put("message", e.getMessage());
        }
        //3.组织相应数据
        String json = JSON.toJSONString(result); //将result转换为字符串
        response.getWriter().println(json);
    }
}

完整实现请假申请功能

controller.ForwardServlet.java
package com.imooc.oa.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 页面跳转Servlet
 */
@WebServlet(name="ForwardServlet", urlPatterns = "/forward/*")
public class ForwardServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String uri = request.getRequestURI();
        /** 动态提取 把第一个/抛出外 再去寻找第一个‘/’
         * /forward/form
         * /forward/a/b/c/form
         */
        String subUri = uri.substring(1);
        String page = subUri.substring(subUri.indexOf("/"));
        request.getRequestDispatcher(page + ".ftl").forward(request,response); //扩展名 + web.xml映射路径
    }
}
form.html 变换为 form.ftl 放在ftl内

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>请假申请</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        /*表单容器*/
        .ns-container {
            position: absolute;
            width: 500px;
            height: 450px;
            top: 150px;
            left: 50%;
            margin-left: -250px;
            padding: 20px;
            box-sizing: border-box;
            border: 1px solid #cccccc;
        }
    </style>
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h2>请假申请</h2>
    </blockquote>
    <table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>
<div class="ns-container">
    <h1 style="text-align: center;margin-bottom: 20px">请假申请单</h1>
    <form class="layui-form">
        <!--基本信息-->
        <div class="layui-form-item">
            <label class="layui-form-label">部门</label>
            <div class="layui-input-block">
                <div class="layui-col-md12" style="padding-top: 10px;">
                    研发部
                </div>

            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">申请人</label>
            <div class="layui-input-block">
                <div class="layui-col-md12" style="padding-top: 10px;">
                    ${current_employee.name}[${current_employee.title}]
                </div>

            </div>
        </div>
        <!--请假类型下拉框-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假类别</label>
            <div class="layui-input-block layui-col-space5">
                    <select name="formType" lay-verify="required" lay-filter="cityCode">
                        <option value="1">事假</option>
                        <option value="2">病假</option>
                        <option value="3">工伤假</option>
                        <option value="4">婚嫁</option>
                        <option value="5">产假</option>
                        <option value="6">丧假</option>
                    </select>
            </div>
        </div>
        
        <!--请假时长日期选择框-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假时长</label>
            <div class="layui-input-block layui-col-space5">
                    <input name="leaveRange" type="text" class="layui-input" id="daterange" placeholder=" - ">
                    <input id="startTime" name="startTime" type="hidden">
                    <input id="endTime" name="endTime" type="hidden">
            </div>
        </div>

        <!--请假事由-->
        <div class="layui-form-item">
            <label class="layui-form-label">请假事由</label>
            <div class="layui-input-block layui-col-space5">
                    <input name="reason" type="text"  lay-verify="required|mobile" placeholder="" autocomplete="off" class="layui-input">
            </div>
        </div>

        <!--提交按钮-->
        <div class="layui-form-item " style="text-align: center">
                <button class="layui-btn" type="button" lay-submit lay-filter="sub">立即申请</button>
        </div>
    </form>
</div>

<script src="/resources/layui/layui.all.js"></script>
<!--Sweetalert2对话框-->
<script src="/resources/sweetalert2.all.min.js"></script>

<script>
        var layDate = layui.laydate; //Layui日期选择框JS对象
        var layForm = layui.form; //layui表单对象
        var $ = layui.$; //jQuery对象
        //日期时间范围
        layDate.render({
            elem: '#daterange'  //daterange渲染成日期选择框
            ,type: 'datetime'
            ,range: true
            ,format: 'yyyy年M月d日H时'
            ,done: function(value, start, end){
                //选择日期后出发的时间,设置startTime与endTime隐藏域
                var startTime = start.year + "-" + start.month + "-" + start.date + "-" + start.hours;
                var endTime = end.year + "-" + end.month + "-" + end.date + "-" + end.hours;
                console.info("请假开始时间",startTime);
                $("#startTime").val(startTime);
                console.info("请假结束时间",endTime);
                $("#endTime").val(endTime);
            }
        });

        //表单提交时间
        layForm.on('submit(sub)', function(data){
            console.info("向服务器提交的表单数据",data.field);
            $.post("/leave/create",data.field,function (json) {
                console.info("服务器返回数值",json);
                if(json.code == "0"){
                    /*SweetAlert2确定对话框*/
                    swal({
                        type: 'success',
                        html: "<h2>请假单已提交,等待上级审批</h2>",
                        confirmButtonText: "确定"
                    }).then(function (result) {
                        window.location.href="/forward/notice";
                    });
                }else{
                    swal({
                        type: 'warning',
                        html: "<h2>" + json.message + "</h2>",
                        confirmButtonText: "确定"
                    });
                }
            },"json");
            return false;
        });

</script>
</body>
</html>
把localhost/index 主页添加数据
index.ftl中的47行到50行
 <!--子节点-->
<dd class="function" data-parent-id="${node.parentId}">
 <a href="#{node.url}" target="ifmMain">${node.nodeName}</a>
</dd>

请假审批功能

leave_form.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.LeaveFormDao">
    <insert id="insert" parameterType="com.imooc.oa.entity.LeaveForm"
        useGeneratedKeys="true" keyProperty="formId" keyColumn="form_id">
        INSERT INTO adm_leave_form( employee_id, form_type, start_time, end_time, reason, create_time, state)
        VALUES ( #{employeeId}, #{formType}, #{startTime}, #{endTime}, #{reason}, #{createTime}, #{state})
    </insert>
    <select id="selectByParams" parameterType="java.util.Map" resultType="java.util.Map">
        select f.* ,e.name , d.*
        from
          adm_leave_form f,adm_process_flow pf , adm_employee e , adm_department d
        where
          f.form_id = pf.form_id
          and f.employee_id = e.employee_id
          and e.department_id = d.department_id
          and pf.state = #{pf_state} and pf.operator_id = #{pf_operator_id}
    </select>
<!--    <select id="selectById" parameterType="Long" resultType="com.imooc.oa.entity.LeaveForm">-->
<!--        select * from adm_leave_form where form_id = #{value}-->
<!--    </select>-->

<!--    <update id="update" parameterType="com.imooc.oa.entity.LeaveForm">-->
<!--        UPDATE adm_leave_form SET employee_id = #{employeeId} , form_type = #{formType}, start_time = #{startTime}, end_time = #{endTime}, reason = #{reason}, state = #{state} ,create_time = #{createTime} WHERE form_id = #{formId}-->
<!--    </update>-->
</mapper>
dao.LeaveFormDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.LeaveForm;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface LeaveFormDao {
    public void insert(LeaveForm form);
    public List<Map> selectByParams(@Param("pf_state") String pfState , @Param("pf_operator_id") Long operatorId);
} 
Test
  @Test
    public void testSelectByParams(){
        MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            List<Map> list = dao.selectByParams("process", 21L);
            System.out.println(list);
            return list;
        });
    }
controller.LeaveFormServlet.java
   /**
     * 查询需要审核的请假单列表
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void getLeaveFormList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User user = (User) request.getSession().getAttribute("login_user");
        List<Map> formList = leaveFormService.getLeaveFormList("process", user.getEmployeeId());
        Map result = new HashMap();
        result.put("code","0"); //服务器处理成功
        result.put("msg",""); //服务器返回具体处理消息
        result.put("count", formList.size()); //数据总数
        result.put("data", formList); //当前显示的对象页表
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

实现待审批请假列表

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>请假审批</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
    <style>
        .form-item{
            padding: 10px;
        }
        .form-item-value{
            padding: 10px;
        }
    </style>
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h1>请假审批</h1>
    </blockquote>
    <!--待审批列表-->
    <table id="grdFormList" lay-filter="grdFormList"></table>
</div>
<!--请假详情对话框-->
<div id="divDialog" style="display: none;padding: 10px">
    <form class="layui-form">

        <div class="layui-form-item">
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">部门</div>
                <div class="layui-col-xs4 form-item-value" id="dname"></div>
                <div class="layui-col-xs2 form-item">姓名</div>
                <div class="layui-col-xs4 form-item-value" id="name"></div>
            </div>
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">起始时间</div>
                <div class="layui-col-xs4 form-item-value" id="startTime"></div>
                <div class="layui-col-xs2 form-item">结束时间</div>
                <div class="layui-col-xs4 form-item-value" id="endTime"></div>
            </div>
            <div class="layui-row">
                <div class="layui-col-xs2 form-item">请假原因</div>
                <div class="layui-col-xs10 form-item-value" id="reason"></div>
            </div>
            <!--表单Id-->
            <input type="hidden" name="formId" id="formId">
            <!--审批结果-->
            <select name="result" lay-verfity="required">
                <option value="approved">同意</option>
                <option value="refused">驳回</option>
            </select>
        </div>
        <div class="layui-form-item">
            <!--审批意见-->
            <input type="text" name="reason" placeholder="请输入审批意见"
                   autocomplete="off" class="layui-input"/>
        </div>
        <div class="layui-form-item">
            <button class="layui-btn layui-btn-fluid " lay-submit lay-filter="audit">确认提交</button>

        </div>
    </form>
</div>

<script src="/resources/layui/layui.all.js"></script>
<script src="/resources/sweetalert2.all.min.js"></script>

<script>
    var $ = layui.$;
    //将毫秒数转换为"yyyy-MM-dd HH时"字符串格式
    function formatDate(time){
        var newDate = new Date(time);
        return newDate.getFullYear() + "-" +
            (newDate.getMonth() + 1) + "-" + newDate.getDate()
            + " " + newDate.getHours() + "时";
    }
    // 将table渲染为数据表格
    layui.table.render({
        elem : "#grdFormList" , //选择器
        id : "grdFormList" , //id
        url : "/leave/list" , //ajax请求url
        page : false , //是否分页 true-是 false-否
        cols :[[ //列描述
            {title : "" , width:70 , style : "height:60px" , type:"numbers"}, // numbers代表序号列
            {field : "create_time" , title : "申请时间" , width : 150 , templet: function (d) {
                //templet代表对数据进行加工后再显示
                return formatDate(d.create_time)
            }},
            {field : "form_type" , title : "类型" , width : 100 , templet: function(d){
                switch (d.form_type) {
                    case 1:
                        return "事假";
                    case 2:
                        return "病假";
                    case 3:
                        return "工伤假";
                    case 4:
                        return "婚假";
                    case 5:
                        return "产假";
                    case 6:
                        return "丧假";
                }
            }},
            {field : "department_name" , title : "部门" , width : 100},
            {field : "name" , title : "员工" , width : 100},
            {field : "start_time" , title : "起始时间" , width : 150, templet: function (d) {
                    return formatDate(d.start_time)
                }},
            {field : "end_time" , title : "结束时间" , width : 150 , templet: function (d) {
                    return formatDate(d.end_time)
                }},
            {field : "reason" , title : "请假原因" , width : 350 },
            {title : "" , width:150 ,type:"space" , templet : function(d){
                var strRec = JSON.stringify(d);
                console.info("请假单数据", d);
                console.info("请假单数据", strRec);
                //将请假单数据存放至data-laf属性中
                return "<button class='layui-btn layui-btn-danger layui-btn-sm btn-audit' data-laf=" + strRec + " >审批</button>";
            }}
        ]]
    })

    // 绑定每一行的审批按钮
    $(document).on("click" , ".btn-audit" , function(){
        //初始化表单
        $("#divDialog form")[0].reset();
        $("#divDialog form form-item-value").text("");
        //获取当前点击按钮的请假单数据,回填至显示项 json对象 内置数据显示页面
        var laf = $(this).data("laf");
        $("#dname").text(laf.department_name);
        $("#name").text(laf.name);
        $("#startTime").text(formatDate(laf.start_time));
        $("#endTime").text(formatDate(laf.end_time));
        $("#reason").text(laf.reason);
        $("#formId").val(laf.form_id);
        //弹出layui对话框
        layui.layer.open({
            type : "1" , //页面层
            title : "请假审批" , //标题
            content : $("#divDialog") , //指定对话框容器对象
            area : ["500px" , "400px"] , //尺寸
            end : function(){ //销毁后触发事件
                $("#divDialog").hide();
            }
        })
    })
    /**
     * 提交审批数据 本质:发送Ajax请求
     */
    layui.form.on("submit(audit)" , function(data){
        $.ajax({
            url : "/leave/audit", //审核URL
            data : data.field ,
            type : "post" ,
            dataType : "json" ,
            success: function (json) {
                //关闭所有layui对话框
                layui.layer.closeAll();
                //显示处理结果
                if(json.code == "0"){
                    swal({
                        type: 'success',
                        html: "<h2>请假已审批完毕</h2>",
                        confirmButtonText: "确定"
                    }).then(function (result) {
                        window.location.href="/forward/notice";
                    });
                }else{
                    swal({
                        type: 'warning',
                        html: "<h2>" + json.message + "</h2>",
                        confirmButtonText: "确定"
                    });
                }
            }
        })
        return false;
    })

</script>
</body>
</html>

实现审批业务逻辑

service.LeaveFormService.java
 public void audit(Long formId, Long operatorId, String result, String reason){
        MybatisUtils.executeQuery(sqlSession -> {
            //1.无论同意/驳回, 当前任务状态变更为complete
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
            if (flowList.size() == 0){
                throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if (processList.size() == 0){
                throw new BussinessException("PF002", "未找到待处理任务");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowDao.update(process);
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = leaveFormDao.selectById(formId);
            //2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
            if (process.getIsLast() == 1){
                form.setState(result); // approved\refused
                leaveFormDao.update(form);
            }else {
                //readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
                //3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if (result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowDao.update(readyProcess);
                } else if (result.equals("refused")) {
                    //4.如果当前任务不是最后一个切点且审核驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for (ProcessFlow p : readyList){
                        p.setState("cancel");
                        processFlowDao.update(p);
                    }
                    form.setState("refused");
                    leaveFormDao.update(form);
                }
            }
            return null;
        });
    }
test.LeaveFormServiceTest.java (在数据库中增加新建查询 导入训练素材的 请假单审核测试数据.sql)
   /**
     * 请假3天以上,部门经理审批通过
     */
    @Test
    public void audit1(){
        leaveFormService.audit(31l,2l,"approved","祝早日康复");
    }

    /**
     * 请假3天以上,部门经理审批驳回
     */
    @Test
    public void audit2(){
        leaveFormService.audit(32l,2l,"refused","工期紧张,请勿拖延");
    }

    /**
     * 部门经理请假,总经理审批通过
     */
    @Test
    public void audit3(){
        leaveFormService.audit(33l,1l,"approved","同意");
    }

完整实现请假审批

controller.LeaveFormServlet.java
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // http://localhost/leave/create
        String uri = request.getRequestURI();
        String methodName = uri.substring(uri.lastIndexOf("/") + 1); //截取本身就包含斜杠
        if (methodName.equals("create")){
            this.create(request,response);
        }else if (methodName.equals("list")){
            this.getLeaveFormList(request,response);
        } else if (methodName.equals("audit")) {
            this.audit(request,response);
        }
    }
...
...
...
  /**
     * 处理审批操作
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    private void audit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String formId = request.getParameter("formId");
        String result = request.getParameter("result");
        String reason = request.getParameter("reason");
        User user = (User) request.getSession().getAttribute("login_user");
        Map mpResult = new HashMap();
        try {
            leaveFormService.audit(Long.parseLong(formId), user.getEmployeeId(), result,reason);
            mpResult.put("code", "0");
            mpResult.put("message", "success");

        } catch (Exception e) {
            logger.error("请假单审核失败", e);
            mpResult.put("code", e.getClass().getSimpleName());
            mpResult.put("message", e.getMessage());
        }
        String json = JSON.toJSONString(mpResult);
        response.getWriter().println(json);
    }

实现系统消息业务逻辑

entity.Notice.java
    private Long noticeId;
    private Long receiverId;
    private String content;
    private Date createTime;
    public Notice(){

    }
    public Notice(Long receiverId, String content){
        this.receiverId = receiverId;
        this.content = content;
        this.createTime = new Date();
    }
    Getter + Setter
service.LeaveFormService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.EmployeeDao;
import com.imooc.oa.dao.LeaveFormDao;
import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.dao.ProcessFlowDao;
import com.imooc.oa.entity.Employee;
import com.imooc.oa.entity.LeaveForm;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.ProcessFlow;
import com.imooc.oa.service.exception.BussinessException;
import com.imooc.oa.util.MybatisUtils;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 请假单流程服务
 */
public class LeaveFormService {
    /**
     * 创建请假单
     * @param form 前端输入的请假单数据
     * @return 持久化后的请假单对象
     */
    public LeaveForm createLeaveForm(LeaveForm form){
            LeaveForm savedForm = (LeaveForm) MybatisUtils.executeQuery(sqlSession -> {
            //1.持久化form表单数据,8级以下员工表单状态位processing,8级(总经理)状态位approved
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());
            if (employee.getLevel() == 8){
                form.setState("approved");
            }else {
                form.setState("processing");
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            leaveFormDao.insert(form);
            //2.增加第一条流程数据,说明表单已提交,状态位complete 初始化数据↓
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            ProcessFlow flow1 = new ProcessFlow();
            flow1.setFormId(form.getFormId());
            flow1.setOperatorId(employee.getEmployeeId());
            flow1.setAction("apply");
            flow1.setCreateTime(new Date());
            flow1.setOrderNo(1);
            flow1.setState("complete");
            flow1.setIsLast(0);
            processFlowDao.insert(flow1);
            //3.分情况创建其余流程数据 employee.xml(动态sql语句)
                // 3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时(后期单独处理),还需生成总经理审批任务
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
                NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            if (employee.getLevel() < 7){
                //动态sql EmployeeDao
                Employee dmanager = employeeDao.selectLeader(employee);
                ProcessFlow flow2 = new ProcessFlow();
                flow2.setFormId(form.getFormId());
                flow2.setOperatorId(dmanager.getEmployeeId());
                flow2.setAction("audit");//审批任务
                flow2.setCreateTime(new Date());
                flow2.setOrderNo(2);
                flow2.setState("process");
                long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); //毫秒数
                float hours = diff/(1000*60*60) * 1f;
                if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS){
                    flow2.setIsLast(0); //最后节点
                    processFlowDao.insert(flow2);
                    Employee manager = employeeDao.selectLeader(dmanager);//总经理
                    ProcessFlow flow3 = new ProcessFlow();
                    flow3.setFormId(form.getFormId());
                    flow3.setOperatorId(manager.getEmployeeId());
                    flow3.setAction("audit");
                    flow3.setCreateTime(new Date());
                    flow3.setState("ready");
                    flow3.setOrderNo(3);
                    flow3.setIsLast(1);
                    processFlowDao.insert(flow3);
                }else {//小于3天{
                    flow2.setIsLast(1);
                    processFlowDao.insert(flow2);
                }
                //请假单已提交消息
                String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
                        , sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
                //通知部门经理审批消息
                noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
                        employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(dmanager.getEmployeeId(),noticeContent));
            } else if (employee.getLevel() == 7) {//部门经理
                //3.2 7级员工,生成总经理审批任务
                Employee manager = employeeDao.selectLeader(employee);
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(manager.getEmployeeId());
                flow.setAction("audit");
                flow.setCreateTime(new Date());
                flow.setState("process");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
                //请假单已提交消息
                String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级审批."
                        , sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
                //通知总经理审批消息
                noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
                        employee.getTitle() , employee.getName() ,sdf.format(form.getStartTime()),sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(manager.getEmployeeId(),noticeContent));
            }else if (employee.getLevel() == 8){
                //3.3 8级员工,生成总经理审批任务,系统自动通过
                ProcessFlow flow = new ProcessFlow();
                flow.setFormId(form.getFormId());
                flow.setOperatorId(employee.getEmployeeId());
                flow.setAction("audit");
                flow.setResult("自动通过");
                flow.setCreateTime(new Date());
                flow.setAuditTime(new Date());
                flow.setState("complete");
                flow.setOrderNo(2);
                flow.setIsLast(1);
                processFlowDao.insert(flow);
                String noticeContent = String.format("您的请假申请[%s-%s]系统已自动批准通过." ,
                        sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()));
                noticeDao.insert(new Notice(employee.getEmployeeId(),noticeContent));
            }
            return form;
        });
        return savedForm;
    }
    /**
     * 获取指定任务状态及指定经办人对应的请假单列表
     * @param pfState ProcessFlow任务状态
     * @param operatorId 经办人编号
     * @return 请假单及相关数据列表
     */
    public List<Map> getLeaveFormList(String pfState, Long operatorId){
        return (List<Map>)MybatisUtils.executeQuery(sqlSession -> {
            LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
            List<Map> formList = dao.selectByParams(pfState, operatorId);
            return formList;
        });
    }
    /**
     * 审核请假单
     * @param formId 表单编号
     * @param operatorId 经办人(当前登录员工)
     * @param result 审批结果
     * @param reason 审批意见
     */
    public void audit(Long formId, Long operatorId, String result, String reason){
        MybatisUtils.executeQuery(sqlSession -> {
            //1.无论同意/驳回, 当前任务状态变更为complete
            ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
            List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
            if (flowList.size() == 0){
                throw new BussinessException("PF001", "无效的审批流程");//自定义错误抛出
            }
            //获取当前任务ProcessFlow对象
            List<ProcessFlow> processList = flowList.stream().filter(p->p.getOperatorId() == operatorId && p.getState().equals("process")).collect(Collectors.toList());
            ProcessFlow process = null;
            if (processList.size() == 0){
                throw new BussinessException("PF002", "未找到待处理任务");
            }else{
                process = processList.get(0);
                process.setState("complete");
                process.setResult(result);
                process.setReason(reason);
                process.setAuditTime(new Date());
                processFlowDao.update(process);
            }
            LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
            LeaveForm form = leaveFormDao.selectById(formId);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
            EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
            Employee employee = employeeDao.selectById(form.getEmployeeId());//表单提交人信息
            Employee operator = employeeDao.selectById(operatorId);//任务经办人信息
            NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            //2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态对应的approved/refused
            if (process.getIsLast() == 1){
                form.setState(result); // approved\refused
                leaveFormDao.update(form);
                String strResult = null;
                if (result.equals("aproved")){
                    strResult = "批准";
                } else if (result.equals("refused")) {
                    strResult = "驳回";
                }
                String noticeContent = String.format("您的请假申请[%s-%s]%s%s已%s,审批意见:%s,审批流程已结束"
                        sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                        operator.getTitle(),operator.getName(), //批准\驳回
                        strResult,reason);//发给表单提交人的通知
                noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent));
            }else {
                //readyList包含所有后续任务节点
                List<ProcessFlow> readyList = flowList.stream().filter(p->p.getState().equals("ready")).collect(Collectors.toList());
                //3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
                if (result.equals("approved")){
                    ProcessFlow readyProcess = readyList.get(0);
                    readyProcess.setState("process");
                    processFlowDao.update(readyProcess);
                    //消息1: 通知表单提交人,部门经理已经审批通过,交由上级继续审批
                    String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已批准,审批意见:%s ,请继续等待上级审批" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));

                    //消息2: 通知总经理有新的审批任务
                    String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()));
                    noticeDao.insert(new Notice(readyProcess.getOperatorId(),noticeContent2));

                    //消息3: 通知部门经理(当前经办人),员工的申请单你已批准,交由上级继续审批
                    String noticeContent3 = String.format("%s-%s提起请假申请[%s-%s]您已批准,审批意见:%s,申请转至上级领导继续审批" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent3));
                } else if(result.equals("refused")) {
                    //4.如果当前任务不是最后一个节点且审批驳回,则后续所有任务状态变为cancel,请假单状态变为refused
                    for(ProcessFlow p:readyList){
                        p.setState("cancel");
                        processFlowDao.update(p);
                    }
                    form.setState("refused");
                    leaveFormDao.update(form);
                    //消息1: 通知申请人表单已被驳回
                    String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已驳回,审批意见:%s,审批流程已结束" ,
                            sdf.format(form.getStartTime()) , sdf.format(form.getEndTime()),
                            operator.getTitle() , operator.getName(),reason);
                    noticeDao.insert(new Notice(form.getEmployeeId(),noticeContent1));

                    //消息2: 通知经办人表单"您已驳回"
                    String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s]您已驳回,审批意见:%s,审批流程已结束" ,
                            employee.getTitle() , employee.getName() , sdf.format( form.getStartTime()) , sdf.format(form.getEndTime()), reason);
                    noticeDao.insert(new Notice(operator.getEmployeeId(),noticeContent2));
                }
            }
            return null;
        });

完整实现系统消息功能

dao.NoticeDao.java
package com.imooc.oa.dao;

import com.imooc.oa.entity.Notice;

import java.util.List;

public interface NoticeDao {
    public void insert(Notice notice);
    public List<Notice> selectByReceiverId(Long receiverId);
}
service.NoticeService.java
package com.imooc.oa.service;

import com.imooc.oa.dao.NoticeDao;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.util.MybatisUtils;

import java.util.List;

/**
 * 消息服务
 */
public class NoticeService {
    /**
     * 查询指定员工的系统消息
     * @param receiverId
     * @return 最近100条消息列表
     */
    public List<Notice> getNoticeList(Long receiverId){
        return (List) MybatisUtils.executeQuery(sqlSession -> {
            NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
            return noticeDao.selectByReceiverId(receiverId);
        });
    }
}
controller.NoticeServlet.java
package com.imooc.oa.controller;

import com.alibaba.fastjson.JSON;
import com.imooc.oa.entity.Notice;
import com.imooc.oa.entity.User;
import com.imooc.oa.service.NoticeService;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@WebServlet(name = "NoticeServlet" , urlPatterns = "/notice/list")
public class NoticeServlet extends HttpServlet {
    private NoticeService noticeService = new NoticeService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        User user = (User)request.getSession().getAttribute("login_user");
        List<Notice> noticeList = noticeService.getNoticeList(user.getEmployeeId());
        Map result = new HashMap<>();
        result.put("code", "0");
        result.put("msg", "");
        result.put("count", noticeList.size());
        result.put("data", noticeList);
        String json = JSON.toJSONString(result);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().println(json);
    }
}
resources.mappers.notice.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.oa.dao.NoticeDao">
    <insert id="insert" parameterType="com.imooc.oa.entity.Notice"
            useGeneratedKeys="true" keyProperty="noticeId" keyColumn="notice_id">
        INSERT INTO sys_notice( receiver_id, content, create_time) VALUES (#{receiverId}, #{content}, #{createTime})
    </insert>

    <select id="selectByReceiverId" parameterType="Long" resultType="com.imooc.oa.entity.Notice">
        select * from sys_notice where receiver_id = #{value} order by create_time desc limit 0,100
    </select>
</mapper>
index.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>办公OA系统</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>

<body class="layui-layout-body">
<!-- Layui后台布局CSS -->
<div class="layui-layout layui-layout-admin">
    <!--头部导航栏-->
    <div class="layui-header">
        <!--系统标题-->
        <div class="layui-logo" style="font-size:18px">慕课网办公OA系统</div>
        <!--右侧当前用户信息-->
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:void(0)">
                    <!--图标-->
                    <span class="layui-icon layui-icon-user" style="font-size: 20px">
                    </span>
                    <!--用户信息-->
                    ${current_employee.name}[${current_department.departmentName}-${current_employee.title}]
                </a>
            </li>
            <!--注销按钮-->
            <li class="layui-nav-item"><a href="/logout">注销</a></li>
        </ul>
    </div>
    <!--左侧菜单栏-->
    <div class="layui-side layui-bg-black">
        <!--可滚动菜单-->
        <div class="layui-side-scroll">
            <!--可折叠导航栏-->
            <ul class="layui-nav layui-nav-tree">
                <#list node_list as node>
                <!--父节点-->
                    <#if node.nodeType == 1>
                <li class="layui-nav-item layui-nav-itemed">
                    <a href="javascript:void(0)">${node.nodeName}</a>
                    <dl class="layui-nav-child module" data-node-id="${node.nodeId}"></dl>
                </li>
                    </#if>
                    <#if node.nodeType == 2>
                <!--子节点-->
                <dd class="function" data-parent-id="${node.parentId}">
                    <a href="${node.url}" target="ifmMain">${node.nodeName}</a>
                </dd>
                    </#if>
                </#list>
            </ul>
        </div>
    </div>
    <!--主体部分采用iframe嵌入其他页面-->
    <div class="layui-body" style="overflow-y: hidden">
        <iframe name="ifmMain" src="/forward/notice" style="border: 0px;width: 100%;height: 100%"></iframe>
    </div>
    <!--版权信息-->
    <div class="layui-footer">
        Copyright © imooc. All Rights Reserved.
    </div>
</div>
<!--LayUI JS文件-->
<script src="/resources/layui/layui.all.js"></script>
<script>
    //将所有功能根据parent_id移动到指定模块下 一开始先dl与dd对齐[程序维护方便]
    layui.$(".function").each(function () {
        var func = layui.$(this);
        var parentId = func.data("parent-id");
        layui.$("dl[data-node-id=" + parentId + "]").append(func);
    })
    //刷新折叠菜单
    layui.element.render('nav');
</script>
</body>
</html>
notice.ftl
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>系统通知</title>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">
</head>
<body>
<div class="layui-row">
    <blockquote class="layui-elem-quote">
        <h2>系统通知</h2>
    </blockquote>
    <table id="grdNoticeList" lay-filter="grdNoticeList"></table>
</div>

<script src="/resources/layui/layui.all.js"></script>

<script>
    layui.table.render({
        elem : "#grdNoticeList" ,
        id : "grdNoticeList" ,
        url : "/notice/list" ,
        page : false ,
        cols :[[
            {field : "" , title : "序号" , width:"10%" , style : "height:60px" , type:"numbers"},
            {field : "create_time" , title : "通知时间" , width : "20%" , templet: function (d) {
                    var newDate = new Date(d.createTime);
                    return newDate.getFullYear() + "-" +
                        (newDate.getMonth() + 1) + "-" + newDate.getDate()
                        + " " + newDate.getHours() + ":" + newDate.getMinutes() + ":" + newDate.getSeconds();
                }},
            {field : "content" , title : "通知内容" , width : "60%"}
        ]]
    })

</script>
</body>
</html>
阅读全文

MyBatis基础与进阶

2023/10/30

MyBatis[ORM]框架基础

软件开发中框架

  • 框架式可被应用开发者定制的应用骨架
  • 框架是一种规则,保证开发者遵循相同的方式开发程序
  • 框架提倡”不要重复造轮子”,对基础功能进行封装

框架的优点

  • 极大提高了开发效率
  • 统一的编码规则,利于团队管理
  • 灵活配置的应用,拥有更好的维护性

SSM开发框架

  • Spring对象容器框架(框架的框架) [提供底层对象的管理]
  • SpringMVC替代servlet更有效的开发 [提供Web界面的交互]
  • MyBatis简化数据库的开发 [数据库增删改查便捷操作]

MyBatis

  • MyBatis是优秀的持久层框架(将内存中的数据保存到数据库中 以防止重启后数据丢失)
  • MyBatis使用XML将SQL与程序解耦,便于维护[改代码 改xml更方便]
  • MyBatis是JDBC的延伸

MyBatis开发流程【推荐使用Maven】

  • 引入MyBatis依赖
  • 创建核心配置文件
  • 创建实体(Entity)类
  • 创建Mapper映射文件
  • 初始化SessionFactory(绘画工厂 读取配置文件 加载Mapper映射)
  • 利用SqlSession对象操作数据

单元测试与JUnit4

单元测试(用于测试方法的方法)
  • 单元测试是指对软件中的最小可测试单元进行检查和验证
  • 测试用例是指写一段代码对已有功能(方法)进行校验
  • JUnit4是Java中最著名的单元测试工具,主流IDE内置支持

JUnit4使用方法

  • 引入JUnit Jar包或增加Maven依赖
  • 编写测试用例验证目标方法是否正确运行
  • 在测试用例上增加**@Test**注解开始单元测试

如果插件plug报错 点settings 找到 Build,Execution,Deployment → Build Tools → Maven 修改下面的三个地址到Maven

Maven home path: D:/apache-maven-3.9.5
User settings files: D:\apache-maven-3.9.5\conf\settings.xml
Local repository: D:\apache-maven-3.9.5\repository

Maven工程有专门测试用例的 test包
方法命名:在原方法前增加test前缀 Class命名:在原有命名后增加Test

快速生成类的测试用例 => 选中类 → Code → Generate → Test
按住Ctrl可以多选执行多个测试用例类
如果想运行所有测试用例类 工程上点右键 → Run ‘All Test’

Calculator.java
package org.example;

public class Calculator {
    public int add(int a, int b){
        return a + b;
    }
    public int substract(int a, int b){
        return a - b;
    }
    public int multiply(int a, int b){
        return a * b;
    }
    public float divide(int a, int b){
        if (b==0){
            throw new ArithmeticException("除数不能为0");
        }
        return (a*1f) / b;
    }
}
CalculatorTest.java
import org.example.Calculator;
import org.junit.Test;

public class CalculatorTest {
    private Calculator cal = new Calculator();
    @Test
    public void testAdd(){
        int result = cal.add(1,2);
        System.out.println(result);
    }
    @Test
    public void testSubstract(){
        int result = cal.substract(1,2);
        System.out.println(result);
    }
    @Test
    public void testMultiply(){
        int result = cal.multiply(1,2);
        System.out.println(result);
    }
    @Test
    public void testDivide(){
        float result = cal.divide(1,2);
        System.out.println(result);
    }
    @Test
    public void testDivide1(){
        float result = cal.divide(1,0);
        System.out.println(result);
    }
}

MyBatis环境配置

mybatis-config.xml
  • MyBatis采用XML格式配置数据库环境信息
  • MyBatis环境配置标签**< environment >**
  • environment包含数据库驱动、URL、用户名与密码
mybatis-config.xml
<!--配置环境,不同的环境不同的id名字-->
<enviroment id="dev">
    <!--采用JDBC方式对数据库事务进行commit/rollback-->
    <transactionManager type="JDBC"></transactionManager>
    <!--采用连接池方式管理数据库连接-->
    <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>
</enviroment>
<groupId>org.example</groupId>
    <artifactId>JUnit4_Maven</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>http://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
    </dependencies>

可以在右侧加载内置Database数据库

<configuration>
    <environments default=""> 
        <environment id="">
            <transactionManager type="JDBC"></transactionManager> 
            <dataSource type=""></dataSource>
        </environment>
    </environments>
</configuration>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="dev"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type=""></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTFF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

SqlSessionFactory

  • SqlSessionFactory是MyBatis的核心对象
  • 用于初始化MyBatis, 创建SqlSession对象
  • SqlSession对象提供了数据表CRUD对应方法
  • 保证SqlSessionFactory在应用中全局唯一
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="prd"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
test-MyBatisTestor.java
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 org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
}
保证SqlSessionFactory在应用中全局唯一 [创建一个工具类]

static块用于初始化静态对象

MyBatisUtils.java
package com.imooc.mybatis.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.Reader;
//MaBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    static{ //利用静态块在初始化类时实例化sqlSessionFactory
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = null;//按照字符流读取
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    public static void closeSession(SqlSession session){
        if (session != null){
            session.close();
        }
    }
}
MyBatisTestor.java
import com.imooc.mybatis.utils.MyBatisUtils;
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 org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
    @Test
    public void testMyBatisUtils() throws Exception{
        SqlSession sqlSession = null;
        try{
            sqlSession = MyBatisUtils.openSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }
}

MyBatis数据查询

用测试类去测试代码
test-java-MyBatisTestor.java
import com.imooc.mybatis.MyBatisUtils;
import com.imooc.mybatis.entity.Goods;
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 org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.util.List;

//JUNIT单元测试类
public class MyBatisTestor {
    @Test
    public void testSqlSessionFactory() throws IOException {
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");//按照字符流读取
        //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        System.out.println("SessionFactory加载成功");
        SqlSession sqlSession = null;
        try{
            //创建SqlSession对象,SqlSession是JDBC的扩展类,用于数据库交互
            sqlSession = sqlSessionFactory.openSession();
            //创建数据库连接(测试用)
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (sqlSession != null){
                //如果type="POOLED",代表使用连接池,close则是将连接回收到连接池中
                //如果type="UNPOOLED",代表直连,close则会调用Connection.close()方法关闭连接
                sqlSession.close();
            }
        }
    }
    /**
     * MyBatisUtils使用指南
     * @throws Exception
     */
    @Test
    public void testMyBatisUtils() throws Exception {
        SqlSession sqlSession = null;
        try {
            sqlSession = MyBatisUtils.openSession();
            Connection connection = sqlSession.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(sqlSession);
        }
    }

    /**
     * select查询语句执行
     * @throws Exception
     */
    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Goods> list = session.selectList("goods.selectAll");
            for(Goods g : list){
                System.out.println(g.getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
}
MyBatis数据查询步骤
  • 创建实体类(Entity) /工具类(Utils)
Goods.java
private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    Getter+Setter...
MyBatisUtils.java
package com.imooc.mybatis;

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.Reader;
//MaBatisUtils工具类,创建全局唯一的SqlSessionFactory对象
public class MyBatisUtils {
    //利用static(静态)属于类不属于对象,且全局唯一
    private static SqlSessionFactory sqlSessionFactory = null;
    static{ //利用静态块在初始化类时实例化sqlSessionFactory
        //利用Reader加载classpath下的mybatis-config.xml核心配置文件
        Reader reader = null;//按照字符流读取
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
            //初始化SqlSessionFactory对象,同时解析mybatis-config.xml文件
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);//read解析上面的对象
        } catch (IOException e) {
            e.printStackTrace();
            //初始化错误时,通过抛出异常ExceptionInInitializerError通知调用者
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * openSession 创建一个新的SqlSession对象
     * @return SqlSession对象
     */
    public static SqlSession openSession(){
        return sqlSessionFactory.openSession();
    }

    /**
     * 释放一个有效的SqlSession对象
     * @param session 准备释放SqlSession对象
     */
    public static void closeSession(SqlSession session){
        if(session != null){
            session.close();
        }
    }
}
  • 创建Mapper XML (在resources下创建) 表属性和字段一一对应

若要让mybatis认识xml 要在mybatis-config.xml里面声明 增加< mappers >

goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
</mapper>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <environments default="prd"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>
</configuration>
  • 编写< select >SQL标签
goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
</mapper>
  • 开启驼峰命名映射
<settings><!--开启驼峰命名转换 goods_id → goodsId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
  • 新增< mapper >
mybatis-config.xml
<mappers>
        <mapper resource="mappers/goods.xml"/>
    </mappers>
  • SqlSession执行select语句

SQL传参[动态传入数据]

goods.xml
<select id="selectById" resultType="com.imooc.mybatis.entity.Goods">
    select * from t_goods where goods_id = #{value}
</select>

MyBatisTestor.java
@Test
    public void testSelectById() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1602);
            System.out.println(goods.getTitle());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
goods.xml
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
            select * from t_goods
            where
                current_price between #{min} and #{max}
            order by current_price
            limit 0,#{limit}
    </select>
        
MyBatisTestor.java (多参数传递要指定Map接口)
@Test
    public void selectByPriceRange() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("min", 100);
            param.put("max", 500);
            param.put("limit", 10);
            List<Goods> list = session.selectList("goods.selectByPriceRange",param);
            for (Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

获取多表关联查询结果[Map]

LinkedHashMap链表形式的HashMap不会出现乱序

利用LinkedHashMap保存多表关联结果
MyBatis会将每一条记录包装为LinkedHashMap对象
key是字段名,value是字段对应的值,字段类型根据表结构进行自动判断
优点:易于扩展,易于使用
缺点:太过灵活,无法进行编译时检查

goods.xml
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>
</mapper>
MyBatisTestor.java [map类 或 实体类]
@Test
    public void selectGoodsMap() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<Map> list = session.selectList("goods.selectGoodsMap");
            for (Map map : list){
                System.out.println(map);
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
}

ResultMap结果映射[多表查询结果 多人协作首选]

把复杂的查询结果映射成DTO对象来进行保存 调用的时候轻松获得属性 [书写大量的映射规则]
  • ResultMap可以将查询结果映射为复杂类型的Java对象
  • ResultMap适用于Java对象保存多表关联结果
  • ResultMap支持对象关联查询等高级特性

结果用java对象进行保存 dto包[GoodsDTO]是数据传输对象包[扩展包] 为了扩展的需要可以扩展

结果映射规则进行赋值 转换为哪个dto
查询出来后每次都会給goods中的goodsId进行赋值
用GoodsDTO.java 来承载多表关联查询的结果

GoodsDTO.java
public class GoodsDTO {
    private Goods goods = new Goods();
    private String categoryName;
    private String test;
    Getter+Setter
}

Goods.java
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
}
MyBatisTestor.java
@Test
    public void selectGoodsDTO() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list){
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
Goods.xml
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="categoryName" column="category_name"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

GoodsDTO.java
public class GoodsDTO {
    private Goods goods = new Goods();
    private Category category = new Category();
    private String test;
    Getter+Setter...
}

Category.java
public class Category { //标准的Java Bean 换DTO的  private Category category = new Category();
    private Integer categoryId;
    private String categoryName;
    private Integer parentId;
    private Integer categoryLevel;
    private Integer categoryOrder;
    Getter+Setter...
}
Goods.xml
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"/>
        <result property="category.categoryName" column="category_name"/>
        <result property="category.parentId" column="parent_id"/>
        <result property="category.categoryLevel" column="category_level"/>
        <result property="category.categoryOrder" column="category_order"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>
MyBatisTestor.java
@Test
    public void selectGoodsDTO() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
            for (GoodsDTO g : list){
                System.out.println(g.getGoods().getTitle());
            }
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

MyBatis数据插入操作

数据库事务
  • 数据库事务是保证数据操作完整性的基础

客户端[Java] → 事务日志[增删改查数据] → 向MySQL写入commit → 数据表作为更新数据 【若执行roll back后事务日志和数据表的数据都会被清除】

MyBatis写操作包含三种
  • 插入< insert >
goods.xml 新增操作
 <!--flushCache="true"在sql执行后强制清空缓存-->
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods"flushCache="true">
 INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
 VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
  <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER"> <!--主键自动生成-->
    select last_insert_id()
  </selectKey>
</insert>
MyBatisTestor.java
@Test
    public void selectInsert() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = session.insert("goods.insert", goods);
            session.commit();
            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if (session != null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }
  • 更新 < update>
    不推荐goods.set…来插入,而是推荐使用获取到原始的商品信息Goods goods = session.selectOne("goods.selectById", 739);再在原始信息上做出调整和更新。对数据影响最小
goods.xml
 <update id="update" parameterType="com.imooc.mybatis.entity.Goods">
        UPDATE t_goods
        SET
          title = #{title},
          sub_title = #{subTitle},
          original_cost = #{originalCost},
          current_price = #{currentPrice},
          discount = #{discount},
          is_free_delivery = #{isFreeDelivery},
          category_id = #{categoryId}
        WHERE
          good_id = #{goodsId} <!--对主键进行筛选-->
    </update>
MyBatisTestor.java
 @Test
    public void testUpdate() throws Exception{
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 739);//得到指定编号的对象
            goods.setTitle("更新测试商品");
            int num = session.update("goods.update", goods);
            session.commit();
        }catch (Exception e){
            if (session!=null)
                session.rollback();
        }
    }
  • 删除 < delete >
    大多数删除操作都是根据主键来运行的
good.xml
<delete id="delete" parameterType="integer">
    delete from t_goods where goods_id = #{value}
</delete>
MyBatisTestor.java
 @Test
    public void Delete(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            int num = session.delete("goods.delete", 739);
            session.commit();
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

SelectKeyUserGeneratedKeys的区别

SelectKey属性用法
<insert id="insert" parameterType="com.itlaoqi.mybatis.entity.Goods">
  INSERT INTO SQL语句
============================获取主键值===============================(↑案例)
 <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
   select last_insert_id()
 </selectKey>
</insert>

★ selectKey标签需要明确编写获取最新主键的SQL语句  获取主键 ★
★ selectKey适用于所有的关系型数据库 ★
★ selectKey标签是通用方案,适用于所有数据库,但编写麻烦 ★
UserGeneratedKeys属性用法
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
   userGeneratedKeys="true"
   keyProperty="goodsId" <!--主键值-->
   keyColumn="goods_id"> <!--字段名-->
  INSERT INTO SQL语句
</insert>
★ useGeneratedKeys属性会自动根据驱动生成对应SQL语句 自动获取主键 ★
★ useGeneratedKeys只支持"自增主键"类型的数据库 ★
★ useGeneratedKeys属性只支持"自增主键"数据库,使用简单
在Oracle中selectKey的用法
<insert id="insert" parameterType="com.itlaoqi.mybatis.entity.Goods">
  InSERT INTO SQL语句
  <selectKey resultType="Integer" order=“BEFORE” keyProperty="goodsId">
      SELECT seq_goods.nextval as id from dual  
  </selectKey>
</insert>

SQL注入攻击[缺少转义操作]

  • SQL注入是指攻击者利用SQL漏洞,绕过系统约束,越权获取数据的攻击方式
SQLd代码:
"select * from a where name ='" + name + " '";

正常情况下:
name:张三 → select * from a where name = '张三';

SQL注入攻击:
name:' or 1=1 or 1='
select * from a whrer name = " or 1=1 or 1="
MyBatis两种传值方式
  • ${}文本替换(原文传值)(产生的sql语句绝不能是外界输入的), 未经过任何处理对SQL文本替换 {根据前台输入的条件不同来选择不同的字段排序 没有对输入的数据进行预编译处理 导致输入的内容变成了sql的一部分}

    ${order}
    param.put("order"," order by title desc");

    用${}原传值:select * from t_goods where title = ‘ ‘ or 1 =1 or title = ‘【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版’”); 成为sql的一部分

  • #{}预编译传值, 使用预编译传值可以预防SQL注入

    用#{}预编译:select * from t_goods where title = “ ‘ ‘ or 1 =1 or title = ‘【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版’”); #输入的会变成字符串

goods.xml
<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
  select * from t_goods where title = ${title}
</select>
MyBatisTestor.java
 @Test
    public void testSelectByTitle(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            pparam.put("title","'【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            param.put("order", " order by title desc");
//param.put("titile","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
//用${}原传值:select * from t_goods where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"); 成为sql的一部分
//用#{}预编译:select * from t_goods where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            List<Goods> list = session.selectList("goods.selectByTitle", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

使用上传照片

MyBatis进阶教程

MyBatis日志管理

什么是日志
  • 日志文件是用于记录系统操作事件的记录文件或文件集合
  • 日志保存历史数据, 是诊断问题以及理解系列活动的重要依据
ch.qos.logback logback-classic 1.2.3

上传照片

goods.xml
<select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where title = #{title}
            ${order}
    </select>
MyBatisTestor.java
@Test
    public void testSelectByTitle(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Map param = new HashMap();
            param.put("title","'【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            param.put("order", " order by title desc");
//param.put("titile","'' or 1=1 or title='【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
//用${}原传值:select * from t_goods where title = '' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'"); 成为sql的一部分
//用#{}预编译:select * from t_goods where title = "'' or 1 =1 or title = '【德国】爱他美婴幼儿配方奶粉1段800g*2罐 铂金版'");
            List<Goods> list = session.selectList("goods.selectByTitle", param);
            for(Goods g:list){
                System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
            }
        }catch (Exception e){
            if (session!=null){
                session.rollback();
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
   }
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
<!--            23:39:16.761 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.-->
        </encoder>
    </appender>
    <!--
        日志输出级别(优先级高到低):
        error: 错误 - 系统的故障日志
        warn: 警告 - 存在风险或使用不当的日志
        info: 一般性消息
        debug: 程序内部用于调试信息
        trace: 程序运行的跟踪信息
     -->
<!--生产环境最低级别设置info以上 开发环境最低级别设置debug以上方便调试-->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

MyBatis动态SQL

动态SQL
  • 动态SQL是指根据参数数据动态组织SQL的技术
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com...Goods">
    select * from t_goods
    where     <!--动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
</select>
goods.xml
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>    <!--根据查询条件动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
        </where>
    </select>
<where></where>  where标签会动态的对子sql进行判断

MyBatis二级缓存 (可共享对象)

sql语句第一次查询 sql存储在硬盘上
优化:第一次查询的时候放在某个内存 再次访问就很快 (缓存)

  • 一级缓存默认开启,缓存范围SqlSession会话
  • 二级缓存手动开启,属于范围Mapper Namespace

二级缓存运行规则

  • 二级开启后默认所有查询操作均使用缓存
  • 写操作commit提交时对该namespace缓存强制清空
  • 配置useCache=false可以不用缓存
  • 配置flushCache=true代表强制清空缓存
@Test
public void testLv1Cache(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            session.commit();//commit提交时对该namespace缓存强制清空
            Goods goods1 = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode() + ":" + goods1.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

Goods goods = session.selectOne(“goods.selectById”, 1603);
session.commit();//commit提交时对该namespace缓存强制清空
缓存和语句距离太短 资源浪费 使用率不高哦

<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>

运行结果只出现一条sql语句 结果的goods.hasCode的结果来自同一块内存区域
MyBatisTestor.java
 @Test
    public void testLv2Cache(){
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }

        try{
            session = MyBatisUtils.openSession();
            Goods goods = session.selectOne("goods.selectById", 1603);
            System.out.println(goods.hashCode());
        }catch (Exception e){
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

08:59:50.304 [main] DEBUG goods - Cache Hit Ratio [goods]: 0.5
缓存命中率越高 证明优化越好
goods.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods"> <!--区分不同的工作空间-->
    <!--开启了二级缓存
        eviction是缓存的清除策略,当缓存对象数量达到上限后,自动触发对应算法对缓存对象清除
        1. LRU - 最近最久未使用:移除最长随时间不被使用的对象{893}
           LFU - 最近时间内方位最少的移除{1}
        【LRU + LFU 增强缓存速度】
        01 02 03 04 .. 0512
        14 99 83 1        893
        2. FIFO - 先进先出:按对象进入缓存的顺序来移除它们
        3. SOFT - 软引用:移除基于垃圾收集器状态和软引用规则的对象
        4. WEAK - 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象
        flushInterval 代表间隔多长时间自动清空缓存,单位毫秒,600000毫秒=10分钟
        size 缓存存储上限,用于保存对象或集合(1个结婚算1个对象)的数据上限
        readOnly 设置为true,代表返回只读缓存,每次从缓存取出的是缓存对象本身,这种执行效率较高
                 设置为false,代表每次取出的是缓存对象的"副本",每一次取出的对象都是不同的,这种安全性较高
    -->
    <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
    <!--useCache="false"代表查询结果不放入缓存-->
    <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
        select * from t_goods order by goods_id desc limit 10<!--将每一条记录包装成↑的goods对象-->
    </select>
    <select id="selectById" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where goods_id = #{value}
    </select>

    <select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
            select * from t_goods
            where
                current_price between #{min} and #{max}
            order by current_price
            limit 0,#{limit}
    </select>
<!--flushCache="true"在sql执行后强制清空缓存 效果和commit相同--> 
    <select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
        select g.* , c.category_name from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

    <resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
        <id property="goods.goodsId" column="goods_id"></id>
        <result property="goods.title" column="title"></result>
        <result property="goods.originalCost" column="original_cost"></result>
        <result property="goods.currentPrice" column="current_price"></result>
        <result property="goods.discount" column="discount"></result>
        <result property="goods.isFreeDelivery" column="is_free_delivery"></result>
        <result property="goods.categoryId" column="category_id"></result>
        <result property="category.categoryId" column="category_id"/>
        <result property="category.categoryName" column="category_name"/>
        <result property="category.parentId" column="parent_id"/>
        <result property="category.categoryLevel" column="category_level"/>
        <result property="category.categoryOrder" column="category_order"/>
        <result property="test" column="test"/>
    </resultMap>
    <select id="selectGoodsDTO" resultMap="rmGoods">
        select g.* , c.*,'1' from t_goods g , t_category c
        where g.category_id = c.category_id
    </select>

    <!--flushCache="true"在sql执行后强制清空缓存 效果和commit相同--> 
    <insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">
        INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
        VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
        <selectKey resultType="Integer" keyProperty="goodsId" order="AFTER"> <!--主键自动生成-->
        select last_insert_id()
        </selectKey>
    </insert>

    <update id="update" parameterType="com.imooc.mybatis.entity.Goods">
        UPDATE t_goods
        SET
          title = #{title},
          sub_title = #{subTitle},
          original_cost = #{originalCost},
          current_price = #{currentPrice},
          discount = #{discount},
          is_free_delivery = #{isFreeDelivery},
          category_id = #{categoryId}
        WHERE
          good_id = #{goodsId} <!--对主键进行筛选-->
    </update>

    <delete id="delete" parameterType="integer">
        delete from t_goods where goods_id = #{value}
    </delete>

    <select id="selectByTitle" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where title = #{title}
            ${order}
    </select>

    <select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods
        <where>    <!--根据查询条件动态增加SQL的数据-->
        <if test="categoryId != null">
            and category_id=#{categoryId}
        </if>
        <if test="currentPrice != null">
            and current_price &lt; #{currentPrice}
        </if>
        </where>
    </select>
</mapper>
一级缓存被默认开启的随着sql开 随着sql关

复习<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/> 当中的各种

<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">

MyBatis多表级联查询 (通过一个对象获得另外一个对象)

一个比较有多个学生 而 一个学生在同一时间只能隶属于一个班级

实体关系分析

商品和详情对象关联查询 [商品是1 详情是多 详情那方要持有商品的主键]

ManyToOne对象关联查询 [collection]

mybatis-config.xml 最底下加一层
<mappers>
   <mapper resource="mappers/goods.xml"/>
   <mapper resource="mappers/goods_detail.xml"/>
</mappers>


good.xml
 <!--
        resultMap可以用于说明一对多或者多对一的映射逻辑
        id 是 resultMap属性引用标志
        type 指向One的实体(Goods)
    -->
    <resultMap id="rmGoods1" type="com.imooc.mybatis.entity.Goods">
        <!-- 映射goods对象的主键到goods_id字段 -->
        <id column="goods_id" property="goodsId"></id>
        <!--
            collection的含义是,在
            select * from t_goods limit 0,1 得到结果后,对所有Goods对象遍历得到goods_id字段值,
            并代入到goodsDetail命名空间的findByGoodsId的SQL中执行查询,
            将得到的"商品详情"集合赋值给goodsDetails List对象.
        -->
        <collection property="goodsDetails" select="goodsDetail.selectByGoodsId"
                    column="goods_id"/>
    </resultMap>
    <select id="selectOneToMany" resultMap="rmGoods1">
        select * from t_goods limit 0,10
    </select>


goods_details.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer"
            resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>
</mapper>
Goods.java 再加一个
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    private List<GoodsDetail> goodsDetails;
    Getter+Setter
}
MaBatisTestor.java 
/**
     * 测试多对一对象关联映射
     */
    @Test
    public void testOneToMany() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            List<Goods> list = session.selectList("goods.selectOneToMany");
            for(Goods goods:list) {
                System.out.println(goods.getTitle() + ":" + goods.getGoodsDetails().size());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

ManyToOne对象关联查询 [association]

商品和详情对象关联查询
goods_detail.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goodsDetail">
    <select id="selectByGoodsId" parameterType="Integer"
            resultType="com.imooc.mybatis.entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>

    <resultMap id="rmGoodsDetail" type="com.imooc.mybatis.entity.GoodsDetail">
        <id column="gd_id" property="gdId"/>
        <!--字段映射-->
        <result column="goods_id" property="goodsId"/>
        <!--从多的一方关联单的一方     在goods.xml中的goods空间 根据查询结果id带入到这个语句赋值到goods语句-->
        <association property="goods" select="goods.selectById" column="goods_id"></association>
    </resultMap>
    <select id="selectManyToOne" resultMap="rmGoodsDetail">
        select * from t_goods_detail limit 0,20
    </select>
</mapper>
MyBatisTestor.java
 @Test
    public void testManyToOne() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            List<GoodsDetail> list = session.selectList("goodsDetail.selectManyToOne");
            for(GoodsDetail gd:list) {
                System.out.println(gd.getGdPicUrl() + ":" + gd.getGoods().getTitle());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }
GoodsDetail.java
public class GoodsDetail {
    private Integer gdId;
    private Integer goodsId;
    private String gdPicUrl;
    private Integer gdOrder;
    private Goods goods;
    Getter + Setter
}

分页查询的麻烦事

  • 当前页数据查询 - select * from tab limit 0,10
  • 总记录数查询 - select count(*) from tab
  • 程序计算总页数、上一页页码、下一页页码
分页插件PageHelper

PageHelper使用流程

  • maven引入PageHelper与jsqlparser
  • mybatis-config.xml增加Plugin配置
  • 代码中使用PageHelper.startPage()自动分页
pom.xml
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId> <!--最核心处理的sql语句-->
    <version>2.0</version>
</dependency>


goods.xml
<select id="selectPage" resultType="com.imooc.mybatis.entity.Goods">
        select * from t_goods where current_price &lt; 1000
</select>
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--环境配置标签-->
    <settings><!--开启驼峰命名转换 goods_id → goodsId-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--启动Pagehelper分页插件-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--设置数据库类型-->
            <property name="helperDialect" value="mysql"/>
            <!--分页合理化-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
    <environments default="dev"> <!--当环境默认数据源为dev时候使用运行的代码[不同id来切换]-->
        <environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="prd">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.155:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml"/>
        <mapper resource="mappers/goods_detail.xml"/>
    </mappers>
</configuration>
MyBatisTestor.java
@Test
    public void testSelectPage() throws Exception {
        SqlSession session = null;
        try {
            session = MyBatisUtils.openSession();
            /*startPage方法自动将下一次查询进行分页*/
            PageHelper.startPage(2,10);
            Page<Goods> page = (Page)session.selectList("goods.selectPage");
            System.out.println("总页数:" + page.getPages());
            System.out.println("总记录数:" + page.getTotal());
            System.out.println("开始行号:" + page.getStartRow());
            System.out.println("结束行号:" + page.getEndRow());
            System.out.println("当前页码:" + page.getPageNum());
            List<Goods> data = page.getResult();//当前页数据
            for (Goods g : data) {
                System.out.println(g.getTitle());
            }
            System.out.println("");
        } catch (Exception e) {
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

不同数据库分页的实现原理 [面试/笔试]

MySQL分页 select * from table limit 10,20; 起始行号 向后取多少值
Oracle分页(三层嵌套)

Oracle分页(三层嵌套)
select t3.* from(
    select t2.*,rownum as row_num from(
        select * from table order by id asc
    ) t2 where rownum <= 20
)t3
where t2.row_num>11

SQL Server 2000

select top 3 * from table
where
  id not in
  (select top 15 id from table)

SQL Server 2012+

select * from table order by id 
   offset 4 rows fetch next 5 rows only

MyBatis配置C3P0连接池

pom.xml
<dependency>
     <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.4</version>
</dependency>
mybatis-config.xml
<environment id="dev">  <!--唯一标识-->
            <!--采用JDBC方式对数据库事务进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
<!--            <dataSource type="POOLED">-->
            <dataSource type="com.imooc.mybatis.datasource.C3P0DataSourceFactory">
                <property name="driverClass" value="com.mysql.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="user" value="root"/>
                <property name="password" value="root"/>
                <property name="initialPoolSize" value="5"/>
                <property name="maxPoolSize" value="20"/>
                <property name="minPoolSize" value="5"/>
                <!--...-->
            </dataSource>
        </environment>
C3P0DataSourceFactory.java
package com.imooc.mybatis.datasource;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

/**
 * C3P0与MyBatis兼容使用的数据源工厂类
 */
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory{
    public C3P0DataSourceFactory(){
        this.dataSource = new ComboPooledDataSource();
    }
}

MyBatis批处理 [利用集合保存批处理数据] [海量数据导入]

goods.xml
<!--    INSERT INTO table-->
<!--    VALUES("a","a1","a2"),("b","b1","b2"),(...)-->
<insert id="batchInsert" parameterType="java.util.List">
     INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
     VALUES
    <foreach collection="list" item="item" index="index" separator=",">
      (#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
    </foreach>
<!--批量插入数据的局限:1.无法获得插入数据的id  2.批量生成的SQL太长,可能会被服务器拒绝(可以分段2次for循环)-->
</insert>
MyBatisTestor.java
/**
     * 批量插入测试
     * @throws Exception
     */
    @Test
    public void testBatchInsert() throws Exception {
        SqlSession session = null;
        try {
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            for (int i = 0; i < 10000; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                //insert()方法返回值代表本次成功插入的记录总数

                list.add(goods);
            }
            session.insert("goods.batchInsert", list);
            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et - st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        } catch (Exception e) {
            if (session != null) {
                session.rollback();//回滚事务
            }
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

====================================================================
 /**
     * 10000次数据插入对比测试用例
     * @throws Exception
     */
    @Test
    public void testInsert1() throws Exception {
        SqlSession session = null;
        try{
            long st = new Date().getTime();
            session = MyBatisUtils.openSession();
            List list = new ArrayList();
            for(int i = 0 ; i < 10000 ; i++) {
                Goods goods = new Goods();
                goods.setTitle("测试商品");
                goods.setSubTitle("测试子标题");
                goods.setOriginalCost(200f);
                goods.setCurrentPrice(100f);
                goods.setDiscount(0.5f);
                goods.setIsFreeDelivery(1);
                goods.setCategoryId(43);
                //insert()方法返回值代表本次成功插入的记录总数
                //每循环一次 插入一次
                session.insert("goods.insert" , goods);
            }

            session.commit();//提交事务数据
            long et = new Date().getTime();
            System.out.println("执行时间:" + (et-st) + "毫秒");
//            System.out.println(goods.getGoodsId());
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

MyBatis常用注解 (注解适合小型敏捷项目 XML适合大型协作项目)

注解 对应XML 说明
@Insert < insert > 更新SQL
@Update < update > 更新SQL
@Delete < delete > 删除SQL
@Select < select > 查询SQL
@Param 参数映射
@Results < resultMap > 结果映射
@Result < id > < result > 字段映射
把XML中的写入代码中
dao.GoodsDAO.java
package com.imooc.mybatis.dao;

import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface GoodsDAO {
    @Select("select * from t_goods where current_price between #{min} and #{max} order by current_price limit 0,#{limit}")
    public List<Goods> selectByPriceRange(@Param("min") Float min, @Param("max") Float max, @Param("limit") Integer limit);

    @Insert("INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id) VALUES (#{title} , #{subTitle} , #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})")
    //<selectKey> befor是true则在这之前执行 keyProperty主键 resultType返回主键类型
    @SelectKey(statement = "select last_insert_id()" , before = false , keyProperty = "goodsId" , resultType = Integer.class)
    public int insert(Goods goods);

    @Select({"select * from t_goods"})
    //<resultMap>
    @Results({ //select实行完以下结果 按照以下规则并赋值給GoodsDTO对象
            //<id>
            @Result(column = "goods_id", property = "goodsId", id = true),
            //<result>
            @Result(column = "title", property = "title"),
            @Result(column = "current_price", property = "currentPrice"),
    })
    public List<GoodsDTO> selectAll();

}
dto.GoodsDTO
package com.imooc.mybatis.dto;

public class GoodsDTO {
    private Integer goodsId;//商品编号
    private String title;//标题
    private Float currentPrice;//当前价格

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }
}
entity.Goods.java
public class Goods {
    private Integer goodsId;//商品编号
    private String title;//标题
    private String subTitle;//子标题
    private Float originalCost;//原始价格
    private Float currentPrice;//当前价格
    private Float discount;//折扣率
    private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮
    private Integer categoryId;//分类编号
    Getter + Setter
}
resources.logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

    <!--
        日志输出级别(优先级高到低):
        error: 错误 - 系统的故障日志
        warn: 警告 - 存在风险或使用不当的日志
        info: 一般性消息
        debug: 程序内部用于调试信息
        trace: 程序运行的跟踪信息
     -->
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>
resources.mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- goods_id ==> goodsId 驼峰命名转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--设置默认指向的数据库-->
    <environments default="dev">
        <!--配置环境,不同的环境不同的id名字-->
        <environment id="dev">
            <!-- 采用JDBC方式对数据库事务进行commit/rollback -->
            <transactionManager type="JDBC"></transactionManager>
            <!--采用连接池方式管理数据库连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
<!--        <mapper class="com.imooc.mybatis.dao.GoodsDAO"/>-->
        <package name="com.imooc.mybatis.dao"/>
    </mappers>
</configuration>
test.java.com.imooc.mybatis.MyBatisTestor
package com.imooc.mybatis;

import com.imooc.mybatis.dao.GoodsDAO;
import com.imooc.mybatis.dto.GoodsDTO;
import com.imooc.mybatis.entity.Goods;
import com.imooc.mybatis.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

//JUNIT单元测试类
public class MyBatisTestor {

    @Test
    public void testSelectByPriceRange() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class); //包含注解的接口
            List<Goods> list = goodsDAO.selectByPriceRange(100f, 500f, 20);
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }

    /**
     * 新增数据
     * @throws Exception
     */
    @Test
    public void testInsert() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            Goods goods = new Goods();
            goods.setTitle("测试商品");
            goods.setSubTitle("测试子标题");
            goods.setOriginalCost(200f);
            goods.setCurrentPrice(100f);
            goods.setDiscount(0.5f);
            goods.setIsFreeDelivery(1);
            goods.setCategoryId(43);
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            //insert()方法返回值代表本次成功插入的记录总数
            int num = goodsDAO.insert(goods);
            session.commit();//提交事务数据
            System.out.println(goods.getGoodsId()); //最新的数据回填給goodsId
        }catch (Exception e){
            if(session != null){
                session.rollback();//回滚事务
            }
            throw e;
        }finally {
            MyBatisUtils.closeSession(session);
        }
    }

    @Test
    public void testSelectAll() throws Exception {
        SqlSession session = null;
        try{
            session = MyBatisUtils.openSession();
            GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
            List<GoodsDTO> list = goodsDAO.selectAll();
            System.out.println(list.size());
        }catch (Exception e){
            throw e;
        } finally {
            MyBatisUtils.closeSession(session);
        }
    }
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.imooc</groupId>
    <artifactId>mybatis-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
</project>
阅读全文

Maven,工厂模式,反射模式,Lambda表达式,stream流式处理

2023/10/26

Maven构建工具

Maven介绍
  • Maven是项目管理工具,对软件项目提供构建与依赖管理
  • Maven是Apache下的Java开源项目
  • Maven为Java项目提供了统一的管理方式,已经成为业界标准
Maven核心特性
  • 项目设置遵循统一的规则,保证不同开发环境的兼容性
  • 强大的依赖管理,项目依赖组件自动下载、自动更新
  • 可扩展的插件机制,使用简单,功能丰富
Maven的坐标
  • GroupId:机构或者团体的英文,采用”逆向域名”形式书写
  • ArtifactId:项目名称,说明其用途,例如:cms、oa…
  • Version:版本号,一般采用”版本+单词”形式,例如:1.0.0.RELEASE

Maven项目标准结构

目录 用途
${basedir} 根目录,用于保存pom.xml
${basedir}/src/main/java Java源代码目录
${basedir}/src/main/resources 资源目录,保存配置文件、静态图片等
${basedir}/src/test/java 测试类的源代码
${basedir}/src/test/resources 测试时需要使用的资源文件
${basedir}/target 项目输出的目录,用于存储jar、war文件
${basedir}/target/class 字节码(.class)的编译输出目录
${basedir}/pom.xml 项目(Project)对象(Object)模型(Model)文件

Maven依赖管理

  • Maven利用dependency(依赖) 自动下载、管理第三方Jar
  • 在pom.xml文件中配置项目依赖的第三方组件
  • maven自动将依赖从远程仓库下载至本地仓库,并在工程中引用
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

Maven Central Repository Search可以搜索maven

pom.xml
<dependencies>
    <dependency>
          <groupId>com.belerweb</groupId>
          <artifactId>pinyin4j</artifactId>
          <version>2.5.1</version>
    </dependency>
</dependencies>
PinyinTestor.java
import net.sourceforge.pinyin4j.PinyinHelper;

import java.util.Scanner;

public class PinyinTestor {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        String[] pingyin = PinyinHelper.toHanyuPinyinStringArray(str.charAt(0)); //将输入的第一个数据变成字符串数组
        for (String py : pingyin){
            System.out.println(py);
        }
    }
}

本地仓库与中央仓库

maven 在项目启动的时候会对 pom.xml 进行加载 之后会在本地仓库 .m2\repository 去查找依赖文件(jar包) 如果查不到或不存在 就会去中央仓库下载(repo.maven.apache.org)到本地仓库

项目打包

  • Maven可将Java项目打包为jar、war
  • Maven项目打包是通过Plugins(插件)技术实现
  • Maven输出jar包插件:maven-assembly-plugin
//阿里云镜像下载地址
<repositories>
  <repository>
      <!-- 创建私服的地址 -->
    <id>aliyun</id>
    <name>aliyun</name>
    <url>https://maven.aliyun.com/repository/public</url>
  </repository>
</repositories>

Maven构建Web工程

创建Maven-Project

Group Id: maven-first
Artifacr Id: maven
Version: 1.0.0-RELEASE
Packaging: jar

IntelliJ IDEA创建maven web项目(IDEA新手适用)_idea maven创建web项目-CSDN博客
IDEA2022版本创建maven web项目(两种方式)最全图文教学_idea创建maven项目没有webapp-CSDN博客
IDEA中创建Maven Web项目的两种方法_idea maven创建web项目-CSDN博客
Project Structure → Modules → +增加Web

web application exploded:这个是以文件夹形式发布项目,发布项目时就会自动生成文件夹在指定的output directory;
web application archive:就是war包形式,将项目打成一个war包在指定位置

Tomcat报404问题解决方案大全(包括tomcat可以正常运行但是报404)_tomcat 404_ISAS的博客-CSDN博客
Windows下如何查看某个端口被谁占用 | 菜鸟教程 (runoob.com)
(.iml的问题)IDEA中用Maven创建web项目部署运行时页面报错404解决方法_maven web项目404-CSDN博客

Web应用打包

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Web_one</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar\war</packaging>
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
            </plugin>
        </plugins>
    </build>

</project>

打包成war\jar后可以通过tomcat中的临时启动服务器来运行

Maven常用命令

命令 用途
mvn archetype:generate 创建Maven工程结构
mvn compile 编译源代码 .class
mvn test 执行测试用例
mvn clean 清除产生的项目
mvn package 项目打包
mvn install 安装至本地仓库

修改本地仓库地址

工厂模式 [运用于真实项目]

设计模式
  • 设计模式是前辈总结的设计经验
  • 设计模式的目标是代码更容易理解,更容易维护
  • 通过设计模式可以让代码更加可靠
设计模式的分类
  • 创建型模式 [帮助我们如何更精巧的创建对象]
  • 结构型模式 [在软件结构上通过重构\抽象 让软件结构变得更有条理]
  • 行为型模式 [现实中的场景对软件的设计和优化]
工厂模式
  • 工厂模式用于隐藏创建对象的细节
  • 工厂模式核心:工厂类(Factory)
  • 工厂模式可以细分为简单工厂、工厂方法与抽象方法

简单工厂图

项目应用-i18n国际化 (软件分工更明确 软件耦合降低)

不同国家显示页面的语言不同
抽象一个接口!!

反射

反射Reflect
  • 反射式在运行时动态访问类与对象的技术 [写死的代码不灵活]
  • 反射是JDK1.2版本后的高级特性,隶属于java.lang.reflect
  • 大多数Java框架都是基于反射实现参数配置、动态注入等特性
初始反射技术
未运用反射技术
 public static void case1(){
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入计算类名:");
        String op = scanner.next();
        System.out.print("请输入a:");
        int a = scanner.nextInt();
        System.out.print("请输入b:");
        int b = scanner.nextInt();
        MathOperation mathOperation = null;
        if(op.equals("Addition")){
            mathOperation = new Addition();
        }else if(op.equals("Subtraction")) {
            mathOperation = new Subtraction();
        }else if(op.equals("Multiplication")){
            mathOperation = new Multiplication();
        }else{
            System.out.println("无效的计算类");
            return;
        }
        float result = mathOperation.operate(a, b);
        System.out.println(result);
    }

======================================================
运用反射技术
 public static void case2(){
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入计算类名:");
        String op = scanner.next(); ****
        System.out.print("请输入a:");
        int a = scanner.nextInt();
        System.out.print("请输入b:");
        int b = scanner.nextInt();
        MathOperation mathOperation = null;
        try {
            mathOperation = (MathOperation) Class.forName("com.imooc.reflect." + op).newInstance(); //class.forName加载指定的类 实例化对象 运行时动态决定op创建哪些对象 访问哪些属性****
        }catch(Exception e){
            System.out.println("无效的计算类");
            return;
        }
        float result = mathOperation.operate(a, b);
        System.out.println(result); 
    }

反射的核心类

Class类

Class是JVM中代表”类和接口”的类
Class对象具体包含了某个特定类的结构信息
通过Class对象可获取对应类的构造方法/方法/成员变量

Class核心方法
方法 用途
Class.forName() [传入完整类名包括包] 静态方法,用于获取指定Class对象
classObj.newInstance() 通过默认构造方法创建新的对象
classObj.getConstructor() 获得指定的public修饰构造方法Constructor对象
classObj.getMethod() 获取指定的public修饰方法Method对象
classObj.getField() 获取指定的public修饰成员变量Field对象
Employee.java
package com.imooc.reflect.entity;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }
}
ClassSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

public class ClassSample {
    public static void main(String[] args) {
        try {
            //Class.forName()方法将指定的类加载到jvm,并返回对应Class对象
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            System.out.println("Employee已被加载到jvm");
            //newInstance通过默认构造方法创建新的对象
            Employee emp = (Employee)employeeClass.newInstance();
            System.out.println(emp);
        } catch (ClassNotFoundException e) {
            //类名与类路径书写错误时抛出"类无法找到"异常
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            //对象无法被实例化,抛出"实例化异常"
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            //非法访问异常 在程序外或作用域外访问对象或成员变量时抛出
            throw new RuntimeException(e);
        }
    }
Constructor构造方法类
  • Constructor类是对Java类中的构造方法的抽象
  • Contructor对象包括了具体类的某个具体构造方法的声明
  • 通过Constructor对象调用带参构造方法创建对象
方法 用途
classObj.getConstructor() 获取指定public修饰的构造方法对象
constructorObj.newInstance() 通过对应的构造方法创建对象
Employee.java
package com.imooc.reflect.entity;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }
}
ConstructorSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConstructorSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{ //得到对应的class对象
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            System.out.println(employee);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
            //类名与类路径书写错误时抛出"类无法找到"异常
        } catch (NoSuchMethodException e) {
            //没有找到与之对应格式的写法
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            //当被调用的方法的内部抛出了异常而没有被捕获时
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            //对象无法被实例化,抛出"实例化异常"
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            //非法访问异常 在程序外或作用域外访问对象或成员变量时抛出
            throw new RuntimeException(e);
        }
    }
}
Method类
  • Method对象指代某个类中的方法的描述
  • Method对象使用classObj.getMethod()方法获取
  • 通过Method对象调用指定对象的对应方法

Method核心方法

方法 用途
classObj.getMethod() 获取指定public修饰的方法对象
methodObj.invoke() 调用指定对象的对应方法
Employee.java
package com.imooc.reflect.entity;

import com.sun.org.apache.bcel.internal.generic.RETURN;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }

    public Employee updateSalary(Float val){
        this.salary = this.salary + val;
        System.out.println(this.ename + "调薪至" + this.salary + "元");
        return this;

    }
}
MethodSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Method updateSalaryMethod = employeeClass.getMethod("updateSalary", new Class[]{ //传入参数
               Float.class
            });
            Employee employee1 = (Employee)updateSalaryMethod.invoke(employee, new Object[]{1000f}); //若有返回值 要强制转换
            System.out.println(employee1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
Field成员变量类
  • Field对应某个具体类中的成员变量的声明
  • Field对象使用**classObj.getField()**方法获取
  • 通过Field对象可为某对象成员变量赋值/取值

Field类核心方法

方法 用途
classObj.getField() 获取指定publicc修饰的成员变量对象
fieldObj.set() 为某对象指定成员变量赋值
fieldObj.get() 获取某对象指定成员变量数值

快速添加包裹try catch → 框上要包裹的 点Code → surround with
get()/set()都是(在entify中)public共有方法

Employee.java
package com.imooc.reflect.entity;

import com.sun.org.apache.bcel.internal.generic.RETURN;

public class Employee {
    static {//静态块初始化
        System.out.println("Employee类已被加载到jvm,并已初始化");
    }
    private Integer eno;
    public String ename;
    private Float salary;
    private String dname;

    public Employee() {
        System.out.println("Employee默认构造方法已被执行");
    }

    public Employee(Integer eno, String ename, Float salary, String dname) {
        this.eno = eno;
        this.ename = ename;
        this.salary = salary;
        this.dname = dname;
        System.out.println("Employee带参构造方法已被执行");
    }

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eno=" + eno +
                ", ename='" + ename + '\'' +
                ", salary=" + salary +
                ", dname='" + dname + '\'' +
                '}';
    }

    public Employee updateSalary(Float val){
        this.salary = this.salary + val;
        System.out.println(this.ename + "调薪至" + this.salary + "元");
        return this;

    }
}
FieldSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class FieldSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Field enameField = employeeClass.getField("ename");
            enameField.set(employee,"李雷");
            String ename = (String) enameField.get(employee);
            System.out.println("ename:" + ename);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}

getDeclared系列方法

  • getDeclaredConstructor(s) | Method(s) | Field(s) 获取对应对象
  • getConstructor(s) | Method(s) | Field(s) 只能获取public对象
  • 访问非作用域内构造方法、方法、成员变量,会抛出异常

public可以直接获取 private只能通过get…获取

Employee.java + getDeclaredSample.java
package com.imooc.reflect;

import com.imooc.reflect.entity.Employee;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class getDeclaredSample {
    public static void main(String[] args) {
        try {
            Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
            Constructor constructor = employeeClass.getConstructor(new Class[]{
                    Integer.class, String.class, Float.class, String.class
            });
            Employee employee = (Employee) constructor.newInstance(new Object[]{
                    100, "李磊", 3000f, "研发部"
            });
            Field[] fields = employeeClass.getDeclaredFields();
            for (Field field : fields){
//                System.out.println(field.getName());
                if (field.getModifiers() == 1) {//成员变量修饰符 public修饰
                    Object val = field.get(employee);
                    System.out.println(field.getName() + ":" + val);
                } else if (field.getModifiers() == 2) { //private修饰
                    String methodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//(0,1)是截取字符串 第一个大写字母
                    Method getMethod = employeeClass.getMethod(methodName);
                    Object ret = getMethod.invoke(employee);
                    System.out.println(field.getName() + ":" + ret);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

反射在项目中的应用

反射最重要的就是可以在运行时

反射的应用网站

对原始程序无需任何调整,只需要把对应的接口进行实现,放到线上服务器,再调整配置文件。

Zhcn.java
package com.imooc.i18n;

public class Zhcn implements I18N{
    @Override
    public String say() {
        return "生命不息奋斗不止";
    }
}
=========================================================
En.java
package com.imooc.i18n;

public class En implements I18N{
    @Override
    public String say() {
        return "Case to the struggle and cease to the life";
    }
}
========================================================
接口I18N.java
package com.imooc.i18n;

public interface I18N {
    public String say();
}
package com.imooc.i18n;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Properties;

public class Application {
    public static void say(){
        Properties properties = new Properties(); //加载指定的配置文件
        String configPath = Application.class.getResource("/config.properties").getPath();
        try {
            configPath = new URLDecoder().decode(configPath,"UTF-8");//路径中的空格默认得到url编码所以要转换一下
            properties.load(new FileInputStream(configPath)); //内容来源于文件 文件输入流
            String language = properties.getProperty("language");
            I18N i18n = (I18N)Class.forName(language).newInstance();
            System.out.println(i18n.say());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        Application.say();
    }
}
language=com.imooc.i18n.En
或
language=com.imooc.i18n.Zhcn

Lambda表达式

  • JDK8(1.8以上)开始支持Lambda表达式,用来让程序编写更优雅
  • 利用Lambda可以更简洁的实现匿名内部类函数声明与调用
  • 基于Lambda提供stream流式处理极大简化对集合的操作
传统代码
List<String> names = Arrays.asList("peter","anna","mike","xenia");
//实现集合排序
Collections.sort(names,new Comparator<String>(){
  @Override
  public int compare(String a,String b){
      return b.compareTo(a);
  }  
});
使用Lambda表达式
List<String> names = Arrays.asList("peter","anna","mike","xenia");
//通过lambda表达式简化匿名类的编写
Collections.sort(names,(a,b) -> b.compareTo(a));

Lambda表达式语法 [代码脚手架]

(参数列表) —>实现语句
[使用逗号分割参数,参数类型可省略,单参数括号可省略]
[单行直接写 多行用{}包括]

MathOperation.java
public interface MathOperation {
    //四则运算接口
    public Float operate(Integer a, Integer b);
}
LambdaSample.java
public class LambdaSample {
    public static void main(String[] args) {
        //标准Lambda使用方法
        //约束条件:Lambda表达式只能实现有且只有一个抽象方法的接口,Java称为"函数式接口"
        //1.标注使用方式
        MathOperation addition = (Integer a, Integer b) -> {
            System.out.println("加法运算");
            return a+b+0f; //定义的接口是Float
        };
        System.out.println(addition.operate(5, 5));

        //2.lambda允许忽略参数类型
        MathOperation substration = (a,b) -> {
            return a-b+0f;
        };
        System.out.println(substration.operate(5, 3));

        //3.单行实现代码可以省略大括号和return
        MathOperation multiplication = (a,b) -> a*b+0f;
        System.out.println(multiplication.operate(3, 5));
    }
}

函数式编程

  • 函数式编程是基于函数式接口并使用lambda表达的编程方式
  • 函数式编程理念是将代码作为可重用数据带入到程序运行中
  • 函数式编程强调”你想做什么“,而不是”你想怎么做

函数式接口

  • 函数式接口是有且只有一个抽象方法的接口
  • Java中拥有大量函数式接口,如java.lang.Runnable
  • JDK8后提供了一系列新的函数式接口,位于java.util.function

函数式接口Perdicate

  • Perdicate是新增的函数式接口,位于java.util.function
  • Perdicate用于测试传入的数据是否满足判断要求
  • Perdicate接口需要实现test()方法进行逻辑判断

lambda表达式来实现predicate的验证
将已有的代码变成可重复使用的资源放入程序中

PredicateSample.java
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/*
    理解函数式编程
    Perdicate函数式接口的使用方法
 */
public class PredicateSample {
    public static void main(String[] args) {
        Predicate <Integer> predicate = n->n>4; //隐藏着return
        boolean result = predicate.test(10);
        System.out.println(result);
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        filter(list,n->n%2==1); //传入函数式接口的实现lambda 取所有奇数
        filter(list,n->n%2==0); //取所有偶数
        filter(list,n->n>5 && n%2==0); //取所有大于5的偶数
    }
    public static void filter(List<Integer> list, Predicate<Integer> predicate){
        for (Integer num:list){
            if (predicate.test(num)){
                System.out.println(num + " ");
            }
        }
    }
}

各种函数接口

JDK8常用函数式接口
consumer函数接口
接口 用途
Consumer< T > 对应有一个输入参数无输出的功能代码
Function< T,R > 对应有一个输入参数且需要返回参数的功能代码
Predicate< T > 用于条件判断,固定返回布尔值
ConsumerSample.java
import java.util.function.Consumer;

/*
    Consumer接口的使用
 */
public class ConsumerSample {
    public static void main(String[] args) {
        output(s-> System.out.println("向控制台打印:" + s));
        //字符串作为网络数据包向某个网站发送
        output(s->{
            System.out.println("向XXX网络发送数据包:" + s);
        });
    }
    public static void output(Consumer<String> consumer){
        String text = "苦其心志,劳其筋骨";
        consumer.accept(text);
    }
}
Function接口
FunctionSample.java
import java.util.Random;
import java.util.function.Function;

/*
    利用Function函数式接口生成定长随机字符串[加密解密会用到]
 */
public class FunctionSample {
    public static void main(String[] args) {
        Function<Integer,String> randomStringFunction = l->{
            String chars = "abcdefghijklmnopqrstuvxwyz0123456789";
            StringBuffer stringBuffer = new StringBuffer();
            Random random = new Random();
            for (int i = 0; i < l; i++) {
                int position = random.nextInt(chars.length());
                stringBuffer.append(chars.charAt(position));//按指定位置将字符提取并追加
            }
            return stringBuffer.toString();
        };
        String randowmString = randomStringFunction.apply(16);//生成16位长的字符串
        System.out.println(randowmString);
    }
}
@functionalInterface注解
MathOperation.java
@FunctionalInterface //通知编译器这是函数式接口,进行抽象方法检查
public interface MathOperation {
    //四则运算接口

    public Float operate(Integer a, Integer b);
}

函数式编程与面向对象编程比较

面向对象编程 函数式编程
设计思路 面向对象 面向过程
开发侧重 侧重过程,重分析,重设计 侧重结果,快速实现
可读性 结构复杂,相对较差 更适合人眼阅读,可读性更好
代码量
并发问题 设计不当,会出现线程安全问题 不会出现线程安全问题
健壮性
使用场景 中大型项目,多人协作工程 小型应用,要求快速实现

Stream流式处理

  • Stream流式处理式建立在Lambda基础上的多数据处理技术
  • Stream对集合数据处理进行高度抽象,极大简化代码量
  • Stream可对集合进行迭代,去重,筛选,排序,聚合等一系列处理

Stream示例

//获取List集合中最大的偶数
Optional<Integer> op = Arrays.asList(1,2,3,4,5,6).stream()
.filter(x->x%2==0) //处理完得到一个只包含偶数的list流数据
.sorted((a,b)->b-a) //大的在前面 小的在后面
.findFirst(); //获取最大的数据
System.out.println(op.get());

Stream常用方法

接口 用途
forEach 循环遍历
map map方法用于映射每个元素到对应的结果
filter filter方法用于通知设置的条件过滤出元素
limit limit方法用于获取指定数量的流
sorted sorted方法用于对流进行排序
Collectors Collectors类实现将流转换成集合和聚合元素
StreamGenerator.java
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/*
Stream流对象的五种创建方式
 */
public class StreamGenerator {
    //1.基于数组进行创建
    @Test
    public void generator1(){
        String[] arr = {"Lily","Andy","Jackson","Smith"};
        Stream<String> stream = Stream.of(arr);
        stream.forEach(s -> System.out.println(s)); //forEach中使用Lambda表达式
    }

    //2.基于集合进行创建
    @Test
    public void generator2(){
        List<String> list = new ArrayList<>();
        list.add("Lily");
        list.add("Andy");
        list.add("Jackson");
        list.add("Smith");
        Stream<String> stream = list.stream(); //利用集合获取stream
        stream.forEach(s -> System.out.println(s));
    }

    //3.利用generate方法创建无限长度流
    @Test
    public void generator3(){
        Stream<Integer> stream = Stream.generate(() -> new Random().nextInt(100000));//Supplier<T> s 创建新对象
        stream.limit(10).forEach(i -> System.out.println(i)); //limit限制长度
    }

    //4.基于迭代器创建流
    @Test
    public void generator4(){
        Stream<Integer> stream = Stream.iterate(1, n -> n + 1);//无限长度自增
        stream.limit(100).forEach(i -> System.out.println(i));
    }

    //5.基于字符序列创建流
    @Test
    public void genetator5(){
        String str = "abcdefg我";
        IntStream stream = str.chars();
        stream.forEach(c -> System.out.println((char)c));
    }
}
StreamMethod.java
import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamMethod {
    @Test //提取集合中所有偶数并求和
    public void case1(){
        List<String> list = Arrays.asList("1", "2", "3", "4", "5");
        int sum = list.stream() //获取stream对象
                .mapToInt(s -> Integer.parseInt(s)) //对每个元素字符串转为整数
                .filter(n -> n%2==0) //filter对流数据进行过滤
                .sum();//求和
        System.out.println(sum);
    }

    @Test //所有名字首字母大写
    public void case2(){
        List<String> list = Arrays.asList("lily","smith","jackson");
        List newList = list.stream()
                .map(s -> s.substring(0, 1).toUpperCase() + s.substring(1)) //首字母大写转换
//                .forEach(s -> System.out.println(s));
                .collect(Collectors.toList()); //collect对流数据进行收集,生成新的List/Set(将重复数据自动清除)
        System.out.println(newList);
    }

    @Test //将所有奇数从大到小进行排序,且不允许出现重复
    public void case3(){
        List<Integer> list = Arrays.asList(1, 60, 38, 21, 51, 60, 51, 73);
        List newList = list.stream().distinct() //去除重复的流数据
                .filter(n -> n%2==1)
                .sorted((a,b) -> b-a) //从大到小的数据排列
                .collect(Collectors.toList());
        System.out.println(newList);
    }
}
阅读全文

java数据库开发(JDBC)

2023/10/23

IDEA窗口快捷键

快捷键 描述
Ctrl+Alt+S Settings面板
Ctrl+Shift+F/R 当前Project中全局查找/替换
Ctrl+Shif+N 文件查找面板
Alt+Insert 快速生成面板
Ctrl+Shift+A Find Action模糊查询快速定位

代码快捷键

快捷键 描述
Ctrl+←→ 上一个/下一个单词
Ctrl+Shift+Enter 自动完成
Alt+Enter 智能提示
Ctrl+Alt+L 格式化代码
Ctrl+(Shift)+/ 行注释/块注释
Ctrl+Alt+Shift+J 列操作
Shift+F6 重命名(当前选择变量修改)
Ctrl+W 选中单词

代码快速定位

快捷键 描述
Ctrl+(Shift)+E 最近访问(编辑)的文件列表
Ctrl+Shift+1~9 创建书签
Shift+F11 查看书签
Ctrl+1~9 快速切换书签
Alt+←→ 切换书签
Template使用

Live Templates可以添加常用快捷字母作为快捷代码

右方 + “custom”
Abbreviation:al”
Description:Create ArrayList”
Template text:List< String >list = new ArrayList();”
Template text:List< $VAR1$>$VAR2$ = new ArrayList();”
点击define设置全部

运行与打包

快捷键 描述
Shift+F9 调试
Shift+F10 运行
F8 单步运行
F9 恢复运行至下一个端点
Shift+Ctrl+F8 查看所有端点
Jar包核心配置文件设置(加载响应的Class)

将编译的类导入jar包

‘……’ compile output
Project Structure → Artifacts → Outpub Layout

设置jar包的入口类 → Create Manifest… → Main Class(Jar包加载相应目录)

生成jar包

上述操作完毕后 点BuildBuild Artifacts…

IDEA快速开发Web应用

New Project → Java Enterprise → SDK1.8 = Java EE7

改变Tomcat启动时自动弹出的地址

Run/Debug Configurations → Deployment → 下方的 Application context

Project Structure

Artifacts 中默认存在 javaweb: war exploded 代表用文件夹的方式与Tomcat联动
点Add → Web Application: Archive打包 右侧是待添加 [文件成功发布到jar包中 ‘javaweb’ compile output] 右侧的Web facet resources 是 jsp, html等静态资源双击放到左边[‘javaweb’module: ‘Web’ facet resources] 至此javaweb.war包就包含了所有文件。上述操作完毕后 点BuildBuild Artifacts… build!!之后若在实际运行的时候放在D:\apache-tomcat-8.5.93\webapps内 之后启动tomcat[D:\apache-tomcat-8.5.93\bin\startup.bat]就可以显示结果

JDBC(Java DataBase Connectivity)快速入门

JDBC作用,在java程序中与关系型数据库进行交互

JDBC优点
  • 统一的API,提供一致的开发过程
  • 易于学习,容易上手,代码结构稳定
  • 功能强大,执行效率高,可处理海量数据

开发流程

1.加载并注册JDBC驱动
2.创建数据库连接
3.创建Satement对象
4.遍历查询结果
5.关闭连接.释放资源

Class.forName的作用

  • Class.forName用于加载指定的JDBC驱动类
  • Class.forName本质是通知JDBC注册这个驱动类
  • 驱动由数据库厂商自行开发,俩厂家不同链接祖父串了,字符串也不同

创建数据库连接代码

String dbDriver = "com.mysql.cj.jdbc.Driver"; //JDBC驱动类
String dbURL = "jdbc:mysql://localhost:3306/imooc"; //连接字符串
String dbUsername =  "root";
String dbPassword = "123456";
//1.加载并初始化JDBC驱动
Class.forName(dbDriver);
//2.创建数据库连接
Connection connection = DriverManager.getConnection(dbURL,dbUsername,dbPassword);
StandardJDBCSample.java
package com.example.imoocjdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class StandardJDBCSample {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            //1.加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver"); //加载指定的类
            //2.创建数据库连接
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&&allowPublicKeyRetrieval=true",
                    "root", "root"
            ); //程序和数据库的网络通信桥梁
            //3.创建Satement对象 ResultSet结果集
            Statement stmt = conn.createStatement(); //一条或多条sql语句
            ResultSet rs = stmt.executeQuery("SELECT * FROM employee WHERE dname='研发部'");
            //4.遍历查询结果
            while (rs.next()) {
                Integer eno = rs.getInt(1); //把当前行指定未知的提取 eno
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (conn != null && conn.isClosed() == false) {
                    //5.关闭连接.释放资源
                    conn.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

JDBC驱动的秘密

DriverManager
  • DriverManager用于注册/管理JDBC驱动程序
  • DriverManager.getConnection(连接字符串,用户名,密码)
  • 返回值Connection对象,对应数据库的物理网络连接
Connection对象
  • Connection对象用于JDBC与数据库的网络通信对象
  • java.sql.Connection是一个接口,具体由驱动厂商实现
  • 所有数据库的操作都建立在Connection上

MySQL连接字符串

  • 格式: jdbc:mysql://[主机ip] [:端口]/数据库名?参数列表
  • 主机ip与端口是可选设置,默认值为127.0.0.1与3306
  • 参数列表采用url编码,格式:参数1=值1&参数2=值2
MySQL连接字符串常用参数
参数名 建议参数值 说明
useSSL true(生产) false(开发) 是否禁用ssl
useUnicode true 启用unicode编码传输数据
characterEncoding UTF-8 使用UTF-8编码传输数据
serverTimezone Asia/Shanghai 使用东8时区时间,UTC+8
allowPublicKeyRetrieval true 允许从客户端获取公钥加密传输
超级异常捕获

选中所需要的代码区域 → Code → Surround With → 6.try…catch

SQL注入攻击 [数据泄露]

当输入部门名称:**’ or 1=1 or 1=’**
只要在or左右两侧有一个成立就都成立的 输入的数据中并没有对单引号加以处理;

package com.imooc.jdbc.hrapp.command;

import java.sql.*;
import java.util.Scanner;

/**
 * 数据查询方法
 */
public class QueryCommand implements Command {
    @Override
    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
            //3. 创建Statement对象
            stmt = conn.createStatement();
            //结果集
            System.out.println("select * from employee where dname='" + pdname + "'");
            rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(stmt != null){
                    stmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

PreparedStatement[解决SQL攻击注入问题(特殊字符转义)]

SQL注入攻击
  • SQL注入攻击是指利用SQL漏洞越权获取数据的黑客行为
  • SQL注入攻击根源是未对原始SQL中的敏感字符做特殊处理
  • 解决方法:放弃Statement改用PreparedStatement处理SQL
PreparedStatement [变化的地方用问号替代]
  • PreparedStatement预编译Statement是Statement的子接口
  • PreparedStatement对SQL进行参数化, 预防SQL注入攻击
  • PreparedStatement比Statement执行效率更高
  • 要用参数化的sql语句(问号只能出现在值的地方 且不能二次计算)
    String sql = “select * from employee where dname=? and eno > ?“;
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1,pdname); //插入多句 自动寻导入上方sql语句
    pstmt.setInt(2,3500);
//利用PreparedStatement预防SQL注入风险
//当dname值为' or 1=1 or 1=' 时,查询不到任何结果
//SQL:select * from employee where dname = '\' or 1=1 or 1=\”
String sql = "select * from employee where dname=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,dname); //设置SQL参数,参数从1开始
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
    ...
}

JDBC实现写数据

封装DbUtils工具类 [重复代码封装工具类(封装打开和关闭连接方法)]
DbUtils.java
package common;

import kotlin.Result;

import java.sql.*;

public class DbUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        //1. 加载并注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 创建数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
        try {
            if(rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(stmt != null){
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(conn != null && !conn.isClosed() ) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

JDBC执行INSERT语句

String sql = "insert into employee(eno,ename) values(?,?)";
PreparedStatement pstmt = conn.PreparedStatement(sql);
pstmt.setInt(1,10);
pstmt.setString(2,"张三");
//executeUpdate方法返回记录数
int cnt = pstmt.executeUpdate(); //cnt=1
System.out.println("数据新增成功");

JDBC执行UPDATE语句

String sql = "update employee set salary = salary + 1000 where dname=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,"研发部");
//executeUpdate方法返回记录数
int cnt = pstmt.exexuteUpdate();
System.out.println(“研发部”+cnt+"名员工提薪1000元");

JDBC执行DELETE语句

String sql = "delete from employee where eno = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1,3395);
//executeUpdate方法返回记录数
int cnt = pstmt.executeUpdate();
System.out.println(cnt+"名员工数据已被删除");

JDBC事务管理方式

  • 事务是以一种可靠的、一致的方式,访问和操作数据库的程序单元
  • 说人话:要么把事情做完,要么什么都不做,不要做一半
  • 事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带
事务的提交操作

应用程序写操作給事务区等全部完成后事务区再commit提交給数据表一次性写入給mysql。提交成功后事务区中的数据被清空

事务的回滚操作

应用程序写操作給事务区,如果+100突然-100程序报错了,由jdbc会向事务区发起rollback回滚操作 清空事务区,最终数据表不会产生任何写操作[要么什么都不做,不要做一半]

JDBC两种事务模式

  • 自动提交事务模式

​ 自动提交模式是指每一次写操作SQL,自动提交事务

自动提交开启方法:
conn.setAutoCommit(true)

​ 自动事务是JDBC默认行为,此模式无法保证多数据一致性[A钱少了 B钱增加]

  • 手动提交事务模式

​ 手动提交模式是指显式调用commit()与rollback()方法管理事务

手动提交开启方法:
conn.setAutoCommit(false)

​ 手动提交事务可保证多数据一致性,但必须手动调用提交/回滚方法

实现批量增加员工

package test;

import common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class TransactionSample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 1000; i < 2000; i++) {
                if (i==1005){
//                    throw new RuntimeException("插入失败");
                }
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
                pstmt.executeUpdate();
            }
            conn.commit(); //提交数据
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}

基于实体类实现分页数据封装

日常开发中如果要对数据进行提取以后最常见的形式是将数据转换为对应的实体类再放到集合中进行保存,即使被关闭数据也不会丢失。

package common;

import common.entity.Employee;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 分页查询员工数据
 */
public class PaginationCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Employee> list = new ArrayList(); //将底下的信息封装到实体类中
        try {
            conn = DbUtils.getConnection(); //?前面第几行 10从这行开始向后取10条记录 limit是分页独有的方言
            String sql = "select * from employee limit ?,10";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1,(page-1)*10); //第二页(2-1)*10从第十条记录开始向后取10条
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt("eno"); //不能在原有字段1前再新增1 按照名字来获取
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                //JDBC获取日期使用java.sql.Date,其继承自java.util.Date
                //所以两者互相兼容
                Date hiredate = rs.getDate("hiredate");
                Employee emp = new Employee(); //每产生一条记录都要封装成Employee对象
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                emp.setHiredate(hiredate);//把每一条记录都封装成为了实体类 放入list
                list.add(emp);
            }
            System.out.println(list.size());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}

JDBC中Date日期对象的处理

package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/*
   新增员工数据
 */
public class InsertCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工姓名:");
        String ename = in.next();
        System.out.println("请输入员工薪资:");
        float salary = in.nextFloat();
        System.out.println("请输入隶属部门:");
        String dname = in.next();
        System.out.println("请输入入职日期:");
        String strHiredate = in.next();
        //String到java.sql.Date分为两步
        //1.前端传入的String字符串转为java.util.Date
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);//对原有字符串解析
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        //2.java.util.Date转为java.sql.Date
        long time = udHiredate.getTime();//获取从1970到现在的毫秒数
        java.sql.Date sdHiredate = new java.sql.Date(time); //sdHiredate成功表达了strHiredate所表达的时间
        Connection conn = null;
        PreparedStatement pstmt = null;
        //新增数据 获取数据库连接
        try {
            conn = DbUtils.getConnection(); //↓作为写操作的sql一定要是参数化的(pstmt)
            String sql = "insert into employee(eno,ename,salary,dname,hiredate) value(?,?,?,?,?)";
            pstmt = conn.prepareStatement(sql); //sql被解析
            pstmt.setInt(1,eno);
            pstmt.setString(2,ename);
            pstmt.setFloat(3,salary);
            pstmt.setString(4,dname);
            pstmt.setDate(5,sdHiredate); //目标java.sql.Date
            int cnt = pstmt.executeUpdate();//所有要改变数据表的都要使用executeUpdate
            System.out.println("cnt:" + cnt); //代表本次写入影响的记录数量
            System.out.println(ename + "员工入职手续已办理");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(null,pstmt,conn); //释放所有资源
        }
    }
}

JDBC批量处理

需要反复执行同时一次性要插入很多数据的操作使用批处理操作

BatchSample.java
package test;

import common.DbUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;

public class BatchSample {
    private static void tc1(){
        //标准未使用批处理
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 100000; i < 200000; i++) {
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
                pstmt.executeUpdate();
            }
            conn.commit(); //提交数据
            long endTime = new Date().getTime();
            System.out.println("tc1()执行时长: " + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }

    private static void tc2(){
        //使用批处理插入若干数据
        Connection conn = null;
        PreparedStatement pstmt = null;
        try { //JDBC默认使用自动提交模式
            long startTime = new Date().getTime();
            conn = DbUtils.getConnection();
            conn.setAutoCommit(false); //关闭自动提交
            String sql = "insert ignore into employee(eno,ename,salary,dname) values(?,?,?,?)";
            for (int i = 200000; i < 300000; i++) {
                pstmt = conn.prepareStatement(sql);
                pstmt.setInt(1,i);
                pstmt.setString(2,"员工" + i);
                pstmt.setFloat(3,4000f);
                pstmt.setString(4,"市场部");
//                pstmt.executeUpdate();
                pstmt.addBatch();//将参数加入批处理任务
            }
            pstmt.executeBatch();//执行批处理任务
            conn.commit(); //提交数据
            long endTime = new Date().getTime();
            System.out.println("tc2()执行时长: " + (endTime-startTime));
        } catch (Exception e) {
            e.printStackTrace();
            try {
                if(conn != null && !conn.isClosed()) {
                    conn.rollback(); //回滚数据
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
    public static void main(String[] args) {
        tc1(); //222279ms
        tc2(); //15827 ms
    }
}

综合数据库的增删改查

HumanResourceApplication.java
package test;

import common.*;

import java.util.Scanner;

public class HumanResourceApplication {
    public static void main(String[] args) {
        System.out.println("1-查询部门员工");
        System.out.println("2-办理员工入职");
        System.out.println("3-调整薪资");
        System.out.println("4-员工离职");
        System.out.println("5-分页查询员工数据");
        System.out.println("请选择功能:");
        Scanner in = new Scanner(System.in);
        Integer cmd = in.nextInt();
        Command command = null;
        switch (cmd){
            case 1://查询部门员工
                command = new PstmtQueryCommand();
                command.execute();
                break;
            case 2:
                command = new InsertCommand();
                command.execute();
                break;
            case 3:
                command = new UpdateCommand();
                command.execute();
                break;
            case 4:
                command = new DeleteCommand();
                command.execute();
                break;
            case 5:
                command = new PaginationCommand();
                command.execute();
                break;
        }
    }
}
插入员工
InsertCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

/*
   新增员工数据
 */
public class InsertCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号:");
        int eno = in.nextInt();
        System.out.println("请输入员工姓名:");
        String ename = in.next();
        System.out.println("请输入员工薪资:");
        float salary = in.nextFloat();
        System.out.println("请输入隶属部门:");
        String dname = in.next();
        System.out.println("请输入入职日期:");
        String strHiredate = in.next();
        //String到java.sql.Date分为两步
        //1.前端传入的String字符串转为java.util.Date
        java.util.Date udHiredate = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            udHiredate = sdf.parse(strHiredate);//对原有字符串解析
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        //2.java.util.Date转为java.sql.Date
        long time = udHiredate.getTime();//获取从1970到现在的毫秒数
        java.sql.Date sdHiredate = new java.sql.Date(time); //sdHiredate成功表达了strHiredate所表达的时间
        Connection conn = null;
        PreparedStatement pstmt = null;
        //新增数据 获取数据库连接
        try {
            conn = DbUtils.getConnection(); //↓作为写操作的sql一定要是参数化的(pstmt)
            String sql = "insert into employee(eno,ename,salary,dname,hiredate) value(?,?,?,?,?)";
            pstmt = conn.prepareStatement(sql); //sql被解析
            pstmt.setInt(1,eno);
            pstmt.setString(2,ename);
            pstmt.setFloat(3,salary);
            pstmt.setString(4,dname);
            pstmt.setDate(5,sdHiredate); //目标java.sql.Date
            int cnt = pstmt.executeUpdate();//所有要改变数据表的都要使用executeUpdate
            System.out.println("cnt:" + cnt); //代表本次写入影响的记录数量
            System.out.println(ename + "员工入职手续已办理");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DbUtils.closeConnection(null,pstmt,conn); //释放所有资源
        }
    }
}
更新员工数据
UpdateCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class UpdateCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();
        System.out.println("请输入员工新的薪资");
        float salary = in.nextFloat();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "update employee set salary=? where dname=?";
             pstmt = conn.prepareStatement(sql);
             pstmt.setFloat(1,salary);
             pstmt.setInt(2,eno);
             int cnt = pstmt.executeUpdate();
             if (cnt == 1){
                 System.out.println("员工薪资调整完毕");
             }else {
                 System.out.println("未找到" + eno + "编号员工数据");
             }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}
删除员工数据
DeleteCommand.java
package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;

public class DeleteCommand implements Command{
    @Override
    public void execute(){
        Scanner in = new Scanner(System.in);
        System.out.println("请输入员工编号");
        int eno = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            conn = DbUtils.getConnection();
            String sql = "delete from employee where eno = ?";
             pstmt = conn.prepareStatement(sql);
             pstmt.setFloat(1,eno);
             int cnt = pstmt.executeUpdate();
             if (cnt == 1){
                 System.out.println("员工离职手续已完成");
             }else {
                 System.out.println("未找到" + eno + "编号员工数据");
             }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(null,pstmt,conn);
        }
    }
}
查找员工数据
PstmtQueryCommand.java
package common;

import java.sql.*;
import java.util.Scanner;

/**
 * PreparedStatement对象使用方法
 */
public class PstmtQueryCommand implements Command {

    public void execute() {
        System.out.print("请输入部门名称:");
        Scanner in = new Scanner(System.in);
        String pdname = in.nextLine();
        Connection conn = null;
//        Statement stmt = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //1. 加载并注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2. 创建数据库连接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
            //3. 创建PreparedStatement对象
            String sql = "select * from employee where dname=? and eno > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1,pdname); //注意:参数索引从1
            pstmt.setInt(2,3500);
            //结果集
            rs = pstmt.executeQuery();
            //4. 遍历查询结果
            //rs.next()返回布尔值,代表是否存在下一条记录
            //如果有,返回true,同时结果集提取下一条记录
            //如果没有,返回false,循环就会停止
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5. 关闭连接,释放资源
            try {
                if(rs != null){
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(pstmt != null){
                    pstmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

            try {
                if(conn != null && !conn.isClosed() ) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
分页查询员工数据
PaginationCommand.java
package common;

import common.entity.Employee;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 分页查询员工数据
 */
public class PaginationCommand implements Command{
    @Override
    public void execute() {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入页号:");
        int page = in.nextInt();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Employee> list = new ArrayList(); //将底下的信息封装到实体类中
        try {
            conn = DbUtils.getConnection(); //?前面第几行 10从这行开始向后取10条记录 limit是分页独有的方言
            String sql = "select * from employee limit ?,10";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1,(page-1)*10); //第二页(2-1)*10从第十条记录开始向后取10条
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt("eno"); //不能在原有字段1前再新增1 按照名字来获取
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                //JDBC获取日期使用java.sql.Date,其继承自java.util.Date
                //所以两者互相兼容
                Date hiredate = rs.getDate("hiredate");
                Employee emp = new Employee(); //每产生一条记录都要封装成Employee对象
                emp.setEno(eno);
                emp.setEname(ename);
                emp.setSalary(salary);
                emp.setDname(dname);
                emp.setHiredate(hiredate);//把每一条记录都封装成为了实体类 放入list
                list.add(emp);
            }
            System.out.println(list.size());
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}
DbUtils【通用】
DbUtils.java
package common;

import kotlin.Result;

import java.sql.*;

public class DbUtils {
    /**
     * 创建新的数据库连接
     * @return 新的Connection对象
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConnection() throws ClassNotFoundException, SQLException {
        //1. 加载并注册JDBC驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2. 创建数据库连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
        return conn;
    }

    /**
     * 关闭连接,释放资源
     * @param rs 结果集对象
     * @param stmt Statement对象
     * @param conn Connection对象
     */
    public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
        try {
            if(rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(stmt != null){
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {
            if(conn != null && !conn.isClosed() ) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
Employee(entity)
package common.entity;

import java.util.Date;

/**
 * 员工实体类
 */
public class Employee {
    /**
     * 1. 具备默认构造函数
     * 2. 属性私有
     * 3. 存在getter与setter
     */
    public Employee(){

    }
    //通常和数据库数据一一对应
    private Integer eno;
    private String ename;
    private Float salary;
    private String dname;
    private Date hiredate;

    public Integer getEno() {
        return eno;
    }

    public void setEno(Integer eno) {
        this.eno = eno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }
}

阿里巴巴Druid连接池[类似于施工仓库 在启动应用时创建连接池]

JDBC先去创建与数据库的连接 比较浪费资源和时间

  • Druid是阿里巴巴开源连接池组件,是最好的连接池之一
  • Druid对数据库连接进行有效管理与重用,最大化程序执行效率
  • 连接池负责创建管理连接,程序只负责取用和归还

/druid-config.properties要放到resources文件夹内

druid-config.properties

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username=root
password=root
initialSize=10 #初始数量
maxActive=20 #数据库最大连接数量
#最好初始数量=最大数量 一开始j
DruidSample.java
package test;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import common.DbUtils;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class DruidSample {
    public static void main(String[] args) {
        //1.加载属性文件
        Properties properties = new Properties();
        String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
        //空格->%20 会被转换
        try {
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8"); //%20还原回去
            properties.load(new FileInputStream(propertyFile));
        } catch (Exception e) {
            e.printStackTrace();
        }

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //2.获取DataSource数据源对象
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //3.创建数据库连接
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0,100");
            rs = pstmt.executeQuery();
            while (rs.next()) {
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(eno + "-" + ename + "-" + salary + "-" + dname);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(rs,pstmt,conn);
        }
    }
}

扩展知识:C3P0连接池

不用属性文件,改用c3p0-config.xml保存文件

在里面&无法转义 要写成 ‘ & amp;

idea C3P0时出现java.sql.SQLException: No suitable driver的几种解决办法

1、对lib包Add as library
2、c3p0-config.xml必须放在source目录下,在此目录会被自动读取
3、c3p0命名必须是c3p0-config.xml(至少xml格式是这样)
4、c3p0-config.xml文件配置错误、书写错误

c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/imooc?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <!-- 连接池初始连接数量 -->
        <property name="initialPoolSize">10</property>
        <!--最大连接数量-->
        <property name="maxPoolSize">20</property>
    </default-config>
</c3p0-config>
C3P0Sample.java
package test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import common.DbUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class C3P0Sample {
    public static void main(String[] args) {
        //1.加载配置文件
        //2.创建DataSource
        DataSource dataSource = new ComboPooledDataSource();
        //3.得到数据库连接
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement("select * from employee limit 0,10");
            rs = pstmt.executeQuery();
            while(rs.next()){
                Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
                String ename = rs.getString("ename");
                Float salary = rs.getFloat("salary");
                String dname = rs.getString("dname");
                System.out.println(eno + "-" + ename + "-" + salary + "-" + dname);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DbUtils.closeConnection(rs,pstmt,conn); //将数据库连接回收到连接池中而不是真正关闭
        }
    }
}

Apache Commos DBUtils

  • commons-dbutils是Apache提供的开源JDBC工具类库
  • 它是对JDBC的简单封装,学习成本极低
  • 使用commons-dbutils可以极大简化JDBC编码工作量
DbUtilsSample.java 【对数据的查询与更新】
package test;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import common.DbUtils;
import common.entity.Employee;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 * Apache DBUtils + Druid 联合使用演示
 */
public class DbUtilsSample {
    private static void query(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        try {
            propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
            properties.load(new FileInputStream(propertyFile));
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //利用Apache DbUtils大幅简化了数据的提取过程
            QueryRunner qr = new QueryRunner(dataSource); //查询执行者  ↓连接自动关闭 不用手动写代码
            List<Employee> list = qr.query("select * from employee limit ?,10",
                    new BeanListHandler<>(Employee.class),
                    new Object[]{10});//没有结果集只能new 结果自动转换成List实体类 后面的是问号赋值
            for (Employee emp : list){
                System.out.println(emp.getEname());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static void update(){
        Properties properties = new Properties();
        String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
        Connection conn = null;
        try {
            propertyFile = new URLDecoder().decode(propertyFile,"UTF-8");
            properties.load(new FileInputStream(propertyFile)); //文件加载
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);//利用工厂类对properties信息进行载入创建对应的datasource对象
            conn = dataSource.getConnection();//获取数据库连接
            conn.setAutoCommit(false);
            String sql1 = "update employee set salary=salary+1000 where eno=?";
            String sql2 = "update employee set salary=salary-599 where eno=?";
            QueryRunner qr = new QueryRunner();
            qr.update(conn, sql1, new Object[]{1000});//写入表操作都用update
            qr.update(conn, sql2, new Object[]{1001});//分别完成加工资和减工资的操作
            conn.commit();//如果都执行成功 就提交 否则回滚
        } catch (Exception e) {
           e.printStackTrace();
            try {
                if (conn!=null && !conn.isClosed()){
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {
            try {
                if (conn!=null && !conn.isClosed()){
                    conn.close(); //对数据库进行回收
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
//        query();
        update();
    }

}
阅读全文

思诚科技项目实训

2023/10/17

html基础知识

打开文件
拖拽法
菜单打开

拓展插件:Chinese、Open in browser

VSCode

代码格式化

  • Tab 往右缩进

  • Shift + Tab 向左回退

  • 一个Tab等于两个空格

  • 设置一个Tab等于2个空格

  • 设置键盘快捷方式 大写与小写 ctrl+shift+u/l

  • shift + alt + ↓ 快速复制上一行

  • ctrl + f 搜索

  • ctrl + h 替换

  • ctrl + z 撤销

  • 多光标修改 选中修改的 ctrl+d 加上上下移动

快速写代码 [按 ctrl+i 自动导入快捷代码]

  • div + Tab 快速输入div块

  • div.red + Tab < div class=”red”> < /div>

  • div#box.red + Tab < div id=”box” class=”red”> < /div>

  • div[name=box] [title=气泡] + Tab < div name=”box” title=”气泡”> < /div>

  • a#link.red[href=#] [ title=我是连接] + Tab < a href=”#” id=”link” class=”red” title=”我是连接”>< /a>

  • div{文本} + Tab < div>aaaa< /div>

  • 输入ul>li{项目} + Tab

    <ul>
      <li>牛逼</li>
    </ul>
    
  • ul#box>li.red[title=’”标题”]{项目1}

    <ul id="box">
      <li class="red" title="标题">项目1</li>
    </ul>
    
  • ui>li*5 一个ul标签和五个li标签 大于号是子类[父子关系]

  • p*3{段落$}

    <p>段落1</p>
    <p>段落2</p>
    <p>段落3</p>
    
  • ul#nav>li.item*5{项目列表$} + Tab

    <ul id="nav">
        <li class="item" 列表1=""></li>
        <li class="item" 列表2=""></li>
        <li class="item" 列表3=""></li>
        <li class="item" 列表4=""></li>
        <li class="item" 列表5=""></li>
    </ul>
    
  • div[name=”box”]>p.red>span*3{文本$} + Tab

    <div name="box">
        <p class="red">
            <span>文本1</span>
            <span>文本2</span>
            <span>文本3</span>
        </p>
    </div>
    <!--#是id    .是class    [name="box"]-->
    
  • h${标题$}*6

    <h1>标题1</h1>
    <h2>标题2</h2>
    <h3>标题3</h3>
    <h4>标题4</h4>
    <h5>标题5</h5>
    <h6>标题6</h6>
    
  • h${标题$}*6 按ctrl+i 自动导入快捷代码

  • (h2{标题}+p{段落})*3 [同类关系]

    <h2>标题</h2>
    <p>段落</p>
    <h2>标题</h2>
    <p>段落</p>
    <h2>标题</h2>
    <p>段落</p>
    
  • div#faq>h2{常见问题}dl.list>(dt{问题$}+dd{答案$})*4
    div#faq>(h2{常见问题}+dl.list>(dt{问题$}+dd{答案$})*4)
    
    <div id="faq">
        <h2>常见问题</h2>
        <dl class="list">
            <dt>问题1</dt>
            <dd>答案1</dd>
            <dt>问题2</dt>
            <dd>答案2</dd>
            <dt>问题3</dt>
            <dd>答案3</dd>
            <dt>问题4</dt>
            <dd>答案4</dd>
        </dl>
    </div>
    
  • 输入 ul>li{列表$$}*10 + Tab 确保位数

    <ul>
        <li>列表01</li>
        <li>列表02</li>
        <li>列表03</li>
        <li>列表04</li>
        <li>列表05</li>
        <li>列表06</li>
        <li>列表07</li>
        <li>列表08</li>
        <li>列表09</li>
        <li>列表10</li>
    </ul>
    

MarkDown语法

在VsCode预览ctrl + shift + v

中划线: ’ ~~ 1111 ~~
分割线:’— + 回车
超链接:’[网易] (http://www.163.com)
图片: ‘! [图片名称] (图片URL)

阅读全文

数据库

2023/10/8

数据库基础内容

数据库系统(DBMS)

  • 关系型数据库系统(RDBMS)是指使用了关系模型的数据库
  • 关系模型中,数据是分类存放的,数据之间可以有联系
  • 淘宝网背后是3000多个数据库并发的集群
  • DB2电信金融领域 Oracle数据库集群 MySQL开源灵活 SQL Server教育领域免费
  • NoSQL数据库[Redis]指的是数据分类存放,但是数据之间没有关联关系的数据库系统
    主流NoSQL数据库 => Redis(内存 双十一秒杀) MemCache MongoDB(新闻) Neo4J
    NoSQL数据库只是关系型数据库的补充

MySQL衍生版

Oracle Percona(Linux系统) MariaDB

重设root密码 (D:/temp.txt)

  • 创建一个Txt文件,定义修改密码的SQL语句

    ALTER USER 'root'@'localhost' IDENTIFIED BY '123456'; 
    
  • Windows PowerSheell(管理员) 窗口打开
    停止服务

    net stop mysql180
    
    mysqld --defaults-file="D:\MySQL\MySQL Server 8.0\my.ini" --init-file=="D:/temp.txt" --console
    ctrl+C取消
    

    启动服务

    net start mysql180
    

MySQL配置文件

  • my.ini文件中,我们可以设置各种MySQL的配置,例如字符集、端口号、目录地址等等

    my.init{客户端配置信息:[client]… [mysql]… 数据库配置信息:[mysqld]…}

[client]

# pipe=

# socket=MYSQL 端口号

port=3306

# 错误时主板没有轰鸣声
[mysql]
no-beep

# server_type=3
[mysqld]
#端口号
port=3306


# basedir="D:/MySQL/MySQL Server 8.0/"
# Path to the database root
datadir=D:/MySQL/MySQL Server 8.0\Data

# with an account. 密码认证插件
authentication_policy=mysql_native_password

#默认存储引擎
default-storage-engine=INNODB

# database servers. 开启严格模式
sql-mode="ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"

# General and Slow logging. 用文件记录日志
log-output=FILE
# 关闭日志输出
general-log=0
# 日志文件名称
general_log_file="PLUMINARY.log"
# 开启慢查询日志
slow-query-log=1
#慢查询日志文件名称
slow_query_log_file="PLUMINARY-slow.log"
#大于多少秒的执行SQL被记录在慢查询日志
long_query_time=10

# Error Logging.错误日志名称
log-error="PLUMINARY.err"

# from every other ID in use by any other source or replica. 集群会用到数据库ID
server-id=1

# 把表名转换成小写
lower_case_table_names=1

# 导入导出数据的目录地址
secure-file-priv="D:/MySQL/MySQL Server 8.0/Uploads"

#最大连接数
max_connections=151

定义逻辑库、数据表

  • SQL是用于访问和处理数据的标准的计算机语言
SQL语句的注释
# 这是一段注释文字
/* 这是另一段注释文字 */

创建逻辑库

CREATE DATABASE 逻辑库名称; #创建
SHOW DATABASES; #展现逻辑库
DROP DATABASE 逻辑库名称; #删除

创建数据表

CREATE TABLE 数据表(
  列名1 数据类型[约束] [COMMENT 注释],
  列名2 数据类型[约束] [COMMENT 注释],
  ......
)[COMMENT = 注释];


CREATA TABLE student(
  id INT UNSIGNED PRIMARY KEY, #主键约束 不可重复
  name VARCHAR(20) NOT NULL, #varchar 字符串 最大不超过20个字符 NOT NULL必填,不允许没有数据
  sex CHAR(1) NOT NULL, #char 字符
  birthday DATE NOT NULL, 
  tel CHAR(11) NOT NULL,
  remark VARCHAR(200)  #备注不超过200字符串
);

INSERT INTO student VALUES(1,"李强","男","1995-05-15","13312345678",NULL);

SHOW tables; #展现数据表名称
DESC student; #数据表结构具体情况
SHOW CREATE TABLE student; #查询当时的sql语句
DROP TABLE student; #删除数据表

数据定义语言:数据类型

数字

类型 大小 说明
TINYINT 1字节 小整数
SMALLINT 2字节 普通整数
MEDIUMIINT 3字节 普通整数
INT 4字节 较大整数
BIGINT 8字节 大整数
FLOAT 4字节 单精度浮点数
DOUBLE 8字节 双精度浮点数
DECIMAL[精确钱] ——– DECIMAL(10,2)
  • 十进制的浮点数无法在计算机中用二进制精确表达 比如0.2
   num FLOAT(20,10) #位数20 小数点后精确10位  在num中输入0.2的时候 变成了0.200000000030
=> num DECIMAL(20,10)

数据类型:字符串

类型 大小 说明
CHAR 1-255字符 固定长度字符串
VARCHAR 1-65535字符 不固定长度字符串
TEXT 1-65535字符 不确定长度字符串[后不加括号]
MEDIUMETEXT 1-1千6百万字符 不确定长度字符串[后不加括号]
LONGTEXT 1-42亿字符 不确定长度字符串[后不加括号]

数据类型:日期类型(年月日中间横线分割 要加引号)

类型 大小 说明
DATE 3字节 日期
TIME 3字节 时间
YEAR 1字节 年份
DATETIME 8字节 日期时间[电影开始时间]
TIMESTAMP 4字节 时间戳

修改表结构

添加字段
ALTER TABLE 表名称
ADD 列1 数据类型 [约束] [COMMENT 注释],
ADD 列2 数据类型 [约束] [COMMENT 注释],
......;

ADD address VARCHAR(200) NOT NULL;
修改字段名称
ALTER TABLE 表名称
CHANGE 列1 新列名1 数据类型 [约束] [COMMENT 注释],
CHANGE 列2 新列名2 数据类型 [约束] [COMMENT 注释],
......;

修改字段
ALTER TABLE student
MODIFY home_tel VARCHAR(20) NOT NULL;
CHANGE address home_address VARCHAR(200) NOT NULL; #改变字段名
删除字段
ALTER TABLE 表名称
DROP 列1,
DROP 列2,
......;

数据库的范式

第一范式:原子性

  • 第一范式是数据库的基本要求,不满足组这一点就不是关系数据库
  • 数据库的每一列都是不可分割的基本数据项,同一列中不能有多个值,也不能存在重复的属性
不符合第一范式
学号 姓名 班级
1000 刘娜 高三年级1班
符合第一范式
学号 姓名 年纪 班级
1000 刘娜 高三 1班

第二范式:唯一性

  • 数据表中的每条记录必须是唯一的。为了实现区分,通常要为表加上一列用来存储唯一标识,这个唯一属性列被称为主键列
无法区分重复的数据
学号 考试成绩 日期
230 58 2018-07-15
230 58 2018-07-15
数据具有唯一性
流水号 学号 考试成绩 日期
201807152687 230 58 2018-07-15
201807152694 230 58 2018-07-15

第三范式:关联性

  • 每列都与主键有直接关系,不存在传递依赖
违反第三范式
爸爸 儿子 女儿 女儿的玩具 女儿的衣服
陈华 陈浩 陈婷婷 海绵宝宝 校服

拿爸爸作为主键 儿子和女儿字段都是依赖于爸爸字段 但是后面的字段违反了第三范式 女儿的玩具和女儿的衣服是依赖于女儿这个字段的并不依赖于爸爸这个字段
数据表关系都非常松散 在检索的时候非常慢 因为爸爸主键字段查询非常快 但是查女儿的玩具和女儿的衣服找不到一样的主键

遵守第三范式
爸爸 儿子 女儿
陈华 陈浩 陈婷婷
女儿 女儿的玩具 女儿的衣服
陈婷婷 海绵宝宝 校服
  • 依照第三范式,数据可以拆分保存到不同的数据表,彼此保持关联
编号 部门 电话
10 财务部 1001
20 技术部 1002
30 销售部 1003
编号 姓名 性别 部门 入职日期
1 陈浩 10 2018-05-10
2 李婷婷 30 2018-03-22

部门字段也是依赖于编号的 两张表没有违反第三范式

字段约束

  • MySQL中的字段约束共有四种:
约束名称 关键字 描述
主键约束 PRIMARY KEY 字段值唯一,且不能为NULL
非空约束 NOT NULL 字段值不能为NULL
唯一约束 UNIQUE 字段值唯一,且可以为NULL
外键约束 FOREIGN KEY 保持关联数据的逻辑性

主键约束

  • 主键约束要求字段的值在全表必须唯一,而且不能为NULL值
  • 建议主键一定要使用数字类型,因为数字的检索速度会非常快
  • 如果主键是数字类型,还可也设置自动增长
CREATE TABLE t_teacher(
    id INT PRIMARY KEY AUTO_INCREMENT, #自带索引功能 自带排序
    ......
);

非空约束

  • 非空约束要求字段的值不能为NULL值
  • NULL值以为没有值,而不是 “” 空字符串
CREATE TABLE t_teacher(
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    married BOOLEAN NOT NULL DEFAULT FALSE #若不写则是默认值false
);

唯一约束

  • 唯一约束要求字段值如果不为NULL,那么在全表必须唯一
CREATE TABLE t_tracher(
    ......
    tel CHAR(11) NOT NULL UNIQUE
);
t_  tb_  一般是真实的表  
v_ vw_   一般是视图虚拟表的意思
总结代码
CREATE TABLE t_teacher(
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    tel CHAR(11) NOT NULL UNIQUE, #唯一约束 UNIQUE 字段值唯一,且可以为NULL
    married BOOLEAN NOT NULL DEFAULT FALSE
);
-------------------------------------------------------------------------
CREATE TABLE student(
    id INT UNSIGNED PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    sex CHAR(1) NOT NULL,
    birthday DATE NOT NULL,
    tel CHAR(11) NOT NULL,
    remark VARCHAR(200)
);
INSERT INTO student VALUES (1,"李强","男","1995-05-15","13312345678",NULL);
DESC student; 
SHOW CREATE TABLE student;

ALTER TABLE student
ADD address VARCHAR(200) NOT NULL, #添加字段信息
ADD home_tel CHAR(11) NOT NULL;

ALTER TABLE student
MODIFY home_tel VARCHAR(20) NOT NULL; #改字段类型信息

ALTER TABLE student
CHANGE address home_address VARCHAR(200) NOT NULL; #改变字段名

ALTER TABLE student
DROP address; #删除字段

外键约束

  • 外键约束用来保证关联数据的逻辑关系
  • 外键约束的定义是写在子表上的
编号 部门 电话
10 财务部 1001
20 技术部 1002
30 销售部 1003
编号 姓名 性别 部门 入职日期
1 陈浩 10 2018-05-10
2 李婷婷 30 2018-03-22
CREATE TABLE t_dept(
    deptno INT UNSIGNED PRIMARY KEY,
    dname VARCHAR(20) NOT NULL UNIQUE,
    tel CHAR(4) UNIQUE #写了电话必须唯一
);
CREATE TABLE t_emp(
    empno INT UNSIGNED PRIMARY KEY,
    ename VARCHAR(20) NOT NULL,
    sex EUNM("男","女") NOT NULL, #枚举 选择其中一个
    deptno INT UNSIGNED,
    hiredate DATE NOT NULL,
    FOREIGN KEY (deptno) REFERENCES t_dept(deptno) #和父表进行外键约束关联
);

不允许删除第一个编号 需要先删除第二个表的记录 再删除第一个表记录 逻辑关系有保证

外键约束的闭环问题 【因此开发中放弃外键约束】

  • 如果形成外键闭环,我们将无法删除任何一张表的记录

数据排序的好处

  • 一旦数据排序后,查找的速度就会翻倍,现实世界跟程序世界都是如此

如何创建索引

CREATE TABLE 表名称(
    ......,
    INDEX [索引名称] (字段),
    ......
);

#数据库对这个索引字段排序生成二叉树 每个字段都是有索引名称的
CREATE TABLE t_message(
    id INT UNSIGNED PRIMARY KEY,
    content VARCHAR(200) NOT NULL,
    type ENUM("公告","通报","个人通知") NOT NULL,
    create_time TIMESTAMP NOT NULL,
    INDEX idx_type (type)
); #利用二叉树的二分查找去查找索引字段就会非常快

如何添加与删除索引

CREATE INDEX 索引名称 ON 表名(字段); #添加索引
ALTER TABLE 表名称 ADD INDEX [索引名](字段); #添加索引
SHOW INDEX FORM 表名; #展示索引表  PRIMARY KEY AUTO_INCREMENT, #自带索引功能 自带排序
DROP INDEX 索引名称 ON 表名; #删除索引

索引的使用原则

  • 数据量很大,而且经常被查询的数据表可以设置索引 (日志表很少查询 无需设置 因为数据库要维护二叉树)
  • 索引只添加在经常被用作检索条件的字段上面
  • 不要在大字段上创建索引

(以上均为DDL语句)

DML语句

数据操作语句:普通查询

记录查询
  • 最基本的查询语句是由SELECTFROM关键字组成
USE demo;
SELECT * FROM t_emp; #FROM是从员工表查数据 *是在结果集里显示员工表所有字段
SELECT empno,ename,sal FROM t_emp; #不查询所有 单独查询想要的
  • SELECT语句屏蔽了物理层的操作,用户不必关心数据的真实存储,交给数据库高效查找数据
  • 通常情况下 ,SELECT子句中使用了表达式,那么这列的名字就默认为表达式,因此需要一种对列明重命名的机制 [起别名:只是对查询的结果集字段改名称]
SELECT
  empno,
  sal*12 AS "income"
FROM t_emp;
  • 上述sql 查询语句的子句执行顺序

词法分析与优化:读取SQL语句
FROM:选择数据来源
SELECT:选择输出内容

数据操作语言:数据分页

  • 朋友圈只会加载少量部分信息,不用一次性加载全部朋友圈,那样只会浪费CPU时间、内存和网络带宽
  • 如果结果集的记录很多,则可以使用LIMIT关键字限定结果集数量
SELECT ... FROM ... LIMIT 起始位置,偏移量; 
SELECT empno,ename FROM t_emp LIMIT 0,20; #从0往后取20条数据
数据分页简写
  • 如果LIMIT子句只有一个参数,它表示的是偏移量,起始值默认为0
SELECT empno,ename FROM t_emp LIMIT 10;
SELECT empno,ename FROM t_emp LIMIT 0,10;
FROM → SELECT → LIMIT

结果集排序

  • 如果没有设置,查询语句不会对结果集进行排序。也就是说,如果想让结果集按照某种顺序排序,就必须使用ORDER BY子句 【默认升序
SELECT ... FROM ... ORDER BY 列名 [ASC|DESC];
SELECT ename,sal FROM t_emp ORDER BY sal;

排序关键字

  • ASC代表升序(默认),DESC代表降序
  • 如果排序列是数字类型,数据库就按照数字大小排序,如果是日期类型就按照日期大小排序,如果是字符串就暗战字符集序号排序。
SELECT ename,sal FROM t_emp ORDER BY hiredate DESC;
排序字段内容相同的情况
  • 如果两条数据排序字段内容相同 sal 都是3000
    默认情况下是按照主键升序
多个排序字段
  • 使用ORDER BY规定首要排序顺序条件和次要排序条件。数据库会先按照要排序条件排序,如果遇到首要排序内容相同的记录,那么就会启动次要排序条件接着排序
SELECT ename,sal,hiredate 
FROM t_emp 
ORDER BY hiredate DESC,sal ASC;
#先按照首要排序hiredate降序 再启动次要排序sal降序

SELECT ename,sal,hiredate
FROM t_emp
ORDER BY sal DESC 
LIMIT 0,5  #工资排在前五位进行降序
排序+分页
  • ODER BY 子句书写的时候放在LIMIT子句的前面
    FROM → SELECT → ORDER BY → LIMIT

结果集中的重复数据

  • 假如我们要查询员工表有多种职业,写出来的sql语句

    SELECT job FROM t_emp; #结果集内可能会出现重复记录
    
  • 如果去除重复的数据,可以使用 DISTINCT 关键字来实现

    SELECT DISTINCT 字段 FROM ...;
    SELECT DISTINCT job FROM t_emp;
    
注意事项
  • 使用DISTINCTSELECT子句中只能查询一列数据,如果查询多列,去除重复记录就会失效

    SELECT DISTINCT job,ename FROM t_emp; #有job相同但是ename不相同 不能查询
    
  • DISTINCT关键字只能再SELECT子句中使用一次 [必须放在第一个字段前面]

数据操作语言:条件查询(一)

  • 满足某一种或几种条件的记录。这类条件要用WHERE子句来实现数据的筛选

    SELECT ... FROM ... WHERE 条件 [AND|OR] 条件 ...;
    
    SELECT empno,ename,sal FROM t_emp
    WHERE deptno=10 AND sal>=2000;
    
    SELECT empno,ename,sal
    FROM t_emp
    WHERE(deptno=10 OR deptno=20) AND sal>=2000;
    
四类运算符
  • WHERE语句中的条件运算会用到以下四种运算符

    序号 运算符
    1 数学运算符
    2 比较运算符
    3 逻辑运算符
    4 按位运算符
算数运算符 [+加 -减 *乘 /除 %模]

NULL值与任何数字加减乘除都是NULL值 如果想要运算 必须加入 IFNULL(null,0); 意思是遇到NULL值就用0来计算 10+IFNULL(null,0) = 10
DATEDIFF(入职日期-现在的日期)/365

从t_emp表中找出 号位是10 和 总工资≥15000 的并且 计算工龄超过20年的人
SELECT empno,ename,sal,hiredate
FROM t_emp
WHERE deptno=10 AND (sal+IFNULL(NULL,0))*12>=15000
AND DATEDIFF(NOW(),hiredate)/365>=20;
比较运算符 [>大于 >=大于等于 <小于 <=小于等于 =等于 !=不等于 IN包含deptno IN(10,30,40)]
查询10 20 30部门里面在1980年以前入职的员工而且不能是SALESMAN职位
SELECT
  empno,ename,sal,deptno,hiredate
FROM t_emp;
WHERE deptno IN(10,20,30) AND job!="SALESMAN"
AND hiredate<"1985-01-01";
+续比较运算符
序号 表达式 意义 例子
8 IS NULL 为空 comm IS NULL
9 IS NOT NULL 不为空 comm IS NOT NULL
10 BETWEEN AND 范围 sal BETWEEN 2000 AND 3000
11 LIKE 模糊查询 ename LIKE “A%”
12 REGEXP 正则表达式 ename REGEXP “[a-zA-Z]{4}”

__代表前方一个未知 %代表前方N个未知

SELECT 
ename,comm,sal
FROM t_emp WHERE comm IS NULL
AND sal BETWEEN 2000 AND 3000;
AND ename LIKE "_LAKE";

SELECT 
ename,comm,sal
FROM t_emp WHERE comm IS NULL
AND sal BETWEEN 2000 AND 3000;
AND ename REGEXP "^[\\u4e00-\\u9fa5]{2,4}$"; #正则表达汉字范围寻找两到四个中文字符
按位运算符 [&位与 |位或 ~位取反 ^位异或 <<左移 >>右移]

<< 左移 10<<1 把10转换成二进制位 在最右面补上一个0 整体向左移动了一个单位
》》左移 10<<1 把10转换成二进制位 在最右面抹去一个0 整体向右移动了一个单位

二进制按位运算

  • 二进制运算的实质是将参与运算的两个操作数,按对应的二进制数逐位进行逻辑运算
    SELECT 3 & 7; 0011 & 0111 = 0011 = 3

数据操作语言:条件查询(二) [AND与 OR或 NOT非 XOR异或]

查询10和20之外部门的信息
SELECT
  ename,deptno,sal
FROM t_emp
WHERE NOT deptno IN(10,20) XOR sal>=2000;

WHERE子句的注意事项

  • WHERE子句中,条件执行的顺序是从左到右的。所以我们应该把索引条件,或者筛选掉记录最多的条件写在最左侧
    SELECT empno,ename FROM t_emp
    WHERE ename = "FORD" AND sal >= 2000;
    
    SELECT empno,ename FROM t_emp
    WHERE deptno = 10 AND sal >= 2000;
    

各种子句的执行排序

FROM → WHERE → SELECT → ORDER BY → LIMIT

先表 再查出符合条件的记录 才能从中挑选出符合的字段 先排序后限制


数据库高级内容

数据操作语言:聚合函数

  • 聚合函数在数据的查询分析中,应用十分广泛。聚合函数可以对数据求和、求最大值最小值、求平均值等等

  • 求公司员工的平均月收入是多少?

    底薪+佣金(不是null)
    SELECT AVG(sal+IFNULL(comm,0)) FROM t_emp;
    
SUM函数
  • SUM函数用于求和,只能用于数字类型,字符类型的统计结果为0,日期类型统计结果是毫秒数相加

    SELECT SUM(ename) FROM t_emp
    
    SELECT SUM(sal) FROM t_emp
    WHERE deptno IN (10,20);
    
MAX函数
  • MAX函数用于获得非空值的最大值

    SELECT MAX(comm) FORM t_emp;
    
    ①查询10和20部门中,月收入最高的员工?
    SELECT 
    MAX(sal+IFNULL(comm,0)) FROM t_temp
    FROM t_emp
    WHERE deptno IN (10,20);
    
    ②查询员工名字最长的是几个字符?
    SELECT MAX(LENGTH(ename)) FROM t_emp;
    
    SELECT
    SUM(sal),MAX(sal+IFNULL(comm,0)) 
    FROM t_emp
    WHERE deptno IN(10,20); 
    
MIN函数
  • MIN函数用于获得非空值的最小值

    SELECT MIN(empno) FROM t_emp;
    
AVG函数
  • AVG函数用于获得非空值的平均值,非数字数据统计结果为0

    SELECT AVG(sal+IFNULL(comm,0)) FROM t_emp;
    
COUNT函数
  • COUNT(*) [找所有]用于获得包含空值的记录数,COUNT(列名)用于获得包含非空值的记录数

    SELECT COUNT(*) FROM t_emp; #统计所有   15 
    SELECT COUNT(comm) FROM t_emp; #统计数量是非空   5
    
  • 查询10和20部门中,底薪超过2000元并且工龄超过15年的员工人数

    SELECT COUNT(*) 
    FROM t_emp
    WHERE deptno IN(10,20) 
    AND sal>=2000
    AND DATEDIFF(NOW(),hiredate)/365>=15;
    
  • 查询1985年以后入职的员工,底薪超过公司平均底薪的员工数量?

    ×××××××××错误示范×××××××××
    SELECT COUNT(*) FROM t_emp
    WHERE hiredate>="1985-01-01"
    AND sal>AVG(sal); #AVG无法运行 聚合函数不能出现在WHERE里面
    

数据操作语言:分组查询

为什么要分组?
  • 默认情况下汇总函数是对全表范围内的数据做统计
  • GROUP BY子句的作用是通过一定的规则将一个数据集划分成若干个小的区域,然后针对每个小区域分别进行数据汇总处理
SELECT deptno,ROUND(AVG(sal))
FROM t_emp
GROUP BY deptno; #分组来计算AVG

逐级分组

  • 数据库支持多列分组条件,执行的时候逐级分组
  • 查询每个部门里,每种职位的人员数量和平均底薪
#按照部门和工作分组 ↓这种职位人数 ↓这种职位底薪平均值
SELECT deptno,job,COUNT(*),AVG(sal)
FROM t_emp 
GROUP BY deptno,job
ORDER BY deptno; #按照deptno去排序

对SELECT子句的要求

  • 查询语句中如果含有GROUP BY子句,那么SELECT子句中的内容就必须要遵守规定:SELECT子句中可以包括聚合函数,或者GROUP BY子句的分组列,其余内容均不可以出现在SELECT子句中
#正确示范
SELECT deptno,COUNT(*),AVG(sal)
FROM t_emp GROUP BY deptno;

#错误示范
SELECT deptno,COUNT(*),AVG(sal),sal
FROM t_emp GROUP BY deptno;

对分组结果集再次做汇总计算

SELECT 
deptno,COUNT(*),AVG(sal),MAX(sal),MIN(sal)
FROM t_emp 
GROUP BY deptno WITH ROLLUP; #WITH ROLLUP对汇总函数再次进行汇总运算

GROUP_CONCAT函数

  • GROUP_CONCAT函数可以把分组查询中的某个字段拼接成一个字符串

  • 查询每个部门内底薪超过2000元的人数和员工姓名

    SELECT deptno,GROUP_CONCAT(ename),COUNT(*)
    FROM t_emp 
    WHERE sal>=2000
    GROUP BY deptno;
    
    deptno COUNT(*) GROUP_CONCAT(ename)
    10 2 CLARK,KING
    20 3 JONES,SCOTT,FORD
    30 1 BLAKE

各种子句的执行顺序

FROM → WHERE → GROUP BY → SELECT → ORDER BY → LIMIT

WHERE符合的留下来交给GROUP BY去分组之后调用SELECT中的聚合函数计算 ORDER BY子句对结果排序交给LIMIT子句来挑选返回哪些数据

分组查询遇到的困难?

  • 查询部门平均底薪超过2000元的部门编号

    #错误演示[因为WHERE语句出现了聚合函数]
    SELECT deptno FROM t_emp
    WHERE AVG(sal)>=2000
    GROUP BY deptno;
    
  • HAVING语句是紧紧跟着GROUP BY语句的 HAVING子句可以写聚合函数作为判断条件

    SELECT deptno
    FROM t_emp
    GROUP BY deptno HAVING AVG(sal)>=2000;
    

HAVING子句的用途

  • 查询每个部门中,1982年以后入职的员工${普通条件可以写在WHERE里}$超过2个人${COUNT(*)>=2}$的部门编号
    不能拿聚合函数某一个字段做判断

    SELECT deptno 
    FROM t_emp
    WHERE hiredate>="1982-01-01"
    GROUP BY deptno HAVING COUNT(*)>=2;
    ORDER BY deptno ASC;
    
    SELECT deptno 
    FROM t_emp
    WHERE hiredate>="1982-01-01"
    GROUP BY deptno HAVING COUNT(*)>=2 AND AVG(sal)>=2000;
    

HAVING子句的特殊用法 [作用类似于WHERE]

  • 按照数字1分组,MySQL会根据SELECT子句中的列进行分组,HAVING子句也可以正常使用
    能用WHERE就不要先用HAVING 它的作用是給聚合函数做判断

    #不推荐写法
    SELECT deptno,COUNT(*) 
    FROM t_emp
    GROUP BY 1 HAVING deptno IN (10,20);
    
    #推荐写法
    SELECT deptno,COUNT(*) 
    FROM t_emp
    WHERE deptno IN(10,20)
    GROUP BY 1;
    

数据操作语言:表链接查询(一)

从多张表中提取数据
  • 从多张表中提取数据,必须指定关联的条件。如果不定义关联条件就会出现无条件链接,两张表的数据会交叉连接,产生笛卡尔积

  • 规定了链接条件的表链接语句,就不会出现笛卡尔积 [On条件]

    SELECT e.empno,e.ename,d.dname
    FROM t_emp e JOIN t_dept d #給表起别名
    ON e.deptno=d.deptno; #员工部门编号等于部门的部门编号
    

表链接的分类

  • 表链接分为两种:内链接外连接
  • 内链接是结果集中只保留符合连接条件的记录
  • 外连接是不管符不符合链接条件,记录都要保留在结果集中
内链接
SELECT ... FROM 表1
[INNER] JOIN 表2 ON 条件
[INNER] JOIN 表3 ON 条件
...
内连接的多种语法形式
SELECT ... FROM 表1 JOIN 表2 ON 连接条件;
SELECT ... FROM 表1 JOIN 表2 WHERE 连接条件
SELECT ... FROM 表1,表2 WHERE 连接条件;

内连接练习1

  • 查询每个员工的工号、姓名、部门名称、底薪、职位、工资等级?且保证工资符合范围
SELECT e.empno,e.ename,d.dname,e.sal,e.job,s.grade
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal;
  • 内连接的数据表不一定必须有同名字段,只要字段之间符合逻辑关系就可以

内连接练习2

  • 查询与SCOTT相同部门的员工都有谁?

  • 相同的数据表也可以做表连接

    SELECT deptno
    FROM t_emp
    WHERE ename="SCOTT"; 
    
    #子查询的结果变成了条件  SCOTT本人不算
    SELECT deptno
    FROM t_emp
    WHERE deptno=(SELECT dptno FROM t_emp WHERE ename="SCOTT");
    AND ename!="SCOTT";
    
    #改造快速一些  ON后是筛选条件 和 WHERE作用差不多
    SELECT e2.ename
    FROM t_emp e1 JOIN t_emp e2 ON e1.deptno=e2.deptno
    WHERE e1.ename="SCOTT" AND e2.ename!="SCOTT"; 
    

数据操作语言:表链接查询(二)

内连接查询练习1
  • 查询底薪超过公司平均底薪的员工信息?
    把聚合函数查询的结果做成一张表 再进行表连接 【分部描述】
#错误展示
SELECT e2.empno,e2.ename,e2.sal  #因为ON可以换成WHERE 然而WHERE后面有聚合函数 所以会报错
FORM t_emp e1 JOIN t_emp e2 ON e2.sal>=AVG(e1.sal);

#把聚合函数查询的结果做成一张表 再进行表连接 【分部描述】
SELECT AVG(sal)
FROM t_emp;

SELECT e.empno,e.ename,e.sal
FROM t_emp e JOIN(SELECT AVG(sal) avg FROM t_emp) t #avg别名
ON e.sal>=t.avg;
  • 查询RESEARCH部门的人数、最高底薪、最低底薪、平均底薪、平均工龄?
SELECT COUNT(*),MAX(e.sal),MIN(e.sal),AVG(e.sal),AVG(DATEDIFF(NOW(),e.hiredate)/365)
FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno
WHERE d.dname="RESEARCH";

SELECT FLOOR(28.9); #变成28 向上取整
SELECT CEIL(1.1);   #变成1  向上取整
内连接查询练习2
  • 查询每种职业的最高工资、最低工资、平均工资、最高工资等级和最低工资等级?

    SELECT 
    e.job,MAX(e.sal+IFNULL(e,comm,0)),
    MIN(e.sal+IFNULL(e,comm,0)),AVG(e.sal+IFNULL(e,comm,0)),
    MAX(s.grade),MIN(s.grade)
    FROM t_emp e JOIN t_salgrade s
    ON (e.sal+IFNULL(e,comm,0)) BETWEEN s.losal AND s.hisal
    GROUP BY e.job;
    
  • 查询每个底薪超过部门平均底薪的员工信息 [用表连接 而不是子查询]

    SELECT e.empno,e.ename,e.sal
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS avg FROM t_emp GROUP BY dptno) t #别名t
    ON e.deptno=t.deptno AND e.sal>=t.avg;
    

数据操作语言:表链接查询(三)

为什么要使用外连接
  • 如果说陈浩是一名临时人员,没有固定的部门编制(NULL),那么我们想查询每名员工和他的部门名称,用内连接就会遗漏掉陈浩,所以要引用外连接的语法才能解决这个问题
外连接简介
  • 外连接与内连接的区别在于,除了符合条件的记录之外,结果集中还会保留不符合条件的记录

    SELECT e.empno,e.ename,d.dname
    FROM t_emp e  #因为陈浩部门编制是NULL 不能直接JOIN 只能LEFT JOIN
    LEFT JOIN t_dept d ON e.deptno=d.deptno; 
    
    7902 FORD RESEARCH
    7934 MILLER ACCOUNTING
    8000 陈浩 (NULL)

左连接和右连接

  • 左外连接就是保留左表所有的记录,与右表做连接。如果右表有符合条件的记录就与左表连接。如果右表没有符合条件的记录,就用NULL与左表连接。右外连接也是如此。

    SELECT e.empno,e.ename,d.dname
    FORM t_dept d RIGHT JOIN t_emp e #要保留所有的t_emp e
    ON e.deptno=d.deptno;
    

外连接练习1

  • 查询每个部门的名称和部门的人数? [40有部门没员工 要保存空值]
    左外连接把部门表写在左侧 右外连接把部门表写在右侧

    SELECT d.dname,COUNT(*)
    FROM t_dept d LEFT JOIN t_emp e #保留左表的所有记录 右表有空
    ON d.deptno=e.deptno
    GROUP BY d.deptno; #因为保留了左表所有记录 所以分组按照部门号分
    #最终有一条t_dept与t_emp中的NULL做连接 所以 COUNT算上此记录
    #若要将右表的NULL值忽略掉 要在COUNT(d.deptno)这样写
    
    SELECT d.dname,COUNT(d.deptno)
    FROM t_dept d LEFT JOIN t_emp e #保留左表的所有记录
    ON d.deptno=e.deptno
    GROUP BY d.deptno;
    
    dname COUNT(d.deptno)
    ACCOUNTING 3
    RESEARCH 5
    SALES 6
    OPERATIONS 0
  • 查询每个部门的名称和部门的人数?如果没有部门的员工,部门名称用NULL代替

  • UNION关键字可以将多个查询语句的结果集进行合并

    (查询语句) UNION (查询语句) UNION (查询语句)...
    (SELECT d.name,COUNT(e.deptno)
    FROM t_dept d LEFT JOIN t_emp e
    ON d.deptno=e.deptno
    GROUP BY d.deptno
    )UNION
    (SELECT d.dname,COUNT(*)
    FROM t_dept d RIGHT JOIN t_emp e
    ON d.deptno=e.deptno
    GROUP BY d.deptno
    );
    

数据库操作语言:表连接查询(四)

  • 查询每名员工的编号、姓名、部门、月薪、工资等级、工龄、上司编号、上司姓名、上司部门?
    [不知道:员工信息 员工的上司信息 两个不知道信息是不能使用子查询的 员工表 部门表 公司等级表]

    #陈浩要保存下来 用外连接
    SELECT
    e.empno,e.ename,d.dname,
    e.sal+IFNULL(e.comm,0),s.grade,
    FLOOR(DATEDIFF(NOW(),e.hiredate)/365),
    t.empno AS mgrno,t.ename AS mname,t.dname AS mdname #定义上司的数据
    FROM t_emp e 
    LEFT JOIN t_dept d ON e.deptno=d.deptno #结果集所有记录保存下来跟工资等级表做连接
    LEFT JOIN t_salgrade s ON e.sal BETWEEN s.losal AND s.hisal
    LEFT JOIN
    (SELECT e1.empno,e1.ename,d1.dname
    FROM t_emp e1 JOIN t_dept d1
    ON e1.deptno=d1.deptno
    )t ON e.mgr=t.empno;
    

外连接的注意事项

  • 内连接值保留符合条件的记录,所以查询条件写在ON子句和WHERE子句中的效果是相同的。但是外连接里,条件写在WHERE子句里,不符合条件的记录是会被过滤掉的,而不是保留下来的。

数据操作语言:子查询(一)

WHERE中的子查询是需要反复查询的 不推荐使用,但是把它所得的结果集作为一张表跟其他表做连接是推荐的

  • 子查询是一种查询中嵌套查询的语句

  • 查询底薪超过公司平均底薪的员工信息

    SELECT empno,ename,sal
    FORM t_emp
    WHERE sal>=(SELECT AVG(sal) FROM t_emp); #不推荐使用 最好用表连接
    
子查询的分类
  • 子查询可以写在三个地方:WHERE子句、FROM子句、SELECT子句,但是只有FROM子句子查询是最可取的
WHERE子查询
  • 这种子查询最简单,最容易理解,但是确实效率很低的子查询

  • 查询底薪超过公司平均底薪的员工信息

    SELECT empno,ename,sal
    FORM t_emp #↓↓↓↓ 比较每条记录都要重写执行子查询 ↓↓↓↓
    WHERE sal>=(SELECT AVG(sal) FROM t_emp); #不推荐使用 最好用表连接
    
FROM子查询
  • 这种子查询只会执行一次,所以查询效率很高

  • 查询底薪超过公司平均底薪的员工信息

    SELECT
    e.empno,e.ename,e.sal,t.avg
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS avg
    FORM t_emp 
    GROUP BY deptno) t #按照部门编号去分组 起别名
    ON e.deptno=t.deptno 
    AND e.sal>=t.avg;
    
SELECT子查询
  • 这种子查询每输出一条记录的时候都要执行一次,查询效率很低

    SELECT
    e.empno,
    e.ename,
    (SELECT dname FROM t_dept WHERE deptno=e.deptno)
    FROM t_emp e;
    

数据操作语言:子查询(二)

单行子查询和多行子查询 [结果集可以作为新表连接]
  • 单行子查询的结果集只有一条记录,多行子查询结果集有多行记录

  • 多行子查询只能出现在WHERE子句和FROM子句中

  • 如何用子查询查找FORD和MARTIN两个人的同事

    SELECT ename #排除那俩人之外
    FROM t_emp 
    WHERE
    deptno IN    # deptno = 不行因为后面返回了两条记录
    (SELECT deptno FROM t_emp WHERE ename IN("FORD","MARTIN")); #返回两条记录
    AND ename NOT IN("FORD","MARTIN");
    

WHERE子句中的多行子查询

  • WHERE子句中,可以使用IN、ALL、ANY、EXISTS关键字来处理多行表达式结果集的条件判断

  • 调查”FORD”和”MARTIN”底薪都高的员工信息

    SELECT ename 
    FROM t_emp 
    WHERE sal > ALL #ALL是sal里的数比结果集里的所有值都大  ANY则是比任何一个人大
    (SELECT sal FROM t_emp #结果集里返回了多条记录
    WHERE ename IN("FORD","MARTIN"))
    AND ename NOT IN("FORD","MARTIN"); #不包含这俩人
    

EXISTS关键字

  • EXISTS关键字是把原来在子查询之外的条件判断,写到了子查询的里面
    EXISTS用上之后WHERE就不写任何语句了

    SELECT ... FROM 表名 WHERE [NOT] EXISTS(子查询);
    
  • 查询工资等级是3级或者4级的员工信息

    #排斥此方法 效率低下
    SELECT
    FROM t_emp
    WHERE EXISTS(
    SELECT * FROM t_salgrade
    WHERE sal BETWEEN losal AND hisal 
    AND grade IN(3,4)
    );
    

MySQL对数据的基本操作

数据操作语言:INSERT语句

  • INSERT语句可以向数据表写入记录,可以是一条记录,也可以是多条记录

    INSERT INTO 表名(字段1,字段2,......) #添加字段可以快速写入 
    VALUES(值1,值2,......);
    
    INSERT INTO 表名(字段1,字段2,......) 
    VALUES(值1,值2,......),(值1,值2,......); #多条记录
    
    INSERT INTO t_dept(deptno,dname,loc)
    VALUES(520,"研发部","河北"),(250,"销售部","江西");
    
    #向技术部添加一条员工记录 
    #[技术部编号不知道 子查询技术部的编号 结果 写到VALUES子句里面通过INSERT插入员工表里面]
    INSERT INTO t_emp
    (empno,ename,job,mgr,hiredate,sal,comm,deptno)
    VALUES(8001,"潘春尧","SALESMAN",8000,"2023-10-15",2000,NULL,
    (SELECT deptno FROM t_dept WHERE dname="技术部")); #子查询单个结果返回
    

INSERT语法方言

  • MySQL的INSERT语句有一种方言语法

    INSERT INTO 表名 SET 字段1=值1,字段2=值2,......; #只适合Mysql数据库
    
    (INSERT) INTO t_emp
    SET empno=8002,ename="JACK",job="SLAESMAN",mgr=8000,
    hiredate="1985-01-01",sal=2500,comm=NULL,deptno=50;
    
    DELETE FROM t_emp WHERE empno=8002;
    

IGNORE关键字

  • IGNORE关键字会让INSERT只插入数据库不存在的记录

    INSERT [IGNORE] INTO 表名...;
    INSERT IGNORE INTO t_dept(deptno,dname,loc)
    VALUES(40,"技术部","北京"); #40编号是主键被占用了 和已经现存的记录有冲突
    
    #运用方言写
    INSERT IGNORE INTO t_dept
    SET deptno=40,dname="技术部",loc="北京";
    
    #多重数据忽略不正确的数据
    INSERT IGNORE INTO t_dept(deptno,dname,loc)
    VALUES(40,"A","北京"),(80,"B","上海");
    
    #VALUES(40,"A","北京"),(80,"B","上海")
    #> Affected rows: 1
    #> 时间: 0.002s
    

数据操作语言:UPDATE语句(一)

  • UPDATE语句用于修改表的记录
    UPDATE中的NIGNORE直接忽略冲突的语句

    UPDATE [IGNORE] 表名
    SET 字段1=值1,字段2=值2,......
    [WHERE 条件1...] #有条件的修改记录
    [ORDER BY...] #对数据先排序 后修改员工编号+1 
    [LIMIT...]; #取分页数据
    
    #把每个员工的编号和上司的编号+1,用ORDER BY子句完成
    UPDATE t_emp 
    SET empno=empno+1,mgr=mgr+1
    ORDER BY empno DESC;
    
    #把用收入前三名的员工底薪减100元,用LIMIT子句完成
    UPDATE t_emp
    SET sal=sal-100 
    ORDER BY sal+IFNULL(comm,0) DESC #降序 
    LIMIT 3; #取前三条记录
    

UPDATE语句的表连接(一)

  • 因为相关子查询效率非常低,所以我们可以利用表连接的方式来改造UPDATE语句

    UPDATE 表1 JOIN 表2 ON 条件
    SET 字段1=值1,字段2=值2,......;
    
  • 表连接的UPDATE语句可以修改多张表的记录[进化]

    UPDATE 表1,表2
    SET 字段1=值1,字段2=值2,......
    WHERE 连接条件;
    
    #把ALLEN调往RESEARCH部门,职务调整为ANALYST
    [员工表+部门表]
    UPDATE t_emp e JOIN t_dept d
    SET e.deptno=d.deptno
    WHERE e.ename="ALLEN" AND d.dname="RESEARCH"
    #//在t_emp表中找到ALLEN + 在t_dept表中找到RESEARCH 
    #//然后把d.deptno赋值給e.deptno
    
    UPDATE t_emp e JOIN t_dept d
    SET e.deptno=d.deptno,e.job="ANALYST",d.loc="北京"
    WHERE e.ename="ALLEN" AND d.dname="RESEARCH"
    
  • 把底薪低于公司平均底薪的员工,底薪增加150元
    [运用表连接的方法比WHERE语句的效率高]

    UPDATE t_emp e JOIN
    (SELECT AVG(sal) AS avg FROM t_emp) t
    ON e.sal<t.avg
    SET e.sal=e.sal+150;
    

UPDATE语句的表连接(二)

  • UPDATE语句的表连接既可以是内连接,又可以是外连接

    UPDATE 表1[LEFT|RIGHT] JOIN 表2 ON 条件 
    SET 字段1=值1,字段2=值2,...;
    
  • 把没有部门的员工,或者SALES部门低于2000元底薪的员工,都调往20部门
    [把所有员工保留下来 但是陈浩没有部门 用内连接的话会把陈浩忽略掉 要用左外连接把左表所有数据保留下来再去跟部门表作连接]

    #把没有部门的员工,或者SALES部门低于2000元底薪的员工,都调往20部门
    UPDATE t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno #连接条件 不符合的也保留
    SET e.deptno=202
    WHERE e.deptno IS NULL OR (d.dname="SALES" AND e.sal<2000);
    

数据操作语言: DELETE语句(一)

  • DELETE语句用于删除记录
    DELETE [IGNORE] FROM 表名
    [WHERE 条件1,条件2,...] #按照条件删除记录
    [ORDER BY...] #排序后删除
    [LIMIT...]; #分页 工资降序排序 排在前五名的删掉
    #顺序依次向下 最后一个是DELETE
    
练习1
  • 删除10部门中,工龄超过20年的员工记录

    DELETE FROM t_emp
    WHERE deptno=10 AND DATEDIFF(NOW(),hiredate)/365>=20;
    
  • 删除20部门中工资最高的员工记录

    DELETE FROM t_emp
    WHERE deptno=20
    ORDER BY sal+IFNULL(comm,0) DESC
    LIMIT 1;
    

DELETE语句的表连接(一)

  • 因为相关子查询效率非常低,所有我们可以利用表连接的方法来改造DELETE语句

    DELETE 表1,...FROM 表1 JOIN 表2 ON 条件 #删除哪张表记录的操作
    [WHERE 条件1,条件2,...]
    [ORDER BY...]
    [LIMIT...];
    
    #删除SALES部门和该部门的全部员工记录
    #无需外连接 因为没有部门需要不删除的
    DELETE e,d
    FROM t_emp e JOIN t_dept d NO e.deptno=d.deptno #部门名称
    WHERE d.dname="SALES";
    
  • 删除SALES部门和该部门的全部员工记录[表连接]

    DELETE e
    FROM t_emp e JOIN
    (SELECT deptno,AVG(sal) AS sal FROM t_emp GOURP BY deptno)
    ON e.deptno=t.deptno AND e.sal<t.sal
    
  • 删除员工KING和他的直接下属的员工记录,用表连接实现

    DELETE e
    FROM t_emp e JOIN
    (SELECT empno FROM t_emp WHERE ename="KING") t
    ON e.mgr=t.empno OR e.empno=t.empno; #KING的下属 OR KING这个人
    

DELETE语句的表连接(二)

  • DELETE语句的表连接既可以是内连接,又可以是外连接

    DELETE 表1,... FROM 表1 [LEFT|RIGHT] JOIN 表2 ON 条件...;
    
  • 删除SALES部门的员工,以及没有部门的员工 [左外 因为陈浩没部门要保留 外连接(不可用内连接)]

    DELETE e
    FROM t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno
    WHERE d.dname="SALES" OR e.deptno IS NULL
    

快速删除数据表全部记录

  • DELETE语句实在事务机制下删除记录,删除记录之前,先把将要删除的记录保存到日志文件里面,然后删除记录

  • TRUNCATE语句再事务机制之外删除记录,速度大于DELETE语句

    TRUNCATE TABLE 表名;
    

MySql的内置函数

数据库函数分类:数字、字符、日期、条件函数

函数 功能 用力
ABS 绝对值 ABS(-100)
ROUND 四舍五入 ROUND(4.62)
FOLLR 强制舍位到最近的整数 FLOOR(9,9) = 9
CEIL 强制仅为最近的整数 CEIL(3.2) = 4
POWER 幂函数 POWER(2,3)
LOG 对数函数 LOG(7,3)
LN 对数函数 LN(1,0)
函数 功能 用例
SQRT 开平方 SQRT(9)
PI 圆周率 P10
SIN 三角函数 sin(1)
TAN 三角函数 TAN(1)
COT 三角函数 COT(1)
COS 三角函数 COS(2)
DADIANS 角度来换成角度 DADIANS(20)
DEGREES 弧度转换角度 DEGRESS(1)

获取系统的时间函数

  • NOW()函数能获得系统日期和时间,yyyy-MMMM-dd hh:mm:ss
  • CURDATE()函数能获得当前系统日期,yyyy=MM=ddd
  • CURTIME()函数能获得当前时间系统信息,hh:mm:ss

日期格式化函数(一)

  • **DATE_FORMAT()**函数用于格式化日期,返回用户想要的日期格式

    DATE_FORMAT(r日期,表达式)
    
    SELECT ename,DATE_FORMAT(hiredate,"%Y") AS "year"
    FROM t_emp; 
    

日期格式化函数(二)

占位符 作用 占位符 作用
%Y 年份 %m 月份
%d 日期 %w 星期(数字)
%W 星期(名称) %j 本年第几天
%U 本年第几周 %H 小时(24)
%h 小时(12) %i 分钟
%s %r 时间(12)
%T 时间(24)
  • 利用日期函数,查询明年你的生日是星期几?

    SELECT DATE_FORMAT("2019-6-20","%w"); #数字
    SELECT DATE_FORMAT("2019-6-20","%W"); #英文星期
    
  • 利用日期函数,查询1981年上半年入职的员工多少人?[聚合函数 全表范围不用分组GROUP BY]

    SELECT COUNT(*) FROM t_emp
    WHERE DATE_FORMAT(hiredate,"%Y")=1981
    AND DATE_FORMAT(hiredate,"%m")<=6 #上半年
    

日期计算的注意事项

  • MySQL数据库里面,两个日期不能直接加减,日期也不能与数字加减
日期偏移计算
  • DATE_ADD()函数可以实现日期的偏移计算,而且时间单位很灵活

    DATE_ADD(日期,INTERVAL 偏移量 时间单位)
    
    SELECT DATE_ADD(NOW(),INTERVAL 15 DAY); #15天之后
    SELECT DATE_ADD(NOW(),INTERVAL -300 MINUTE); #300分钟之前  2023-10-16 04:48:48
    SELECT DATE_FORMAT(
    DATE_ADD(DATE_ADD(NOW(),INTERVAL -6 MONTH),INTERVAL -3 DAY),"%Y/%m/%d"); #2023/04/13
    
计算日期之间相隔的天数
  • **DATEDIFF()**函数用来计算两个日期之间相差的天数

    DATEDIFF(日期,日期)
    

字符函数(一)

函数 功能 用例
LOWER 转换小写字符 LOWER(ename)
UPPER 转换大写字符 UPPER(ename)
LENGTH 字符数量 LENGTH(ename)
CONCAT 连接字符串 CONCAT(sal, “$”)
INSTR 字符出现的位置 INSTR(ename, “A”)
INSERT 插入/替换字符 INSERT(“你好”,1,0,”先生”)
替换1个字符 0表示不替换—候补
REPLACE 替换字符 REPLACE(“你好先生”,“先生”,”女士”)
先生换成女士
SELECT
  LOWER(ename),UPPER(ename),LENGTH(ename),CONCAT(sal,"$"),INSTR(ename,"A")
FROM t_emp;

字符函数(二)

函数 功能 用例
SUBSTR 截取字符串 SUBSTR(“你好世界”,3,4)
3开始位置 4结束位置
SUBSTRING 截取字符串 SUBSTRING(“你好世界”,3,2)
3个字符开始往后截取2个位置
LPAD 左侧填充字符 LPAD(“Hello”,10,”“)
* 10最终字符串一共为10个字符
电话隐私保护加※号
RPAD 右侧填充字符 RPAD(“Hello”,10,”*”)
TRIM 去除首位空格 TRIM(“ 你好先生 “)
SELECT LPAD(SUBSTRING("15027597319",8,4),11,"*");
SELECT RPAD(SUBSTRING("潘春尧",1,1),LENGTH("潘春尧")/3,"*"); 
#LENGTH是算英文字符 再除以3就是汉字的字符个数

条件函数

  • SQL语句中可以利用条件函数来实现变成语言里的条件判断

    IFNULL(表达式,值)
    IF(表达式,值1,值2)
    
  • 中秋节公司发放礼品,SALES部门发放礼品A,其余部门发放礼品B,打印每名员工获得的礼品
    [按部门名称作表连接 部门表和员工表 内连接没有部门的没有礼品]

    SELECT 
      e.empno,e.ename,d.dname,
      IF(d.dname="SALES","礼品A","礼品B")
    FROM t_emp e JOIN t_tept d ON e,deptno=d.deptno;
    

条件语句

  • 复杂的条件判断可以用条件语句来实现,比IF语句功能更强大

    CASE 
      WHEN 表达式 THEN 值1
      WHEN 表达式 THEN 值2
      ......
      ELSE 值N
    END
    
  • 公司年庆决定组织员工集体旅游,每个部门旅游目的地是不同的。SALES部门去P1地点,ACCOUNTING部门去P2地点,RESEARCH部门去P3地点,查询每名员工的旅行地点。

    SELECT 
      e.empno,e.ename,
      CASE 
        WHEN d.dname="SALES" THEN "p1"
        WHEN d.dname="ACCOUNTING" THEN "p2"
        WHEN d.dname="RESEARCH" THEN "P3"
    FROM t_emp e JOIN t_dept d ON e.deptno=d.deptno;
    
课堂练习
  • 某公司决定为员工调整基本工资,具体调整方案如下:

    序号 条件 涨幅
    1 SALES部门中工龄超过20年 10%
    2 SALES部门中工龄不满20年 5%
    3 ACCOUNTING部门 +300元
    4 RESEARCH部门里低于部门平均底薪 +200元
    5 没有部门的员工 +100元
    [员工表连接部门表]
    UPDATE t_emp e LEFT JOIN t_dept d ON e.deptno=d.deptno
    LEFT JOIN (SELECT deptno,AVG(sal) AS avg FROM t_temp GOURP BY deptno) t
    ON e.deptno=t.deptno
    SET sal=(
        CASE 
          WHEN d,dname="SALES" AND DATEDIFF(NOW(),e.hiredate)/365>=20 
          THEN e.sal*1.1
          WHEN d,dname="ALES" AND DATEDIFF(NOW(),e.hiredate)/365<20 
          THEN e.sal*1.50
          WHEN d,dname="ACCOUNTING" THEN e.sal+300
          WHEN d,dname="RESEARCH" THEN e.sal<t.avg THEN e.sal+200
          WHEN e.deotno IS NULL THEN e.sal+100
          ELSE e.sal
        END
    );
    

事务机制(一) 进入企业市场的第一步

避免写入直接操作数据文件
  • 如果数据的写入直接操作数据文件是非常危险的事情
利用日志来实现间接写入
  • MySQL总共有5种日志,其中只有redo日志和undo日志与事务有关
    [数据库 拷贝数据给 undo日志 记录修改 redo日志 与数据库同步数据]
事务机制(Transaction)
  • RDBMS = SQL语句 + 事务(ACID)
  • 事务是一个或者多个SQL语句组成的整体,要么全部执行成功,要么全都执行失败
事务案例
  • 把10部门中MANGER员工调往20部门,其他岗位的员工调往30部门,然后删除10部门
    事务开启事务 [把要修改的数据拷贝到undo日志[可恢复]内,做的修改会被记录到redo日志[同步]里面] UPDATE语句  DELETE语句  提交事务

管理事务

  • 默认情况下,MySQL执行每条SQL语句都会自动开启和提交事务

  • 为了让多条SQL语句纳入到一个事务之下,可以手动管理事务

    START TRANSACTION;
    SQL语句
    [COMMIT|ROLLBACK];
    
    START TRANSACTION;
    DELETE FROM t_emp
    DELETE FROM t_dept;
    SELECT * FROM t_emp;
    SELECT * FROM t_dept;
    #这些删除修改只是在redo日志文件中进行的修改并未提交 
    #虽然SELECT查不到了 但是点开左列的表中数据仍未删除
    #只要不提交事务 redo日志就不会和数据库做同步
    COMMIT; #把结果提交到日志里面 就会同步了
    ROLLBACK; #做标记 回滚 一起失败
    

事务的ACID属性

原子性 一致性 隔离性 持久性

事务的原子性

  • 一个事务中的所有操作要么全部完成,要么全部失败。事务执行后,不允许停留在中间某个状态

事务的一致性

  • 不管任何给定的时间、并发事务由多少,事务必须保证运行结果的一致性
    [阻止事务之间互相读取临时数据] [A給B 10元 事务没有提交后且回滚了 C给A20元 此时A应该有30元 ]

隔离性

  • 隔离性要求事务不受其他并发事务的影响,如同在给定的时间内,该事务是数据库唯一运行的事务
  • 默认情况下A事务,只能看到日志中该事务的相关数据 [A,B事务可以看undo和redo日志]

持久性

  • 事务一旦提交,结果便是永久性的。即便发生宕机,仍然可以依靠事务日志完成数据的持久化

事务机制(二)

事务的四个隔离级别(可设置相互读取)

序号 隔离级别 功能
1 read uncommitted 读取未提交数据
2 read committed 读取已提交数据
3 repeatable read 重复读取
4 serializable 序列化

业务案例1 [购票系统]

A事务 B事务
车次 车厢 坐席 状态
G8047 1 1A 未售出
G8047 1 1B 未售出

A事务看到G8047 1A坐席未售出 用UPDATE把状态修改成已售出 因为没有提交 所以只修改在了undo日志里 真实的数据没有发生改变。此时B事务启动了看到了1A坐席还有票 于是更新且提交了 于是数据已经发生了改变。

A事务
车次 车厢 坐席 状态
G8047 1 1A 已售出
G8047 1 1B 未售出
所以在这个案例中我们需要B事务去读取A事务的状态 发现在A事务的临时数据里购买了坐席 那么B事务就可以去购买其他的坐席

修改事务隔离级别

  • READ UNCOMMITTED 代表可以读取其他事务未提交的数据

    # ↓设置当前绘画的事务级别↓ [并非全局]
    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    
    *无标题-查询1
    START TRANSACTION;
    UPDATE t_emp SET sal=1;
    -----------------------
    *无标题-查询2
    START TRANSACTION;
    SELECT empno,enam,sal FROM t_emp;
    #发现员工的sal并未变成1块钱
    
    #解决问题:增加事务隔离级别  一个事务读到了另一个事务的数据
    SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
    START TRANSACTION;
    SELECT empno,enam,sal FROM t_emp;
    COMMIT;
    

业务案例2 [银行转账]

Scott账户有5000元余额 B事务开始执行支出100元 此时A事务执行转账1000元到该用户 如果AB都正常commit 最终账户余额是5900元 但是如果B事务是一个错误的消费被回滚[退款操作] 最终账户应该是6000元。如果允许A事务去读取B事务的临时数据 按照4900+1000=5900元 若A回滚就凭空少了100元

修改事务隔离级别

  • READ COMMITTED 代表只能读取其他事务提交的数据

    # ↓设置当前绘画的事务级别↓ [并非全局]
    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
    

业务案例3 [电商案例]

A事务 下单购买 商品(价格350元) B事务要对 该商品涨价

修改事务隔离级别【READ为默认级别】

  • REPEATABLE READ 代表事务在执行中反复读取数据,得到的结果是一致性的,不会受其他事务影响

    #就算事务A提交了数据 也不影响事务B的查询原始数据
    SET SESSION TRANSACTION ISOLATION LEVEL REPEATALE READ;
    
START TRANSACTION; 
UPDATE t_emp SET sal=1; 
COMMIT;
-----------------------
SET SESSION TRANSACTION ISOLATION LEVEL REPEATALE READ;
START TRANSACTION; 
SELECT empno,ename,sal FROM t_emp; 
COMMIT;

事务的序列化

  • 由于事务并发执行所带来的各种问题,前三种隔离级别只适用于在某些业务场景中,但是序列化的隔离性,让事务逐一执行,就不会产生上述问题了。

    SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    
START TRANSACTION; ①
UPDATE t_emp SET sal=1; ②
COMMIT; ⑤
-----------------------
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION; ③
SELECT empno,ename,sal FROM t_emp; ④ #此时会等待 只需要执行上方COMMIT
COMMIT;

数据导入与导出

数据导出与备份的区别

  • 数据导出,导出的纯粹是业务数据
  • 数据备份,备份的是数据文件、日志文件、索引文件灯

全量备份 → 增量备份1 → 增量备份2

数据导出的分类

数据库可以导出SQL文档(数据不多) 也可以 导出文本文档(数据巨大)

导出SQL文件 [或者手动右键库 存储SQL文件]

  • mysqldump用来把业务数据导出成SQL文件,其中也包括了表结构

    #导出命令行 不写no-date就导出即包含 表结构 又包含 数据
    mysqldump -uroot -p [no-date] 逻辑库 > 路径
    # D:/MySQL/demo.sql
    

导入SQL文件

  • source命令用于导入SQL文件,包括创建数据表,写入记录等
    sql > USE demo;
    sql > SOURCE backup.sql;
    
导出纯粹的业务数据

若数据巨大 先导出表结构 再导出数据库文档(.txt) 之后再导入 就会快很多

  • t_emp 右键 存储sql文件 仅结构 再次右键 导出向导… txt文档 (跳过词法分析与语法优化)

    "empno"    "ename"    "job"    "mgr"    "hiredate"    "sal"    "comm"    "deptno"
    "7369"    "SMITH"    "CLERK"    "7902"    "17/12/1980"    "800"        "20"
    "7499"    "ALLEN"    "SALESMAN"    "7698"    "20/2/1981"    "1600"    "300"    "30"
    "7521"    "WARD"    "SALESMAN"    "7698"    "22/2/1981"    "1250"    "500"    "30"
    "7566"    "JONES"    "MANAGER"    "7839"    "2/4/1981"    "2975"        "20"
    "7654"    "MARTIN"    "SALESMAN"    "7698"    "28/9/1981"    "1250"    "1400"    "30"
    "7698"    "BLAKE"    "MANAGER"    "7839"    "1/5/1981"    "2850"        "30"
    "7782"    "CLARK"    "MANAGER"    "7839"    "9/6/1981"    "2450"        "10"
    "7788"    "SCOTT"    "ANALYST"    "7566"    "9/12/1982"    "3000"        "20"
    "7839"    "KING"    "PRESIDENT"        "17/11/1981"    "5000"        "10"
    "7844"    "TURNER"    "SALESMAN"    "7698"    "8/9/1981"    "1500"    "0"    "30"
    "7876"    "ADAMS"    "CLERK"    "7788"    "12/1/1983"    "1100"        "20"
    "7900"    "JAMES"    "CLERK"    "7698"    "3/12/1981"    "950"        "30"
    "7902"    "FORD"    "ANALYST"    "7566"    "3/12/1981"    "3000"        "20"
    "7934"    "MILLER"    "CLERK"    "7782"    "23/1/1982"    "1300"        "10"
    
  • 导入回来 先删除t_emp 在dmeo右键 运行文件 导入刚刚右键存储的t_emp的结构(仅结构) 此时员工表结构就回来了 导入向导… 下一步 找到导出的txt文档 第一个数据行1(第一行有效数据) 在导入向导中根据源字段匹配目标字段 (txt+结构=数据表)

阅读全文

油画项目(介于JavaWeb与数据库之间)

2023/10/1

企业门户网站项目实战

  • 需求说明与环境准备
  • 实现前端展示模块
  • 实现后台数据管理模块

(MVC架构模型) 数据显示与数据处理分开

Model模型 View试图 Controller控制器 [是一种设计理念]

Model - 模型
  • 模型(Model)负责生产业务需要的数据 Service[结尾]实现业务逻辑

    public class MathService{
        public List square(int max){
            List list = new ArrayList();
            for(int i = 0; i <= max; i++){
                long result = i * i;
                list.add(result);
            }
            return list;
        }
    }
    
Controller - 控制器 (Java Web领域是WebServlet)
  • 控制器(Controller)是界面(View)与模型(Model)的粘合剂
@WebServlet("/square")
public class HelloWord  extends HttpServlet{
    public void doGet(HttpServletRequest req, HttpServletResponse res){
        //1.接收数据 接收web的http请求参数
        int max = Integer.parselnt(request.getParameter("max"));
        //2.调用处理类(模型)进行处理 
        MathService ms = new MathService();
        List list = ms.square(max);//前台调用的参数传入square方法中
        req.setAttribute("squareList", list);//执行结果放到请求属性中
        //3.跳转界面(View)
        request.getRequestDispatcher("/result.jsp").forward(req,res);
    }
}
View - 视图
  • 试图(View)用于展示最终结果
URL:http://localhost:8080/square?max=100
----------------------------------------
<ul>
<c:foreach items = "${squareList}" var = "r" varStatus = "idx">
   <li>${idx.index}的平方是${r}</li>
</<c:foreach>
</ul>

MVC架构模式优点

  • 保证了软件层面的解耦,同时保障了团队合作的组织架构

  • 软件团队分工合作,成员各司其职 通过controller联合

  • 组件可灵活替代,互不影响

通过控制器将前端传入的参数进行接收调用后台的模型产生结果,结果通过控制器保存在当前的属性中在视图中进行展现

工程结构与开发规约

工程结构
mgallery - eclipse工程项目
 /src - java源代码目录
 /WebContent - Web资源目录
 /css - css文件目录
 /js - js文件目录
 /image - 图片资源目录
 /upload - 上传文件目录
 /WEB-INF   //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
   /jsp - jsp页面目录
   /lib - jar文件目录
   /classes - 编译后的class目录
   /web.xml web描述符文件
包结构
com.imooc.mgallery //逆命名法
    /controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
    /service - 存放处理逻辑类model[伪数据库] //完成业务逻辑 service与dao进行传递调用
    /dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
    /entity - 存放实体类 JavaBean java中的简单对象
    /utils - 通用工具类 底层通用的工具类或方法

Dao类[Data Access Object]

  • XxxDao类只负责对数据进行读取、写入操作
  • 只负责对数据 增、删、改、查
示例:PaintingDao //针对油画数据进行增删改查
public class PaintingDao{
    public void append(){...} //新增数据
    public void update(){...} //修改数据
    public void delete(){...} //删除数据
    public void findAll(){...} //查询数据
}

Service与Dao的关系

  • Service负责进行流程处理,需**持久化[java处理在内存中 存储在数据库防止丢失]**时调用Dao
  • Dao只负责单纯对数据进行增删改查操作

JavaBean

  • 对一种类的使用形式的统称

  • JavaBean是一种Java中可重用组件

  • JavaBean不是技术,而是一种Java类的格式要求

  • JavaBean在Java领域非常常见,通常用于存储数据

JavaBean格式要求

  • 类必须是pubilc并提供默认构造函数
  • 所有属性private私有化
  • 属性通过getter与setter方法进行读写
public class Painting{//类公有化
    public Painting(){...}; //提供默认构造函数,可不写
    private Integer id; //属性私有
    private String pname;
    public Integer getId(){return id;} //getter获取属性值
    public void setId(Integer id){this.id = id;} //setter设置属性值
    public String getPname(){return pname;}
    public void setPname(String pname){this.pname = pname;}
}

创建mgallery工程(ViewControllerServiceDao){VCSD}

  • 开发PaintingDao读取XML数据,实现分页操作
  • 开发PaintingService服务类,对PaintingDao进行调用
  • 开发PaintingController控制器,调用PaintingService
  • 重写index.html,利用JSP技术读取分页数据

关键类与方法

  • XmlDataSource类 - XML数据源工具类,简化Dao提取操作
  • PaintingDao.pagination() - 数据分页查询方法
  • PageModel类 - 分页结果的包装类

Dom4j

  • Dom4j是一个易用的、开源的库,用于解析XML。它应用于Java平台
  • Dom4j将XML视为Document对象
  • XML标签被Dom4j定义为Element对象
Dom4j开发流程回顾
  • SAXReader.read()读取XML文件,得到Document对象
  • document.selectNodes()利用Xpath得到XML节点集合
  • 遍历XML节点,包装成JavaBean或者集合对象返回

开发XmlDataSource (utils)

【详细】IntelliJ IDEA: 无法创建Java Class文件_idea 不能新建java class-CSDN博客
IDEA部署Tomcat提示:Warning no artifacts configured-CSDN博客
Class.getResource(“xxx.css”)得到值为null-CSDN博客

utils/XmlDataSource.java
package com.example.mgallery.utils;

import com.example.mgallery.entity.Painting;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

//用于将XML文件解析为Java对象
public class XmlDataSource {
    //通过static静态关键字保证数据全局唯一
    private static List data = new ArrayList(); //油画集合
    private static String dataFile;
    static{ //程序运行以后去得到类路径目录下的/painting.xml的路径地址
        dataFile = XmlDataSource.class.getResource("/painting.xml").getPath();
        System.out.println(dataFile);
          //若得到特殊字符进行编码转换 空格 c:\new style\painting.xml
        try {
            URLDecoder.decode(dataFile, "UTF-8");
            //利用Dom4j对XML进行解析 读取XML
            SAXReader reader = new SAXReader();
            //1.获取Document文档对象
            Document document = reader.read(dataFile);
            //2.Xpath得到XML节点集合 获取多个xml节点
            List<Node> nodes = document.selectNodes("/root/painting");
            for (Node node : nodes){
                Element element = (Element) node;
                String id = element.attributeValue("id");//获得id
                String pname = element.elementText("pname");//获得子节点
                Painting painting = new Painting();
                painting.setId(Integer.parseInt(id));
                painting.setPname(pname);
                painting.setCategory(Integer.parseInt(element.elementText("category")));
                painting.setPrice(Integer.parseInt(element.elementText("price")));
                painting.setPreview(element.elementText("preview"));
                painting.setDescription(element.elementText("description"));
                data.add(painting);//将List data 保存油画的完整信息

                System.out.println(id + ";" + pname);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     *  获取所有油画Painting对象
     * @return Painting List
     */
    public static List<Painting> getRawData(){
        return data;
    }
    public static void main(String[] args) {
//        new XmlDataSource();
        List<Painting> ps = XmlDataSource.getRawData();
        System.out.println(ps);
    }
}
META-INF/painting.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 数据文件 -->
<root>
    <painting id="1">
        <pname>古典油画鉴赏1</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/1.jpg</preview>
        <description>古典油画鉴赏1的描述文本</description>
    </painting>
    <painting id="2">
        <pname>古典油画鉴赏2</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/2.jpg</preview>
        <description>古典油画鉴赏2的描述文本</description>
    </painting>
    <painting id="3">
        <pname>古典油画鉴赏3</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/3.jpg</preview>
        <description>古典油画鉴赏3的描述文本</description>
    </painting>
    <painting id="4">
        <pname>古典油画鉴赏4</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/4.jpg</preview>
        <description>古典油画鉴赏4的描述文本</description>
    </painting>
    <painting id="5">
        <pname>古典油画鉴赏5</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/5.jpg</preview>
        <description>古典油画鉴赏5的描述文本</description>
    </painting>
    <painting id="6">
        <pname>古典油画鉴赏6</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/6.jpg</preview>
        <description>古典油画鉴赏6的描述文本</description>
    </painting>
    <painting id="7">
        <pname>古典油画鉴赏7</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/7.jpg</preview>
        <description>古典油画鉴赏7的描述文本</description>
    </painting>
    <painting id="8">
        <pname>古典油画鉴赏8</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/8.jpg</preview>
        <description>古典油画鉴赏8的描述文本</description>
    </painting>
    <painting id="9">
        <pname>古典油画鉴赏9</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/9.jpg</preview>
        <description>古典油画鉴赏9的描述文本</description>
    </painting>
    <painting id="9">
        <pname>古典油画鉴赏9</pname>
        <category>1</category>
        <price>3800</price>
        <preview>/upload/9.jpg</preview>
        <description>古典油画鉴赏9的描述文本</description>
    </painting>
    <painting id="10">
        <pname>抽象派油画鉴赏1</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/10.jpg</preview>
        <description>抽象派油画鉴赏1的描述文本</description>
    </painting>
    <painting id="11">
        <pname>抽象派油画鉴赏2</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/11.jpg</preview>
        <description>抽象派油画鉴赏2的描述文本</description>
    </painting>
    <painting id="12">
        <pname>抽象派油画鉴赏3</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/12.jpg</preview>
        <description>抽象派油画鉴赏3的描述文本</description>
    </painting>
    <painting id="13">
        <pname>抽象派油画鉴赏4</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/13.jpg</preview>
        <description>抽象派油画鉴赏4的描述文本</description>
    </painting>
    <painting id="14">
        <pname>抽象派油画鉴赏5</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/14.jpg</preview>
        <description>抽象派油画鉴赏5的描述文本</description>
    </painting>
    <painting id="15">
        <pname>抽象派油画鉴赏6</pname>
        <category>2</category>
        <price>3800</price>
        <preview>/upload/15.jpg</preview>
        <description>抽象派油画鉴赏6的描述文本</description>
    </painting>
    
</root>
entity/Painting.java
public class Painting{
    private Integer id;
    private String pname;
    private Integer category;
    private Integer price;
    private String preview; //油画图片地址
    private String description; //描述
    ......
}

开发PageMode1 (utils)

utils/PageModel.java
public class PageModel {
    private int page;
    private int totalPages;
    private int rows; //每页几条数据
    private int totalRows; //原始数据多少条
    private int pageStartRow; //当前页是从第几行开始的
    private int pageEndRow; //到第几行结束的 结束行号
    private boolean hasNextPage; //是否下一页(尾页)
    private boolean hasPreviousPage; //是否上一页(首页)
    private List pageData; //当前页面数据

    public PageModel() {
    }

    /**
     * 初始化PageMode1对象,计算分页属性
     * @param page
     * @param rows
     * @param data
     */
    public PageModel(List data, int page, int rows) {
        this.page = page;
        this.rows = rows;
        totalRows = data.size();
        //总页数计算规则:总行数/每页记录数,能整除页数取整,不能整除向上取整
        //18/6=3 | 2/6≈3.33 向上取整=4  intValue()得到整数部分
        //Nath.ceil向上取整  Math.floor浮点数向下取整
        //小细节: 20/6≈3.33 但是totalRows 和 rows都是整数 20/6=3 向上取整还是3
        //仅需在一个后面×1f即可解决问题 (rows*1f)默认类型转换返回浮点数
        totalPages = new Double(Math.ceil(totalRows/(rows * 1f))).intValue();
        pageStartRow = (page - 1) * rows; //0
        pageEndRow = page * rows; //6
        //totalRows:20 | totalPage:4 | rows:6
        //pageEndRow=4*6=24>20 执行subList()抛出下标越界异常
        if (pageEndRow > totalRows){
            pageEndRow = totalRows; //让20作为结束行号 不会越界
        }
        pageData = data.subList(pageStartRow, pageEndRow); //得到分页数据
        if (page > 1) {
            hasPreviousPage = true;
        }else {
            hasPreviousPage = false;
        }
        if (page < totalPages) { //判断是否存在下一页
            hasNextPage = true;
        }else {
            hasNextPage = false;
        }
    }

    public static void main(String[] args) {
        List sample = new ArrayList();
        for (int i = 1; i < 100; i++) {
            sample.add(i);
        }
        PageModel pageModel = new PageModel(sample,6,8);
        System.out.println(pageModel.getPageData()); //当前页的list集合
        System.out.println(pageModel.getTotalPages());
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow()) ;
    }
+getter and setter 

油画数据分页设计思路

@Param注解的使用方法

@Param的作用就是給参数命名,比如在mapper里面某个方法A(int id),当添加注解后A(@Param(“userId”) int id),也就是说外部想要取出传入的id值,只需要取它的参数名userId就可以了。若在SQL中,通过#{userId}进行取值給SQL的参数赋值。

快速添加注解只需要在前面 /** + 回车

油画数据分页展示

开发PaintingDao与PaintingService
service/PaintingDao.java
package com.example.mgallery.dao;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;
import com.example.mgallery.utils.XmlDataSource;

import java.util.List;

//获得最原始的 对其进行分页处理
public class PaintingDao {
    public PageModel pagination(int page, int rows){
        //Painting油画对象集合
        List<Painting> list = XmlDataSource.getRawData();
        //PageModel分页处理得到分页数据及分页附加
        PageModel pageModel = new PageModel(list,page,rows);
        return pageModel;
    }
}
service/PaintingService
package com.example.mgallery.service;

import com.example.mgallery.dao.PaintingDao;
import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;

import java.util.List;
//完成业务逻辑 service与dao进行传递调用
public class PaintingService {
    private PaintingDao paintingDao = new PaintingDao();
    public PageModel pagination(int page, int rows){
        if (rows == 0){
            throw new RuntimeException("无效的rows参数");
        }
        return paintingDao.pagination(page, rows); //调用并返回
    }

    public static void main(String[] args) {
        PaintingService paintingService = new PaintingService();
        PageModel pageModel = paintingService.pagination(2, 6);//每页显示六条
        List<Painting> paintingList = pageModel.getPageData();
        for (Painting painting : paintingList){
            System.out.println(painting.getPname());
        }
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow());

    }
}
开发PaintingController控制器
controller/PaintingController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/page")
public class PaintingController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();
    public PaintingController(){
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收Http数据
        String page = req.getParameter("p"); //页号
        String rows = req.getParameter("r"); //每页记录数
        if (page == null){
            page = "1"; //没传入则默认查询第一个
        }if (rows == null){
            rows = "6"; //每页默认显示六条数据
        }
        //2.调用Service方法,得到处理结果
        PageModel pageModel = paintingService.pagination(Integer.parseInt(page), Integer.parseInt(rows));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/src/main/webapp/index.jsp").forward(req,resp); //跳转jsp
    }
}

前台门户首页(由静态转换为动态)

HTML转换JSP变更流程
  • 打开HTML文件,在首行增加 < %@page contentType % >
  • 更改扩展名从 .html.jsp,移动到WEB-INF/jsp目录下
  • 提示:JSP不要实用<%%>代码块,实用EL+JSTL提取数据

输入(http://localhost:8080/)不能正常访问,但是添加项目名后可以访问-CSDN博客
实例化servlet类[web.LoginServlet]异常 servle-CSDN博客

WEB-INF/jsp/index.jsp
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="css\common.css">
    <script type="text/javascript" src="js\js1.js"></script>
</head>
<body>
<div class="header">
    <div class="logo">
        <img src="image\logo.png">
    </div>
    <div class="menu" onclick="show_menu()" onmouseleave="show_menu1()">
        <div class="menu_title" ><a href="###">内容分类</a></div>
        <ul id="title">
            <li>现实主义</li>
            <li>抽象主义</li>
        </ul>
    </div>
    <div class="auth">
        <ul>
            <li><a href="#">登录</a></li>
            <li><a href="#">注册</a></li>
        </ul>
    </div>
</div>
<div class="content">
    <div class="banner">
        <img src="image/welcome.png" class="banner-img">
    </div>
    <div class="img-content">
        <ul>
            <c:forEach items="${pageModel.pageData}" var="painting"> <!--pageModel.java-->
                <li>
                    <img src="${painting.preview}" class="img-li">  <!--painting.java-->
                    <div class="info">
                        <h3>${painting.pname}</h3>
                        <p>
                                ${painting.description}
                        </p>
                        <div class="img-btn">
                            <div class="price"><fmt:formatNumber pattern="¥0.00" value="${painting.price}"></fmt:formatNumber></div>
                            <a href="#" class="cart">
                                <div class="btn">
                                    <img src="image/cart.svg">
                                </div>
                            </a>
                        </div>
                    </div>
                </li>
            </c:forEach>
        </ul>
    </div>
    <div class="page-nav">
        <ul>
            <li><a href="/mgallery/page?p=1">首页</a></li> <!--当前页减一就是上一页 否则就是1-->
            <li><a href="/mgallery/page?p=${pageModel.hasPreviousPage?pageModel.page-1:1}">上一页</a></li>
            <c:forEach begin="1" end="${pageModel.totalPages}" var="pno" step="1">
                <li><span ${pno==pageModel.page?"class='first-page'":""}> <!--选中的页才有⚪圈圈 三目运算符不满足产生空字符串-->
                    <a href="/mgallery/page?p=${pno}">
                            ${pno}
                    </a></span></li>
            </c:forEach>
            <li><a href="/mgallery/page?p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}">下一页</a></li>
            <li><a href="/mgallery/page?p=${pageModel.totalPages}">尾页</a></li>
        </ul>
    </div>
</div>
<div class="footer">
    <p><span>P_luminary</span>©2023.10.3 POWERED BY GITHUB.COM</p>
</div>
</body>
</html>

实现分类查询功能 [改造代码]

从Dao层面进行对数据的过滤

PaintingDao.java
package com.example.mgallery.dao;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;
import com.example.mgallery.utils.XmlDataSource;

import java.util.ArrayList;
import java.util.List;

//获得最原始的 对其进行分页处理
public class PaintingDao {
    public PageModel pagination(int page, int rows){
        //Painting油画对象集合
        List<Painting> list = XmlDataSource.getRawData();
        //PageModel分页处理得到分页数据及分页附加
        PageModel pageModel = new PageModel(list,page,rows);
        return pageModel;
    }

    public PageModel pagination(int catagory, int page, int rows){ //int catagory添加Dao层对数据进行筛选
        List<Painting> list = XmlDataSource.getRawData();
        List<Painting> categoryList = new ArrayList();
        for (Painting p : list){
            //如果等于从外侧添加的筛选条件 则添加categoryList内
            if (p.getCategory() == catagory){
                categoryList.add(p);
            }
        }
        PageModel pageModel = new PageModel(categoryList,page,rows);
        return  pageModel;
    }
}
----------------------------------------------------------------------
PaintingService.java
package com.example.mgallery.service;

import com.example.mgallery.dao.PaintingDao;
import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;

import java.util.List;
//完成业务逻辑 service与dao进行传递调用
public class PaintingService {
    private PaintingDao paintingDao = new PaintingDao();
    public PageModel pagination(int page, int rows, String...category){  //最后一个是添加 可选参数(可能/不一定出现一个或多个)
        if (rows == 0){
            throw new RuntimeException("无效的rows参数");
        }
        if (category.length==0 || category[0] == null){
        return paintingDao.pagination(page, rows); //调用并返回
        }else { //程序进行可选调用 两个不同路径 尽量不要去修改类结构
            return paintingDao.pagination(Integer.parseInt(category[0]), page, rows);
        }
    }

    public static void main(String[] args) {
        PaintingService paintingService = new PaintingService();
        PageModel pageModel = paintingService.pagination(2, 6);//每页显示六条
        List<Painting> paintingList = pageModel.getPageData();
        for (Painting painting : paintingList){
            System.out.println(painting.getPname());
        }
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow());

    }
}
---------------------------------------------------------------------
PaintingController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class PaintingController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();
    public PaintingController(){
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.接收Http数据
        String page = req.getParameter("p"); //页号
        String rows = req.getParameter("r"); //每页记录数
        String category = req.getParameter("c");
        if (page == null){
            page = "1"; //没传入则默认查询第一个
        }if (rows == null){
            rows = "6"; //每页默认显示六条数据
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(page), Integer.parseInt(rows), category);
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(req,resp); //跳转jsp
    }
}
---------------------------------------------------------------------
webapp/WEB-INF/jsp/index.jsp
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="css\common.css">
    <script type="text/javascript" src="js\js1.js"></script>
</head>
<body>
<%--JSTL混合书写判断 判断c是否存在 存在追加链接--%>
<c:if test="${param.c != null}">
    <c:set var = "categoryParam" value="&c=${param.c}"></c:set>
</c:if>
<c:if test="${param.c == null}">
    <c:set var = "categoryParam" value=""></c:set>
</c:if>
<div class="header">
    <div class="logo">
        <img src="image\logo.png">
    </div>
    <div class="menu"   onclick="show_menu()" onmouseleave="show_menu1()">
        <div class="menu_title" ><a href="###">内容分类</a></div>
        <ul id="title">
            <li><a href="/mgallery/page?c=1">现实主义</a></li>
            <li><a href="/mgallery/page?c=2">抽象主义</a></li>
        </ul>
    </div>
    <div class="auth">
        <ul>
            <li><a href="#">登录</a></li>
            <li><a href="#">注册</a></li>
        </ul>
    </div>
</div>
<div class="content">
    <div class="banner">
        <img src="image/welcome.png" class="banner-img">
    </div>
    <div class="img-content">
        <ul>
            <c:forEach items="${pageModel.pageData}" var="painting">
                <li>
                    <img src="${painting.preview}" class="img-li">
                    <div class="info">
                        <h3>${painting.pname}</h3>
                        <p>
                                ${painting.description}
                        </p>
                        <div class="img-btn">
                            <div class="price"><fmt:formatNumber pattern="¥0.00" value="${painting.price}"></fmt:formatNumber></div>
                            <a href="#" class="cart">
                                <div class="btn">
                                    <img src="image/cart.svg">
                                </div>
                            </a>
                        </div>
                    </div>
                </li>
            </c:forEach>
        </ul>
    </div>
    <div class="page-nav">
        <ul>
            <li><a href="/mgallery/page?p=1${categoryParam}">首页</a></li> <!--当前页减一就是上一页 否则就是1-->
            <li><a href="/mgallery/page?p=${pageModel.hasPreviousPage?pageModel.page-1:1}${categoryParam}">上一页</a></li>
            <c:forEach begin="1" end="${pageModel.totalPages}" var="pno" step="1">
                <li><span ${pno==pageModel.page?"class='first-page'":""}> <!--选中的页才有⚪圈圈 三目运算符不满足产生空字符串-->
                     <%--  c不存在,则href="/mgallery/page?p=1"  c存在,测href="/mgallery/page?p=1&c=1"  --%>
                    <a href="/mgallery/page?p=${pno}${categoryParam}">
                            ${pno}
                    </a></span></li>
            </c:forEach>
            <li><a href="/mgallery/page?p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}${categoryParam}">下一页</a></li>
            <li><a href="/mgallery/page?p=${pageModel.totalPages}${categoryParam}">尾页</a></li>
        </ul>
    </div>
</div>
<div class="footer">
    <p><span>P_luminary</span>©2023.10.3 POWERED BY GITHUB.COM</p>
</div>
</body>
</html>
利用默认首页跳转到任何地址上 [常用开发技巧]
index.html
<script>
    window.location.href="/page"
</script>

实现后台数据管理

前台与后台的区别
前台系统 后台系统
开放度 对外开放 不对外开放
面向群体 客户 企业内部人员
功能 提供查询与交互 管理前台数据
设计侧重点 良好的用户体验 严密的业务逻辑
访问量
典型案例 猫眼电影网 [后台有评论审核系统] XX公司无纸质化办公系统
后台实现功能

油画列表 油画预览 删除油画 修改油画 新增与上传油画

实现油画列表功能

重用pagination方法实现分页列表

原有的PaintingController不可以继续被用 术业有专攻 要再创建一个使用

ManagementController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();
    public ManagementController() {
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        String method = req.getParameter("method");
        if (method.equals("list")){ //去显示分页的数据
            this.list(req, resp);
        } else if (method.equals("delete")) {
            // this.delete(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p==null){
            p = "1";
        }
        if (r==null){
            r = "6";
        }
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel", pageModel);
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req, resp);
    }
}
management.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>门户数据管理平台</title>
    <style>
        body{
            margin: 0 auto;
        }
        a{
            text-decoration: none;
        }
        .left{
            float: left;
        }
        .right{
            float: right;
        }
 
        .pg_header{
            position: fixed;
            top: 0;
            width: 100%;
            height: 48px;
            background-color: lightseagreen;
            color: white;
            z-index: 10;
        }
        .pg_header .logo{
            height: 48px;
            width: 200px;
            text-align: center;
            color: white;
        }
        .pg_header .logo a img{
            height: 48px;
            width: 200px;
            cursor: pointer;
        }
        .pg_header .person_info{
            position: relative;
            height: 48px;
            width: 160px;
            /*text-align: center;*/
        }
        .pg_header .person_info img{
            border: 0;
            height: 48px;
            width: 50px;
            /*使用border-radius可以定义边框的圆角程度*/
            border-radius: 50%;
        }
        .pg_header .person_info .info{
            position: absolute;
            width: 150px;
            background-color: lightseagreen;
            top: 50px;
            z-index: 20;
            display: none;
        }
        .pg_header .person_info .info a{
            display: block;
            color: white;
            padding: 5px;
        }
        .pg_header .person_info:hover .info{
            display: block;
        }
        .pg_header .icons{
            line-height: 48px;
            padding: 0 20px 0 5px;
        }
        .pg_header .icons:hover{
            background-color: lightseagreen;
        }
        .pg_header .icons span{
            padding: 1px 5px;
            line-height: 1px;
            border-radius: 50%;
            background-color: red;
            font-size: 12px;
        }
        .pg_content .menu{
            position: absolute;
            top: 50px;
            left: 0;
            bottom: 0;
            width: 300px;
            border:0px;
            border-right: 1px solid #ccc;
        }
        .pg_content .content{
            position: absolute;
            top: 50px;
            right: 0;
            bottom: 0;
            left: 302px;
            overflow: auto;
            min-width: 980px;
            z-index: 8;
            border:0px;
            overflow: hidden;
        }
        .menu_item{
            display: block;
            padding: 10px 20px;
            border-bottom: 1px solid #ccc;
            font-size: 20px; 
            color: #666666;
        }
        
        .menu_item:hover{
            color: white;
            background: lightseagreen;
        }
    </style>
</head>
<body>
    <!-- 顶端导航栏 -->
    <div class="pg_header">
        <!-- Logo与名称 -->
        <div class="logo left">
            <a href="javascript:void(0)" target="_blank">
                <img src="image/logo_1.png">    
            </a>
            
        </div>
        
        <!-- 用户头像与菜单 -->
        <div class="person_info right" style="vertical-align: top;" >
            <img src="image/head.png">
            <span style="line-height: 50px;vertical-align: top;">小企鹅</span>
            <div class="info">
                <a href="javascript:void(0)">我的信息</a>
                <a href="javascript:void(0)">修改密码</a>
                <a href="javascript:void(0)">注销</a>
            </div>
        </div>
        <div class="icons right">
            <i class="far fa-bell"></i>
        </div>
    </div>
    <div class="pg_content">
        <!-- 左侧功能区菜单 -->
        <div class="menu">
            <a href = "#" class="menu_item" target="ifcontent">油画列表</a>
            <a href = "#" class="menu_item" target="ifcontent">新增作品</a>
        </div>
        <!-- 主体框架 -->
        <div class="content">
             <iframe name="ifcontent" style="width:100%;height:100%;overflow-y: hidden;border:0px;min-width: 800px;" src=""></iframe>
        </div>
    </div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>门户数据管理平台</title>
    <style>
        body{
            margin: 0 auto;
        }
        a{
            text-decoration: none;
        }
        .left{
            float: left;
        }
        .right{
            float: right;
        }

        .pg_header{
            position: fixed;
            top: 0;
            width: 100%;
            height: 48px;
            background-color: lightseagreen;
            color: white;
            z-index: 10;
        }
        .pg_header .logo{
            height: 48px;
            width: 200px;
            text-align: center;
            color: white;
        }
        .pg_header .logo a img{
            height: 48px;
            width: 200px;
            cursor: pointer;
        }
        .pg_header .person_info{
            position: relative;
            height: 48px;
            width: 160px;
            /*text-align: center;*/
        }
        .pg_header .person_info img{
            border: 0;
            height: 48px;
            width: 50px;
            /*使用border-radius可以定义边框的圆角程度*/
            border-radius: 50%;
        }
        .pg_header .person_info .info{
            position: absolute;
            width: 150px;
            background-color: lightseagreen;
            top: 50px;
            z-index: 20;
            display: none;
        }
        .pg_header .person_info .info a{
            display: block;
            color: white;
            padding: 5px;
        }
        .pg_header .person_info:hover .info{
            display: block;
        }
        .pg_header .icons{
            line-height: 48px;
            padding: 0 20px 0 5px;
        }
        .pg_header .icons:hover{
            background-color: lightseagreen;
        }
        .pg_header .icons span{
            padding: 1px 5px;
            line-height: 1px;
            border-radius: 50%;
            background-color: red;
            font-size: 12px;
        }
        .pg_content .menu{
            position: absolute;
            top: 50px;
            left: 0;
            bottom: 0;
            width: 300px;
            border:0px;
            border-right: 1px solid #ccc;
        }
        .pg_content .content{
            position: absolute;
            top: 50px;
            right: 0;
            bottom: 0;
            left: 302px;
            overflow: auto;
            min-width: 980px;
            z-index: 8;
            border:0px;
            overflow: hidden;
        }
        .menu_item{
            display: block;
            padding: 10px 20px;
            border-bottom: 1px solid #ccc;
            font-size: 20px;
            color: #666666;
        }

        .menu_item:hover{
            color: white;
            background: lightseagreen;
        }
    </style>
</head>
<body>
<!-- 顶端导航栏 -->
<div class="pg_header">
    <!-- Logo与名称 -->
    <div class="logo left">
        <a href="javascript:void(0)" target="_blank">
            <img src="image/logo_1.png">
        </a>

    </div>

    <!-- 用户头像与菜单 -->
    <div class="person_info right" style="vertical-align: top;" >
        <img src="image/head.png">
        <span style="line-height: 50px;vertical-align: top;">小企鹅</span>
        <div class="info">
            <a href="javascript:void(0)">我的信息</a>
            <a href="javascript:void(0)">修改密码</a>
            <a href="javascript:void(0)">注销</a>
        </div>
    </div>
    <div class="icons right">
        <i class="far fa-bell"></i>
    </div>
</div>
<div class="pg_content">
    <!-- 左侧功能区菜单 -->
    <div class="menu">
        <a href = "/mgallery/management?method=list" class="menu_item" target="ifcontent">油画列表</a>
        <a href = "#" class="menu_item" target="ifcontent">新增作品</a>
    </div>
    <!-- 主体框架 -->
    <div class="content">
        <iframe name="ifcontent" style="width:100%;height:100%;overflow-y: hidden;border:0px;min-width: 800px;" src="/mgallery/management?method=list"></iframe>
    </div>
</div>
</body>
</html>

利用SweetAlert实现预览功能 [美观对话框]

正确使用 equals 方法避免产生空指针异常 - 简书 (jianshu.com)

list.jsp
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>油画列表</title>
    <script src="js\jquery-3.4.1.min.js" type="text/javascript"></script>
    <script src="js\sweetalert2.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="css\list.css">
    <script type="text/javascript">
        //对话框显示预览 // 变成jquary对象 对自定义属性名进行提取
        function showPreview(previewObj){
            var preview = $(previewObj).attr("data-preview");
            var pname = $(previewObj).attr("data-pname");
            Swal.fire({
                title: pname,
                html : "<img src='" + preview + "' style='width:361px;height:240px'>",
                showCloseButton: true,
                showConfirmButton: false
            })
        }
    </script>
</head>
<body>
    <div class="container">
        <fieldset>
            <legend>油画列表</legend>
            <div style="height: 40px">
                <a href="#" class="btn-button">新增</a>
            </div>
            <!-- 油画列表 -->
            <table cellspacing="0px">
                <thead>
                    <tr style="width: 150px;">
                        <th style="width: 100px">分类</th>
                        <th style="width: 150px;">名称</th>
                        <th style="width: 100px;">价格</th>
                        <th style="width: 400px">描述</th>
                        <th style="width: 100px">操作</th>
                    </tr>
                </thead>
                <c:forEach items="${pageModel.pageData }" var="painting">
                    <tr>
                        <td>
                            <c:choose>
                                <c:when test="${painting.category==1 }">现实主义</c:when>
                                <c:when test="${painting.category==2 }">抽象主义</c:when>
                                <c:otherwise>未知的类型</c:otherwise>
                            </c:choose>
                        </td>
                        <td>${painting.pname }</td>
                        <td><fmt:formatNumber pattern="¥0.00" value="${painting.price }"></fmt:formatNumber> </td>
                        <td>${painting.description }</td>
                        <td> <!-- 自定义用 data-preview data-pname    this就是指向这个超链接的本身 -->
                            <a class="oplink" data-preview="${painting.preview}" data-pname = "${painting.pname}" href="javascript:void(0)" onclick="showPreview(this)">预览</a>
                            <a class="oplink" href="#">修改</a>
                            <a class="oplink" href="#">删除</a>
                        </td>
                    </tr>
                </c:forEach>
            </table>
            <!-- 分页组件 -->
            <ul class="page">
                <li><a href="/mgallery/management?method=list&p=1">首页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasPreviousPage?pageModel.page-1:1}">上页</a></li>
                <c:forEach begin="1" end="${pageModel.totalPages }" step="1" var="pno">
                    <li ${pno==pageModel.page?"class='active'":""}>
                        <a href="/mgallery/management?method=list&p=${pno }">${pno }</a>
                    </li>
                </c:forEach>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}">下页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.totalPages}">尾页</a></li>
            </ul>
        </fieldset>
    </div>

</body>
</html>

处理文件上传页面 [表单校验 文件上传 处理XML文件]

  • 利用Apache Commons FileUpload组件实现上传功能
  • 封装可重用的js脚本解决表单校验问题
  • 基于Dom4j对XML文件进行追加操作
create.jsp
<!-- 新增油画页面 -->
<%@page contentType = "text/html;charset=utf-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>新增油画</title>
<link rel="stylesheet" type="text/css" href="css\create.css">
<script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
</head>
<body>
    <div class="container">
        <fieldset>
            <legend>新增油画</legend>
            <form action="" method="post"
                autocomplete="off" enctype="multipart/form-data">
                <ul class="ulform">
                    <li>
                        <span>油画名称</span>
                        <span id="errPname"></span>
                        <input id="pname" name="pname" />
                    </li>
                    <li>
                        <span>油画类型</span>
                        <span id="errCategory"></span>
                        <select id="category" name="category">
                            <option value="-1">请选择油画类型</option>
                            <option value="1">现实主义</option>
                            <option value="2">抽象主义</option>
                        </select>
                    </li>
                    <li>
                        <span>油画价格</span>
                        <span id="errPrice"></span>
                        <input id="price" name="price"/>
                    </li>
                    <li>
                        <span>作品预览</span>
                        <span id="errPainting"></span>
                        <input id="painting" name="painting" type="file" 
                            style="padding-left: 0px;" accept="image/*" />
                    </li>

                    <li>
                        <span>详细描述</span>
                        <span id="errDescription"></span>
                        <textarea
                            id="description" name="description"></textarea>
                    </li>
                    <li style="text-align: center;">
                        <button type="submit" class="btn-button">提交表单</button>
                    </li>
                </ul>
            </form>
        </fieldset>
    </div>

</body>
</html>
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
}

文件上传必要前提条件

  • form表单method = “post 因为二进制文件不可以放在url中传递
  • form表单enctype = “multipart/form-data 允许保存二进制数据存放在请求体中发送到服务端
    enctype=”application/x-www-form-urlencoded 采用url编码的方式以字符串的形式将数据保存在请求体中发送到服务器
  • form表单持有file类型input进行文件选择

Apache Commons FileUpload [简化文件上传]

在java服务器端完成上传文件的处理
  • FileUpload组件提供了java文件上传底层支持

FileItem其本质就是对前台数据进行封装

ManagementController.java
package com.example.mgallery.controller;

import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        } else if (Objects.equals(method, "create")) {
            this.create(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    //显示新增页面
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
    //新增油画方法
    private void create(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*文件上传时的数据处理与标准表单完全不同
        String pname = req.getParameter("pname"); form表单enctype = "multipart/form-data"运用后 无法得到字符串格式的数据
        System.out.println(pname);
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); 请求转发*/
        //1.初始化FileUpload组件 包含表单项的数据对象
        FileItemFactory factory = new DiskFileItemFactory();
        /**
         * FileItemFactory 用于将前端表单的数据转换为一个个FileItem对象
         * ServletFileUpload 是为FileUpload组件提供Java web的Http请求解析
         */
        ServletFileUpload sf = new ServletFileUpload(factory);
        //2.遍历所有FileItem
        try {
            List<FileItem> formData = sf.parseRequest(req);//将表单数据转换为FileItem对象 和前台输入项一一对应
            //区分哪个是普通对象 哪个是文件对象
            for (FileItem fi : formData){
                if (fi.isFormField() == true){//普通输入框
                    System.out.println("普通输入项:" + fi.getFieldName() + ":" + fi.getString("UTF-8"));
                }else { //文件上传框
                    System.out.println("文件上传项:" + fi.getFieldName()); //没有文本数值不用输出
                    //3.文件保存到服务器目录 已经确定了文件上传项
                    String path = req.getServletContext().getRealPath("/upload");
                    System.out.println("上传文件目录:" + path);
//                    String fileName = "test.jpg";
                    String fileName = UUID.randomUUID().toString();//随机生成文件名 根据计算机的特性 生成随机唯一字符串
                    //fi.getName()得到原始文件名, 截取最后一个"."后所有字符串,例如:wxml.jpg -> .jpg
                    String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
                    fi.write(new File(path, fileName + suffix)); //传入文件对象会自动的帮我们把客户端上传的文件传到服务器某个文件中
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Dom4j写操作开发流程

  • **SAXReader.read()**读取XML文件,得到Document对象
  • p=root,addElement(“painting”) - 创建新的节点
  • p.addElement(“pname”) - 创建新的子节点
  • document.write(writer) - 向XML写入新的节点
请求转发
请求转发是一种服务器端的行为,通过request对象来完成操作。当客户端发送请求以后,对应的Servlet会做出业务逻辑处理,然后调用forward()方法,将请求发送到服务器中另外的Servlet中去。

实现方法:
request.getRequestDispatcher("URL地址").forward(request,response);
响应重定向
响应重定向是一种客户端的行为,通过response对象来完成操作。当客户端第一次发出请求后,服务器中Servlet接收请求以后,通过调用sendRedirect()方法指定另一个Servlet,此时客户端会根据路径再次发出请求访问下一个Servlet,服务器再次接收请求后做出响应返回给客户端。

实现方法:
response.sendRedirect("URL地址")
请求转发与响应重定向的区别
1、请求转发以后浏览器URL地址栏不变,响应重定向以后浏览器URL地址栏发生改变
2、请求转发是服务器端行为,响应重定向是客户端行为
3、请求转发只做了一次访问请求,得到一次响应;响应重定向是做了两次请求,得到两次响应
4、请求转发是在服务器内部进行的,所以不可以跨域访问;响应重定向可以做到跨域访问
5、请求转发可以使用request作用域共享数据;响应重定向不可以使用request作用域,但是可以使用session域共享资源
XmlDataSource.java[新增了append追加操作]
package com.example.mgallery.utils;

import com.example.mgallery.entity.Painting;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

//用于将XML文件解析为Java对象
public class XmlDataSource {
    //通过static静态关键字保证数据全局唯一
    private static List data = new ArrayList(); //油画集合
    private static String dataFile;

    static { //程序运行以后去得到类路径目录下的/painting.xml的路径地址
        dataFile = XmlDataSource.class.getResource("/painting.xml").getPath();
        reload(); //在所有的写入操作以后都要写入
    }
    private static void reload(){
        //若得到特殊字符进行编码转换 空格 c:\new style\painting.xml
        try {
            URLDecoder.decode(dataFile, "UTF-8");
            System.out.println(dataFile);
            //利用Dom4j对XML进行解析 读取XML
            SAXReader reader = new SAXReader();
            //1.获取Document文档对象
            Document document = reader.read(dataFile);
            //2.Xpath得到XML节点集合 获取多个xml节点
            List<Node> nodes = document.selectNodes("/root/painting");
            data.clear(); //清空 在空的数据基础上重新添加
            for (Node node : nodes) {
                Element element = (Element) node;
                String id = element.attributeValue("id");//获得id
                String pname = element.elementText("pname");//获得子节点
                Painting painting = new Painting();
                painting.setId(Integer.parseInt(id));
                painting.setPname(pname);
                painting.setCategory(Integer.parseInt(element.elementText("category")));
                painting.setPrice(Integer.parseInt(element.elementText("price")));
                painting.setPreview(element.elementText("preview"));
                painting.setDescription(element.elementText("description"));
                data.add(painting);//将List data 保存油画的完整信息

                System.out.println(id + ";" + pname);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取所有油画Painting对象
     *
     * @return Painting List
     */
    public static List<Painting> getRawData() {
        return data;
    }

    //Dom4j实现XML追加操作
    public static void append(Painting painting) { //末尾追加新的油画
        //1.读取XML文档,得到Document对象
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            //2.创建新的painting节点
            Element root = document.getRootElement();//得到原始文档xml根节点 <root>
            Element p = root.addElement("painting");//创建一个新的子节点
            //3.创建painting节点的各个子节点
            p.addAttribute("id", String.valueOf(data.size() + 1)); //生成新的id对象
            p.addElement("pname").setText(painting.getPname()); //返回新的节点 设置其文本值
            p.addElement("category").setText(painting.getCategory().toString());
            p.addElement("price").setText(painting.getPrice().toString());
            p.addElement("preview").setText(painting.getPreview());
            p.addElement("description").setText(painting.getDescription());
            //4.写入XML,完成追加操作
            writer = new OutputStreamWriter(new FileOutputStream(dataFile), "UTF-8");
            document.write(writer); //向目标dataFile原始xml中进行新节点的更新
            System.out.println(dataFile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) { //write有开就有关 已经被实例化
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace(); //遇到异常 打印堆栈
                }
            }
            //清空 在空的数据基础上重新添加 无论成功与否都会重新加载数据 如果照片没找到 就清空数据
            reload();
        }

    }

    public static void main(String[] args) {
//        new XmlDataSource();
//        List<Painting> ps = XmlDataSource.getRawData();
//        System.out.println(ps);
        Painting p = new Painting();
        p.setPname("油画测试");
        p.setCategory(1);
        p.setPrice(4000);
        p.setPreview("upload/10.jpg");
        p.setDescription("测试油画描述");
        XmlDataSource.append(p);
    }
}
ManagementController.java[新增了create方法]
package com.example.mgallery.controller;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        } else if (Objects.equals(method, "create")) {
            this.create(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    //显示新增页面
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
    //新增油画方法
    private void create(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*文件上传时的数据处理与标准表单完全不同
        String pname = req.getParameter("pname"); form表单enctype = "multipart/form-data"运用后 无法得到字符串格式的数据
        System.out.println(pname);
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); 请求转发*/
        //1.初始化FileUpload组件 包含表单项的数据对象
        FileItemFactory factory = new DiskFileItemFactory();
        /**
         * FileItemFactory 用于将前端表单的数据转换为一个个FileItem对象
         * ServletFileUpload 是为FileUpload组件提供Java web的Http请求解析
         */
        ServletFileUpload sf = new ServletFileUpload(factory);
        //2.遍历所有FileItem
        try {
            List<FileItem> formData = sf.parseRequest(req);//将表单数据转换为FileItem对象 和前台输入项一一对应
            Painting painting = new Painting();//进行封装
            //区分哪个是普通对象 哪个是文件对象
            for (FileItem fi : formData){
                if (fi.isFormField() == true){//普通输入框
                    System.out.println("普通输入项:" + fi.getFieldName() + ":" + fi.getString("UTF-8"));
                    switch (fi.getFieldName()) { //得到字段名
                        case "pname":
                            painting.setPname(fi.getString("UTF-8"));
                            break;
                        case "category":
                            painting.setCategory(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "price":
                            painting.setPrice(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "description":
                            painting.setDescription(fi.getString("UTF-8"));
                            break;
                        default:
                            break;
                    }
                }else { //文件上传框
                    System.out.println("文件上传项:" + fi.getFieldName()); //没有文本数值不用输出
                    //3.文件保存到服务器目录 已经确定了文件上传项
                    String path = req.getServletContext().getRealPath("/upload");
                    System.out.println("上传文件目录:" + path);
//                    String fileName = "test.jpg";
                    String fileName = UUID.randomUUID().toString();//随机生成文件名 根据计算机的特性 生成随机唯一字符串
                    //fi.getName()得到原始文件名, 截取最后一个"."后所有字符串,例如:wxml.jpg -> .jpg
                    String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
                    fi.write(new File(path, fileName + suffix)); //传入文件对象会自动的帮我们把客户端上传的文件传到服务器某个文件中
                    painting.setPreview("upload/" + fileName + suffix); //形成一个完整的可以访问的url地址
                }
            }
            paintingService.create(painting); //新增功能
            //若弹出另一个页面进行另外一个操作 新页面以后的后续操作 和前面的操作紧密联系的 用请求转发 当前请求給下一个功能继续操作
            resp.sendRedirect("/mgallery/management?method=list");//响应重定向跳转列表页继续相应的处理 跟前面的新增功能联系不那么紧密
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

开发表单校验JS脚本[可重用的表单校验]

  • 以独立的js脚本文件存储可被不同表单项重用的校验规则
  • 每一个校验逻辑均独立存储
  • 可自定义触发时机,例如:失去焦点、选择时、提交前…
实现思路
/*
检查是否为空 input表单域选择器    errSelector错误提示选择器    true-校验成功 false-校验失败
*/
function checkEmpty(input, errSelector){
    var val = $(input).val();
    if($.trim(val)==""){
        switchValid(false, input, errSelector, "请输入内容")
        return false;
    }else{
        switchValid(true, input, errSelector);
        return true;
    }
}
调用方法1 失去焦点的时候触发onblur
<span>详细描述</span>
<span id="errDescription"></span>
<textarea id="description" name="description" onblur="checkEmpty('#description','#errDescription')"></textarea>
调用方法2 在提交前组合校验
<script type="text/javascript">
    function checkSubmit(){ //提交前表单校验
        var result = true;
        var r1 = checkPname('#pname','#errPname',-1); //检查名称
        var r2 = checkCategory('#category','#errCategory'); //检查分类
        var r3 = checkPrice('#price','#errPrice'); //检查价格
        var r4 = checkFile('#painting', '#errPainting'); //检查上传文件
        var r5 = checkEmpty('#description', '#errDescription'); //检查描述
        if(r1 && r2 && r3 && r4 && r5){//所有检查通过允许提交表单
            return true;
        }else{
            return false;
        }
    }
</script>
validation.js 且在create.jsp中把引用的标签打上js状态 onblur/onchange
/**
 * 隐藏、显示错误信息
 * @param onOff true 验证成功, 隐藏错误     false 校验失败,显示错误
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @param message 错误信息
 * @returns
 */
function switchValid(onOff, input, errSelector, message) {
    if (onOff == false) {
        $(errSelector).text(message);
        $(input).addClass("error_input"); //错误标红
        $(errSelector).addClass("error_message");
    } else {
        $(errSelector).text("");
        $(input).removeClass("error_input"); //错误标红
        $(errSelector).removeClass("error_message");
    }
}

/**
 * 检查是否为空
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @returns true-校验成功 false-校验失败
 */
function checkEmpty(input, errSelector) {
    var val = $(input).val(); //获取当前选择的数值
    if ($.trim(val) == "") { //将字符串前后空格删掉
        switchValid(false, input, errSelector, "请输入内容"); //非空校验失败时显示错误
        return false;
    } else {
        switchValid(true, input, errSelector); //正确情况
        return true;
    }
}

function checkCategory(input, errSelector) {
    var val = $(input).val(); //获取当前选择的数值
    if (val == -1) {
        switchValid(false, input, errSelector, "请选择油画类型"); //非空校验失败时显示错误
        return false;
    } else {
        switchValid(true, input, errSelector); //正确情况
        return true;
    }
}

/**
 * 价格必须是整数
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @returns true-校验成功 false-校验失败
 */
function checkPrice(input, errSelector) {
    var val = $(input).val(); //获取当前选择的数值
    var regex = /^[1-9][0-9]*$/   //利用正则表达式进行校验信息
    if (!regex.test(val)) {
        switchValid(false, input, errSelector, "无效的价格"); //非空校验失败时显示错误
        return false;
    } else {
        switchValid(true, input, errSelector); //正确情况
        return true;
    }
}

/**
 * 上传文件必须是图片
 * @param input 表单域选择器
 * @param errSelector 错误提示选择器
 * @returns true-校验成功 false-校验失败
 */
function checkFile(input, errSelector) {
    if (checkEmpty(input, errSelector) == false) {
        return false;
    }
    var val = $(input).val().toLowerCase()//小写转换 PNG png
    if (val.length < 4) { //x.xxxx
        switchValid(false, input, errSelector, "请选择有效的图片"); //非空校验失败时显示错误
        return false;
    }
    suffix = val.substring(val.length - 3); //拿到最后的扩展名
    if (suffix == "jpg" || suffix == "png" || suffix == "gif") {
        switchValid(true.input, errSelector);
        return true;
    }else {
        switchValid(false, input, errSelector, "请选择有效的图片");
        return false;
    }
}

实现新增油画功能[表单submit全验证]

添加一个全局验证
create.jsp
<!-- 新增油画页面 -->
<%@page contentType="text/html;charset=utf-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>新增油画</title>
    <link rel="stylesheet" type="text/css" href="css\create.css">
    <script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="js/validation.js"></script>
    <script type="text/javascript">
        function checkSubmit() {
            var result = true;
            var r1 = checkEmpty("#pname", "#errPname");
            var r2 = checkCategory('#category', '#errCategory');
            var r3 = checkPrice('#price', '#errPrice');
            var r4 = checkFile('#painting', '#errPainting');
            var r5 = checkEmpty('#description', '#errDescription');
            if (r1 && r2 && r3 && r4 && r5) {
                return true;
            }else {
                return false;
            }
        }
    </script>
</head>
<body>
<div class="container">
    <fieldset>
        <legend>新增油画</legend>
        <form action="/mgallery/management?method=create" method="post"
              autocomplete="off" enctype="multipart/form-data" onsubmit="return checkSubmit()">
            <ul class="ulform">
                <li>
                    <span>油画名称</span>
                    <span id="errPname"></span>
                    <input id="pname" name="pname" onblur="checkEmpty('#pname','#errPname')"> <!--在失去焦点时触发-->
                </li>
                <li>
                    <span>油画类型</span>
                    <span id="errCategory"></span>
                    <select id="category" name="category" onchange="checkCategory('#category','#errCategory')">
                        <option value="-1">请选择油画类型</option>
                        <option value="1">现实主义</option>
                        <option value="2">抽象主义</option>
                    </select>
                </li>
                <li>
                    <span>油画价格</span>
                    <span id="errPrice"></span>
                    <input id="price" name="price" onblur="checkPrice('#price','#errPrice')">
                </li>
                <li>
                    <span>作品预览</span>
                    <span id="errPainting"></span>
                    <input id="painting" name="painting" type="file"
                           style="padding-left: 0px;" accept="image/*"
                           onchange="checkFile('#painting','#errPainting')"/>
                    <%--    accept="image/*" 默认保留所有图片格式的文件--%>
                </li>

                <li>
                    <span>详细描述</span>
                    <span id="errDescription"></span>
                    <textarea
                            id="description" name="description"
                            onblur="checkEmpty('#description','#errDescription')"></textarea>
                </li>
                <li style="text-align: center;">
                    <button type="submit" class="btn-button">提交表单</button>
                </li>
            </ul>
        </form>
    </fieldset>
</div>

</body>
</html>
management.html  和  list.jsp 添加href超链接 
客户点击新增的时候可以跳转到新增页面 
<a href = "/mgallery/management?method=show_create"></a>

实现修改页表单数据回填

修改实现思路
  • 修改与新增的最大不同是在修改前要加载原有数据
  • 在修改页面放置hidden隐藏域保存id编号,随表单提交
  • 对XML更新时,先按id获取原始记录,在此基础上覆盖更新

Dao service

PaintingService.java
package com.example.mgallery.service;

import com.example.mgallery.dao.PaintingDao;
import com.example.mgallery.entity.Painting;
import com.example.mgallery.utils.PageModel;

import java.util.List;
//完成业务逻辑 service与dao进行传递调用
public class PaintingService {
    private PaintingDao paintingDao = new PaintingDao();
    public PageModel pagination(int page, int rows, String...category){  //最后一个是添加 可选参数(可能/不一定出现一个或多个)
        if (rows == 0){
            throw new RuntimeException("无效的rows参数");
        }
        if (category.length==0 || category[0] == null){
        return paintingDao.pagination(page, rows); //调用并返回
        }else { //程序进行可选调用 两个不同路径 尽量不要去修改类结构
            return paintingDao.pagination(Integer.parseInt(category[0]), page, rows);
        }
    }

    public void create(Painting painting){
        paintingDao.create(painting);
    }
    // 按编号查询油画 id油画编号 return油画对象
    public Painting findById(Integer id){
        Painting p = paintingDao.findById(id);
        if (p==null){
            throw new RuntimeException("[id=]" + id + "]油画不存在");
        }
        return p;
    }

    public static void main(String[] args) {
        PaintingService paintingService = new PaintingService();
        PageModel pageModel = paintingService.pagination(2, 6);//每页显示六条
        List<Painting> paintingList = pageModel.getPageData();
        for (Painting painting : paintingList){
            System.out.println(painting.getPname());
        }
        System.out.println(pageModel.getPageStartRow() + ":" + pageModel.getPageEndRow());

    }
}
update.jsp
<%@page contentType="text/html;charset=utf-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!-- 修改油画页面 -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>作品更新</title>
    <link rel="stylesheet" type="text/css" href="css\create.css">
    <script type="text/javascript" src="js/jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="js/validation.js"></script>
    <script type="text/javascript">
        <!-- 提交前表单校验 -->
        function checkSubmit() {
            var result = true;
            var r1 = checkEmpty("#pname", "#errPname");
            var r2 = checkCategory('#category', '#errCategory');
            var r3 = checkPrice('#price', '#errPrice');
            var r4 = checkFile('#painting', '#errPainting');
            var r5 = checkEmpty('#description', '#errDescription');
            if (r1 && r2 && r3 && r4 && r5) {
                return true;
            } else {
                return false;
            }
        }
        //整个html被解析完后执行代码
        $(function(){//前面的被html解释完才执行 后面的EL表达式jsp渲染在服务器端产生 油画类型默认产生
            $("#category").val(${painting.category})
        })
    </script>
</head>
<body>
<div class="container">
    <fieldset>
        <legend>作品名称</legend>
        <form action="[这里写更新URL]" method="post"
              autocomplete="off" enctype="multipart/form-data"
              onsubmit="return checkSubmit()">
            <ul class="ulform">
                <li>
                    <span>油画名称</span>
                    <span id="errPname"></span>
                    <input id="pname" name="pname" onblur="checkEmpty('#pname','#errPname')" value="${painting.pname}"/>
                </li>
                <li>
                    <span>油画类型</span>
                    <span id="errCategory"></span>
                    <select id="category" name="category" onchange="checkCategory('#category','#errCategory')"
                            value="${painting.category}">
                        <option value="-1">请选择油画类型</option>
                        <option value="1">现实主义</option>
                        <option value="2">抽象主义</option>
                    </select>
                </li>
                <li>
                    <span>油画价格</span>
                    <span id="errPrice"></span>
                    <input id="price" name="price" onblur="checkPrice('#price','#errPrice')" value="${painting.price}"/>
                    </li>
                    <li>
                        <span>作品预览</span>
                        <span id=" errPainting"></span><br/>
                    <img id="preview" src="${painting.preview}" style="width:361px;height:240px"/><br/>
                    <input id="painting" name="painting" type="file" style="padding-left:0px;" accept="image/*"/>
                </li>
                <li>
                    <span>详细描述</span>
                    <span id="errDescription"></span>
                    <textarea
                            id="description" name="description"
                            onblur="checkEmpty('#description','#errDescription')"
                    >
                        ${painting.description}
                    </textarea>
                </li>
                <li style="text-align: center;">
                    <button type="submit" class="btn-button">提交表单</button>
                </li>
            </ul>
        </form>
    </fieldset>
</div>

</body>
</html>
ManagmentController.java
package com.example.mgallery.controller;

import com.example.mgallery.entity.Painting;
import com.example.mgallery.service.PaintingService;
import com.example.mgallery.utils.PageModel;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

//实现增删改查
@WebServlet("/management")
public class ManagementController extends HttpServlet {
    private PaintingService paintingService = new PaintingService();

    public ManagementController() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        //通过method参数区分不同的操作
        String method = req.getParameter("method");
        if(Objects.equals(method,"list")) {//分页查询列表
            this.list(req,resp);
            //正确使用 equals 方法避免产生空指针异常https://www.jianshu.com/p/306de20dd228
        } else if (Objects.equals(method,"delete")) {
            //
        } else if (Objects.equals(method,"show_create")) {
            this.showCreatePage(req,resp); //带show的一定是跳转页面
        } else if (Objects.equals(method, "create")) {
            this.create(req, resp);
        } else if (Objects.equals(method,"show_update")) {
            this.showUpdatePage(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    // 控制器代码的实现
    private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String p = req.getParameter("p");
        String r = req.getParameter("r");
        if (p == null) {
            p = "1";
        }
        if (r == null) {
            r = "6";
        }
        //2.调用Service方法,得到处理结果      增加了按类型筛选category
        PageModel pageModel = paintingService.pagination(Integer.parseInt(p), Integer.parseInt(r));
        req.setAttribute("pageModel",pageModel);//数据解耦最关键的一步
        //3.请求转发至对应JSP(view)进行数据展现
        req.getRequestDispatcher("/WEB-INF/jsp/list.jsp").forward(req,resp); //跳转jsp
    }
    //显示新增页面
    private void showCreatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); //请求转发
    }
    //新增油画方法
    private void create(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*文件上传时的数据处理与标准表单完全不同
        String pname = req.getParameter("pname"); form表单enctype = "multipart/form-data"运用后 无法得到字符串格式的数据
        System.out.println(pname);
        req.getRequestDispatcher("/WEB-INF/jsp/create.jsp").forward(req, resp); 请求转发*/
        //1.初始化FileUpload组件 包含表单项的数据对象
        FileItemFactory factory = new DiskFileItemFactory();
        /**
         * FileItemFactory 用于将前端表单的数据转换为一个个FileItem对象
         * ServletFileUpload 是为FileUpload组件提供Java web的Http请求解析
         */
        ServletFileUpload sf = new ServletFileUpload(factory);
        //2.遍历所有FileItem
        try {
            List<FileItem> formData = sf.parseRequest(req);//将表单数据转换为FileItem对象 和前台输入项一一对应
            Painting painting = new Painting();//进行封装
            //区分哪个是普通对象 哪个是文件对象
            for (FileItem fi : formData){
                if (fi.isFormField() == true){//普通输入框
                    System.out.println("普通输入项:" + fi.getFieldName() + ":" + fi.getString("UTF-8"));
                    switch (fi.getFieldName()) { //得到字段名
                        case "pname":
                            painting.setPname(fi.getString("UTF-8"));
                            break;
                        case "category":
                            painting.setCategory(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "price":
                            painting.setPrice(Integer.parseInt(fi.getString("UTF-8")));
                            break;
                        case "description":
                            painting.setDescription(fi.getString("UTF-8"));
                            break;
                        default:
                            break;
                    }
                }else { //文件上传框
                    System.out.println("文件上传项:" + fi.getFieldName()); //没有文本数值不用输出
                    //3.文件保存到服务器目录 已经确定了文件上传项
                    String path = req.getServletContext().getRealPath("/upload");
                    System.out.println("上传文件目录:" + path);
//                    String fileName = "test.jpg";
                    String fileName = UUID.randomUUID().toString();//随机生成文件名 根据计算机的特性 生成随机唯一字符串
                    //fi.getName()得到原始文件名, 截取最后一个"."后所有字符串,例如:wxml.jpg -> .jpg
                    String suffix = fi.getName().substring(fi.getName().lastIndexOf("."));
                    fi.write(new File(path, fileName + suffix)); //传入文件对象会自动的帮我们把客户端上传的文件传到服务器某个文件中
                    painting.setPreview("upload/" + fileName + suffix); //形成一个完整的可以访问的url地址
                }
            }
            paintingService.create(painting); //新增功能
            //若弹出另一个页面进行另外一个操作 新页面以后的后续操作 和前面的操作紧密联系的 用请求转发 当前请求給下一个功能继续操作
            resp.sendRedirect("/mgallery/management?method=list");//响应重定向跳转列表页继续相应的处理 跟前面的新增功能联系不那么紧密
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //显示更新页面
    private void showUpdatePage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String id = req.getParameter("id");//前台传来的id号
        Painting painting = paintingService.findById(Integer.parseInt(id));
        req.setAttribute("painting", painting); //将得到的放入其中
        req.getRequestDispatcher("/WEB-INF/jsp/update.jsp").forward(req,resp);
    }
}
list.jsp
<ul class="page">
                <li><a href="/mgallery/management?method=list&p=1">首页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasPreviousPage?pageModel.page-1:1}">上页</a></li>
                <c:forEach begin="1" end="${pageModel.totalPages }" step="1" var="pno">
                    <li ${pno==pageModel.page?"class='active'":""}>
                        <a href="/mgallery/management?method=list&p=${pno }">${pno }</a>
                    </li>
                </c:forEach>
                <li><a href="/mgallery/management?method=list&p=${pageModel.hasNextPage?pageModel.page+1:pageModel.totalPages}">下页</a></li>
                <li><a href="/mgallery/management?method=list&p=${pageModel.totalPages}">尾页</a></li>
            </ul>

利用Dom4j对XML进行更新

XmlDataSource.java
package com.example.mgallery.utils;

import com.example.mgallery.entity.Painting;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

//用于将XML文件解析为Java对象
public class XmlDataSource {
    //通过static静态关键字保证数据全局唯一
    private static List data = new ArrayList(); //油画集合
    private static String dataFile;

    static { //程序运行以后去得到类路径目录下的/painting.xml的路径地址
        dataFile = XmlDataSource.class.getResource("/painting.xml").getPath();
        reload(); //在所有的写入操作以后都要写入
    }
    private static void reload(){
        //若得到特殊字符进行编码转换 空格 c:\new style\painting.xml
        try {
            URLDecoder.decode(dataFile, "UTF-8");
            System.out.println(dataFile);
            //利用Dom4j对XML进行解析 读取XML
            SAXReader reader = new SAXReader();
            //1.获取Document文档对象
            Document document = reader.read(dataFile);
            //2.Xpath得到XML节点集合 获取多个xml节点
            List<Node> nodes = document.selectNodes("/root/painting");
            data.clear(); //清空 在空的数据基础上重新添加
            for (Node node : nodes) {
                Element element = (Element) node;
                String id = element.attributeValue("id");//获得id
                String pname = element.elementText("pname");//获得子节点
                Painting painting = new Painting();
                painting.setId(Integer.parseInt(id));
                painting.setPname(pname);
                painting.setCategory(Integer.parseInt(element.elementText("category")));
                painting.setPrice(Integer.parseInt(element.elementText("price")));
                painting.setPreview(element.elementText("preview"));
                painting.setDescription(element.elementText("description"));
                data.add(painting);//将List data 保存油画的完整信息

                System.out.println(id + ";" + pname);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取所有油画Painting对象
     *
     * @return Painting List
     */
    public static List<Painting> getRawData() {
        return data;
    }

    //Dom4j实现XML追加操作
    public static void append(Painting painting) { //末尾追加新的油画
        //1.读取XML文档,得到Document对象
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            //2.创建新的painting节点
            Element root = document.getRootElement();//得到原始文档xml根节点 <root>
            Element p = root.addElement("painting");//创建一个新的子节点
            //3.创建painting节点的各个子节点
            p.addAttribute("id", String.valueOf(data.size() + 1)); //生成新的id对象
            p.addElement("pname").setText(painting.getPname()); //返回新的节点 设置其文本值
            p.addElement("category").setText(painting.getCategory().toString());
            p.addElement("price").setText(painting.getPrice().toString());
            p.addElement("preview").setText(painting.getPreview());
            p.addElement("description").setText(painting.getDescription());
            //4.写入XML,完成追加操作
            writer = new OutputStreamWriter(new FileOutputStream(dataFile), "UTF-8");
            document.write(writer); //向目标dataFile原始xml中进行新节点的更新
            System.out.println(dataFile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) { //write有开就有关 已经被实例化
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace(); //遇到异常 打印堆栈
                }
            }
            //清空 在空的数据基础上重新添加 无论成功与否都会重新加载数据 如果照片没找到 就清空数据
            reload();
        }

    }

    public static void main(String[] args) {
//        new XmlDataSource();
//        List<Painting> ps = XmlDataSource.getRawData();
//        System.out.println(ps);
        Painting p = new Painting();
        p.setPname("油画测试");
        p.setCategory(1);
        p.setPrice(4000);
        p.setPreview("upload/10.jpg");
        p.setDescription("测试油画描述");
        XmlDataSource.append(p);
    }

    /**
     * 更新对应id的XML油画数据
     * @param painting 要更新的油画数据
     * @throws IOException
     */
    public static void update(Painting painting){
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            //节点路径[@属性名=属性值]
            // /root/paintin[@id=x]  根节点
            List<Node> nodes = document.selectNodes("/root/painting[@id=" + painting.getId() + "]");
            if (nodes.size() == 0){
                throw new RuntimeException("id=" + painting.getId() + "编号油画不存在");
            }
            Element p = (Element) nodes.get(0); //唯一的节点提取出来
            p.selectSingleNode("pname").setText(painting.getPname()); //得到指定标签名的唯一节点 指定油画更新id操作
            p.selectSingleNode("category").setText(painting.getCategory().toString());
            p.selectSingleNode("price").setText(painting.getPrice().toString());
            p.selectSingleNode("preview").setText(painting.getPreview());
            p.selectSingleNode("description").setText(painting.getDescription());
            writer = new OutputStreamWriter(new FileOutputStream(dataFile),"UTF-8");
            document.write(writer);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (writer != null){
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            reload(); //对原始集合进行重载更新
        }
    }

    /**
     * 按id号删除XML油画数据
     * @param id 油画id
     * @throws IOException
     */

    public static void delete(Integer id) {
        SAXReader reader = new SAXReader();
        Writer writer = null;
        try {
            Document document = reader.read(dataFile);
            List<Node> nodes = document.selectNodes("/root/painting[@id=" + id + "]");
            if(nodes.size() == 0) {
                throw new RuntimeException("id=" + id + "编号油画不存在");
            }
            Element p = (Element)nodes.get(0);
            p.getParent().remove(p);
            writer = new OutputStreamWriter(new FileOutputStream(dataFile),"UTF-8");
            document.write(writer);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(writer!=null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            reload();
        }
    }
}


项目总结

CRUD增删改查

工程结构
mgallery - eclipse工程项目
 /src - java源代码目录
 /WebContent - Web资源目录
 /css - css文件目录
 /js - js文件目录
 /image - 图片资源目录
 /upload - 上传文件目录
 /WEB-INF   //jsp数据来自controller 不允许在web中直接访问 要从控制器跳转
   /jsp - jsp页面目录
   /lib - jar文件目录
   /classes - 编译后的class目录
   /web.xml web描述符文件
包结构[src目录下 根据MVC进行结构划分]
com.imooc.mgallery //逆命名法
    /controller - 存放Servlet控制器类 //承上启下接收参数 调用逻辑 返回处理结果
    /service - 存放处理逻辑类model //完成业务逻辑 service与dao进行传递调用
    /dao - Data Access Object 数据访问对象类 数据读写的java类 数据来自xml文件
    /entity - 存放实体类 JavaBean java中的简单对象
    /utils - 通用工具类 底层通用的工具类或方法

Dao类[Data Access Object]

  • XxxDao类只负责对数据进行读取、写入操作
  • 只负责对数据 增、删、改、查
示例:PaintingDao //针对油画数据进行增删改查
public class PaintingDao{
    public void append(){...} //新增数据
    public void update(){...} //修改数据
    public void delete(){...} //删除数据
    public void findAll(){...} //查询数据
}

Service与Dao的关系

  • Service负责进行流程处理,需**持久化[java处理在内存中 存储在数据库防止丢失]**时调用Dao
  • Dao只负责单纯对数据进行增删改查操作
  • Service允许单向调用Dao,反向不允许

JavaBean

  • 对一种类的使用形式的统称

  • JavaBean是一种Java中可重用组件

  • JavaBean不是技术,而是一种Java类的格式要求

  • JavaBean在Java领域非常常见,通常用于存储数据

JavaBean格式要求

  • 类必须是pubilc并提供默认构造函数
  • 所有属性private私有化
  • 属性通过getter与setter方法进行读写
public class Painting{//类公有化
    public Painting(){...}; //提供默认构造函数,可不写
    private Integer id; //属性私有
    private String pname;
    public Integer getId(){return id;} //getter获取属性值
    public void setId(Integer id){this.id = id;} //setter设置属性值
    public String getPname(){return pname;}
    public void setPname(String pname){this.pname = pname;}
}

创建mgallery工程 实现思路

  • 开发PaintingDao读取XML数据,实现分页操作
  • 开发PaintingService服务类,对PaintingDao进行调用
  • 开发PaintingController控制器,调用PaintingService
  • 重写index.html,利用JSP技术读取分页数据

关键类与方法[对于前台来说]

  • XmlDataSource类[全局有且只有一份数据保存在内存中] - XML数据源工具类,简化Dao提取操作
  • PaintingDao.pagination() - 数据分页查询方法
  • PageModel类 - 分页结果的包装类 {分页处理的核心对象}

Dom4j

  • Dom4j是一个易用的、开源的库,用于解析XML。它应用于Java平台
  • Dom4j将XML视为Document对象
  • XML标签被Dom4j定义为Element对象

Dom4j开发流程回顾

  • SAXReader.read()读取XML文件,得到Document对象
  • document.selectNodes()利用Xpath得到XML节点集合
  • 遍历XML节点,包装成JavaBean或者集合对象返回

开发XmlDataSource (utils)

油画数据分页设计思路

​ [视图 控制器 模型]

浏览器会发送请求查询指定的数据 PaintingController得到分页信息以后 再调用PaintingService 再调用PaintingDao 首先从PaintingDao调用XmlDataSource得到xml所有数据集合传入到PageModel进行分页 再返回PaintingDao-PaintingService-PaintingController放在当前的请求中,将请求跳转至index.jsp对数据进行渲染返回浏览器。

前台系统与后台系统区别

前台系统 后台系统
开放度 对外开放 不对外开放
面向群体 客户 企业内部人员
功能 提供查询与交互 管理前台数据
设计侧重点 良好的用户体验 严密的业务逻辑
访问量
典型案例 猫眼电影网 [后台有评论审核系统] XX公司无纸质化办公OA系统
后台实现功能

油画列表 油画预览 删除油画 修改油画 新增与上传油画

SweetAlert对话框

替代了传统的alert函数 有精心设计的对话框

新增功能实现难点

表单校验、文件上传、处理XML文件

文件上传:Apache Commons FileUpload

文件上传必要前提条件

文件上传必要前提条件

  • form表单method = “post 因为二进制文件不可以放在url中传递
  • form表单enctype = “multipart/form-data 允许保存二进制数据存放在请求体中发送到服务端
    enctype=”application/x-www-form-urlencoded 采用url编码的方式以字符串的形式将数据保存在请求体中发送到服务器
  • form表单持有file类型input进行文件选择

//1.初始化FileUpload组件 包含表单项的数据对象 每个输入项解析成FileItem
//2.ServletFileUpload* *是为FileUpload组件提供Java webHttp请求解析 将factory传入
//3.遍历所有FileItem 判断各种框 isFormField(true/false)来走是普通框还是文件框 文件框先把文件保存到服务器 UUID使文件名不重复

可重用的表单js

封装成JavaScript函数 在validation.js中呈现 可以自定义触发条件 别忘了还有个全局触发条件function写上面

实现新增油画功能

在xml中执行写入操作 利用Dom4j写操作开发
  • SAXReader.read()读取XML文件,得到Document对象
  • p=root.addElement(“painting”) - 创建新的节点
  • p.addElement(“pname”) - 创建新的子节点
  • document.write(write) - 向XML写入新的节点

实现修改与删除功能

所有更新逻辑都是在原始数据基础上覆盖更新,通过id号得到原始的旧数据,对旧的对象进行update

客户端采用Ajax方式提交Http请求
Controller方法处理后不再跳转任何jsp,而是通过响应输出JSON格式字符串
Tips:作为Ajax与服务器交互后,得到的不是整页HTML,而是服务器处理后的数据

阅读全文

JSON,JQuery,Ajax,Freemarker,百度Echarts,正则表达,过滤器,监听与实践

2023/9/20

JSON (JavaScript Object Notation) [JavaScript对象表示法]

  • 掌握JSON语法的书写规则
  • 掌握JSON与JavaScript的交互技巧
  • 掌握JSON与Java之间的序列化与反序列化

JSON是轻量级的文本数据交换格式,独立于语言,具有自我描述性,更易理解,已经逐渐替代了XML

{
    "sites":[
        {"name":"慕课网","url":"www.imooc.com"},
        {"name":"百度","url":"www.baidu.com"}
    ]
}

JSON语法规则

  • 数据由键(key)/值(value)描述,由逗号分隔
  • 大括号代表一个完整的对象,拥有多个键/值对
  • 中括号保存数组,多个对象之间使用逗号分割

所有的key和value都要用双引号进行标注

JSON存储员工信息

[
  {
    "empno": 7369,
    "ename": "李宁",
    "job": "软件工程师",
    "hiredate": "2017-05-12",
    "salary": 13000,
    "dname": "研发部"
  },
  {
    "empno": 8848,
    "ename": "小明",
    "job": "销售总监",
    "hiredate": "2022-4-23",
    "salary": 8000,
    "dname": "人事部",
    "customers": [
      {
        "cname": "李东"
      },
      {
        "cname": "刘楠"
      }
    ]
  }
]
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
        var json = [
            {
                "empno": 7369,
                "ename": "李宁",
                "job": "软件工程师",
                "hiredate": "2017-05-12",
                "salary": 13000,
                "dname": "研发部"
            },
            {
                "empno": 8848,
                "ename": "小明",
                "job": "销售总监",
                "hiredate": "2022-4-23",
                "salary": 8000,
                "dname": "人事部",
                "customers": [
                    {
                        "cname": "李东"
                    },
                    {
                        "cname": "刘楠"
                    }
                ]
            }
        ];
        //在浏览器控制台中对json内容进行输出
        console.log(json);
        for (var i = 0; i < json.length; i++){
            var emp = json[i];
            document.write("<h1>");
            document.write(emp.empno);
            document.write("," + emp.ename);
            document.write("," + emp.job);
            document.write("," + emp.hiredate);
            document.write("," + emp.salary);
            document.write("," + emp.dname);
            document.write("</h1>");
            if (emp.customers != null){
                document.write("<h2>"+emp.ename+"的顾客是")
                for (var j = 0; j < emp.customers.length; j++){
                    var customer = emp.customers[j];
                    document.write(customer.cname + ",");
                }
                document.write("</h2>");
            }
        }
    </script>
</head>
<body>

</body>
</html>

JSON与字符串相互转换

  • JSON.parse()方法字符串转换为JSON对象
  • JSON.stringify()方法JSON对象转换为字符串
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
       var str = "{\"class_name\" : \"五年fdhgfs级三班\"}"; <!--字符串 斜杠进行原意的输出-->
       var json = JSON.parse(str);
       console.log(str);
       console.log(json);
       document.write("班级:" + json.class_name);
    </script>
</head>
<body>

</body>
</html>

JS中JSON转为字符串

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
       var json1 = "{\"class_name\" : \"五年级三班\"}"; <!--字符串 斜杠进行原意的输出-->
       var str1 = JSON.stringify(json1);
       console.log(json1);
       console.log(str1);
       var json2 = {};
       json2.class_name = "五年级五班";
       json2.floor = "团委楼四层";
       json2.teacher = "王义夫"
       console.info(json2);
    </script>
</head>
<body>

</body>
</html>

JSON与Java交互

JSON工具包:json与java之间的互相转换

  • FastJson是阿里巴巴著名的JSON序列化与反序列化工具包
  • FastJson国内拥有大量使用者,拥有API简单,效率高等优点
https://repo1.maven.org/maven2/com/alibaba/fastjson/ 下载fastjson
或者配置maven依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>x.x.x</version>
</dependency>

FastJson序列化与JSON注解

public class Employee{
    private Integer empno;
    private String ename;
    private String job;
    //日期格式化
    @JSONField(name="hiredate", format="yyyy-MM-dd HH:mm:ss")
    private Date hdate;
    private Float salary;
    @JSONField(serialize = false) //不对dname进行序列化
    private String dname;
    ......
}
public class FastJsonSample1{
    public static void main(String[] args){
        Employee employee = new Employee();
        employee.setEmpno(4488);
        employee.setEname("王晓东");
        employee.setJob("客户经理");
        employee.setSalary(1000f);
        employee.setDname("市场部");
        //用日期来对时间进行初始化
        --------------//对应上方日期格式化@JSONField-------------------
        Calendar c = Calendar.getInstance();
        c.set(2019,0,30,0,0,0);
        employee.setHdate(c.getTime());//获取所需要的日期对象
        -------------------------------------------------------------
        //FastJSON中提供了JSON对象,完成对象对JSON字符串的转换
        String json = JSON.toJSONString(employee);
        System.out.println(json);
        //                            ↓↓ 原始字符串 反序列化类 ↓↓
        Employee emp = JSON.parseObject(json, Employee.class); //转换为何种对象
        //JSNO.parse()方法将字符串转换为JSON对象
        System.out.println(emp.getEname());
    }
}

FastJSON对象数组序列化与反序列化

JSON序列化与反序列化的用途:数据传输和数据保存 [A电脑中有100个员工信息,通过JSON把数据变成字符串发送給另一个系统,再进行转换]

public class FastJsonSample2{
    public static void main(String[] args){
        List emplist = new ArrayList();
        for(int i = 1; i <= 100; i++){
            Employee employee = new Employee();
            employee.setEmpno(4488 + i);
            employee.setEname("员工" + i);
            emplist.add(employee);
        }
        String json = JSON.toJSONString(emplist);
        System.out.println(json);
        List<Employee> emps = JSON.parseArray(json, Employee.class);
        for(Employee e : emps){
            System.out.println(e.getEmpno() + ":" + e.getEname());
        }
    }
}

JSON教程 https://www.cainiaojc.com/json/json_objects.html
JSON用于与Web服务器交换数据。数据发送到Web服务器时,数据必须是字符串

JSON格式几乎与JavaScript对象相同 
在JSON中,键必须是字符串,并用双引号引起来 {"name":"Seagull"}
在JavaScript中,键可以是字符串数字或标识符 {name : "Seagull"} 
JSON字符串类型 {"name":"Seagull"}
JSON数字类型 {"age":22}
JSON布尔类型 {"isAdult":true}
JSON对象类型 {"user":{"name:""Seagull","age":22,"city":"New Delhi"}} 
JSON数组类型 {"user":["Seagull","Cynthia","Tarush"]} 
//来自服务器的JSON
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
   if (this.readyState = 4 && this.status = 200) {
      var myObj = JSON.parse(this.responseText);
      document.getElementById("output").innerHTML = myObj.name;
   }
};
httpRequest.open("GET", "demo.json", true);
httpRequest.send();

**JSON.parse()**方法解析JSON字符串来构造JavaScript值或该字符串描述
**JSON.stringify()**方法将JavaScript对象或值转换为JSON字符串

解析日期parse()
<!DOCTYPE html>
<html>
<title>JSON.parse() 日期与json转换示例 - 基础教程(cainiaojc.com)</title>
<body>
<p>如果需要包含日期,则将其写为字符串,然后稍后将其转换回日期对象:</p>
<p id="output"></p>
<script>
var myJSON = '{"name":"Seagull", "birth":"1997-11-10", "city":"New Delhi"}';
var myObj = JSON.parse(myJSON);
myObj.birth = new Date(myObj.birth);
document.getElementById("output").innerHTML = myObj.name + " DOB is, " + myObj.birth;
</script>
</body>
</html> 
/*如果需要包含日期,则将其写为字符串,然后稍后将其转换回日期对象:
Seagull DOB is, Mon Nov 10 1997 08:00:00 GMT+0800 (中国标准时间)*/ 
解析日期stringify()
<!DOCTYPE html>
<html>
<title>JSON.stringify() 将日期对象转换为字符串示例 - 基础教程(cainiaojc.com)</title>
<body>
<p> JSON.stringify()方法会将任何日期对象转换为字符串:</p>
<p id="output"></p>
<script>
var myObj = { name: "Seagull", today: new Date(), city : "New Delhi" };
var myJSON = JSON.stringify(myObj);
document.getElementById("output").innerHTML = myJSON;
</script>
</body>
</html>
/*JSON.stringify()方法会将任何日期对象转换为字符串:
{"name":"Seagull","today":"2023-10-19T09:21:17.353Z","city":"New Delhi"*/

Java中的this.对象不会被函数内部修改 去寻找定义的值
this可以去调到外面的东西 解决了变量名一样的冲突
this区分成员变量和局部变量
this调用成员方法

Java中的static是公共的变量池


jQuery[主流的JavaScript库]与Ajax

  • 了解jQuery3的基本使用方法
  • 掌握Ajax处理流程与实现流程
  • 掌握jQuery中Ajax方法的使用

jQuery是一个轻量级JS库,使用十分简单;jQuery的核心是选择器用于获取页面元素

jQuery选择器 [先引用]

//引用jQuery
<script type="text/javascript">
    $("span").css("color","red");
    //简化形式 中间加JSON表达式
    $("a").css({"color" : "red", "font-size" : "30px", "font-weight" : "bold", "font-style" : "italic"})
    //为选中的选择器增添类效果 为li增加两个css类
    $("li").addClass("highlight myclass");
    $("p").removeClass("myclass") //移除类
    var color = $("a").css("color","orange");
    // alert(color)



### 设置元素内容

- **val()** 获取或设置输入项的值
- **text()** 获取或设置元素的纯文本
- **html()** 获取或设置元素内部的HTML

```html
.. <input type = "text" name = "uname" value = "admin"/> ..//将初始uname值变admin..
.. <span class = "myclass">我是myclass类的span标签</span> ..
<script type="text/javascript" src="js/jquery-3.1.1.js" ></script>
<script type = "text/javascript">
    //获取或设置文本输入框的内容
    $("input[name='uname']").val("administrator");
    
    //text与html方法最大的区别在于对文本中的html标签是否进行转义
    var vspan = $("span.myclass").text("<b>锄禾日当午</b>");
    alert(vspan);//锄禾日当午 纯文本中带了<b>标签
    
    var vspan = $("span.myclass").html("<b>锄禾日当午</b>");  
    alert(vspan);//<b>锄禾日当午</b>
</script>

jquery事件处理方法

  • on(“click”, function) 为选中的页面元素绑定单机事件
  • click(function) 是绑定事件的简写形式
  • 处理方法中提供了event参数包含了事件的相关信息
鼠标事件 键盘事件 表单事件 文档/窗口事件
click keypress
(键盘完整按下弹起)
submit
(表单提交)
load
(文档加载)
dblclick(双击) keydown
(键盘按下)
change
(表单发生变化)
resize
(文档窗口产生变化)
mouseenter
(滑鼠移动)
keyup
(键盘弹起)
focus
(表单输入项获得焦点)
scroll
(文档窗口滚动变化)
mouseleave
(滑鼠移出)
blur
(表单输入项失去焦点)
unload
(文档窗口关闭/卸载)
mouseover
(滑鼠移动过程)
.. <p class = "myclass">我是拥有myclass的p标签</p> ..
.. <input type = "text" name = "uname" value = "admin"/> ..
<script type = "text/javascript" src = "js/jquery-3.3.1.js"></script>
<script type = "text/javascript">
    $("p.myclass").on("click", function(){
    //$(this)是指当前事件产生的对象$("p.myclass")
    $(this).css("background-color", "yellow");
})
    $("span.myclass").click(function(){
    $(this).css("background-color", "lightgreen");                      
}) 
    
    $("input[name='uname']").keypress(function(event){
        console.log(event); //打印事件 F12 每次输入按键的Console
        $(this).css("color", "red");
    })
    
</script>
----------------------------------------------------------------
<!DOCTYPE html >
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn,staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script>
$(document).ready(function(){
 $("p").click(function()){
    <!--动作触发后执行的代码-->
    #(this).hide();
});
});
</script>
    </head>
    <body>
        <p>
            如果你点我,我就会消失。
        </p>
        <p>
            点我消失!
        </p>
    </body>

jQuery hide()和show()

使用hide()和show()方法来隐藏和显示HTML元素

$("#hide").click(function(){
    $("p").hide();
});
$("#show").click(function(){
    $("p").show();
});
隐藏速度

$(selector).hide(speed,callback);

$(selector).show(speed,callback);

<!DOCTYPE html >
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn,staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
<script>
$(document).ready(function(){
$("button").click(function(){
    $("p").hide(1000);
 });
});
</script>
    </head>
    <body>
      <button>隐藏</button>
      <p> 这是个段落</p>
      <p> 小段落</p>
    </body>

jQuery选择器实验室

sample2.html
<!DOCTYPE html >
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery实验室</title>

<style>
.myclass {
    font-style: italic;
    color: darkblue;
}
/* 高亮css类 */
.highlight {
    color: red;
    font-size: 30px;
    background: lightblue;
}
</style>

</head>

<body>
    <div class="section">
        <h2>jQuery选择器实验室</h2>
        <input style="height: 24px" id="txtSelector" />
        <button id="btnSelect" style="height: 30px">选择</button>
        <hr />
        <div>
            <p id="welcome">欢迎来到选择器实验室</p>
            <ul>
                <li>搜索引擎:<a href="http://www.baidu.com">百度</a> <span> <a
                        style="color: darkgreen" href="http://www.so.com">360</a>
                </span>
                </li>
                <li>电子邮箱:<a href="http://mail.163.com">网易邮箱</a> <span> <a
                        style="color: darkgreen" href="http://mail.qq.com">QQ邮箱</a>
                </span>
                </li>
                <li>中国名校:<a href="http://www.tsinghua.edu.cn">清华大学</a> <span>
                        <a style="color: darkgreen" href="https://www.pku.edu.cn/">北京大学</a>
                </span>
                </li>
            </ul>

            <span class="myclass ">我是拥有myclass类的span标签</span>

            <p class="myclass">我是拥有myclass的p标签</p>
            <form id="info" action="#" method="get">
                <div>
                    用户名:<input type="text" name="uname" value="admin" /> 密码:<input
                        type="password" name="upsd" value="123456" />
                </div>
                <div>
                    婚姻状况: <select id="marital_status">
                        <option value="1">未婚</option>
                        <option value="2">已婚</option>
                        <option value="3">离异</option>
                        <option value="4">丧偶</option>
                    </select>
                </div>
                <div class="left clear-left">
                    <input type="submit" value="提交" /> <input type="reset" value="重置" />
                </div>
            </form>
        </div>
    </div>
    <script type="text/javascript" src="js/jquery-3.1.1.js" ></script>
    <script type="text/javascript">
        /*
            id选择器使用"#id值"进行选择
            css选择器使用".css类名"进行选择
            $(".myclass").addClass("highlight");
        */
        document.getElementById("btnSelect").onclick = function(){
            var selector = document.getElementById("txtSelector").value;
            //jquery选择器方法 选择器表达式
            $("*").removeClass("highlight") //在增加高亮之前 在当前页面将所有的类移除
            $(selector).addClass("highlight"); //对前面所选中的元素追加css类
        }
    </script>
</body>
</html>

Ajax(Asynchronous JavaScript And XML)介绍

Ajax(异步的 JavaScriptXML)

  • Ajax可以**在不刷新页面的前提下,进行页面布局更新,与后台交互**
    [不对整个页面刷新 只对局部数据更新刷新]
  • Ajax不是新技术,不是W3C的标准

Ajax的使用流程

  • 创建XmlHttpRequest对象[用于后台与服务器交换数据 是Ajax的核心]
  • 发送Ajax请求
  • 处理服务器响应
创建XmlHttpRequest对象
// 1.创建XmlHttpRequest对象
     var xmlhttp;
      if(windows.XMLHttpRequest){
       xmlhttp = new XMLHttpRequest();
    }else{
       xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
发送Ajax请求
  • xmlhttp.open() 用于创建请求
//创建请求
xmlhttp.open("Get","http://localhost/test?name=admin",true);
//发送到服务器
xmlhttp.send();
处理服务器响应
  • xmlhttp.onreadystatechange() 事件用于监听Ajax的执行过程
  • xmlhttp.readyState = “number 属性说明XMLHttpRequest当前状态
  • xmlhttp.status 属性服务器响应状态码,200:成功 404:未找到
readyState指 说明
readyState = 0 请求未初始化
readyState = 1 服务器连接已建立
readyState = 2 请求已被接收
readyState = 3 请求正在处理
readyState = 4 响应文本已被接收
完整编写
<body>
    <input id="btnLoad" type="button" value="加载">
    <div id="divContent"></div>
    <script>
        document.getElementById("btnLoad").onclick = function(){
            //1.创建XmlHttpRequest对象
            var xmlhttp;
            if(windows.XMLHttpRequest){
                xmlhttp = new XMLHttpRequest();
            }else{
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            console.log(xmlhttp); //在控制台console里有数据
            //2.发送Ajax请求 //F12中Network content有Response返回
            xmlhttp.open("GET", "/json_war_exploded/news.html", true)
            //3.处理服务器响应
            xmlhttp.onreadystatechange = function(){
            //响应已被接收且服务器处理成功时才执行
                if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
                //获取响应体的文本
                var responseText = xmlhttp.responseText;
                //对服务器结果进行处理 
                alert(t);
                //点击加载后 数据输出在页面的div上
                document.getElementById("divContent").innerHTML = t;
                }
            }
        }
    </script>
</body>
ContentServlet.java
@WebServlet("/content")
public class ContentServlet extends HttpServlet{
    public ContentServlet(){
        super();
    }
    //用Ajax进行请求的时候不进行任何页面跳转而是直接输出想产生的数据结果[一般用JSON传递]
    protected void doGet(HttpServletReq req, HttpServletRespon res){
        res.getWritre().println("<b style = 'color:red'>I'm server content</b>");
    }
}

利用Ajax实现新闻列表[案例]

需要下载fast json-1.2.52.jar

News.java
public class News(){
    private String title;
    private String date;
    private String source;
    private String content;
    Getter and Setter + Constructor
}
NewsListServlet.java
@WebServlet("/news_list")
public class NewsListServlet extends HttpServlet{
    public NewsListServlet(){
        super();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List list = new ArrayList();
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        list.add(new News("TIOBE:2023年9月份日本排放核污染水","2020-9","TIOBE","..."));
        //用于把List或者java对象給json序列化生成对应的字符串
        String json = JSON.toJSONString(list); //提供的java对象
        System.out.println(json);
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().println(json);
    }
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
</head>
<body>
<div id="container">
</div>
<script type="text/javascript">
        //1.创建XmlHttpRequest对象
        var xmlhttp;
        if(window.XMLHttpRequest){
            xmlhttp = new XMLHttpRequest();
        }else{
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        //2.发送Ajax请求 //F12中Network content有Response返回
        //true代表异步执行 false代表同步执行
        xmlhttp.open("GET", "/json_war_exploded/news_list", false)
        //同步:在网络发布的过程中 数据没有返回的话会一直处于阻塞的状态(前面的没做完后面的不让做)
        xmlhttp.send();
        console.log("请求发送完成");
        if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            //获取响应体的文本
            var text = xmlhttp.responseText;
            //对服务器结果进行处理
            console.log(text);
            var json = JSON.parse(text);//内置的json对象
            console.log(json);
            var html = "";
            for (var i = 0; i < json.length; i++) {
                var news = json[i];
                html = html + "<h1>" + news.title + "</h1>";
                html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                html = html + "<hr/>"
            }
            document.getElementById("container").innerHTML = html;
        }
        /*3.处理服务器响应 异步[一直往下进行 onreadystatechange数据监控]
        xmlhttp.onreadystatechange = function(){
            //响应已被接收且服务器处理成功时才执行
            if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
                //获取响应体的文本
                var text = xmlhttp.responseText;
                //对服务器结果进行处理
                console.log(text);
                var json = JSON.parse(text);//内置的json对象
                console.log(json);
                var html = "";
                for (var i = 0; i < json.length; i++) {
                    var news = json[i];
                    html = html + "<h1>" + news.title + "</h1>";
                    html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                    html = html + "<hr/>";
                }
                //对html进行动态加载
                document.getElementById("container").innerHTML = html;
            }
        }*/
</script>
</body>
</html>

同步与异步

xmlhttp.open(“GET”, “/json_war_exploded/news_list”, false)

**同步(false)**:程序会阻塞 进入等待的状态 数据不返回时程序是不会往下进行的
异步(true)[推荐]:程序不会阻塞 程序依旧往下进行 数据返回是通过触发onreadystatechange数据监控进行处理的

//    2.发送Ajax请求 //F12中Network content有Response返回
        //true代表异步执行 false代表同步执行
        xmlhttp.open("GET", "/json_war_exploded/news_list", false)
        //同步:在网络发布的过程中 数据没有返回的话会一直处于阻塞的状态(前面的没做完后面的不让做)
        xmlhttp.send();
        console.log("请求发送完成");
        if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            //获取响应体的文本
            var text = xmlhttp.responseText;
            //对服务器结果进行处理
            console.log(text);
            var json = JSON.parse(text);//内置的json对象
            console.log(json);
            var html = "";
            for (var i = 0; i < json.length; i++) {
                var news = json[i];
                html = html + "<h1>" + news.title + "</h1>";
                html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                html = html + "<hr/>"
            }
            document.getElementById("container").innerHTML = html;
        }
        /*3.处理服务器响应 异步[一直往下进行 onreadystatechange数据监控]
        xmlhttp.onreadystatechange = function(){
            //响应已被接收且服务器处理成功时才执行
            if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
                //获取响应体的文本
                var text = xmlhttp.responseText;
                //对服务器结果进行处理
                console.log(text);
                var json = JSON.parse(text);//内置的json对象
                console.log(json);
                var html = "";
                for (var i = 0; i < json.length; i++) {
                    var news = json[i];
                    html = html + "<h1>" + news.title + "</h1>";
                    html = html + "<h2>" + news.date + "&nbsp" + news.source + "</h2>";
                    html = html + "<hr/>";
                }
                //对html进行动态加载
                document.getElementById("container").innerHTML = html;
            }
        }*/

<button type = "button" onclick="loadXMLDoc()">修改内容</button>
<script>
/* XMLHttpRequest 用于在后台与服务器交换数据。
这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。
*/ 
function loadXMLDoc(){
    //AJAX脚本执行
    var xmlhttp;
    if(windows.XMLHttpRequest){
        xmlhttp=new XMLHttpRequest();
    }else{
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }
}
</script>

/*
如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open() 和 send() 方法:

open(method,url,async)
method:请求的类型;GET 或 POST
url:文件在服务器上的位置
async:true(常用异步)或 false(同步)

对于 web 开发人员来说,发送异步请求是一个巨大的进步。
很多在服务器执行的任务都相当费时。AJAX 出现之前,这可能会引起应用程序挂起或停止。
通过 AJAX,JavaScript 无需等待服务器的响应,而是:
在等待服务器响应时执行其他脚本
当响应就绪后对响应进行处理
*/ 

xmlhttp.open("GET","ajax_info.html",true);
xmlhttp.send(); //send(string) 仅用于POST请求 

/*
若需要来自服务器的响应,请使用XMLHttpRequest对象的responseText或responseXML属性 
responseText => 获得字符串形式的相应数据 如果来自服务器的响应并非XML
responseXML => 获得XML形式的相应数据 如果来自服务器的响应式XML 
*/ 
// responseText 
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
// responseXML
xmlDoc=xmlhttp.responseXML;
txt="";
x=xmlDoc.getElementsByTagName("ARTIST");
    for (i=0;i<x.length;i++)
    {
        txt=txt + x[i].childNodes[0].nodeValue + "<br>";
    }
document.getElementById("myDiv").innerHTML=txt;
/*onreadystatechange事件中我们规定当服务器响应已做好被处理的准备时所执行的任务
0:请求未初始化  1:服务器连接已建立  2:请求已接收  3:请求处理中  4:请求已完成且响应已就绪 
 */ 
 //如果您的网站上存在多个 AJAX 任务,那么您应该为创建 XMLHttpRequest对象编写一个标准的函数
 //并为每个 AJAX 任务调用该函数 
 xmlhttp.onreadystatechange=function()
{
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
    }
}

jQuery对Ajax的支持

  • jQuery对Ajax进行封装,提供了**$.ajax()**方法
  • 语法:**$.ajax(options)**
常用设置项 说明
url 发送请求地址
type 请求类型get|post
data 向服务器传递的参数
dataType 服务器响应的数据类型
text|json|xml|html|jsonp|script
success 接收响应时的处理函数
error 请求失败时的处理函数
jquery_news.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="js/jquery-3.1.1.js"></script>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                "url": "/json_war_exploded/news_list",
                "type": "get",
                "data": "t=tiobe",//多个传参的参数用json格式写
                //"data": {"t":"tiobe", "abc":"123", "uu":"777"}
                //但是实际过程中jquery会以url形式传参 t=tiobe&abc=123&uu=777
                "dataType": "json", //解析成json文件
                "success": function (json){
                    console.log(json);
                    for (var i = 0; i < json.length; i++){
                        $("#container").append("<h1>" + json[i].title + "</h1>"); //append追加
                    }
                },   //ajax的核心信息 ↓
                "error": function (xmlhttp, errorText){
                    console.log(xmlhttp);
                    console.log(errorText);
                    if(xmlhttp.status == "405"){
                        alert("无效的请求方式");
                    }else if(xmlhttp.status == "404"){
                        alert("未找到URL资源");
                    }else if(xmlhttp.status == "500"){
                        alert("服务器内部错误,请联系管理员");
                    }else{
                        alert("产生异常,请联系管理员");
                    }
                }
            })
        })
    </script>
</head>
<body>
    <div id = "container"></div>
</body>
</html>

实现二级联动菜单 [省 市 县]

ChannelServlet.java
package com.example.json;

import com.alibaba.fastjson.JSON;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/channel")
public class ChannelServlet extends HttpServlet {
    public ChannelServlet() {
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String level = req.getParameter("level");
        String parent = req.getParameter("parent");
        List chlist = new ArrayList();
        if(level.equals("1")) {
            chlist.add(new Channel("ai" , "前沿/区块链/人工智能"));
            chlist.add(new Channel("web" , "前端/小程序/JS"));
        }else if(level.equals("2")) {
            if(parent.equals("ai")) {
                chlist.add(new Channel("micro" , "微服务"));
                chlist.add(new Channel("blockchain" , "区块链"));
                chlist.add(new Channel("other" , "..."));
            }else if(parent.equals("web")){
                chlist.add(new Channel("html" , "HTML"));
                chlist.add(new Channel("css" , "CSS"));
                chlist.add(new Channel("other" , "..."));
            }
        }
        //json序列化
        String json = JSON.toJSONString(chlist);
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().println(json);
    }
}
cascade_menu.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="js/jquery-3.1.1.js"></script>
    <script type="text/javascript">
        $(function(){
            $.ajax({
                "url" : "/json_war_exploded/channel",
                "data" : {"level" : "1"},
                "type" : "get" ,
                "dataType" : "json" ,
                "success" : function(json){
                    console.log(json);
                    for(var i = 0 ; i < json.length ; i++){
                        var ch = json[i];
                        //append是在组件内部进行追加内容
                        $("#lv1").append("<option value='" + ch.code + "'>" + ch.name + "</option>")
                    }
                }
            })
        })

        $(function(){ //on是绑定事件 change是当变化时候发生function
            $("#lv1").on("change" , function(){
                var parent = $(this).val();//val()获取输入项的值
                console.log(parent);
                $.ajax({
                    "url" : "/json_war_exploded/channel" ,
                    "data" : {"level" : "2" , "parent" : parent}, //parent 一级频道的value值
                    "dataType" : "json" ,
                    "type" : "get" ,
                    "success" : function(json){
                        console.log(json);
                        //移除所有lv2下的原始option选项
                        $("#lv2>option").remove();
                        for(var i = 0 ; i < json.length ; i++){
                            var ch = json[i];
                            $("#lv2").append("<option value='" + ch.code +"'>" + ch.name + "</option>")
                        }
                    }
                })
            })
        })
    </script>
</head>
<body>
<select id="lv1" style="width:200px;height:30px">
    <option selected="selected">请选择</option>
</select>
<select id="lv2" style="width:200px;height:30px"></select>
</body>
</html>

正则表达式

  • 正则表达式是检查、匹配字符串的表达式
  • 正则表达式是描述规则、主流语言都有良好支持
  • 字符串校验、查找与替换是正则表达式主要使用场景
正则表达式的案例
  • 检查输入的身份证号是否合法(15位、18位)
  • 示例:13010220200328091x
正则表达式:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)

字符范围匹配

正则表达式 说明 正确 错误
A 精准匹配单个字符 A a
x|y 允许出现的2个字符 y n
[xyz] 字符集合,允许出现集合内任意单个字符 z c
[a-z] [A-Z] [0-9] 字符范围 a D 8 A a A
[^xyz] [‘^0-9] 集合内字符不允许出现 0 A y 8
训练题目
  • 精准匹配字符串”hallo” 或者 “hello” => h**[ae]**llo
  • 请匹配数字范围(0570-0579) => 057[0-9]
  • 单选题只允许输入ABCD其中一项 => [ABCD] / [A-D]

元字符

正则表达式 说明 正确 错误
\d 匹配任意单个数字 8 i
\D 匹配\d规则之外的任意单个字符 i 8
\w 匹配任意单个字母数字下划线 Y &
\W 匹配\w之外的任意单个字符 & Y
\s 匹配单个空格 x
\n 匹配单个换行符 x
. 匹配任意单个字符(换行符除外)
\。 特殊字符,只匹配 “.” . 1
训练题目
  • 请匹配数字(3213.383219)

    \d\d\d\d\.\d\d\d\d\d\d\d
    
  • 匹配杭州与宁波的座机号码(0571|0574-XXXXXXXX)

    057[14]-\d\d\d\d\d\d\d\d
    
  • 请匹配18位身份证号 [第一位1~6和8]

    [1234568]\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d[0-9xX]
    

多次重复匹配

正则表达式 说明 正确 错误
A{3} 精准N次匹配 AAA AA
A{3,} 最少出现N AAA AA
\d{3,5} 约定出现最少次数与最大次数 1234 12
\d* 可以出现零次至无限次,相当于{0,} 1234
\d+ 最少出现一次, 相当于{1,} 12
\d? 最多出现一次, 相当于{0,1} 1 12
联系题目
  • 验证短信验证码(6位数字) => \d[6]

  • 请匹配全国座机号(区号3或4位-电话号码7或8位) => \d[3,4]-\d[7,8]

  • 请匹配英文姓名(例如:James Watson)

    [A-Z][a-z]{1,}\s[A-Z][a-z]{0,}
    [A-Z][a-z]{1,}\s[A-Z][a-z]*
    ^[A-Z][a-z]{1,}+\s[A-Z][a-z]*$
    

定位匹配

正则表达式 说明 正确 错误
^A.* 头匹配 ABC CBA
**.A$* 尾匹配 CBA ABC
^A.*A$ 全字匹配 ACCCA ACCCB
abb123123ab => ^ab.*ab$

贪婪模式[默认匹配规则]

  • 在满足条件的情况下尽可能匹配到字符串
  • 示例:111222333 正则:\d{6,8}
  • 匹配结果:11122233

非贪婪模式

  • 在满足条件的情况下尽可能匹配到字符串

  • 示例:111222333 正则:\d{6,8}?

  • 匹配结果:111222

贪婪模式举例
<a href = "www.baidu.com"> 百度 </a> <a href =  "www.baidu.com">新浪</a>
规则: 想搜索"www.baidu.com" 仅此一个从"开始 从"结束 中间的片段
正则表达式: ".*" 由于默认是贪婪模式 它会找到的结果如下
"www.baidu.com"> 百度 </a> <a href = “www.sina.com”

改成非贪婪模式 正则表达式:".*?"
"www.baidu.com"  "www.baidu.com"

表达式分组

  • 分组将”正则”分组为多个子表达式
  • 示例:abababcdcdcd
  • 正则表达式:(ab){3}(cd){3}
训练题目
  • 匹配验证码(4位或6位)

    (^\d{4}$)|(^\d{6}$)
    
  • 匹配车牌号(冀B-U888G)

    ^([冀黑粤晋][A-Z])-([A-Z0-9]{5})$
    
  • 中文名字匹配 [Unicall码] [张三…]

    ^[\u4e00-\u9fa5]{2,8}$
    
  • 中英文名字匹配

    (^[\u4e00-\u9fa5]{2,8}$)|(^[A-Z][a-z]*$)
    

正则表达式验证JavaScript表单

<body>
    <form action="#" method="post" id="frmInfo">
        <div id="err" style="color : red">
            
        </div> 
        <div>
            姓名: <input id="name" name="name"/>
        </div>
        <div>
            身份证: <input id="idno" name="idno"/>
        </div>
        <div>
            <input type="submit"/>
        </div>
    </form>
    <script type="text/javascript">
        document.getElementById("frmInfo").onsubmit = function(){
            //在JS中定义正则表达式对象只需要在 /正则表达式/
            var regex1 = /^[\u4e00-\u9fa5]{2,8}$/
            var regex2 = /^rehextal
            var name = document.getElementById("name").value;
            var idno = document.getElementByid("inamd")
            if(regex1.test(name)==false); //返回true或false校验是否成功
                document.getElementById("err").innerHTM="无效姓名";
                return false;
            }else if(regx2.test(idno) == false){
                document.getElementById("err").innerHTM="无效身份证号";
            }else{
                alert("验证通过准备提交")
                return true;
            }
    </script>
</body>

Java中web页面信息提取

sample.html
<!DOCTYPEhtml>
<html>
<head>
<meta charset="UTF-8">
<title>国际主要城市</title>
</head>
<body>
    <h1>国际主要城市</h1>
    <ul>
        <li>纽约NewYork</li>
        <li>伦敦London</li>
        <li>东京Tokyo</li>
        <li>巴黎Paris</li>
        <li>香港HongKong</li>
        <li>新加坡Singapore</li>
        <li>悉尼Sydney</li>
        <li>米兰Milano</li>
        <li>上海Shanghai</li>
        <li>北京Beijing</li>
        <li>马德里Madrid</li>
        <li>莫斯科Moscow</li>
        <li>首尔Seoul</li>
        <li>曼谷Bangkok</li>
        <li>多伦多Toronto</li>
        <li>布鲁塞尔Brussels</li>
        <li>芝加哥Chicago</li>
        <li>吉隆坡KualaLumpur</li>
        <li>孟买Mumbai</li>
        <li>华沙Warsaw</li>
        <li>圣保罗SaoPaulo</li>
        <li>苏黎世Zurich</li>
        <li>阿姆斯特丹Amsterdam</li>
        <li>墨西哥城MexicoCity</li>
        <li>雅加达Jakarta</li>
        <li>都柏林Dublin</li>
        <li>曼谷Bangkok</li>
        <li>台北Taipei</li>
        <li>伊斯坦布尔Istanbul</li>
        <li>里斯本Lisbon</li>
        <li>罗马Rome</li>
        <li>法兰克福Frankfurt</li>
        <li>斯德哥尔摩Stockholm</li>
        <li>布拉格Prague</li>
        <li>维也纳Vienna</li>
        <li>布达佩斯Budapest</li>
        <li>雅典Athens</li>
        <li>加拉加斯Caracas</li>
        <li>洛杉矶LosAngeles</li>
        <li>新西兰NewZealand</li>
        <li>圣地亚哥SanDiego</li>
        <li>布宜诺斯艾利斯BuenosAires</li>
        <li>华盛顿Washington</li>
        <li>墨尔本Melbourne</li>
        <li>约翰内斯堡Johannesburg</li>
        <li>亚特兰大Atlanta</li>
        <li>巴塞罗那Barcelona</li>
        <li>旧金山SanFrancisco</li>
        <li>马尼拉Manila</li>
        <li>波哥大Bogota</li>
        <li>特拉维夫TelAviv-Yafo</li>
        <li>新德里NewDelhi</li>
        <li>迪拜Dubai</li>
        <li>布加勒斯特Bucharest</li>
        <li>奥斯陆Oslo</li>
        <li>柏林Berlin</li>
        <li>赫尔辛基Helsinki</li>
        <li>日内瓦Geneva</li>
        <li>利雅得Riyadh</li>
        <li>哥本哈根Copenhagen</li>
        <li>汉堡Hamburg</li>
        <li>开罗Cairo</li>
        <li>卢森堡Luxembourg</li>
        <li>班加罗尔Bangalore</li>
        <li>达拉斯Dallas</li>
        <li>科威特城Kuwaitcity</li>
        <li>波士顿Boston</li>
        <li>慕尼黑Munich</li>
        <li>迈阿密Miami</li>
        <li>利马Lima</li>
        <li>基辅Kiev</li>
        <li>休斯顿Houston</li>
        <li>广州Guangzhou</li>
        <li>贝鲁特Beirut</li>
        <li>卡拉奇Karachi</li>
        <li>索菲亚Sophia</li>
        <li>蒙得维的亚Montevideo</li>
        <li>里约热内卢RioDEJaneiro</li>
        <li>胡志明市HoChiMinhCity</li>
        <li>蒙特利尔Montreal</li>
        <li>内罗毕Nairobi</li>
        <li>巴拿马城Panamacity</li>
        <li>金奈Chennai</li>
        <li>布里斯班Brisbane</li>
        <li>卡萨布兰卡Casablanca</li>
        <li>丹佛Denver</li>
        <li>基多Quito</li>
        <li>斯图加特Stuttgart</li>
        <li>温哥华Vancouver</li>
        <li>麦纳麦MaiNaMai</li>
        <li>危地马拉市Guatemalacity</li>
        <li>开普敦CapeTown</li>
        <li>圣何塞SanJose</li>
        <li>西雅图Seattle</li>
        <li>深圳Shenzhen</li>
        <li>珀斯Perth</li>
        <li>加尔各答Calcutta</li>
        <li>安特卫普Antwerp</li>
        <li>费城Philadelphia</li>
        <li>鹿特丹Rotterdam</li>
        <li>拉各斯Lagos</li>
        <li>波特兰Portland</li>
        <li>底特律Detroit</li>
        <li>曼彻斯特Manchester</li>
        <li>惠灵顿Wellington</li>
        <li>里加Riga</li>
        <li>爱丁堡Edinburgh</li>
        <li>圣彼得堡StPetersburg</li>
        <li>圣迭戈SanDiego</li>
        <li>伊斯兰堡Islamabad</li>
        <li>伯明翰Birmingham</li>
        <li>多哈Doha</li>
        <li>阿拉木图AlmaAtaAlmaty</li>
        <li>卡尔加里Calgary</li>
    </ul>
</body>
</html>
RegexSample.java
package com.imooc.regex;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexSample {
    public static void main(String[] args) {
        StringBuilder content = new StringBuilder();
        try {//读取文件
            FileInputStream fis = new FileInputStream("D:/workspace/regex/WebContent/sample.html");
            InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
            BufferedReader bufferedReader = new BufferedReader(isr);//缓冲提高读取效率
            String lineText = "";//每一行
            while((lineText = bufferedReader.readLine()) != null) {//读取完成
//                System.out.println(lineText);
                content.append(lineText + "\n");//追加到后面完整字符串
            }
            bufferedReader.close();
            System.out.println(content);
        
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        //1.创建正则表达式对象 用group进行分组提取
        //[正则表达式字符串] \\两个斜杠是原意输出 +至少出现一次
        Pattern p = Pattern.compile("<li>([\\u4e00-\\u9fa5]{2,10})([a-zA-Z]+)</li>");
        //2.匹配正则表达式
        Matcher m = p.matcher(content);
        //3.查找匹配的结果
        while(m.find()) {//原始字符串中进行查找 有返回true 无返回false
//            System.out.println(m.group(0)); //完整字符串
            String chs = m.group(1); //group分组
            String eng = m.group(2);
            System.out.println(chs + "-" + eng);
        }
    }
}

初始过滤器-Filter [机场检查]

  • 过滤器(Filter)是J2EE Servlet模块下的组件
  • Filter的作用是对URL进行统一的拦截处理
  • Filter通常用于应用程序层面进行全局处理

开发过滤器三要素

  • 任何过滤器都要实现 javax.servlet.Filter 接口
  • 在Filter接口的doFIlter()方法中编写过滤器的功能代码
  • 在web.xml中对过滤器进行配置,说明拦截URL的范围
MyFirstFilter.java
public class MyFirstFilter implements Filter{
   public void destroy(){}
   public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("过滤器已生效");
       chain.doFilter(req, resp);
   }
   public void init(FilterConfig filterConfig)thorws ServletException{}
}
<filter>
    <filter-name>MyFirstFilter</filter-name>
    <filter-class>filter.MyFirstFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFirstFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>

<!-- 
    filter-mapping标签用于说明过滤器对URL应用的范围
    1. filter-name 过滤器名称与filter.filter-name保持一致
    2. url-pattern 说明过滤器作用范围 "/*"代表对所有url进行过滤
-->
<body>
    我是默认首页
</body>
HelloServlet.java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletReq req, HttpServletResp resp){
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().println("Hello World!!!")
    }
}

过滤器的生命周期

  • 初始化init() - Filter.init()
  • 提供服务 - Filter.doFilter()
  • 销毁 - Filter.destroy()

过滤器特性

  • 过滤器对象在Web应用启动时被创建且全局唯一
  • 唯一的过滤器对象在并发环境中采用”多线程“提供服务

过滤器两种开发方式

过滤器的配置形式
<filter>
   <filter-name>MyFirstFilter</filter-name>
   <filter-class>filter.MyFirstFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>MyFirstFilter</filter-name>
   <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>
过滤器的注解形式
@WebFilter(filterName = "MyAnnoationFilter", urlPatterns = "/*")
public class MyAnnoationFilter implements Filter{
    
}

MyAnnoationFilter.java      //自定义过滤器名称  设置去过滤哪些url
@@WebFilter(filterName = "MyAnnoationFilter", urlPatterns = "/*")
public class MyAnnoationFilter implements Filter{
    public void destroy(){}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
        System.out.println("过滤器已生效");
        chain.doFilter(req, resp);
    }
    public void init(FilterConfig filterConfig)thorws ServletException{}
}

配置与注解如何选择

  • 配置形式维护性更好,适合应用全局过滤 [中, 大型项目]
  • 注解形式开发体验更好,适合于小型项目敏捷开发

Web中文乱码的解决 [需要强制转换]

  • GET请求-server.xml增加URIEncoding = “UTF-8”;
  • POST请求-使用request.setCharacterEncoding(“UTF-8”);
  • 响应-response.setContentType(“text/html; charset = UTF-8”);
CharacterEncodingFilter.java
public class CharacterEncodingFilter implements Filter{
    public void destroy(){}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       //上面的不是Httpservlet要自己写HttpServlet并导入 解决post请求中的中文乱码
        HttpServletRequest req = (HttpServletRequest)request;//解决中文乱码问题
        req.serCharacterEncoding("UTF-8");
        HttpServletResponse res = (HttpServletResponse)response;//解决中文乱码问题
        res.serContentType("text/html;charset=UTF-8");
        chain.doFilter(req, response);
    }
    public void init(FilterConfig filterConfig)thorws ServletException{}
}
ServletRequest[最顶级] 和 HTTPServletRequest[需继承最顶级]关系所在

web.xml
<filter>
    <filter-name>MyFirstFilter</filter-name>
    <filter-class>filter.MyFirstFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFirstFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>

====================================================================

<!--或者在CharacterEncodingFilter上方进行注解 必须是全局唯一-->
@WebFilter(filterName="CharacterEncodingFilter", urlPatterns="/*")
HelloServlet.java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletReq req, HttpServletResp resp){
        resp.getWriter().println("你好!世界!!")
    }
}

过滤器开发技巧

过滤器参数化
  • 过滤器为了增强灵活性,允许配置信息放在web.xml
  • 在web.xml中配置**< init-param >**设置过滤器参数
优化字符集过滤器 [为了不去修改java代码]
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value> <!--方便参数发生变化-->
    </init-param>
    <init-param>
        <param-name>v1</param-name>
        <param-value>GBK</param-value>
    </init-param>
    ......
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern> //对所有url进行拦截
</filter-mapping>
-------------------------------------------------------------
<!--或者在CharacterEncodingFilter上方进行注解 必须是全局唯一-->
@WebFilter(filterName="CharacterEncodingFilter", urlPatterns="/*",initParams={
    @WebInitParam(name="encoding", value="UTF-8"),
    @WebInitParam(name="p1", value="v1")
})
-------------------------注意init与doFilter----------------------------
public class CharacterEncodingFilter implements Filter{
    private String encoding; //类中的全局私有变量
    public void init(FilterConfig filterConfig)thorws ServletException{
        encoding=filterConfig.getInitParameter("encoding");
        System.out.println(encoding); //用debug打开
    }
    public void doFilter(ServletRequest req, ServeltResponse resp, FilterChain chain)throws IOException,ServletException{
       //上面的不是Httpservlet要自己写HttpServlet并导入 解决post请求中的中文乱码
        HttpServletRequest req = (HttpServletRequest)request;//解决中文乱码问题
        req.setCharacterEncoding("encoding");
        HttpServletResponse res = (HttpServletResponse)response;//解决中文乱码问题
        res.setContentType("text/html;charset=" + encoding);// GBK/UTF-8或者其他
        chain.doFilter(req, response);
    }
}

url-pattern设置过滤范围

url-pattern常用写法
  • /index.jsp - 执行资源精准匹配
  • /servlet/* - 以前缀进行模糊匹配
  • *.jsp - 以后缀进行模糊匹配
SampleServlet1.java
@WebServlet("/servlet/sample1")
public class SampleServlet1 extends HttpServlet{
   public SampleServlet1(){}
   protected void doGet(HttpServletRequest req,HttpServletResponse resp){
       resp.getWriter().println("I'm" + this.getClass().getSimpleName());
   }
}
UrlPatternFilter.java
public class UrlPatternFilter implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
//上面的不是Httpservlet要自己写HttpServlet并导入 解决post请求中的中文乱码
    HttpServletRequest req = (HttpServletRequest)request;//解决中文乱码问题
    HttpServletResponse res = (HttpServletResponse)response;//解决中文乱码问题
}
<filter>
    <filter-name>UrlPatternFilter</filter-name>
    <filter-class>filter.UrlPatternFilter</filter-class>
</filter>
    <filter-mapping> <!--  <url-pattern>/test.jsp</url-pattern> <!--只对其进行过滤 -->
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>/servlet/*</url-pattern>
    </filter-mapping>
    ......

在控制台Console显示:拦截到http://localhost:8080/url-pattern/test.jsp 因为这是精准匹配 需要换成 /*

/ 映射的问题

  • / 指映射Web应用根路径, 且只会对Servlet生效
  • 默认首页index.jsp会让 / 失效
  • //* 含义不同, 前者指向根路径, 后者代表所有
SampleServlet2.java
@WebServlet("/")
public class SampleServlet2 extends HttpServlet{
    public SampleServlet1(){}
    protected void doGet(HttpServletRequest req,HttpServletResponse resp){
        resp.getWriter().println("I'm" + this.getClass().getSimpleName());
    }
}
<filter>
    <filter-name>UrlPatternFilter</filter-name>
    <filter-class>filter.UrlPatternFilter</filter-class>
</filter>
    <filter-mapping>
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>/</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>/servlet/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>UrlPatternFilter</param-name>
        <url-pattern>*.jsp</url-pattern>
    </filter-mapping>
    ......
-------------------------------------------------------------
<!--或者在UrlPatternFilter上方进行注解 必须是全局唯一-->
@WebFilter(filterName="UrlPatternFilter", urlPatterns={
    "/","/servlet/*","*.jsp"
})

这个url-pattern 中的**/** 只会映射到根路径的SampleServlet2

@WebServlet(“/“)
public class SampleServlet2 extends HttpServlet{

Web.xml中默认配置了首页为index.jsp 如果想对默认首页拦截需要写
< url-pattern> ***/** < /url-pattern> 或 < url-pattern> /index.jsp < /url-pattern>
默认首页优先级比servlet要高

过滤链

过滤链开发注意事项
  • 每一个过滤器应具有单独职能
  • 过滤器的执行顺序以**< filter-mapping >**的前后顺序为准
  • 调用**chain.doFilter()**将请求向后传递
FilterA.java
public class FilterA implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("I'm Filter A");
       chain.doFilter(req, resp);
    }
    public void destroy(){}

FilterB.java
public class FilterA implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("I'm Filter B");
       chain.doFilter(req, resp);
    }
    public void destroy(){}

FilterC.java
public class FilterA implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       System.out.println("I'm Filter C");
       chain.doFilter(req, resp);
    }
    public void destroy(){}
<filter>
    <filter-name>FilterA</filter-name>
    <filter-class>filter.FilterA</filter-class>
</filter>
<filter>
    <filter-name>FilterB</filter-name>
    <filter-class>filter.FilterA</filter-class>
</filter>
<filter>
    <filter-name>FilterC</filter-name>
    <filter-class>filter.FilterA</filter-class>
</filter>
    <filter-mapping>
        <param-name>FilterA</param-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>FilterB</param-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <param-name>FilterC</param-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
---------------------不推荐使用注解--------------------------
用注解方式需要在每个Filter.java上面添加注解 
@WebFilter(filterName="FilterA", urlPatterns="/*")
public class FilterA implements Filter{
    //按照字母表升序排序 且不区分大小写
}
HelloServlet.java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException{
        response.getWriter().print("Hello World!");
        Sysotem.out.println("Hello World!")
    }
}

Console显示:顺序由决定
I’m Filter A //chain.doFilter(req, resp); 请求顺着过滤链往下走
I’m Filter B
I’m Filter C //过滤链没有格外的过滤器了 回到HelloServlet.java最终处理
Hello World!
//servlet处理完以后按照原先的顺序 逆向由Hello、C、B、A顺序返回

刻意去控制过滤器访问 VPN 仅限中国访问 如果是中国地址就调用chain.doFilter进行放行 其本质是防火墙

多端设备自动匹配(Pc端 移动端)

设备适配过滤器
index.html
自动脑补绘制两个html首页
一个是电脑端的图片展示
一个是手机端的图片展示
DeviceAdapterFilter.java
public class DeviceAdapterFilter implements Filter{
    public void init(FilterConfig filterConfig)thorws ServletException{}
    public void doFilter(ServletRequest req, ServeltResponse resp)throws IOException,ServletException,FilterChain chain{
       HttpServletRequest req = (HttpServletRequest)request;//强制类型转换
          HttpServletResponse res = (HttpServletResponse)response;
       /*
       /index.html
        PC: /desktop/index.html
        MOBILE: /mobile/index.html

       /test.html
        PC: /desktop/test.html
        MOBILE: /mobile/test.html
       */
       String uri = req.getRequestURI();
       System.out.println("URI: " + uri);
       if(uri.startsWith("/desktop") || uri.startsWith("/mobile")){
           chain.doFilter(req, resp);
       }else {    //读取客户端请求头 所有字符串转成小写
           String userAgent = req.getHeader("user-agent").toLowerCase(); 
           String targetURL = "";
           if(userAgent.indexOf("android")!=-1 || userAgent.indexOf("iphone")!=-1){
               targetURI = "/mobile" + uri;
               System.out.println("移动端设备正在访问, 重新跳转URI: " + targetURI)
               res.sendRedircet(targetURI); //向/mobile对应页面进行获取
           }else {
               targetURI = "/desktop" + uri;
                System.out.println("PC端设备正在访问, 重新跳转URI: " + targetURI)
               res.sendRedircet(targetURI); //向/mobile对应页面进行获取
           }
       }
    }
    public void destroy(){}
}
<filter>
    <filter-name>DeviceAdapterFilter</filter-name>
    <filter-class>filter.DeviceAdapterFilter</filter-class>
</filter>
    <filter-mapping>
        <param-name>DeviceAdapterFilter</param-name>
        <url-pattern>*.html</url-pattern> <!--对其进行过滤-->
    </filter-mapping>

URI: /index.html
PC端设备正在访问, 重新跳转URI: /desktop/index.html
URI: /desktop/index.html

++++++++++++++++++++++++++++++++++++++++++++

URI: /index.html
移动端设备正在访问, 重新跳转URI: /mobile/index.html
URI: /mobile/index.html

监听器、Freemarker

监听器:对Web应用对象的行为进行监控 [触发事件后进行捕获] ★★★
Freemarker[模板引擎]:模板脚本+数据来实现最终数据的产生 ★★★★★

监听器

生活中的”监听器”:汽车自动刹车系统 自动检测前方障碍物触发自动刹车;实时监控电表水表 钱不足直接断电

监听器 - Listener
  • 监听器(Listener)是J2EE Servlet模块下的组件
  • Listener的作用对Web应用对象的行为进行监控
  • 通过Listener监听自动触发指定的功能代码

三种监听对象

  • ServletContext - 对全局ServletContext及其属性进行监听
  • HttpSession - 对用户会话及其属性操作进行监听
  • ServletRequest - 对请求及属性操作进行监听
过滤器与监听器的区别
  • 过滤器(Filter)的职责是对**URL进行过滤拦截**, 是主动的执行
  • 监听器(Listener)的职责是对**Web对象进行监听**, 是被动触发

开发监听器三要素

  • 实现XxxListener接口, 不同接口对应不同监听对象
  • 实现每个接口中独有的方法, 实现触发监听的后续操作
  • 在web.xml中配置**< listener >**使监听器生效

``

第一个监听器 [全局推荐使用配置形式]

FirstListener.java //Debug启动
@WebListener //启动时tomcat会自动扫描
public class FirstListener implements ServletContextListener{
    //项目初始化所触发
    public void contextInitialized(ServletContextEvent sce){
        System.out.println("ServletContext已初始化");
    }
    //上下文被销毁时所触发 关闭时自动销毁
    public void contextDestoryed(ServletContextEvent sce){
       System.out.println("ServletContext已销毁");
    }
}
<listener>
    <listener-class>com.imooc.listener.FirstListener</listener-class>
</listener>

内置对象监听接口

  • ServletContextListener - 监听ServletContext对象创建、销毁等操作
  • HttpSessionListener - 监听HttpSession对象创建、销毁等操作
  • ServletRequestListener - 监听HttpServletRequest对象创建、销毁等操作
<listener>
    <listener-class>com.example.json.WebListener</listener-class>
</listener>
HelloServlet.java //设置属性的时候会触发WebListener
WebServlet("/hello")
public class HelloServlet extends HttpServlet{
    public HelloServlet(){}
    protected void doGet(HttpServletRequest req,HttpServletResponse resp){
        resp.getWriter().println("Hello World");
        req.getServletContext().setAttribute("sc-attr1","sc-attr-value1");
        req.getSession().setAttribute("session-attr1","session-attr-value1");
        req.setAttribute("request-attr1","request-attr-value1");
    }
}
WebListener.java
package com.example.json;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class WebListener implements ServletContextListener, HttpSessionListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext已被销毁");
    }

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        System.out.println("Session已被创建, SessionId:" + session);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        System.out.println("Session已被销毁");
    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        System.out.println("HttpServletRequest已被销毁");
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        HttpServletRequest request = (HttpServletRequest)servletRequestEvent.getServletRequest();
        //请求初始化完毕
        System.out.println("HttpServletRequest已被创建, URI" + request.getRequestURI());
    }
}

//session被创建了以后 第二次从同样的浏览器发出的请求覆盖了sessionid(3min后过期可人为而改变) 隐藏且可用
HttpServletRequest已被创建, URI/json_war_exploded/
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@71ed02bc
HttpServletRequest已被销毁
HttpServletRequest已被创建, URI/json_war_exploded/
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@194a08e0
HttpServletRequest已被销毁

属性监听接口 [了解]

  • ServletContextAttributeListener - 监听全局属性操作
  • HttpSessionAttributeListener - 监听用户会话属性操作
  • ServletRequestAttributeListener - 监听请求属性操作
web.xml 和 helloServlet.java的代码都一样
WebAttributeListener.java
package com.example.json;

import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class WebAttributeListener implements ServletContextAttributeListener, HttpSessionAttributeListener, ServletRequestAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
        System.out.println("ServletContext新增属性:" + servletContextAttributeEvent.getName() + "->" + servletContextAttributeEvent.getValue());
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {

    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {

    }

    @Override
    public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {
        System.out.println("ServletRequestAttributeEvent新增属性:" + servletRequestAttributeEvent.getName() + "->" + servletRequestAttributeEvent.getValue());

    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {

    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {

    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
        System.out.println("HttpSessionBindingEvent新增属性:" + httpSessionBindingEvent.getName() + "->" + httpSessionBindingEvent.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {

    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {

    }
}

ServletContext已初始化
[2023-09-28 04:44:52,590] Artifact json:war exploded: Artifact is deployed successfully
[2023-09-28 04:44:52,590] Artifact json:war exploded: Deploy took 394 milliseconds
HttpServletRequest已被创建, URI/json_war_exploded/
ServletContext新增属性:org.apache.jasper.runtime.JspApplicationContextImpl->org.apache.jasper.runtime.JspApplicationContextImpl@7753fcd4
ServletContext新增属性:org.apache.jasper.compiler.ELInterpreter->org.apache.jasper.compiler.ELInterpreterFactory$DefaultELInterpreter@3b424c58
ServletContext新增属性:org.apache.jasper.compiler.StringInterpreter->org.apache.jasper.compiler.StringInterpreterFactory$DefaultStringInterpreter@15bdbfaf
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@6baa8579
HttpServletRequest已被销毁
HttpServletRequest已被创建, URI/json_war_exploded/
Session已被创建, SessionId:org.apache.catalina.session.StandardSessionFacade@2843a202
HttpServletRequest已被销毁
HttpServletRequest已被创建, URI/json_war_exploded/hello
ServletContext新增属性:sc-attr1->sc-attr-value1
HttpSessionBindingEvent新增属性:session-attr1->session-attr-value1
ServletRequestAttributeEvent新增属性:request-attr1->request-attr-value1
HttpServletRequest已被销毁

监听器的应用场景

请求流量分析 [以图表形式展现]

流程:在应用启动的时候将两个List初始化 一个保存时间一个保存数值 分情况 若时间不存在初始化1 若存在则在原始+1进行更新操作

web.xml
<listener>
  <listener-class>com.example.json.RequestTotalListener</listener-class>
</listener>
RequestTotalListener.java
package com.example.json;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class RequestTotalListener implements ServletContextListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        List timeList = new ArrayList(); //时间数据
        List valueList = new ArrayList(); //具体时间访问量数据
        servletContextEvent.getServletContext().setAttribute("timeList", timeList);//得到最原始的context
        servletContextEvent.getServletContext().setAttribute("valueList", valueList);//启动ServletContext时自动启动倆请求
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        //请求初始化 每来一个新的请求监听器都会执行此方法 记录某一时间点的访问量
        //TimeList:10:02  10:03  10:04  10:05
        //ValueList: 5       7     10    1+1=2
        List<String> timeList = (List)servletRequestEvent.getServletContext().getAttribute("timeList");
        List<Integer> valueList = (List)servletRequestEvent.getServletContext().getAttribute("valueList");
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); //按照小时分钟来取
        String time = sdf.format(date); //date对象传入
        if (timeList.indexOf(time) == -1) { //查找对应的数据在集合中是否存在
            //若时间不存在 则追加时间
            timeList.add(time);
            valueList.add(1);//当前有1个请求被创建
            servletRequestEvent.getServletContext().setAttribute("timeList", timeList);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }else { //假设时间存在 不增加新数据 在原有数据加1
            int index = timeList.indexOf(time); //10:05返回索引值3
            int value = valueList.get(index);
            valueList.set(index, value+1);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }
    }
}
RequestTotalServlet.java
package com.imooc.total;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;

/**
 * Servlet implementation class RequestTotalServlet
 */
@WebServlet("/rt")
public class RequestTotalServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public RequestTotalServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext context = request.getServletContext();//得到全局context对象
        List<String> timeList = (List)context.getAttribute("timeList");//提取之前创建的
        List<Integer> valueList = (List)context.getAttribute("valueList");
        response.setContentType("text/html;charset=utf-8");
        /*response.getWriter().println(timeList.toString()); //打印输出时间数值数据
        response.getWriter().println("<br/>");
        response.getWriter().println(valueList.toString());*/
        
        Map result = new HashMap();
        result.put("timeList", timeList);
        result.put("valueList", valueList);
        String json = JSON.toJSONString(result);
        response.getWriter().println(json);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
}
text3.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test Page</title>
</head>
<body>
<h1>I'm test page 3</h1>
</body>
</html>

<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/echarts.min.js"></script>
<script type="text/javascript" src="js/jquery-3.1.1.js"></script>
</head>
<body>
   <div id="main" style="width: 600px; height: 400px;"></div>
   <script type = "text/javascript">
     //基于准备好的dom, 初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));
     //指定图表的配置项和数据
    var option = {
        title:{
            text:'Echarts 入门示例'
        },
        tooltip: {},
        legend: {
            data: ['销量']
        },
        xAxis: {
            data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
        },
        yAxis: {},
        series:[{
            name: '销量',
            type: 'bar',
            data: [5,20,36,10,10,20]
        }]
    };
       //使用刚指定的配置项和数据显示图表
       myChart.setOption(option);
    </script>
</body>

入门百度Echarts组件

利用Ajax和Jquery进行前后端通信

Apache ECharts

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    <script type="text/javascript" src="js/echarts.min.js"></script>
    <script type="text/javascript" src="js/jquery-3.1.1.js"></script>
</head>
<body>
<div id="main" style="width: 600px; height: 400px;"></div>
<script type="text/javascript">
    //每一秒钟向服务器查询一次数据把变化数据重新写入图表
//需要Javascript定时器完成
    function showChat(){//显示图表
        //基础的通讯部分
    $.ajax({
        url:"./rt",
        type:"get",
        dataType:"json",
        success:function(json){
            console.log(json.timeList);
            console.log(json.valueList);
    //基于准备好的dom, 初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));
    //指定图表的配置项和数据
    var option = {
        title: {
            text: '请求流量分析统计'
        },
        tooltip: {},
        legend: {
            data: ['访问量']
        },
        xAxis: {
           // data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
            data: json.timeList
        },
        yAxis: {},
        series: [{
            name: '访问量',
            type: 'line', //柱状图  line是折线图
           // data: [5, 20, 36, 10, 10, 20] //图中展示的数值 与商法data对应
            data: json.valueList
        }]
    };
    //激活使用刚指定的配置项和数据显示图表
    myChart.setOption(option);
        }
    })
}
       window.setInterval("shwoChart()", 1000);//间隔某时长去执行指定代码
    //每秒钟通过shwoChart()向服务器"./rt"发送请求 对此请求进行排除用RequestTotalListener去改写
</script>
</body>
</html>
RequestTotalListener.java
package com.example.json;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class RequestTotalListener implements ServletContextListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        List timeList = new ArrayList(); //时间数据
        List valueList = new ArrayList(); //具体时间访问量数据
        servletContextEvent.getServletContext().setAttribute("timeList", timeList);//得到最原始的context
        servletContextEvent.getServletContext().setAttribute("valueList", valueList);//启动ServletContext时自动启动倆请求
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        HttpServletRequest request = (HttpServletRequest)servletRequestEvent.getServletRequest();
        String url = request.getRequestURL().toString();
        if (url.endsWith("/rt") == true){
            return; //将rt的url排除之外
        }
        //请求初始化 每来一个新的请求监听器都会执行此方法 记录某一时间点的访问量
        //TimeList:10:02  10:03  10:04  10:05
        //ValueList: 5       7     10    1+1=2
        List<String> timeList = (List)servletRequestEvent.getServletContext().getAttribute("timeList");
        List<Integer> valueList = (List)servletRequestEvent.getServletContext().getAttribute("valueList");
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm"); //按照小时分钟来取
        String time = sdf.format(date); //date对象传入
        if (timeList.indexOf(time) == -1) { //查找对应的数据在集合中是否存在
            //若时间不存在 则追加时间
            timeList.add(time);
            valueList.add(1);//当前有1个请求被创建
            servletRequestEvent.getServletContext().setAttribute("timeList", timeList);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }else { //假设时间存在 不增加新数据 在原有数据加1
            int index = timeList.indexOf(time); //10:05返回索引值3
            int value = valueList.get(index);
            valueList.set(index, value+1);
            servletRequestEvent.getServletContext().setAttribute("valueList", valueList);
        }
    }
}
RequestTotalServlet.java
package com.example.json;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;

/**
 * Servlet implementation class RequestTotalServlet
 */
@WebServlet("/rt")
public class RequestTotalServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public RequestTotalServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext context = request.getServletContext();
        List<String> timeList = (List)context.getAttribute("timeList");
        List<Integer> valueList = (List)context.getAttribute("valueList");
        response.setContentType("text/html;charset=utf-8");
        /*response.getWriter().println(timeList.toString());
        response.getWriter().println("<br/>");
        response.getWriter().println(valueList.toString());*/ //将对象进行封装
        
        Map result = new HashMap();
        result.put("timeList", timeList);
        result.put("valueList", valueList);
        String json = JSON.toJSONString(result); //将java对象转换为json字符串
        response.getWriter().println(json);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }
}

静态数据预加载

通过监听器在上下文初始化的时候放在系统的全局属性中

Channel.java
public class Channel{
    private String channelName;
    private String url;
    ......
}
StaticDataListener.java
package com.imooc.listener;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.imooc.listener.entity.Channel;

public class StaticDataListener implements ServletContextListener{

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // TODO Auto-generated method stub
        List list  = new ArrayList();
        list.add(new Channel("免费课程" , "http://www.imooc.com/1"));
        list.add(new Channel("实战课程" , "http://www.imooc.com/2"));
        list.add(new Channel("就业班" , "http://www.imooc.com/3"));
        sce.getServletContext().setAttribute("channelList", list);
        //一次性写入到全局属性种
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // TODO Auto-generated method stub
        
    }

}
web.xml
<listener>
    <listener-class>listener.StaticDataListener</listener-class>
</listener>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:forEach items="${applicationScope.channelList }" var="c"> <!--有效数据提取-->
    <a href="${c.url }">${c.channelName }</a> | 
</c:forEach>
<hr/>
</body>
</html>

FreeMarker(FTL)模板引擎[可以替代jsp]

FreeMarker是一款用java语言编写的模版引擎,它虽然不是web应用框架,但它很合适作为web应用框架的一个组件。

特点:
1.轻量级模版引擎,不需要Servlet环境就可以很轻松的嵌入到应用程序中
2.能生成各种文本,如html,xml,java
3.入门简单,它是用java编写的,很多语法和java相似

基础语法及入门基础

freemarker模板文件(*.ftl)的基本组成部分
1.文本:直接输出的内容部分
2.注释:不会输出的内容,格式为 <#– 注释内容 –>
3.取值(插值):代替输出数据模型的部分,格式为${数据模型}或#{数据模型}
4.ftl指令:Freemarker指令,类似于HTML标记。

字符输出
$(emp.name?if_exists)  //变量存在,输出该变量,否则不输出
${emp.name!}       //变量存在,输出该变量,否则不输出
${emp.name?default("xxx")} // 变量不存在,取默认值xxx
${emp.name!"xxx"}           // 变量不存在,取默认值xxx
常用内部函数
${"123<br>456"?html}   // 对字符串进行HTML编码,对html中特殊字符进行转义
${"str"?cap_first}    // 使字符串第一个字母大写 
${"Str"?lower_case}    // 将字符串转换成小写
${"Str"?upper_case}   // 将字符串转换成大写
${"str"?trim}            // 去掉字符串前后的空白字符
字符串的两种拼接方法
${"hello${emp.name!}"} //输出hello+变量名
${"hello"+emp.name!}   //使用+号来连接,输出hello+变量名
截取子串
可以通过如下语法来截取子串:
<#assign str = "abcdefghijklmn"/>
// 方法1${str?substring(0,4)} // 输出abcd
// 方法2${str[0]}${str[4]} // 结果是ae
${str[1..4]}     // 结果是bcde// 返回指定字符的索引${str?index_of("n")}
日期输出
${emp.date?string('yyyy-MM-dd')} //日期格式
数字输出(以数字20为例)
${emp.name?string.number} //输出20
${emp.name?string.currency} //¥20.00
${emp.name?string.precent} //20%
${1.222?int}          // 将小数转为int,输出1
<#setting number_format="percent"/> // 设置数字默认输出方式('percent',百分比)
<#assign answer=42/>          // 声明变量 answer 42
#{answer}          // 输出 4,200%
${answer?string}      // 输出 4,200%
${answer?string.number}   // 输出 42
${answer?string.currency} // 输出 ¥42.00
${answer?string.percent}  // 输出 4,200%
#{answer}         // 输出 42
比较运算符
表达式中支持的比较运算符有如下几个:= 或 == :判断两个值是否相等.
!= :判断两个值是否不等.
> 或 gt :判断左边值是否大于右边值
>= 或 gte :判断左边值是否大于等于右边值
< 或 lt :判断左边值是否小于右边值
<= 或 lte :判断左边值是否小于等于右边值
算术运算符
FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , % 注意:
(1)运算符两边必须是数字
(2)使用+运算符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符串再连接,如:${3 + "5"},结果是:35
FreeMarker中的运算符优先级如下(由高到低排列):

①、一元运算符:!
②、内建函数:?
③、乘除法:*, / , %
④、加减法:- , +
⑤、比较:> , < , >= , <= (lt , lte , gt , gte)
⑥、相等:== , = , !=
⑦、逻辑与:&&
⑧、逻辑或:||
⑨、数字范围:..实际上,我们在开发过程中应该使用括号来严格区分,这样的可读性好,出错少

if逻辑判断(注意:elseif 不加空格)
<#if condition>
...
<#elseif condition2>
...
<#elseif condition3>
...
<#else>
...
</#if>
switch(条件可为数字,可为字符串)
<#switch value>
 <#case refValue1>
....
<#break> 
<#case refValue2> 
....
<#break>
 <#case refValueN>
 ....
<#break>
 <#default>
 ....
 </#switch>
集合 & 循环
//遍历集合
<#list empList! as emp>
    ${emp.name!}    
</#list>

//使用<#break>跳出循环
<#if emp_index = 0><#break></#if>

//集合长度判断 
<#if empList?size!=0></#if> //判断=的时候,注意只要一个=符号,而不是==
<#assign l=0..100/>       // 定义一个int区间的0~100的集合,数字范围也支持反递增,如100..2
<#list 0..100 as i>   // 等效于java for(int i=0; i <= 100; i++)
  ${i}
</#list>

// 截取子集合:
empList[3..5] //返回empList集合的子集合,子集合中的元素是empList集合中的第4-6个元素

// 创建集合:
<#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as x>

// 集合连接运算,将两个集合连接成一个新的集合
<#list ["星期一","星期二","星期三"] + ["星期四","星期五","星期六","星期天"] as x>

// 除此之外,集合元素也可以是表达式,例子如下:
[2 + 2, [1, 2, 3, 4], "whatnot"]

// seq_contains:判断序列中的元素是否存在
<#assign x = ["red", 16, "blue", "cyan"]> 
${x?seq_contains("blue")?string("yes", "no")}    // yes
${x?seq_contains("yellow")?string("yes", "no")}  // no
${x?seq_contains(16)?string("yes", "no")}        // yes
${x?seq_contains("16")?string("yes", "no")}      // no

// seq_index_of:第一次出现的索引
<#assign x = ["red", 16, "blue", "cyan", "blue"]> 
${x?seq_index_of("blue")}  // 2

// sort_by:排序(升序)
<#list movies?sort_by("showtime") as movie></#list>

// sort_by:排序(降序)
<#list movies?sort_by("showtime")?reverse as movie></#list>

// 具体介绍:
// 不排序的情况:
<#list movies as moive>
  <a href="${moive.url}">${moive.name}</a>
</#list>
//要是排序,则用
<#list movies?sort as movie>
  <a href="${movie.url}">${movie.name}</a>
</#list>
    
/ 这是按元素的首字母排序。若要按list中对象元素的某一属性排序的话,则用
<#list moives?sort_by(["name"]) as movie>
  <a href="${movie.url}">${movie.name}</a>
</#list>

//这个是按list中对象元素的[name]属性排序的,是升序,如果需要降序的话,如下所示:
<#list movies?sort_by(["name"])?reverse as movie>
  <a href="${movie.url}">${movie.name}</a>
</#list>
    
//Map对象 创建map
<#assign scores = {"语文":86,"数学":78}>

// Map连接运算符
<#assign scores = {"语文":86,"数学":78} + {"数学":87,"Java":93}>

// Map元素输出
emp.name       // 全部使用点语法
emp["name"]    // 使用方括号

// FreeMarker支持如下转义字符:
\" :双引号(u0022)
\' :单引号(u0027)
\\ :反斜杠(u005C)
\n :换行(u000A)
\r :回车(u000D)
\t :Tab(u0009)
\b :退格键(u0008)
\f :Form feed(u000C)
\l :<
\g :>
\a :&
\{ :{
\xCode :直接通过4位的16进制数来指定Unicode码,输出该unicode码对应的字符.
    
// include指令的作用类似于JSP的包含指令:
<#include "/test.ftl" encoding="UTF-8" parse=true>

// 在上面的语法格式中,两个参数的解释如下:
encoding="GBK"  // 编码格式
parse=true    // 是否作为ftl语法解析,默认是true,false就是以文本方式引入,
注意:在ftl文件里布尔值都是直接赋值的如parse=true,而不是parse="true"
    
//import指令
类似于jsp里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件
<#import "/libs/mylib.ftl" as my>
上面的代码将导入/lib/common.ftl模板文件中的所有变量,
交将这些变量放置在一个名为com的Map对象中,"my"在freemarker里被称作namespace

数据 + 模板 = 结果

数据 [Java代码]
User user = new User();
user.setName(“张三”);

模板 [HTML简化]
< span >
${user.name}
< /span >

结果
< span >
张三
< /span >

  • Freemarker是免费开源的模板引擎技术
  • Freemarker脚本为(Freemarker Template Language)
  • Freemarker提供了大量内建函数来简化开发

JSP与Freemarker

JSP Freemarker
官方标准
执行方式 编译型 解释型
执行效率
开发效率
扩展能力
数据提取 JSTL + EL 内置标签

前端工程师把数据写在ftl 然后后端工程师拿着名称去添加后台数据

package com.example.json;


import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class FreemarkerSample1 {
    public static void main(String[] args) throws IOException, TemplateException {
        //1. 加载模板
        Configuration confige = new Configuration(Configuration.VERSION_2_3_32);
                     // FreemarkerSample1类所在包中加载ftl所在文件
        confige.setClassForTemplateLoading(FreemarkerSample1.class, "");
        Template t = confige.getTemplate("sample1.ftl");
        //2. 创建数据
        Map<String,Object> data = new HashMap<String, Object>();
        data.put("site","百度");
        data.put("url","https://www.abc.com");
        data.put("data",new Date());
        data.put("number",83719.88321);
        Map info = new HashMap();
        info.put("cpu","i5");
        Computer c1 = new Computer("123456","ALIENWARE",1,"李四",new Date(), 12900f)
        data.put("computer",c1);
        //3. 产生输出    向目标输出
        t.process(data, new OutputStreamWriter(System.out));
    }
}
${site}
${url}
<#--类似于EL表达式-->
Computer.java
public class Computer{
    private String sn; //序列号
    private String mode1; //型号
    private int state; //状态 1-在用 2-闲置 3-报废
    private String user; //使用人
    private Date dog; //采购日期
    private Float price; //购买价格
    private Map info; //电脑配置信息
    ......
}
sample1.ftl
<#-- Freemarker取值 -->
${site}
${url}

<#-- !默认值 -->
${author!"不存在的属性"}

<#-- ?string格式化输出 -->
${date?string("yyyy年MM月dd日 HH:mm:ss SSS")}
${number?string("0.00")}

SN:${computer.sn}
型号:${computer.mode1}
状态:${computer.state}
用户:${computer.user}
采购时间:${computer.dop?string("yyyy年MM月dd日")}
采购价格:${computer.price?string("0.00")}

//对map的提取
CPU:${computer.info["cpu"]}
内存:${computer.info["memory"]!"无内存信息"}

FTL(FreeMarker Template Language)取值

  • ${属性名} 取值,可对属性进行计算

  • ${属性名!默认值} 使用默认值[解决空值异常]
    ${author!”不存在的属性”} => 不存在的属性

  • ${属性名?string} 格式化输出
    ${data?string(“yyyy年MM月dd日 HH:mm:ss SSS”)}

    ${number?string(“0.00”)} 小数部位保留两位

if分支判断
<#if 条件1>
  条件1成立执行代码
<#if 条件2>
  条件2成立执行代码
<#if 条件3>
  条件3成立执行代码
<#else>
  其他情况下执行代码
switch分支判断
<#switch value>
  <#case refValue1>
    ...
    <#break>
  <#case refValue2>
    ...
    <#break>
  <#case refValueN>
    ...
    <#break>
<#default>
...
</#switch>

sample1.ftl
<#-- Freemarker取值 -->
${site}
${url}

<#-- !默认值 -->
${author!"不存在的属性"}

<#-- ?string格式化输出 -->
${date?string("yyyy年MM月dd日 HH:mm:ss SSS")}
${number?string("0.00")}
<#if computer.sn == "1234567">
重要设备
</#if>

SN:${computer.sn}
型号:${computer.mode1}
状态:${computer.state}

<#if computer.state == 1>
状态:正在使用
<#elseif computer.state == 2>
状态:闲置
<#elseif computer.state == 3>
状态:已作废
</#if>

<#if computer.user??> //??代表判断对象是否为空,true不为空,false为空
用户:${computer.user}
</#if>
--------------------------------------------------------
<#switch computer.state>  //同一个属性的不同值进行判断
    <#case1>
        状态:正在使用
        <#break>
    <#case2>
        状态:闲置
        <#break>
    <#case3>
        状态:已作废
        <#break>
    <#default>
        状态:无效状态
--------------------------------------------------------
采购时间:${computer.dop?string("yyyy年MM月dd日")}
采购价格:${computer.price?string("0.00")}

//对map的提取
CPU:${computer.info["cpu"]}
内存:${computer.info["memory"]!"无内存信息"}

list迭代列表

<#list students as stu>
  <li>${stu_index}-${stu.name}</li>
</#list>
list迭代Map
<#list map?keys as key>
  ${key}:${map[key]}
</#list>
FreemarkerSample2.java
package com.example.json;


import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class FreemarkerSample1 {
    public static void main(String[] args) throws IOException, TemplateException {
        //1. 加载模板
        Configuration confige = new Configuration(Configuration.VERSION_2_3_32);
          // FreemarkerSample1类所在包中加载ftl所在文件
        confige.setClassForTemplateLoading(FreemarkerSample2.class, "");
        Template t = confige.getTemplate("sample1.ftl"); //得到模板对象
        //2. 创建数据
        Map<String,Object> data = new HashMap<String,object>();
        List<Computer> computers = new ArrayList();
        computers.add(new Computer("123456","ALIENWARE",2,null,new Date(), 12900f, new HashMap()));
        computers.add(new Computer("123456","HP XXX",1,"张三",new Date(), 12900f, new HashMap()));
        computers.add(new Computer("1233145","DELL XXX",3,"李四",new Date(), 12900f, new HashMap()));
        computers.add(new Computer("1234536","ACER XXX",1,"王五",new Date(), 12900f, new HashMap()));
        computers.add(new Computer("12344456","MSI XXX",1,"赵六",new Date(), 12900f, new HashMap()));
        data.put("computers", computers);
        //LinkedHashMap可以保证数据按存放顺序进行提取
        Map computerMap = new LinkedHashMap();
        for(Computer c : computers){
            computerMap.put(c.getSn(), c);
        } //下方新增
        //3. 产生输出    向目标输出
        t.process(data, new OutputStreamWriter(System.out));
    }
}
sample2.ftl
<#list computers as c> //迭代循环 用变量c
序号:${c_index} //迭代变量 index保存了循环的索引,从0开始 +1则从1开始
SN:${c.sn}
型号:${c.mode1}
<#switch c.state>  //同一个属性的不同值进行判断
    <#case1>
        状态:正在使用
        <#break>
    <#case2>
        状态:闲置
        <#break>
    <#case3>
        状态:已作废
        <#break>
    <#default>
        状态:无效状态
</#switch>
<#if c.user??>
状态:${c.state}
</#if>
用户:${c.user}
采购时间:${c.dop?string("yyyy-MM-dd")}
采购价格:${c.price?string("0.00")}
</#list>
-------------------------------------------------------------
<#list computer_map?keys as k >
${k}-${computer_map[k].model}
${computer_map[k].price?string("0.00")}
</#list>

Freemarker内建函数[看成java代码 就是点”.”换成了问号”?”]

freemarker.foofun.cn 在线学习

函数名 说明 示例
lower_case/upper_case 大小写转换 “abcde”?upper_case
cap_first 首字母大写 “”abcde””?cap_first
index_of 查找字符索引 “abcde”?index_of(“b”)
length 返回字符串长度 “abcde”?length
round/floor/ceiling 四舍五入/下取整/上取整 pi?floor
size 得到集合元素总数 students?size
first/last 获取第一个/最后一个元素 students?first
sort_by 按某个属性对集合排序 list?sort_by(“time”)
example:
Map<String,Object> data = new HashMap<String,Object>();
data.put("name", "jackson");
----------------------------
${name?cap_first}
//违禁字母屏蔽
${words?replace("blood", "******")}
${words?index_of("blood")}
//三目运算 
${(words?index_of("blood") != -1)?string("包含敏感词汇","不包含敏感词汇")}

公司共有${computers?size}台电脑
//集合排序 返回一个新的集合
<#list computers?sort_by("price") as c >
    ${c.sn}-${c.price}
</#list>
//集合排序{降序}
<#list computers?sort_by("price")?reverse as c >
    ${c.sn}-${c.price}
</#list>

Freemarker与Servlet整合

web.xml
<servlet>
  <servlet-name>freemarker</servlet-name>
  <servlet-class>freemarkerServlet</servlet-class>
  <init-param>
      <param-name>TemplatePath</param-name>
    <param-value>/WEB-INF/ftl</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>freemarker</servlet-name>
  <url-pattern>*.ftl</url-pattern> //地址映射 输入这样的后缀时自动去/WEB-INF/ftl查找
</servlet-mapping>
test.ftl
这是一个测试FTL文件

localhost:8080/fm-web/test.ftl
employee.ftl
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>员工列表</title>
    <link href="./css/bootstrap.css" type="text/css" rel="stylesheet"/>
    <script type="text/javascript" src="./js/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="./js/bootstrap.js"></script>

    <style type="text/css">
        .pagination {
            margin: 0px
        }

        .pagination > li > a, .pagination > li > span {
            margin: 0 5px;
            border: 1px solid #dddddd;
        }

        .glyphicon {
            margin-right: 3px;
        }

        .form-control[readonly] {
            cursor: pointer;
            background-color: white;
        }
        #dlgPhoto .modal-body{
            text-align: center;
        }
        .preview{

            max-width: 500px;
        }
    </style>
    <script>
        $(function () {
            
            $("#btnAdd").click(function () {
                $('#dlgForm').modal()
            });
        })
    </script>
</head>
<body>

<div class="container">
    <div class="row">
        <h1 style="text-align: center">IMOOC员工信息表</h1>
        <div class="panel panel-default">
            <div class="clearfix panel-heading ">
                <div class="input-group" style="width: 500px;">
                    <button class="btn btn-primary" id="btnAdd"><span class="glyphicon glyphicon-zoom-in"></span>新增
                    </button>
                </div>
            </div>

            <table class="table table-bordered table-hover">
                <thead>
                <tr>
                    <th>序号</th>
                    <th>员工编号</th>
                    <th>姓名</th>
                    <th>部门</th>
                    <th>职务</th>
                    <th>工资</th>
                    <th>&nbsp;</th>
                </tr>
                </thead>
                <tbody>
//<c:forEach items="${applicationScope.employees}" var="emp" varStatus="idx">
                <#list employee_list as emp>
                <tr>
                    <td>${emp_index + 1}</td>
                    <td>${emp.empno?string("0")}</td> //整数输出用0初始化
                    <td>${emp.ename}</td>
                    <td>${emp.department}</td>
                    <td>${emp.job}</td>
//<td style="color: red;font-weight: bold">¥<fmt:formatNumber value="${emp.salary}" pattern="0,000.00"></fmt:formatNumber></td>
                    <td style="color: red;font-weight: bold">¥${emp.salary?string("0.00")}</td>
                </tr>
                </c:forEach>
            </table>
        </div>
    </div>
</div>

<!-- 表单 -->
<div class="modal fade" tabindex="-1" role="dialog" id="dlgForm">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title">新增员工</h4>
            </div>
            <div class="modal-body">
                <form action="/untitled_war_exploded/create" method="post" >
                    <div class="form-group">
                        <label for="empno">员工编号</label>
                        <input type="text" name="empno" class="form-control" id="empno" placeholder="请输入员工编号">
                    </div>
                    <div class="form-group">
                        <label for="ename">员工姓名</label>
                        <input type="text" name="ename" class="form-control" id="ename" placeholder="请输入员工姓名">
                    </div>
                    <div class="form-group">
                        <label>部门</label>
                        <select id="dname" name="department" class="form-control">
                            <option selected="selected">请选择部门</option>
                            <option value="市场部">市场部</option>
                            <option value="研发部">研发部</option>
                            <option value="后勤部">后勤部</option>
                        </select>
                    </div>

                    <div class="form-group">
                        <label>职务</label>
                        <input type="text" name="job" class="form-control" id="sal" placeholder="请输入职务">
                    </div>

                    <div class="form-group">
                        <label for="sal">工资</label>
                        <input type="text" name="salary" class="form-control" id="sal" placeholder="请输入工资">
                    </div>

                    <div class="form-group" style="text-align: center;">
                        <button type="submit" class="btn btn-primary">保存</button>
                    </div>
                </form>
            </div>

        </div><!-- /.modal-content -->
    </div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</body>
</html>
Employee.java
public class Employee {
    private Integer empno;
    private String ename;
    private String department;
    private String job;
    private Float salary;
    ...
}
ListServlet.java
package com.imooc.freemarker.servlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class ListServlet
 */
@WebServlet("/list")
public class ListServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public ListServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List list = new ArrayList();
        list.add(new Employee(7731,"张三" , "市场部" , "客户代表" , 8000f));
        list.add(new Employee(8871,"李四" , "研发部" , "运维工程师" , 7000f));
        request.getServletContext().setAttribute("employee_list", list);
        request.getRequestDispatcher("/employee.ftl").forward(request, response);
    }
}
阅读全文

XML_Schema dtd Dom4j XPath

2023/9/12

XML(Extensible Markup Language)

XML可扩展标记语言,编写XML就是编写标签,与HTML非常类似,扩展名.xml。有良好的人机可读性

hr.xml
<employee>
 <name>张三</name>
 <age>31</age>
 <height>178</height>
</employee>
  • XML与HTML非常相似,都是编写标签
  • XML没有预定义标签,HTML存在大量预定义标签
  • XML重在保存与传输数据,HTML用于显示信息

XML用途

① 程序中有各种设置项提取出来存放在XML文件中,利用XML进行程序的设计

web.xml - web应用配置文件
<web-app>
 <servlet>
  <servlet-name>InitTest</servlet-name>
  <servlet-class>moreservlets.InitServlet</servlet-class>
   <init-param>
    <param-name>param1</param-name>
    <param-value>value1</param-value>
   </init-param>
   <init-param>
    <param-name>param2</param-name>
    <param-value>2</param-value>
   </init-param>
 </servlet>
</web-app>

② 用于保存程序产生的数据 [简单的变成可以把数据库导成xml]

hr.xml
<employee>
 <name>张三</name>
 <age>31</age>
 <height>178</height>
 <salary>7800</salary>
</employee>

③ 网络间的数据传输

webservice底层soap协议
<Envelop>
<Body>
<m:reversexmlns:m="urn:strings-com:IString">
<theString>Hello,World</theString>
</m:reversexmlns:m>
</Body>
</Envelop>

XML文档结构

  • 第一行必须是XML声明

    XML声明说明XML文档的基本信息,包括版本号与字符集,写在XML第一行

    <?xml version="1.0" encoding="UTF-8"?>
    version代表版本号1.0
    encoding UTF-8设置字符集,用于支持中文
    <!-- 人力资源管理系统 -->
    <hr>
      <employee no="3309">
        <name>张三</name>
        <age>31</age>
        <salary>4000</salary>
        <department>
          <dname>会计部</dname>
          <addresss>XX大厦-B103</addresss>
        </department>
      </employee>
    
      <employee no="3310">
        <name>李四</name>
        <age>23</age>
        <salary>4000</salary>
        <department>
          <dname>工程部</dname>
          <addresss>XX大厦-B104</addresss>
        </department>
      </employee>
    </hr>
    
  • 有且只有一个根节点

  • XML标签的书写规则与HTML相同

合法的标签名

标签名要有意义
建议使用英文,小写字母,单词之间使用”-“分割

适当的注释与缩进

适当的注释与缩进可以让XML文档更容易阅读

合理使用属性

标签属性用于描述标签不可或缺的信息
对标签分组或者为标签设置Id时常用属性表示

<shop-cart>
<item sn="771938" category="电器">
 <name>XXX空调</name>
 <price>2000.00</price>
 <num>1</num>
</item>
<item sn="890321" category="食品">
 <name>法式面包</name>
 <price>10.00</price>
 <num>5</num>
</item>
</shop-cart>
特殊字符与CDATA标签

标签体中,出现”<”、”>”特殊字符,会破坏文档结构

错误的XML
<question> 1+4<3是否正确?</question>

解决方案①:使用实体引用

实体引用 对应符号 说明
& It; < 小于
& gt; > 大于
& amp; & 和号
& apos; 单引号
& quot; 双引号
修改后的XML
<question> 1+4&lt;3是否正确?</question>

解决方案②:使用CDATA标签
CDATA指的是不应由XML解析器进行解析的文本数据
从”< ![CDATA[“开始,到”]] >“结束

<lesson>
<content>
 <![CDATA[
 本节我们来学习html中a标签的使用:
 <body>
  <a href="index.html">首页</a>
 </body>
 ]]>
</content>
</lesson>
有序的子元素

在XML多层嵌套的子元素中,标签前后顺序应保持一致
<…> </…>

XML语义约束之DTD

XML文档结构正确,但可能不是有效的

  • 例如,员工档案XML中绝不允许出现 “植物品种” 标签。XML语义约束就是用于规定XML文档中允许出现哪些元素
  • XML语义约束由两种定义方式:DTDXML Schema

DTD(Document Type Definition, 文档类型定义) 是一种简单易用的语义约束方式
DTD文件的扩展名为.dtd [用于说明HTML中拥有哪些节点可以出现]

hr.dtd
<!ELEMENT hr (employee+)>
<!ELEMENT employee (name,age,salary,department)>
<!ATTLIST employee no CDATA "">
<!ELEMENT name (#PCDATA)>
...
定于hr节点下只允许出现1个employee子节点
<!ELEMENT hr (employee)>

employee节点下必须包含以下四个节点,且按顺序出现
<!ELEMENT employee (name,age,salary,department)
    
定义name标签体只能是文本,#PCDATA代表文本元素
<!ELEMENT name (#PCDATA)>
DTD定义节点数量
  • 如果某个子节点需要多个重复出现,则需要在子节点后增加相应的描述符
    [后面带个+号 最少出现一个]
hr节点下最少出现1个emploee子节点    [+]
<!ELEMENT hr (employee+)>

hr节点下可出现0..n个employee子节点  [*]
<!ELEMENT hr (employee*)>

hr节点下最多出现1个emploee子节点    [?]
<!ELEMENT hr (employee?)>
XML引用DTD文件
  • 在XML中使用**< !DOCTYPE >**标签来引用DTD文件
书写格式:
<!DOCTYPE 根节点 SYSTEM "dtd文件路径">
示例:
<!DOCTYPE hr SYSTEM "hr.dtd">
案例 [XML像是描述 dtd像是约束 ]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hr SYSTEM "hr.dtd">
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>4000</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>
</hr>
<?xml version="1.0" encoding="UTF-8" ?>
  <!ELEMENT hr (employee+)>
  <!ELEMENT employee (name,age,salary,department)>
  <!--前后顺序必须匹配 一一对应-->
  <!ATTLIST employee no CDATA "">
  <!ELEMENT name (#PCDATA)>
  <!ELEMENT age (#PCDATA)>
  <!ELEMENT salary (#PCDATA)>
  <!ELEMENT department (dname,address)>
  <!ELEMENT dname (#PCDATA)>
  <!ELEMENT address (#PCDATA)>
  <!--里面是纯文本节点 department有两个子节点-->

XML Schema(比dtd更高级)

  • XML Schema比DTD更为复杂,提供了多个功能
  • XML Schema提供了数据类型、格式限定、数据范围等特性
  • XML Schema是W3C标准
<?xml version="1.0" encoding="UTF-8" ?>
<hr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="hr.xsd">
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>4000</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>
</hr>
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
  <element name="hr">
<!--complexType标签含义是复杂节点,包含子节点时必须使用这个标签-->
    <complexType>
      <sequence>
        <!-- 节点最少出现一次  -->
        <element name="employee" minOccurs="1">
          <complexType>
            <sequence>
              <element name="name" type="string"></element>
              <element name="age">
                <simpleType>
                  <restriction base="integer"> <!--给年龄做限定-->
                    <minInclusive value="18"></minInclusive>
                    <maxInclusive value="60"></maxInclusive>
                  </restriction>
                </simpleType>
              </element>
              <element name="department">
                <complexType>
                  <sequence>
                    <element name="dname" type="string"></element>
                    <element name="address" type="string"></element>
                  </sequence>
                </complexType>
              </element>
            </sequence>
          </complexType>
        </element>
      </sequence>
      <!--no在任何employee节点下必须存在-->
      <attribute name="no" type="string" use="required"></attribute>
    </complexType>
  </element>
</schema>

DOM文档对象模型

DOM(Document Object Model)定义了访问和操作XML文件的标准方法,DOM把XML文档作为树结构来看,能够通过DOM树来读写所有元素。

Dom4j[以java的形式解析xml]

Dom4j是一个易用的、开源的库,用于解析XML。它应用于Java平台,具有性能优异、功能强大和极其易用的特点。

  • Dom4j将XML视为Document对象
  • XML标签被Dom4j定义为Element对象
Dom4j对xml的解析读取和遍历

[原则是按照documents根节点和子节点依次类推的顺序对其进行分析、解析、提取]

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>4000</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>
</hr>
package src;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

public class HrReader{
  public void readXml() throws DocumentException {
    String file = "D:\\JetBrains\\IdeaProjects\\Test_html\\src\\hr.xml";
    //SAXReader类是读取XML文件的核心类,用于将XML解析后
      SAXReader reader = new SAXReader();
      Document document = reader.read(file);
      //获取XML文档的根节点,即hr标签
      Element root = document.getRootElement();
      //elements方法用于获取指定的标签集合
      List<Element> employees = root.elements("employee");
      for (Element employee : employees){
       /* //element方法用于获取唯一的子节点对象
        Element name = employee.element("name");
        //getText()方法用于获取标签文本
        String empName = name.getText();
        System.out.println(empName);
        */
        System.out.println(employee.elementText("age"));
        System.out.println(employee.elementText("salary"));
        Element department = employee.element("department");
        System.out.println(department.elementText("dname"));
        System.out.println(department.element("address").getText());
        Attribute att = employee.attribute("no"); /*获取对应文本*/
        System.out.println(att.getText());
        System.out.println("====================");
        }
      }

  public static void main(String[] args) throws DocumentException {
    HrReader reader = new HrReader();
    reader.readXml();
  }
}
Dom4j更新(写入)XML
已追加写入的信息
<hr>
 <employee no="3311">
    <name>李铁柱</name>
    <age>28</age>
    <salary>3600</salary>
    <department>
      <dname>人事部</dname>
      <address>XX大厦-B105</address>
    </department>
  </employee>
</hr>
package src;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class HrWriter {
  public void weiteXml(){
    String file = "D:\\JetBrains\\IdeaProjects\\Test_html\\src\\hr.xml";
    SAXReader reader = new SAXReader();
    try {
      Document document = reader.read(file);
      Element root = document.getRootElement();
      //创建employee子节点 全新空子节点
      Element employee = root.addElement("employee");
      employee.addAttribute("no","3311");
      Element name = employee.addElement("name");
      name.setText("李铁柱");
      employee.addElement("age").setText("28");
      employee.addElement("salary").setText("3600");
      Element department = employee.addElement("department");
      department.addElement("dname").setText("人事部");
      department.addElement("address").setText("XX大厦-B105");
      //内存中组织的dom模型重新写入到对应文件中
      Writer writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
      document.write(writer);
      writer.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    HrWriter hrWriter = new HrWriter();
    hrWriter.weiteXml();
  }
}

XPath路径表达式

  • XPath路径表达式是XML文档中查找数据的语言
  • 掌握XPath可以极大的提高在读取数据时的开发效率
  • 学习XPath本质就是掌握各种形式表达式的使用技巧
最常用的基本表达式
表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
路径表达式 结果
bookstore 选取bookstore元素的所有子节点
/bookstore 选取根元素bookstore
注释:假如路径起始于正斜杠(/), 则此路径始终代表到某元素的绝对路径
bookstore/book 选取属于bookstore的子元素的所有book元素
//book 选取所有book子元素,而不管它们在文档中的位置
bookstore//book 选取属于bookstore元素的后代的所有book元素,而不管它们位于bookstore之下的什么位置
//@lang 选取名为lang的所有属性
XPath谓语表达式
路径表达式 结果
/bookstore/book[1] 选取属于bookstore子元素的第一个book元素
/bookstore/book[last()] 选取属于bookstore子元素的最后一个book元素
/bookstore/book[position()<3] 选取最前面的两个属于bookstore元素的子元素的book元素
//title[@lang] 选取所有拥有名为lang的属性的title元素
/bookstore/book[price>35.00] 选取bookstore元素的所有book元素,且其中的price元素的值须大于35.00
/bookstore/book[price>35.00]/title 选取bookstore元素中的book元素的所有title元素,且其中的price元素的值必须大于35.00

XPath实验室 [优先使用 查询数据]

Jaxen介绍
  • Jaxen是一个Java编写的开源的XPath库。这是适合多种不同的对象模型,包括DOM,XOM,dom4j和JDOM
  • Dom4j底层一来Jaxen实现XPath查询
<?xml version="1.0" encoding="UTF-8"?>
<!-- 人力资源管理系统 -->
<hr>
  <employee no="3309">
    <name>张三</name>
    <age>31</age>
    <salary>4000</salary>
    <department>
      <dname>会计部</dname>
      <address>XX大厦-B103</address>
    </department>
  </employee>

  <employee no="3310">
    <name>李四</name>
    <age>23</age>
    <salary>3200</salary>
    <department>
      <dname>工程部</dname>
      <address>XX大厦-B104</address>
    </department>
  </employee>

  <employee no="3311">
    <name>李铁柱</name>
    <age>28</age>
    <salary>3600</salary>
    <department>
      <dname>人事部</dname>
      <address>XX大厦-B105</address>
    </department>
  </employee>
</hr>
package src;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.util.List;

public class XPathTestor {
  public void xpath(String xpathExp){
    String file = "D:\\JetBrains\\IdeaProjects\\Test_html\\src\\hr.xml";
    SAXReader reader = new SAXReader();
    try {
      Document document = reader.read(file);
      //执行xpath表达式 Node不仅查询标签还可以查询属性
      List<Node> nodes = document.selectNodes(xpathExp);
      for (Node node : nodes){
        //转换成常用的Element对象 父类node强转
        Element emp = (Element)node;
        System.out.println(emp.attribute("no"));
        System.out.println(emp.elementText("name"));
        System.out.println(emp.elementText("age"));
        System.out.println(emp.elementText("salary"));
        System.out.println("===========================");
      }
    } catch (DocumentException e) {
      throw new RuntimeException(e);
    }
  }

  public static void main(String[] args) {
    XPathTestor testor = new XPathTestor();
    //单斜杠要按照根目录一级一级搜索
//    testor.xpath("/hr/employee");
    //双斜杠 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
//    testor.xpath("//employee");
//    testor.xpath("//employee[salary<4000]");
//    testor.xpath("//employee[name='李铁柱']");
//    testor.xpath("//employee[@no=3309]");
//    testor.xpath("//employee[1]");
//    testor.xpath("//employee[last()]");
//    testor.xpath("//employee[position()<6]"); 当前位置小于6
//    testor.xpath("//employee[1] | //employee[3]");
  }
}
阅读全文
头像
Asuna
You are the one who can always get to me even with screen between us.