irpas技术客

Java Web从入门到实战_Hello Code._java web开发从入门到实战

大大的周 4784

🌟个人博客:·/CentOS SecureCRT

简介:SecureCRT是一款支持SSH(SSH1和SSH2)的终端仿真程序,简单地说是Windows下登录Unix或Linux服务器主机的软件。

目录和文件 Linux没有盘符这个概念,只有一个根目录/,所有文件都在他下面etc表示系统中的配置文件usr、usr/bin、usr/sbin都表示系统预设执行文件的放置目录var/log表示程序运行日志的存放目录切换根目录:cd /查看目录内容:ls -l 时间同步

克隆与快照

克隆:将原系统完完全全的拷贝一份,原系统丢失后克隆的系统还能正常使用

占用空间大原系统不存在,克隆体还能用

快照:记录系统当前状态,并不会把系统完整拷贝

占用空间小原系统不存在,快照也就无法使用

克隆和拍摄快照时都需要关闭虚拟机

系统与设置命令 账号管理

与用户相关的命令,必须在管理员权限下才能执行

命令:su root

创建用户:useradd (选项) 用户名用户口令:passwd (选项) 用户名 密码不能是一个回文长度必须大于8位必须是字母和数字的结合

在root权限下切换其它用户可直接切换,无需输入密码

修改用户:usermod 选项 用户名

删除用户:userdel (选项) 用户名

用户组

将用户分成小组,方便对用户的管理

创建用户组:groupadd (选项) 用户组名修改用户组:groupmod (选项) 用户组名 查询用户所属组:groups 用户名删除用户组:groupdel 用户组名管理用户组内成员:gpasswd (可选项) 组名

gpasswd是Linux下的管理工具,用于将一个用户添加到组或者从组中删除

-a:添加用户到组-d:从组中删除用户-A:指定管理员-M:指定组员和-A的用途差不多-r:删除密码-R:限制用户登入组,只有组中的成员才可以用newgrp加入该组 系统管理相关命令 日期管理:date [参数选项] 参数选项: -d "字符串":显示字符串所指的日期与时间。字符串前后必须加上双引号-s "字符串":根据字符串来设置日期与时间。字符串前后必须加上双引号-u:显示GMT(北京时间为CST)--help:在线帮助--version:显示版本信息 显示登陆账号的信息:logname切换用户:su 用户名查看当前用户详细信息(用户id、群组id、所属组):id 用户名提高普通用户的操作权限:sudo [参数选项] 进程相关命令

实时显示process的动态 :top

pid:每个进程的id

user:进程是属于哪个用户

PR:进程的优先级

NI:进程优先级(负数为高优先级,正数为低优先级)

VIRT:当前进程占用虚拟内存的总量

S:当前进程的状态

实时显示所有进程信息(显示完整命令):top -c实时显示指定进程的信息:top -p PID结束实时监控:q

查看当前正在运行的进程信息:ps

显示系统中所有的进程信息:ps -A显示系统中所有的进程信息(完整信息):ps -ef显示指定用户的进程信息:ps -u 用户名

中断执行中的程序:kill PID

例如:kill 1111表示杀死PID为1111的进程kill -9 PID:强制杀死指定PID的进程killall -u 用户名:杀死这个用户中的所有进程kill -9 $(ps -ef|grep 用户名):杀死指定用户的所有进程kill -l:查看对应编号

关机命令:shutdown(延迟关机)

shutdown -h now:立即关机shutdown +1 "1分钟以后关机":延迟一分钟以后关机,并给出警告信息shutdown -r +1 "准备重启了":延迟一分钟以后重启,并给出警告信息shutdown -c:取消关机命令 重启命令:reboot(立即重启)

显示当前登录系统的用户:who

who -H:显示明细(标题)信息

校正服务器时间、时区:timedatectl

几个小概念

项目说明时区因时区不同显示的时间不同,牵扯到夏令时和调整等问题,date命令可查看系统时钟:System ClockLinux OS的时间,date命令可查看硬件时钟:RTC:Real Time Clock主板上由电池供电的BIOS时间,hwclock -r可查看NTP:Network Time Protocol本机时间和实际的时间之间的经常会有差别,一般使用NTP服务器进行时间校准
timedatectl status:显示系统的当前时间和日期timedatectl list-timezones:查看所有可用的时区timedatectl set-timezone "Asia/Shanghai":设置本地时区timedatectl set-ntp false:禁用时间同步timedatectl set-time "2022-02-22 20:20:00":设置时间timedatectl set-ntp true:启用时间同步

清除屏幕:clear

目录管理 常见命令作用ls列出目录cd切换目录pwd显示目前的目录mkdir创建新目录rmdir删除空目录cp复制文件或目录rm删除文件或目录mv移动文件或目录修改文件或者目录的名字
ls命令相当于在Windows系统中打开文件夹,看到的目录以及文件的明细。 语法:ls [参数选项] 目录名称-a:显示所有文件或目录(包含隐藏)-d:仅列出目录本身,而不是列出目录内的文件数据(常用)-l:长数据串列出,包含文件的属性与权限等等数据(常用)

ls:显示不隐藏的文件与文件夹 ls -l:显示不隐藏的文件与文件夹的详细信息 ls -al:显示所有文件与文件夹的详细信息

pwd -P:查看当前所在目录cd [相对路径或绝对路径]:切换目录 cd ..:返回上一级目录 mkdir mkdir 文件夹的名字:创建单级目录创建多级文件夹,使用mkdir -p aaa/bbb rmdir rmdir 文件夹名字:删除空目录rmdir -p aaa/bbb:删除多级目录(先删bbb,如果删完aaa也为空,则aaa也一起删除) rm rm 文件路径:删除文件rm -r 目录路径:删除目录和目录里面所有的内容(单级目录或多级目录都行) touch 文件名.后缀名:创建一个文件cp cp 数据源 目的地:文件复制(仅文件)cp -r aaa/* ccc:将aaa目录中的所有文件及目录拷贝到ccc中(*代指所有) mv mv 数据源 目的地:改名(数据源和目的地相同)、移动文件或文件夹mv 文件名 文件名:将源文件名改为目标文件名mv 目录名 目录名:目标目录已存在,将源目录移动到目标目录;目标目录不存在则改名 文件基本属性 文件权限(共10位) 第一位为d表示是一个文件夹;是 - 表示是一个文件;| 表示是一个链接文档(快捷方式)r表示可读;w表示可写;x表示可执行; - 表示没有当前权限2-4位表示属主权限(文件所属的用户可以做的事情)5-7位表示属组权限(文件所在用户组可以对它做的事)8-10位表示其它用户权限 chgrp命令(change group) chgrp [选项参数] [所属群组] [文件或目录...]:更改所属组chgrp root aaa:将aaa文件夹所属组更改为rootchgrp -v root aaa:将aaa的属组改为root(-v会多一句提示语) chown命令 chown 属主名 文件名:更改属主chown [参数选项] 属主名:属组名 文件名:更改属主和属组-R:处理指定目录以及其子目录下的所有文件 chmod命令 作用:修改属主、属组、其他用户的权限数字方式语法:chmod [参数选项] 数字权限 文件或目录符号方式语法:chmod u=rwx,g=rx,o=r a.txt-R:对目前目录下的所有档案与子目录进行相同的权限变更(即以递回的方式逐个变更)

修改方式

数字方式 r-----4w----2x----1-----0

设置时会把三个数字加在一起设置 例如:rwx则为7

符号方式 user属主权限----ugroup属组权限----gothers其它权限----oall全部身份----a+(加入)、-(除去)、=(设定)r(可读)、w(可写)、x(可执行) 综合案例

需求:一个公司的开发团队有三个用户:Java、erlang、golang 有一个文件目录tmp/work供他们开发,如何实现让这三个用户都对其具有写权限

思路

创建三个用户,并把他们加入到对应的用户组(dev-group)将tmp/work文件夹所属组更改为dev-group修改文件夹所属组的权限

文件管理 touch 语法:touch [参数选项] 文件名

如果文件不存在就创建文件,如果存在就修改时间属性

touch a{1..10}.txt:创建a1.txt一直到a10.txt共10个文件(批量创建空文件)stat a.txt:查看文件的详细信息 vi/vim编辑器 vi编辑器:只能是编辑文本内容,不能对字体、段落进行排版 不支持鼠标操作没有菜单只有命令 vim编辑器:vim是从vi发展出来的一个文本编辑器。 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用

简单来说: vi是老式的文字处理器,不过功能已经很齐全了,但是还是有可以改进的地方 vim则可以说是程序员开发者的一项很好用的工具

vi/vim三种模式 阅读模式(命令模式)编辑模式(编辑模式)保存模式(末行模式)

命令模式下只能读不能写 在命令模式下输入i可以进入编辑模式,在编辑完成之后按下Esc又可以退出到命令模式 在命令模式下输入:可以进入末行模式,在保存完之后还可以按下两次Esc继续回退到命令模式

打开和新建文件 语法:vim 文件名如果文件已经存在,会直接打开文件(命令模式)如果文件不存在,打开一个临时文件,在保存且退出后,就会新建一个文件 进入编辑模式 命令英文功能常用iinsert在当前字符前插入文本常用Iinsert在行首插入文本较常用aappend在当前字符后添加文本Aappend在行末添加文本较常用o在当前行后面插入一空行常用O在当前行前面插入一空行常用
进入末行模式保存文件 :q:当vim进入文件没有对文件内容做任何操作可以按“q”退出:q!:当vim进入文件对文件内容有操作但不想保存退出:wq:正常保存退出:wq!:强行保存退出,只针对于root用户或文件所有人 vim定位行 语法:vim 文件名 +行数:查看文件并定位到具体行数vim a.txt +5:查看a.txt文件并定位到第5行 异常处理 如果vim异常退出,在磁盘上可能会保存有交换文件

在修改一个文件时(a.txt),为保证文件安全,vim不会对源文件进行修改,会产生一个新的文件(a.txt.swp),并对该文件进行编辑 只有在保存的时候,才会将新文件写回到源文件中

如果有交换文件,在下次使用vim编辑文件时,系统会提示是否对交换文件继续操作(将交换文件删除即可) 文件查看 命令功能cat 文件名查看小文件内容less -N 文件名分屏显示大文件内容head -n 文件名查看文件的前一部分tail -n 文件名查看文件的最后部分grep 关键字 文件名根据关键字搜索文本文件内容
cat -n a.txt:可以加入参数选项-n显示行号 只能阅读小文件阅读大文件可能显示不完整 less 文件名:查看大文件 加入-N可显示行号ctrl + F :向前移动一屏ctrl + B :向后移动一屏ctrl + D :向前移动半屏ctrl + U :向后移动半屏j :向前移动一行k :向后移动一行G:移动到最后一行g:移动到第一行q/ZZ:退出less命令 tail 文件名:默认查看文件最后10行内容 tail -3 b.txt:查看文件最后3行内容tail -f b.txt:动态显示文件最后10行内容(ctrl + C停止)tail -n +2 b.txt:显示文件b.txt的内容,从第2行至文件末尾tail -c 45 b.txt:显示文件最后45个字符 head 文件名:查看文件前10行内容 其它内容和tail类似 grep命令 类似于Windows下打开文件后ctrl+F的查找功能查找对应的进程语法:grep [参数选项] 关键字 文件:根据关键字,搜索文本文件内容-n:把包含关键字的行号展示出来-i:把包含关键字的行展示出来,搜索时忽略大小写-v:把不包含关键字的行展示出来ps -ef | grep sshd:将进程中含有sshd的进程展示出来ps -ef | grep -c sshd:将进程中含有sshd的进程的个数展示出来 echo命令 echo 字符串:展示文本echo 字符串 > 文件名:将字符串写到文件中(覆盖文件内容)echo 字符串 >> 文件名:将字符串写到文件中(不覆盖原内容)cat 不存在的目录 &>> 存在的文件:将命令的失败结果追加到指定的文件的后面 awk命令 AWK是一种处理文本文件的语言,是一个强大的文本分析工具(名字取自三位创始人名字中的字母)语法:awk [参数选项] '语法' 文件过滤:cat a.txt | awk '/zhang|li/':查看a.txt文件中包含zhang或者li的行切割 选项含义-F ','使用指定字符分割$ + 数字获取第几段内容$0获取当前行内容OFS="字符"向外输出时的段分割字符串toupper()字符转成大写tolower()字符转成小写length()返回字符长度

cat a.txt | awk -F ' ' '{print $1,$2,$3}':将a.txt中的内容按空格分割并打印第1-3列 cat a.txt | awk -F ' ' '{print toupper($1),$2,$3}':将a.txt中的内容分隔后并将$1中的内容转为大写字符后输出 cat a.txt | awk -F '' '{OFS="---"}{print $1,$2,$3}':将a.txt中的内容分隔后并用—重新分隔后输出

计算 命令含义awkBEGIN{初始化操作}{每行都执行}END{结束时操作}文件名固定语法BEGIN{这里面放的是执行前的语句}{这里面放的是处理每一行要执行的语句}END{这里面放的是处理完所有的行后要执行的语句}文件名

cat a.txt | awk -F' ' 'BEGIN{}{totle=totle+$2}END{print totle}':将a.txt分割后把$2的值相加并输出 cat a.txt | awk -F' ' 'BEGIN{}{totle=totle+$2}END{print totle}':输出totle以及总人数(行数)

软连接 类似于Windows里面的快捷方式语法:ln -s 目标文件路径 快捷方式路径ln -s aaa/bbb/ccc/ddd/eee/a.txt a.txt 压缩命令 查找命令 语法:find [参数选项] <指定目录> <指定条件> <指定内容>:在指定目录下查找文件参数选项 -name filename:查找名为filename的文件-ctime -n或+n:按时间来查找文件,-n指n天以内,+n指n天以前 find . -name "*.txt":查找当前目录下所有以txt为后缀的文件find . -ctime -1:当前文件夹下1天内操作过的文件

将 . 替换为 / 则表示在全盘范围进行查找

gzip命令 语法:gzip [参数选项] [文件]:压缩文件gzip a.txt:将a.txt文件压缩gzip *:将当前目录下所有文件都进行压缩(已经压缩过的不能再次被压缩)gzip -dv 文件名:解压文件并显示详细信息 gunzip命令 语法:gunzip [参数] [文件]:解压文件gunzip 压缩文件:解压压缩文件gunzip *:解压当前目录下的所有压缩文件 tar命令 语法:tar [必要参数] [选择参数] [文件]:打包、压缩和解压(文件/文件夹)注意:tar本身不具有压缩功能,它是调用压缩功能实现的参数选项 -c:建立新的压缩文件-v:显示指令执行过程-f <备份文件>:指定压缩文件-z:通过gzip指令处理压缩文件-t:列出压缩文件中的内容-x:表示解压 tar -cvf 打包文件名 文件名:打包文件并指定打包之后的文件名(仅打包不压缩)tar -zcvf b.gz b.txt:通过gzip指令将b.txt压缩为b.gz并显示过程(打包压缩)tar -zcvf aaa.gz aaa:将aaa文件夹压缩为aaa.gztar -ztvf aaa.gz:查看压缩文件中的内容tar -zxvf aaa.gz:解压aaa.gz文件 zip命令 语法:zip [必要参数] [选择参数] [文件]:压缩注意:zip是个使用广泛的压缩程序,文件经他压缩后会另外产生具有".zip"扩展名的压缩文件参数选项 -q:不显示指令执行过程-r:递归处理,将指定目录下的所有文件和子目录一并处理 zip -q -r 压缩文件名 文件/文件夹:压缩对应的文件/文件夹 unzip命令 语法:unzip [必要参数] [选择参数] [文件]:解压注意:只能解压".zip"扩展名的压缩文件参数选项 -l:显示压缩文件内所包含的文件-d <目录>:指定文件解压缩后要存储的目录 unzip -l aaa.zip:查看aaa.zip压缩文件所包含的文件unzip -d aaa aaa.zip:将aaa.zip压缩文件解压到aaa文件夹中(aaa文件夹可以不存在,会自动创建) bzip2命令 语法:bzip2 [参数选项] 文件:压缩文件注意:使用新的压缩算法,和zip相比压缩后的文件比原来的要小,但花费时间变长若没有加上任何参数,bizp2压缩完文件后会产生bz2的压缩文件,并删除原始文件bzip2 a.txt:压缩并删除a.txtbunzip2命令:bunzip2 [参数选项] 文件:解压 -v:解压文件时,显示详细的信息bunzip2 -v a.bz2:解压并显示详细信息 网络与磁盘管理 网络管理 ifconfig命令 语法:ifconfig [参数选项]:显示或配置网络设备的命令ifconfig ens37 down:关闭ens37这张网卡ifconfig ens37 up:开启网卡ens37ifconfig ens37 192.168.23.199:将ens37网卡的ip更改为192.168.23.199ifconfig ens37 192.168.23.199 netmask 255.255.255.0:配置ip地址和子网掩码 ping命令 语法:ping [参数选项]:检测是否与主机联通-c <完成次数>:设置完成要求回应的次数ping -c 2 ·f将 server-uuid 更改一个数字即可 启动相关服务 关闭两台服务器的防火墙:systemctl stop firewalld启动两台服务器的MySQL:service mysqld restart启动两台服务器的MyCat:cd /root/mycat/bin ./mycat restart查看两台服务器的监听端口:netstat -ant|grep 3306 netstat -ant|grep 8066 主从复制 为了使用MyCat进行读写分离,我们先要配置MySQL 数据库的主从复制从服务器自动同步主服务器的数据,从而达到数据一致进而,我们可以在写操作时,只操作主服务器,而读操作,就可以操作从服务器了

配置

主服务器

在第一个服务器上,编辑mysql配置文件

编辑mysql配置文件:vi /etc/my.cnf在[mysqld]下面加上 // log-bin代表开启主从复制,server-id代表主从服务器的唯一标识 log-bin=mysql-bin server-id=1 innodb_flush_log_at_trx_commit=1 sync_binlog=1

查看主服务器的配置

重启mysql:service mysqld restart登录mysql:mysql -u root -p查看主服务的配置:show master status; 需要记住 File 列和 Position 列的数据,将来配置从服务器需要使用

从服务器

在第二个服务器上,编辑mysql配置文件

编辑mysql配置文件:vi /etc/my.cnf在[mysqld]下面加上:server-id=2

登录mysql:mysql -u root -p

执行

use mysql; drop table slave_master_info; drop table slave_relay_log_info; drop table slave_worker_info; drop table innodb_index_stats; drop table innodb_table_stats; source /usr/share/mysql/mysql_system_tables.sql;

重启mysql,重新登录,配置从节点

重启mysql:service mysqld restart重新登录mysql:mysql -u root -p执行:change master to master_host='192.168.59.143',master_port=3306,master_user='root',master_password='itheima',master_log_file='mysql-bin.000001',master_log_pos=154;开启从节点:start slave;查询结果:show slave status\G; Slave_IO_Running和Slave_SQL_Running都为YES才表示同步成功。

测试:在主服务器上创建一个db1数据库,查看从服务器上是否自动同步

读写分离 写操作只写入主服务器,由于有主从复制,从服务器中也会自动同步数据读操作是读取从服务器中的数据

配置

修改主服务器 server.xml:vi /root/mycat/conf/server.xml

<user name="root"> <property name="password">MyCat密码</property> <property name="schemas">MyCat逻辑数据库显示的名字(虚拟数据库名)</property> </user>

修改主服务器 schema.xml:vi /root/mycat/conf/schema.xml

<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="和MyCat逻辑数据库名一致" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"></schema> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- 主服务器负责写的操作 --> <writeHost host="hostM1" url="localhost:3306" user="root" password="itheima"> <!-- 从服务器负责读的操作 --> <readHost host="hostS1" url="192.168.59.182:3306" user="root" password="itheima" /> </writeHost> </dataHost> </mycat:schema>

重启MyCat 进入mycat路径:cd /root/mycat/bin 重启mycat:./mycat restart 查看端口监听:netstat -ant|grep 8066

分库分表 分库分表:将庞大的数据量拆分为不同的数据库和数据表进行存储水平拆分 根据表的数据逻辑关系,将同一表中的数据按照某种条件,拆分到多台数据库服务器上,也叫做横向拆分 例如:一张 1000万的大表,按照一模一样的结构,拆分成4个250万的小表,分别保存到4个数据库中垂直拆分 根据业务的维度,将不同的表切分到不同的数据库上,也叫做纵向拆分 例如:所有的动物表都保存到动物库中,所有的水果表都保存到水果库中,同类型的表保存在同一个库中

水平拆分

修改主服务器中 server.xml:vi /root/mycat/conf/server.xml

<!--配置主键方式 0代表本地文件方式--> <property name="sequnceHandlerType">0</property>

修改主服务器中 sequence_conf.properties:vi /root/mycat/conf/sequence_conf.properties

#default global sequence GLOBAL.HISIDS= # 可以自定义关键字 GLOBAL.MINID=10001 # 最小值 GLOBAL.MAXID=20000 # 最大值

修改主服务器中 schema.xml:vi /root/mycat/conf/schema.xml

<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="HEIMADB" checkSQLschema="false" sqlMaxLimit="100"> <table name="product" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long"/> </schema> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost1" database="db2" /> <dataNode name="dn3" dataHost="localhost1" database="db3" /> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- 主服务器负责写的操作 --> <writeHost host="hostM1" url="localhost:3306" user="root" password="itheima"> <!-- 从服务器负责读的操作 --> <readHost host="hostS2" url="192.168.59.182:3306" user="root" password="itheima" /> </writeHost> </dataHost> </mycat:schema>

修改主服务器中 rule:vi /root/mycat/conf/rule.xml

<function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- 指定节点数量 --> <property name="count">3</property> </function>

垂直拆分

修改主服务器中 schema.xml:vi /root/mycat/conf/schema.xml

<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="HEIMADB" checkSQLschema="false" sqlMaxLimit="100"> <table name="product" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long"/> <!-- 动物类数据表 --> <table name="dog" primaryKey="id" autoIncrement="true" dataNode="dn4" /> <table name="cat" primaryKey="id" autoIncrement="true" dataNode="dn4" /> <!-- 水果类数据表 --> <table name="apple" primaryKey="id" autoIncrement="true" dataNode="dn5" /> <table name="banana" primaryKey="id" autoIncrement="true" dataNode="dn5" /> </schema> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost1" database="db2" /> <dataNode name="dn3" dataHost="localhost1" database="db3" /> <dataNode name="dn4" dataHost="localhost1" database="db4" /> <dataNode name="dn5" dataHost="localhost1" database="db5" /> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- 主服务器负责写的操作 --> <writeHost host="hostM1" url="localhost:3306" user="root" password="itheima"> <!-- 从服务器负责读的操作 --> <readHost host="hostS1" url="192.168.59.182:3306" user="root" password="itheima" /> </writeHost> </dataHost> </mycat:schema>
JDBC

JDBC(Java DataBase Connectivity java数据库连接) 是一种用于执行SQL语句的Java API,可以为多种关系型数据库提供统一访问,它是由一组用Java语言编写的类和接口组成的

本质:其实就是Java官方提供的一套规范(接口)。用于帮助开发人员快速实现不同关系型数据库的连接 快速入门 导入jar包注册驱动获取数据库连接获取执行者对象执行sql语句并返回结果处理结果释放资源 package study.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class Demo01 { public static void main(String[] args) throws Exception { // 导入jar包 // 注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 获取连接 Connection con = DriverManager.getConnection("jdbc:mysql://192.168.23.129:3306/db2","root","lh18391794828"); // 获取执行者对象 Statement stat = con.createStatement(); // 执行sql语句,并接收结果 String sql = "SELECT * FROM user"; ResultSet rs = stat.executeQuery(sql); // 处理结果 while(rs.next()){ System.out.println(rs.getInt("id") + "\t" + rs.getString("name")); } // 释放资源 con.close(); stat.close(); rs.close(); } } 功能类详解

DriverManager 驱动管理对象

注册驱动 注册给定的驱动程序:static void registerDriver(Driver driver); 写代码使用:Class.forName("com.mysql.jdbc.Driver"); 在com.mysql.jdbc.Driver类中存在静态代码块

static{ try{ DriverManager.registerDriver(new Driver()); }catch(SQL Exception E){ throw new RuntimeException("Can't register driver!"); } }

我们不需要通过DriverManager调用静态方法registerDriver(),因为只要Driver类被使用,就会执行其静态代码块完成注册驱动 mysql 5之后可以省略注册驱动的步骤。在jar包中,存在一个java.sql.Driver配置文件,文件中指定了 com.mysql.jdbc.Driver

获取数据库连接 获取数据库连接对象:static Connection getConnection(String url,String user,String password); 返回值:Connection 数据库连接对象

连接成功返回Connection对象,连接失败则会报错 url:指定连接的路径。语法:jdbc:mysql://ip地址(域名):端口号/数据库名称 user:用户名 password:密码

Connection 数据库连接对象

获取执行者对象 获取普通执行者对象:Statement createStatement(); 获取预编译执行者对象:PreparedStatement prepareStatement(String sql);管理事务 开启事务:setAutoCommit(boolean autoCommit); 参数为false,则开启事务 提交事务:commit(); 回滚事务:rollback();释放资源 立即将数据库连接对象释放:void close();

Statement 执行者对象

执行DML语句:int executeUpdate(String sql); 返回值int:返回影响的行数 参数sql:可以执行insert、update、delete语句执行DQL语句:ResultSet executeQuery(String sql); 返回值ResultSet:封装查询的结果 参数sql:可以执行select语句释放资源 立即将执行者对象释放:void close();

ResultSet 结果集对象

判断结果集中是否还有数据:boolean next(); 有数据返回true,并将索引向下移动一行 没有数据返回false获取结果集中的数据:XXX getXXX("列名"); XXX代表数据类型(要获取某列数据,就指这一列的数据类型) 例如:String getString("name"); int getInt("age");释放资源 立即将结果集对象释放:void close(); 案例

使用 JDBC 技术完成对student 表的 CRUD 操作

数据准备

创建数据库和数据表

-- 创建db11数据库 CREATE DATABASE db11; -- 使用db11数据库 USE db11; -- 创建student表 CREATE TABLE student( sid INT PRIMARY KEY AUTO_INCREMENT, -- 学生id NAME VARCHAR(20), -- 学生姓名 age INT, -- 学生年龄 birthday DATE -- 学生生日 ); -- 添加数据 INSERT INTO student VALUES (NULL, '张三', 23, '1999-09-23'), (NULL, '李四', 24, '1998-08-10'), (NULL, '王五', 25, '1996-06-06'), (NULL, '赵六', 26, '1994-10-20');

创建Student类

package study.jdbc.Demo.domain; import java.util.Date; public class Student { private Integer sid; private String name; private Integer age; private Date birthday; public Student() { } public Student(Integer sid, String name, Integer age, Date birthday) { this.sid = sid; this.name = name; this.age = age; this.birthday = birthday; } public Integer getSid() { return sid; } public void setSid(Integer sid) { this.sid = sid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Student{" + "sid=" + sid + ", name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'; } }

注意: 自定义类的功能是为了封装表中每列数据,成员变量和列要保持一致 所有基本数据类型需要使用对应的包装类,以免表中null值无法赋值

需求实现 查询所有学生信息根据id查询学生信息新增学生信息修改学生信息删除学生信息 代码展示

查询所有学生信息

/* 查询所有学生信息 */ @Override public ArrayList<Student> findAll() { Connection con = null; Statement stat = null; ResultSet res = null; ArrayList<Student> list = new ArrayList<>(); try{ // 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取数据库连接 con = DriverManager.getConnection("jdbc:mysql://192.168.23.129:3306/db11", "root", "密码"); // 3.获取执行者对象 stat = con.createStatement(); // 4.执行sql语句,并接收返回的结果集 String sql = "SELECT * FROM student"; res = stat.executeQuery(sql); // 5.处理结果集 while(res.next()){ Integer sid = res.getInt("sid"); String name = res.getString("name"); Integer age = res.getInt("age"); Date birthday = res.getDate("birthday"); list.add(new Student(sid,name,age,birthday)); } }catch(Exception e){ e.printStackTrace(); }finally { // 6.释放资源 if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } if(res != null) { try { res.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 将集合对象返回 return list; }

根据id查询学生信息

/* 根据id查询学生信息 */ @Override public Student findById(Integer id) { Connection con = null; Statement stat = null; ResultSet res = null; Student stu = new Student(); try{ // 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取数据库连接 con = DriverManager.getConnection("jdbc:mysql://192.168.23.129:3306/db11", "root", "密码"); // 3.获取执行者对象 stat = con.createStatement(); // 4.执行sql语句,并接收返回的结果集 String sql = "SELECT * FROM student WHERE sid = " + id; res = stat.executeQuery(sql); // 5.处理结果集 while(res.next()){ Integer sid = res.getInt("sid"); String name = res.getString("name"); Integer age = res.getInt("age"); Date birthday = res.getDate("birthday"); // 封装学生对象 stu.setSid(sid); stu.setName(name); stu.setAge(age); stu.setBirthday(birthday); } }catch(Exception e){ e.printStackTrace(); }finally { // 6.释放资源 if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } if(res != null) { try { res.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 将对象返回 return stu; }

新增学生信息

/* 添加学生信息 */ @Override public int insert(Student stu) { Connection con = null; Statement stat = null; int result = 0; try{ // 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取数据库连接 con = DriverManager.getConnection("jdbc:mysql://192.168.23.129:3306/db11", "root", "密码"); // 3.获取执行者对象 stat = con.createStatement(); // 4.执行sql语句,并接收返回的结果集 Date d = stu.getBirthday(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String birthday = sdf.format(d); String sql = "INSERT INTO student VALUES ('"+stu.getSid()+"','"+stu.getName()+"','"+stu.getAge()+"','"+birthday+"')"; result = stat.executeUpdate(sql); // 5.处理结果集 }catch(Exception e){ e.printStackTrace(); }finally { // 6.释放资源 if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 将结果返回 return result; }

修改学生信息

/* 修改学生信息 */ @Override public int update(Student stu) { Connection con = null; Statement stat = null; int result = 0; try{ // 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取数据库连接 con = DriverManager.getConnection("jdbc:mysql://192.168.23.129:3306/db11", "root", "密码"); // 3.获取执行者对象 stat = con.createStatement(); // 4.执行sql语句,并接收返回的结果集 Date d = stu.getBirthday(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String birthday = sdf.format(d); String sql = "UPDATE student SET sid='"+stu.getSid()+"',name='"+stu.getName()+"',age='"+stu.getAge()+"',birthday='"+birthday+"' WHERE sid='"+stu.getSid()+"'"; result = stat.executeUpdate(sql); // 5.处理结果集 }catch(Exception e){ e.printStackTrace(); }finally { // 6.释放资源 if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 将结果返回 return result; }

删除学生信息

/* 删除学生信息 */ @Override public int delete(Integer id) { Connection con = null; Statement stat = null; int result = 0; try{ // 1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取数据库连接 con = DriverManager.getConnection("jdbc:mysql://192.168.23.129:3306/db11", "root", "密码"); // 3.获取执行者对象 stat = con.createStatement(); // 4.执行sql语句,并接收返回的结果集 String sql = "DELETE FROM student WHERE sid='"+id+"'"; result = stat.executeUpdate(sql); // 5.处理结果集 }catch(Exception e){ e.printStackTrace(); }finally { // 6.释放资源 if(con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 将结果返回 return result; } 工具类 抽取工具类

编写配置文件 在src目录下创建config.properties 配置文件

driverClass=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3006/db4 username=root password=密码

编写JDBC 工具类

package study.jdbc.Demo.utils; import com.mysql.jdbc.Driver; import java.io.IOException; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class JDBCUtils { // 1. 私有构造方法 private JDBCUtils(){} // 2. 声明所需要的配置变量 private static String driverClass; private static String url; private static String username; private static String password; private static Connection con; // 3. 提供静态代码块。读取配置文件的信息为变量赋值,注册驱动 static { try { // 读取配置文件的信息为变量赋值 InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("config.properties"); Properties prop = new Properties(); prop.load(is); driverClass = prop.getProperty("driverClass"); url = prop.getProperty("url"); username = prop.getProperty("username"); password = prop.getProperty("password"); // 注册驱动 Class.forName(driverClass); } catch (Exception e) { e.printStackTrace(); } } // 4. 提供获取数据库连接方法 public static Connection getConnection(){ try { con = DriverManager.getConnection(url, username, password); } catch (SQLException e) { e.printStackTrace(); } return con; } // 5. 提供释放资源的方法 public static void close(Connection con, Statement stat, ResultSet rs){ if(con != null){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null){ try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void close(Connection con, Statement stat){ if(con != null){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null){ try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } } } 优化学生案例 package study.jdbc.Demo.dao; import study.jdbc.Demo.domain.Student; import study.jdbc.Demo.utils.JDBCUtils; import java.sql.*; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; public class StudentDaoImpl implements StudentDao{ /* 查询所有学生信息 */ @Override public ArrayList<Student> findAll() { Connection con = null; Statement stat = null; ResultSet res = null; ArrayList<Student> list = new ArrayList<>(); try{ con = JDBCUtils.getConnection(); // 获取执行者对象 stat = con.createStatement(); // 执行sql语句,并接收返回的结果集 String sql = "SELECT * FROM student"; res = stat.executeQuery(sql); // 处理结果集 while(res.next()){ Integer sid = res.getInt("sid"); String name = res.getString("name"); Integer age = res.getInt("age"); Date birthday = res.getDate("birthday"); list.add(new Student(sid,name,age,birthday)); } }catch(Exception e){ e.printStackTrace(); }finally { // 释放资源 JDBCUtils.close(con, stat, res); } // 将集合对象返回 return list; } /* 条件查询,根据id获取学生信息 */ @Override public Student findById(Integer id) { Connection con = null; Statement stat = null; ResultSet res = null; Student stu = new Student(); try{ con = JDBCUtils.getConnection(); // 获取执行者对象 stat = con.createStatement(); // 执行sql语句,并接收返回的结果集 String sql = "SELECT * FROM student WHERE sid = " + id; res = stat.executeQuery(sql); // 处理结果集 while(res.next()){ Integer sid = res.getInt("sid"); String name = res.getString("name"); Integer age = res.getInt("age"); Date birthday = res.getDate("birthday"); // 封装学生对象 stu.setSid(sid); stu.setName(name); stu.setAge(age); stu.setBirthday(birthday); } }catch(Exception e){ e.printStackTrace(); }finally { // 释放资源 JDBCUtils.close(con,stat,res); } // 将对象返回 return stu; } /* 新增学生信息 */ @Override public int insert(Student stu) { Connection con = null; Statement stat = null; int result = 0; try{ con = JDBCUtils.getConnection(); // 获取执行者对象 stat = con.createStatement(); // 执行sql语句,并接收返回的结果集 Date d = stu.getBirthday(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String birthday = sdf.format(d); String sql = "INSERT INTO student VALUES ('"+stu.getSid()+"','"+stu.getName()+"','"+stu.getAge()+"','"+birthday+"')"; result = stat.executeUpdate(sql); // 处理结果集 }catch(Exception e){ e.printStackTrace(); }finally { // 释放资源 JDBCUtils.close(con,stat); } // 将结果返回 return result; } /* 修改学生信息 */ @Override public int update(Student stu) { Connection con = null; Statement stat = null; int result = 0; try{ con = JDBCUtils.getConnection(); // 获取执行者对象 stat = con.createStatement(); // 执行sql语句,并接收返回的结果集 Date d = stu.getBirthday(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String birthday = sdf.format(d); String sql = "UPDATE student SET sid='"+stu.getSid()+"',name='"+stu.getName()+"',age='"+stu.getAge()+"',birthday='"+birthday+"' WHERE sid='"+stu.getSid()+"'"; result = stat.executeUpdate(sql); // 处理结果集 }catch(Exception e){ e.printStackTrace(); }finally { // 释放资源 JDBCUtils.close(con,stat); } // 将结果返回 return result; } /* 删除学生信息 */ @Override public int delete(Integer id) { Connection con = null; Statement stat = null; int result = 0; try{ con = JDBCUtils.getConnection(); // 获取执行者对象 stat = con.createStatement(); // 执行sql语句,并接收返回的结果集 String sql = "DELETE FROM student WHERE sid='"+id+"'"; result = stat.executeUpdate(sql); // 处理结果集 }catch(Exception e){ e.printStackTrace(); }finally { // 释放资源 JDBCUtils.close(con,stat); } // 将结果返回 return result; } } 注入攻击 什么是SQL注入攻击 就是利用SQL语句的漏洞来对系统进行攻击 /* 使用Statement的登录方法,有注入攻击 */ @Override public User findByLoginNameAndPassword(String loginName, String password) { //定义必要信息 Connection conn = null; Statement st = null; ResultSet rs = null; User user = null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //2.定义SQL语句 String sql = "SELECT * FROM user WHERE loginname='"+loginName+"' AND password='"+password+"'"; System.out.println(sql); //3.获取操作对象,执行sql语句,获取结果集 st = conn.createStatement(); rs = st.executeQuery(sql); //4.获取结果集 if (rs.next()) { //5.封装 user = new User(); user.setUid(rs.getString("uid")); user.setUcode(rs.getString("ucode")); user.setUsername(rs.getString("username")); user.setPassword(rs.getString("password")); user.setGender(rs.getString("gender")); user.setDutydate(rs.getDate("dutydate")); user.setBirthday(rs.getDate("birthday")); user.setLoginname(rs.getString("loginname")); } //6.返回 return user; }catch (Exception e){ throw new RuntimeException(e); }finally { JDBCUtils.close(conn,st,rs); } }

在上面代码中,登录时账户随便输入,密码输入bbb’ or ‘1’ = '1 就会直接登录成功 执行者对象会执行SELECT * FROM user WHERE loginname='aaa' AND password='bbb' or '1' = '1'语句

SQL注入攻击的原理

按照正常道理来说,我们在密码处输入的内容,都应该认为是密码的组成但是现在Statement对象在执行sql语句时,将密码的一部分内容当作查询条件来执行了

SQL注入攻击的解决

PreparedStatement 预编译执行者对象

在执行sql语句之前,将sql语句进行提前编译。明确sql语句的格式后,就不会改变了。剩余的内容都会认为是参数SQL语句中的参数使用?作为占位符

为?占位符赋值的方法:setXxx(参数1,参数2);

Xxx代表:数据类型

参数1:? 的位置编号(编号从1开始)

参数2:? 的实际参数

String sql = "SELECT * FROM user WHERE loginname=? AND password=?"; PreparedStatement st = conn.prepareStatement(sql); st.setString(1,loginName); st.setString(2,password); rs = st.executeQuery();

执行SQL 语句

执行 insert、update、delete 语句:int executeUpdate();执行select 语句:ResultSet executeQuery(); 事务管理

JDBC 如何管理事务

管理事务的功能类:Connection 开启事务:setAutoCommit(boolean autoCommit); 参数为false,则开启事务提交事务:commit();回滚事务:rollback();

演示批量添加数据并在业务层管理事务

@Override public void batchAdd(List<User> users) { //获取数据库连接对象 Connection con = JDBCUtils.getConnection(); try { //开启事务 con.setAutoCommit(false); for (User user : users) { //1.创建ID,并把UUID中的-替换 String uid = UUID.randomUUID().toString().replace("-", "").toUpperCase(); //2.给user的uid赋值 user.setUid(uid); //3.生成员工编号 user.setUcode(uid); //出现异常 //int n = 1 / 0; //4.保存 userDao.save(con,user); } //提交事务 con.commit(); }catch (Exception e){ //回滚事务 try { con.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally { //释放资源 JDBCUtils.close(con,null); } } 连接池

数据库连接是一种关键的、有限的、昂贵的资源,这一点在多用户的网页应用体现的尤为突出

对数据库连接的管理能显著影响到整个应用程序的性能指标,数据库连接池正是针对这个问题提出来的

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。这项技术能明显提高对数据库操作的性能 自定义数据库连接池

DataSource

javax.sql.DataSource 接口:数据源(数据库连接池)。Java官方提供的数据库连接池规范(接口)如果想完成数据库连接池技术,就必须实现DataSource 接口核心功能:获取数据库连接对象:Connection getConnection();

自定义数据库连接池

定义一个类,实现DataSource 接口定义一个容器,用于保存多个 Connection 连接对象定义静态代码块,通过 JDBC 工具类获取10个连接保存到容器中重写 getConnection方法,从容器中获取一个连接并返回定义 getSize方法,用于获取容器的大小并返回 package jdbc.demo01; /* * 自定义数据库连接池 * */ import jdbc.utils.JDBCUtils; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; public class MyDataSource implements DataSource { // 1.准备容器。保存多个连接对象(通过Collections的方法获取一个线程安全的集合对象) private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>()); // 2. 定义静态代码块,通过工具类获取10个连接对象 static { for(int i = 1; i <= 10; i++){ Connection con = JDBCUtils.getConnection(); pool.add(con); } } // 3. 重写getConnection方法,获取连接对象 @Override public Connection getConnection() throws SQLException { if(pool.size() > 0){ Connection con = pool.remove(0); return con; }else{ throw new RuntimeException("连接数量已用尽"); } } // 4. 定义getSize方法,获取连接池容器的大小 public int getSize(){ return pool.size(); } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } }

测试

package jdbc.demo01; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class MyDataSourceTest { public static void main(String[] args) throws SQLException { // 1. 创建连接池对象 MyDataSource dataSource = new MyDataSource(); // 2.通过连接池对象获取连接对象 Connection con = dataSource.getConnection(); // 3. 查询学生表全部信息 String sql = "SELECT * FROM student"; PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday")); } // 释放资源 rs.close(); ps.close(); // 用完还是关闭连接了,r存在问题 con.close(); } } 归还连接 归还数据库连接的方式 继承方式装饰设计模式适配器设计模式动态代理方式

继承方式(行不通)

通过打印连接对象,发现DriverManager 获取的连接实现类是JDBC4Connection

那我们就可以自定义一个类,继承JDBC4Connection 这个类,重写close()方法,完成连接对象的归还

实现步骤

定义一个类,继承JDBC4Connection定义Connection 连接对象和连接池容器对象的成员变量通过有参构造方法完成对成员变量的赋值重写close方法,将连接对象添加到池中

继承方式归还数据库连接存在问题

通过查看JDBC 工具类获取连接的方法发现:我们虽然自定义了一个子类,完成了归还连接的操作。但是DriverManager 获取的还是 JDBC4Connection 这个对象,并不是我们的子类对象,而我们又不能整体去修改驱动包中类的功能,所以继承方式行不通

装饰设计模式

装饰设计模式归还数据库连接的思想

我们可以自定义一个类,实现Connection 接口。这样就具备了和 JDBC4Connection 相同的行为了重写 close()方法,完成连接的归还。其余功能还调用mysql 驱动包实现类原有的方法即可

实现步骤

定义一个类,实现Connection接口定义 Connection 连接对象和连接池容器对象的成员变量通过有参构造方法完成对成员变量的赋值重写 close() 方法,将连接对象添加到池中剩余方法,只需要调用mysql 驱动包的连接对象完成即可在自定义的连接池中,将获取的连接对象通过自定义连接对象进行包装 package jdbc.demo02; import java.sql.*; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; public class MyConnection2 implements Connection { private Connection con; List<Connection> pool; public MyConnection2(Connection con, List<Connection> pool){ this.con = con; this.pool = pool; } @Override public void close() throws SQLException { pool.add(con); } @Override public Statement createStatement() throws SQLException { return con.createStatement(); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return con.prepareStatement(sql); } @Override public CallableStatement prepareCall(String sql) throws SQLException { return con.prepareCall(sql); } @Override public String nativeSQL(String sql) throws SQLException { return con.nativeSQL(sql); } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { con.setAutoCommit(autoCommit); } @Override public boolean getAutoCommit() throws SQLException { return con.getAutoCommit(); } @Override public void commit() throws SQLException { con.commit(); } @Override public void rollback() throws SQLException { con.rollback(); } @Override public boolean isClosed() throws SQLException { return con.isClosed(); } @Override public DatabaseMetaData getMetaData() throws SQLException { return con.getMetaData(); } @Override public void setReadOnly(boolean readOnly) throws SQLException { con.setReadOnly(readOnly); } @Override public boolean isReadOnly() throws SQLException { return con.isReadOnly(); } @Override public void setCatalog(String catalog) throws SQLException { con.setCatalog(catalog); } @Override public String getCatalog() throws SQLException { return con.getCatalog(); } @Override public void setTransactionIsolation(int level) throws SQLException { con.setTransactionIsolation(level); } @Override public int getTransactionIsolation() throws SQLException { return con.getTransactionIsolation(); } @Override public SQLWarning getWarnings() throws SQLException { return con.getWarnings(); } @Override public void clearWarnings() throws SQLException { con.clearWarnings(); } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return con.createStatement(resultSetType,resultSetConcurrency); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return con.prepareStatement(sql,resultSetType,resultSetConcurrency); } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return con.prepareCall(sql,resultSetType,resultSetConcurrency); } @Override public Map<String, Class<?>> getTypeMap() throws SQLException { return con.getTypeMap(); } @Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException { con.setTypeMap(map); } @Override public void setHoldability(int holdability) throws SQLException { con.setHoldability(holdability); } @Override public int getHoldability() throws SQLException { return con.getHoldability(); } @Override public Savepoint setSavepoint() throws SQLException { return con.setSavepoint(); } @Override public Savepoint setSavepoint(String name) throws SQLException { return con.setSavepoint(name); } @Override public void rollback(Savepoint savepoint) throws SQLException { con.rollback(savepoint); } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { con.releaseSavepoint(savepoint); } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return con.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return con.prepareStatement(sql,resultSetType,resultSetConcurrency,resultSetHoldability); } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return con.prepareCall(sql,resultSetType,resultSetConcurrency,resultSetHoldability); } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return con.prepareStatement(sql,autoGeneratedKeys); } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return con.prepareStatement(sql,columnIndexes); } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return con.prepareStatement(sql,columnNames); } @Override public Clob createClob() throws SQLException { return con.createClob(); } @Override public Blob createBlob() throws SQLException { return con.createBlob(); } @Override public NClob createNClob() throws SQLException { return con.createNClob(); } @Override public SQLXML createSQLXML() throws SQLException { return con.createSQLXML(); } @Override public boolean isValid(int timeout) throws SQLException { return con.isValid(timeout); } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { con.setClientInfo(name,value); } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { con.setClientInfo(properties); } @Override public String getClientInfo(String name) throws SQLException { return con.getClientInfo(name); } @Override public Properties getClientInfo() throws SQLException { return con.getClientInfo(); } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return con.createArrayOf(typeName,elements); } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return con.createStruct(typeName,attributes); } @Override public void setSchema(String schema) throws SQLException { con.setSchema(schema); } @Override public String getSchema() throws SQLException { return con.getSchema(); } @Override public void abort(Executor executor) throws SQLException { con.abort(executor); } @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { con.setNetworkTimeout(executor,milliseconds); } @Override public int getNetworkTimeout() throws SQLException { return con.getNetworkTimeout(); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return con.unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return con.isWrapperFor(iface); } } // 重写getConnection方法,获取连接对象 @Override public Connection getConnection() throws SQLException { if(pool.size() > 0){ Connection con = pool.remove(0); // 通过自定义的连接对原有的对象进行包装 MyConnection2 myCon = new MyConnection2(con,pool); return myCon; }else{ throw new RuntimeException("连接数量已用尽"); } } 存在的问题 实现Connection 接口后,有大量的方法需要在自定义类中进行重写

适配器设计模式

思想 可以提供一个适配器类,实现Connection 接口,将所有的方法进行实现(除了close方法)自定义连接类只需要继承这个适配器类,重写需要改进的close()方法即可 步骤 定义一个适配器类,实现Connection 接口定义 Connection 连接对象的成员变量通过有参构造方法完成对成员变量的赋值重写所有方法(除了close),调用mysql 驱动包的连接对象完成即可定义一个连接类,继承适配器类定义 Connection 连接对象和连接池容器对象的成员变量,并通过有参构造进行赋值重写 close()方法,完成归还连接在自定义连接池中,将获取的连接对象通过自定义连接对象进行包装 public class MyConnection3 extends MyAdapter{ private Connection con; private List<Connection> pool; public MyConnection3(Connection con, List<Connection> pool){ super(con); this.con = con; this.pool = pool; } @Override public void close() throws SQLException { pool.add(con); } } 存在的问题 自定义连接类虽然很简洁了,但适配器还是我们自己编写的,也比较麻烦 动态代理

动态代理:在不改变目标对象方法的情况下对方法进行增强

组成

被代理的对象:真实的对象代理对象:内存中的一个对象

要求:代理对象必须和被代理对象实现相同的接口

实现:Proxy.newProxyInstance()

三个参数 类加载器:和被代理对象使用相同的类加载器(对象.getClass().getClassLoader()) 接口类型的Class数组:和被代理对象使用相同接口(new Class[]{接口名.class}) 代理规则:完成代理增强的功能(匿名内部类方式new InvocationHandler(){})

代理规则这个内部类中需要重写一个抽象方法:invoke invoke方法三个参数,第一个参数proxy不用管 第二个method方法对象,用来表示被代理对象中的每个方法(被代理对象的每个方法在执行前都会先经过invoke方法) 第三个参数args表示被代理对象对应的形参

动态代理方式归还数据库连接

思想 我们可以通过 Proxy 来完成对Connection 实现类对象的代理代理过程中判断如果执行的是close方法,就将连接归还池中。如果是其他方法则调用连接对象原来的功能即可 步骤 定义一个类,实现 DataSource 接口定义一个容器,用于保存多个 Connection 连接对象定义静态代码块,通过 JDBC 工具类获取10个连接保存到容器中重写 getConnection 方法,从容器中获取一个连接通过 Proxy 代理,如果是close 方法,就将连接归还池中。如果是其他方法则调用原有功能定义 getSize方法,用于获取容器的大小并返回 /* * 动态代理方式 */ @Override public Connection getConnection() throws SQLException { if(pool.size() > 0){ Connection con = pool.remove(0); Connection proxyCon = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() { /* * 执行Connection实现类连接对象所有方法都会经过invoke * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("close")){ pool.add(con); return null; }else{ return method.invoke(con,args); } } }); return proxyCon; }else{ throw new RuntimeException("连接数量已用尽"); } } 存在的问题 我们自己写的连接池技术不够完善,功能也不够强大 开源数据库连接池

C3P0 数据库连接池

使用步骤 导入jar包导入配置文件到src目录下创建 C3P0 连接池对象获取数据库连接进行使用

注意: C3P0的配置文件会自动加载,但是必须叫 c3p0-config.xml 或 c3p0-config.properties

<!--配置文件--> <c3p0-config> <!-- 使用默认的配置读取连接池对象 --> <default-config> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://192.168.23.129:3306/db11</property> <property name="user">root</property> <property name="password">lh密码</property> <!-- 连接池参数 --> <!--初始化连接数量--> <property name="initialPoolSize">5</property> <!--最大的连接数量--> <property name="maxPoolSize">10</property> <!--超时时间--> <property name="checkoutTimeout">3000</property> </default-config> <named-config name="otherc3p0"> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/db15</property> <property name="user">root</property> <property name="password">itheima</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">8</property> <property name="checkoutTimeout">1000</property> </named-config> </c3p0-config> public class C3P0Test1 { public static void main(String[] args) throws SQLException { // 创建c3p0数据库连接池对象 DataSource dataSource = new ComboPooledDataSource(); // 通过连接池对象获取数据库连接 Connection con = dataSource.getConnection(); // 执行操作 String sql = "SELECT * FROM student"; PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday")); } // 释放资源 rs.close(); ps.close(); con.close(); } }

Druid 数据库连接池

步骤 导入jar包编写配置文件,放在src 目录下通过 Properties 集合加载配置文件通过 Druid 连接池工厂类获取数据库连接池对象获取数据库连接进行使用

注意:Druid 不会自动加载配置文件,需要手动加载,但是文件的名称可以自定义

# Druid配置文件 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/db3 username=root password=root # 初始化连接数量 initialSize=5 # 最大连接数量 maxActive=10 # 超时时间 maxWait=3000 package jdbc.demo04; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Properties; public class DruidTest1 { public static void main(String[] args) throws Exception { // 获取配置文件的流对象 InputStream is = DruidTest1.class.getClassLoader().getResourceAsStream("druid.properties"); // 加载配置文件 Properties prop = new Properties(); prop.load(is); // 获取数据库连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); // 获取数据库连接 Connection con = dataSource.getConnection(); // 执行操作 String sql = "SELECT * FROM student"; PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday")); } // 释放资源 rs.close(); ps.close(); con.close(); } } 连接池的工具类 私有化构造方法(不让其他人创建对象)声明数据源变量提供静态代码块。完成配置文件的加载以及获取数据库连接池对象提供获取数据库连接的方法提供获取数据库连接池对象的方法释放资源 package jdbc.utils; /* * 数据库连接池工具类 **/ import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class DataSourceUtils { // 1. 私有化构造方法(不让其他人创建对象) private DataSourceUtils(){} // 2.声明数据源变量 private static DataSource dataSource; // 3. 提供静态代码块。完成配置文件的加载以及获取数据库连接池对象 static { try{ // 完成配置文件的加载 InputStream is = DataSourceUtils.class.getClassLoader().getResourceAsStream("druid.properties"); Properties prop = new Properties(); prop.load(is); // 获取数据库连接池对象 dataSource = DruidDataSourceFactory.createDataSource(prop); }catch (Exception e){ e.printStackTrace(); } } // 4.提供获取数据库连接的方法 public static Connection getConnection(){ Connection con = null; try { con = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return con; } // 5. 提供获取数据库连接池对象的方法 public static DataSource getDataSource(){ return dataSource; } // 6. 释放资源 public static void close(Connection con, Statement stat, ResultSet rs){ if(con != null){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null){ try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void close(Connection con, Statement stat){ if(con != null){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } if(stat != null){ try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } } } /* 测试类 */ package jdbc.demo04; import jdbc.utils.DataSourceUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class DruidTest2 { public static void main(String[] args) throws Exception{ // 通过连接池工具类获取数据库连接 Connection con = DataSourceUtils.getConnection(); // 执行操作 String sql = "SELECT * FROM student"; PreparedStatement ps = con.prepareStatement(sql); ResultSet rs = ps.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("sid") + "\t" + rs.getString("name") + "\t" + rs.getInt("age") + "\t" + rs.getDate("birthday")); } DataSourceUtils.close(con,ps,rs); } } 框架

在之前的JDBC网页版案例中,定义必要的信息、获取数据库连接、释放资源都是重复的代码,我们最终的核心功能仅仅只是执行一条sql语句,所以我们可以抽取出一个 KDBC 模板类,来封装一些方法(update、query),专门帮我们执行增删改查的sql语句。将之前那些重复的操作,都抽取到模板类中的方法里,就能大大简化使用步骤

源信息

DataBaseMetaData:数据库的源信息(了解)

java.sql.DataBaseMetaData封装了整个数据库的综合信息 例如: String getDatabaseProductName():获取数据库产品的名称int getDatabaseProductVersion():获取数据库产品的版本号

ParameterMetaData:参数的源信息

java.sql.ParameterMetaData封装的是预编译执行者对象中每个参数的类型和属性,这个对象可以通过预编译执行者对象中的getParameterMetaData()方法来获取核心功能:int getParameterCount()用于获取sql语句中参数的个数

ResultSetMetaData:结果集的源信息

java.sql.ResultSetMetaData:封装的是结果集对象中列的类型和属性,这个对象可以通过结果集对象中的getMetaData()方法来获取核心功能 int getColumnCount()用于获取列的总数 String getColumnName(int i)用于获取列名 update方法 用于执行增删改功能的update()方法 定义所需成员变量(数据源、数据库连接、执行者、结果集)定义有参构造,为数据源对象赋值定义update()方法,参数:sql语句、sql语句所需参数定义int类型变量,用于接收sql 语句执行后影响的行数通过数据源获取一个数据库连接通过数据库连接对象获取执行者对象并对sql语句预编译 package jdbc.demo05; import jdbc.utils.DataSourceUtils; import javax.sql.DataSource; import java.sql.*; /* * JDBC框架类 * */ public class JDBCTemplate { // 1. 定义所需成员变量(数据源、数据库连接、执行者、结果集) private DataSource dataSource; private Connection con; private PreparedStatement pst; private ResultSet rs; // 2. 定义有参构造,为数据源对象赋值 public JDBCTemplate(DataSource dataSource){ this.dataSource = dataSource; } // 3. 定义update()方法,参数:sql语句、sql语句所需参数 public int update(String sql, Object...objs){ // 4. 定义int类型变量,用于接收sql 语句执行后影响的行数 int result = 0; try { // 5. 通过数据源获取一个数据库连接 con = dataSource.getConnection(); // 6. 通过数据库连接对象获取执行者对象并对sql语句预编译 pst = con.prepareStatement(sql); // 7.通过执行者对象获取参数源信息对象 ParameterMetaData parameterMetaData = pst.getParameterMetaData(); // 8.通过源信息对象获取sql语句中的参数个数 int count = parameterMetaData.getParameterCount(); // 9.判断参数数量是否一致 if(count != objs.length){ throw new RuntimeException("参数个数不匹配"); } // 10.为sql语句中问号占位符赋值 for(int i = 0; i < objs.length; i++){ pst.setObject(i+1,objs[i]); } // 11.执行sql语句并接收结果 result = pst.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally { // 12.释放资源 DataSourceUtils.close(con,pst); } // 13.返回结果 return result; } } 测试 package jdbc.demo05; /* * 模拟dao层 * */ import jdbc.utils.DataSourceUtils; import org.junit.Test; public class JDBCTemplateTest1 { private JDBCTemplate template = new JDBCTemplate(DataSourceUtils.getDataSource()); @Test public void delete(){ // 删除数据测试 String sql = "DELETE FROM student WHERE name=?"; int result = template.update(sql, "周七"); if(result != 0){ System.out.println("删除成功"); }else{ System.out.println("删除失败"); } } @Test public void update(){ // 修改数据测试 String sql = "UPDATE student SET age=? WHERE name=?"; Object[] params = {37,"周七"}; int result = template.update(sql, params); if(result != 0){ System.out.println("修改成功"); }else{ System.out.println("修改失败"); } } @Test public void insert(){ // 新增数据测试 String sql = "INSERT INTO student VALUES (?,?,?,?)"; Object[] params = {null,"周七",27,"1997-07-07"}; int result = template.update(sql, params); if(result != 0){ System.out.println("添加成功"); }else{ System.out.println("添加失败"); } } } 查询功能

方法介绍

查询一条记录并封装对象的方法:queryForObject()查询多条记录并封装集合的方法:queryForList()查询聚合函数并返回单条数据的方法:queryForScalar()

实体类的编写

定义一个类,提供一些成员变量 注意:成员变量的数据类型和名称要和表中的列保持一致

private Integer sid; private String name; private Integer age; private Date birthday; // 其他就是标准类中的构造、get、set方法以及toString方法

处理结果集的接口

定义泛型接口 ResultSetHandler<T>定义用于处理结果集的泛型方法:<T> T handler(ResultSet rs)

注意:此接口仅用于为不同处理结果集的方式提供规范,具体的实现类还需要自行编写

处理结果集的接口实现类BeanHandler

定义一个类,实现ResultSetHandler接口定义Class对象类型变量通过有参构造为变量赋值重写handler方法。用于将一条记录封装到自定义对象中声明自定义对象类型创建传递参数的对象,为自定义对象赋值判断结果集中是否有数据通过结果集对象获取结果集源信息的对象通过结果集源信息对象获取列数通过循环遍历列数通过结果集源信息对象获取列名通过列名获取该列的数据创建属性描述器对象,将获取到的值通过该对象的set方法进行赋值返回封装好的对象 package jdbc.demo05.handler; /* 实现类1:用于将查询到的一条记录,封装为Student对象并返回 */ import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.ResultSetMetaData; // 1. 定义一个类,实现ResultSetHandler接口 public class BeanHandler<T> implements ResultSetHandler<T>{ // 2. 定义Class对象类型变量 private Class<T> beanClass; // 3. 通过有参构造为变量赋值 public BeanHandler(Class<T> beanClass){ this.beanClass = beanClass; } // 4. 重写handler方法。用于将一条记录封装到自定义对象中 @Override public T handler(ResultSet rs) { // 5. 声明自定义对象类型 T bean = null; try { // 6. 创建传递参数的对象,为自定义对象赋值 bean = beanClass.newInstance(); // 7. 判断结果集中是否有数据 if(rs.next()){ // 8. 通过结果集对象获取结果集源信息的对象 ResultSetMetaData metaData = rs.getMetaData(); // 9. 通过结果集源信息对象获取列数 int count = metaData.getColumnCount(); // 10. 通过循环遍历列数 for(int i = 1; i <= count; i++){ // 11. 通过结果集源信息对象获取列名 String columnName = metaData.getColumnName(i); // 12. 通过列名获取该列的数据 Object value = rs.getObject(columnName); // 13. 创建属性描述器对象,将获取到的值通过该对象的set方法进行赋值 PropertyDescriptor pd = new PropertyDescriptor(columnName.toLowerCase(),beanClass); // 获取set方法 Method writeMethod = pd.getWriteMethod(); // 执行set方法,给成员变量赋值 writeMethod.invoke(bean,value); } } } catch (Exception e) { e.printStackTrace(); } // 14. 返回封装好的对象 return bean; } }

用于查询一条记录并封装对象的方法 queryForObject()

/* * 执行查询的方法:将一条记录封装成一个自定义类型对象并返回 * */ public <T> T queryForObject(String sql, ResultSetHandler<T> rsh, Object...objs){ T obj = null; try { // 通过数据源获取一个数据库连接 con = dataSource.getConnection(); // 通过数据库连接对象获取执行者对象并对sql语句预编译 pst = con.prepareStatement(sql); // 通过执行者对象获取参数源信息对象 ParameterMetaData parameterMetaData = pst.getParameterMetaData(); // 通过源信息对象获取sql语句中的参数个数 int count = parameterMetaData.getParameterCount(); // 判断参数数量是否一致 if(count != objs.length){ throw new RuntimeException("参数个数不匹配"); } // 为sql语句中问号占位符赋值 for(int i = 0; i < objs.length; i++){ pst.setObject(i+1,objs[i]); } // 执行sql语句并接收结果 rs = pst.executeQuery(); // 通过BeanHandler 方式对结果处理 obj = rsh.handler(rs); } catch (Exception e) { e.printStackTrace(); }finally { // 释放资源 DataSourceUtils.close(con,pst); } // 返回结果 return obj; } @Test public void queryForObject(){ // 查询数据测试 String sql = "SELECT * FROM student WHERE sid=?"; Student stu = template.queryForObject(sql, new BeanHandler<>(Student.class), 1); System.out.println(stu); }

处理结果集的接口实现类BeanListHandler

定义BeaanListHandler l类实现 ResultSetHandler接口定义class 对象类型的变量定义有参构造为变量赋值重写handler方法,用于将结果集中的所有记录封装到集合中并返回创建List集合对象遍历结果集对象创建传递参数的对象通过结果集对象获取结果集的源信息对象通过结果集源信息对象获取列数通过循环遍历列数通过结果集源信息获取列名通过列名获取该列的数据创建属性描述器对象,将获取到的值通过对象的set方法进行赋值将封装好的对象添加到集合中返回集合对象 package jdbc.demo05.handler; /* 实现类2:用于将查询到的一条记录,封装为Student对象并添加到集合返回 */ import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList; import java.util.List; // 1. 定义一个类,实现ResultSetHandler接口 public class BeanListHandler<T> implements ResultSetHandler<T>{ // 2. 定义Class对象类型变量 private Class<T> beanClass; // 3. 通过有参构造为变量赋值 public BeanListHandler(Class<T> beanClass){ this.beanClass = beanClass; } // 4. 重写handler方法。用于将多条记录封装到自定义对象中并添加到集合返回 @Override public List<T> handler(ResultSet rs) { // 5. 声明集合对象类型 List<T> list = new ArrayList<>(); try { // 6. 判断结果集中是否有数据 while(rs.next()){ // 7. 创建传递参数的对象,为自定义对象赋值 T bean = beanClass.newInstance(); // 8. 通过结果集对象获取结果集源信息的对象 ResultSetMetaData metaData = rs.getMetaData(); // 9. 通过结果集源信息对象获取列数 int count = metaData.getColumnCount(); // 10. 通过循环遍历列数 for(int i = 1; i <= count; i++){ // 11. 通过结果集源信息对象获取列名 String columnName = metaData.getColumnName(i); // 12. 通过列名获取该列的数据 Object value = rs.getObject(columnName); // 13. 创建属性描述器对象,将获取到的值通过该对象的set方法进行赋值 PropertyDescriptor pd = new PropertyDescriptor(columnName.toLowerCase(),beanClass); // 获取set方法 Method writeMethod = pd.getWriteMethod(); // 执行set方法,给成员变量赋值 writeMethod.invoke(bean,value); } // 将对象保存到集合中 list.add(bean); } } catch (Exception e) { e.printStackTrace(); } // 14. 返回封装好的对象 return list; } }

queryForList实现和测试

/* * 执行查询的方法:将多条记录封装成一个自定义类型对象并添加到集合返回 * */ public <T> List<T> queryForList(String sql, ResultSetHandler<T> rsh, Object...objs){ List<T> list = new ArrayList<>(); try { // 通过数据源获取一个数据库连接 con = dataSource.getConnection(); // 通过数据库连接对象获取执行者对象并对sql语句预编译 pst = con.prepareStatement(sql); // 通过执行者对象获取参数源信息对象 ParameterMetaData parameterMetaData = pst.getParameterMetaData(); // 通过源信息对象获取sql语句中的参数个数 int count = parameterMetaData.getParameterCount(); // 判断参数数量是否一致 if(count != objs.length){ throw new RuntimeException("参数个数不匹配"); } // 为sql语句中问号占位符赋值 for(int i = 0; i < objs.length; i++){ pst.setObject(i+1,objs[i]); } // 执行sql语句并接收结果 rs = pst.executeQuery(); // 通过BeanListHandler 方式对结果处理 list = rsh.handler(rs); } catch (Exception e) { e.printStackTrace(); }finally { // 释放资源 DataSourceUtils.close(con,pst); } // 返回结果 return list; } @Test public void queryForList(){ // 查询数据测试 String sql = "SELECT * FROM student"; List<Student> list= template.queryForList(sql, new BeanListHandler<>(Student.class)); for(Student stu : list){ System.out.println(stu); } }

处理结果集的接口实现类ScalarHandler

定义ScalarHandler 类实现ResultSetHandler接口重写handler方法,用于返回一个聚合函数的查询结果定义Long类型变量判断结果集对象是否有数据通过结果集对象获取结果集源信息对象通过结果集源信息对象获取第一列的列名通过列名获取该列的数据将结果返回 package jdbc.demo05.handler; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; // 1.定义ScalarHandler<T> 类实现ResultSetHandler<T>接口 public class ScalarHandler<T> implements ResultSetHandler<T>{ // 2.重写handler方法,用于返回一个聚合函数的查询结果 @Override public Long handler(ResultSet rs) { // 3.定义Long类型变量 Long value = null; try { // 4.判断结果集对象是否有数据 if(rs.next()){ // 5.通过结果集对象获取结果集源信息对象 ResultSetMetaData metaData = rs.getMetaData(); // 6. 获取第一列的列名 String columnName = metaData.getColumnName(1); // 7.根据列名获取该列的值 value = rs.getLong(columnName); } } catch (Exception e) { e.printStackTrace(); } // 8.返回结果 return value; } }

queryForScalar实现和测试

/* * 执行查询的方法:将聚合函数的查询结果返回 * */ public Long queryForScalar(String sql, ResultSetHandler<Long> rsh, Object...objs){ Long value = null; try { // 通过数据源获取一个数据库连接 con = dataSource.getConnection(); // 通过数据库连接对象获取执行者对象并对sql语句预编译 pst = con.prepareStatement(sql); // 通过执行者对象获取参数源信息对象 ParameterMetaData parameterMetaData = pst.getParameterMetaData(); // 通过源信息对象获取sql语句中的参数个数 int count = parameterMetaData.getParameterCount(); // 判断参数数量是否一致 if(count != objs.length){ throw new RuntimeException("参数个数不匹配"); } // 为sql语句中问号占位符赋值 for(int i = 0; i < objs.length; i++){ pst.setObject(i+1,objs[i]); } // 执行sql语句并接收结果 rs = pst.executeQuery(); // 通过ScalarHandler 方式对结果处理 value = rsh.handler(rs); } catch (Exception e) { e.printStackTrace(); }finally { // 释放资源 DataSourceUtils.close(con,pst); } // 返回结果 return value; } @Test public void queryForScalar(){ // 查询聚合函数测试 String sql = "SELECT COUNT(*) FROM student"; Long value = template.queryForScalar(sql, new ScalarHandler<Long>()); System.out.println(value); }
Mybatis
JavaScript
jQuery
AJAX
Vue + Element
Redis
Maven基础
Web项目实战-黑马页面

因为正文字数过多,后续内容请前往专栏查阅: 《Java Web从入门到实战》


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #JAVA #web开发从入门到实战