1 WEB基础回顾
软件开发中大概分成两种架构,C/S(Client/Server)客户端/服务器架构和B/S(Browser/Server)浏览器/服务器架构。而Java WEB大多基于后者,用来开发网站后台、管理系统等。
1.1 什么是协议?Java WEB的通信遵循什么协议?它基于什么协议?
网络中的计算机进行数据交换遵循的规范,称为协议。在Java WEB中,客户端和服务器之间进行通信遵循HTTP协议。HTTP协议是建立在TCP协议基础之上(传输双发需要建立连接),基于请求响应模式的无状态的应用层协议。
-
TCP/IP传输的四个层次:
-
什么是TCP/IP
互联网协议(英语:Internet Protocol Suite,缩写IPS)是一个网络通信模型,以及一整个网络传输协议家族,为互联网的基础通信架构。它常被通称为TCP/IP协议族(英语:TCP/IP Protocol Suite,或TCP/IP Protocols),简称TCP/IP。因为该协议家族的两个核心协议:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准。由于在网络通讯协议普遍采用分层的结构,当多个层次的协议共同工作时,类似计算机科学中的堆栈,因此又被称为TCP/IP协议栈(英语:TCP/IP Protocol Stack)。转自维基百科 -
上图中提到的4个层,应用层关注的是应用程序的细节,而不是数据在网络中的传输活动;其他三层主要处理所有的通信细节,对应用程序一无所知。
- 应用层
负责处理特定的应用程序细节,直接为用户的应用进程提供服务,这里主要应用了HTTP服务。
HTTP(超文本传输协议)
HTTP协议建立在TCP协议基础之上(传输双发需要建立链接),基于请求/响应模式的、无状态的应用层协议。所谓的无状态指的是前一次和后一次请求没有关系,若要建立关系,在Java中靠的是cookie和session、持久层建立关系。- 传输层
为两台主机上的应用程序提供端到端的通信。由于一个主机可同时运行多个进程,因此传输层有复用和分用的功能。复用指的是多个应用层进程可同时使用下面运输层的服务。分用则是运输层把收到的信息分别交付给上面应用层中的相应的进程。运输层主要使用以下两种协议:
1.传输控制协议TCP(数据传输的单位是报文段),提供高可靠性的数据通信,为了提供高可靠性,TCP采用了三次握手、超时重传等机制,是一个面向连接的协议
2.用户数据报协议UDP(数据传输的单位是用户数据报),不保证提供可靠的交付,只能提供“尽最大努力交付”,这个协议面向无连接
TCP和UDP协议的区别:
在实现信息的可靠传递方面,TCP协议中包含了专门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止(三次握手)。而UDP协议并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中出现数据报的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把UDP协议称为不可靠的传输协议。
在处理接收突发性的多个数据包方面,在网络非常拥挤的情况下,UDP不能确保数据的发送和接收顺序,但这种乱序情况非常偶发;而TCP一定可以保证数据的发送接收顺序事实上。
虽然UDP是一种不可靠的传输协议,但在有些情况下UDP协议可能会变得非常有用。因为UDP具有TCP所望尘莫及的速度优势。虽然TCP协议中植入了各种安全保障功能,但是在实际执行的过程中会占用大量的系统开销,无疑使速度受到严重的影响。反观UDP由于排除了信息可靠传递机制,将安全和排序等功能移交给上层应用来完成,极大降低了执行时间,使速度得到了保证。-
网络(网际)层
使用无连接的网际协议IP和许多种路由选择协议。负责为分组交换网上的不同主机提供通信服务,把传输层产生的报文段或用户数据报封装成分组(也叫IP数据报或数据报)或包进行传送。另一个任务就是选择合适的路由,尽可能快地将分组从源结点送到目的结点。 -
物理(链路)层
通常包括操作系统的设备驱动程序和计算机对应的网络接口卡,主要处理有关通信媒介的细节(如以太网,令牌环网等)。
- 应用层
1.2 HTTP中的请求和响应
- 请求
- 从客户端发往服务器端信息(字符串)
- 请求的数据报中包含什么?
(1)请求行:POST /index.html HTTP/1.1(分别为请求方式、路径、WEB请求需遵循HTTP哪个版本的协议)
(2)请求头:内容的题目包括Host、Accept、Accep-Language、Cookie等
(3)消息体:key=value&key1=value2…(一般情况下,POST的向后台传的参数存放在这里,GET请求的参数一般拼接存放在请求行的路径后面)
联动自己几天前发的文章:Content-Type(setRequestHeader()中的参数)(了解)
联动自己几天前发的文章:表单提交时GET与POST的区别
- 响应
- 服务器回送给客户端的信息(字符串)
- 响应的数据报中包含什么?
(1)状态行:HTTP/1.1 200 OK(分别为WEB请求需遵循HTTP哪个版本的协议、状态码、状态字符串)
(2)响应头:内容的题目包括Content-Type、Content-Lenght、set-cookie等
(3)响应正文:<html>… </html>
- HTTP1.0和HTTP1.1的区别
最显著的区别是HTTP1.1更新为长连接模式。HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。
HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。例如:一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应答仍然需要使用各自的连接。
HTTP 1.1还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间。
1.3 浏览器的作用是什么?
能够发出标准的HTTP请求,返回标准的HTTP响应并解析响应正文显示的软件。
1.4 WEB服务器、Servlet容器
能够接受标准的HTTP请求并解析,发出标准的HTTP响应的软件。Tomcat就是一款免费、开源、纯Java实现的WEB服务器。
管理Servlet生命周期,提供Servlet的运行环境的类库(组件)。一般Servlet容器存在于WEB服务器中,Tomcat中有一个Servlet容器—Catalina。Calalina作为一种技术规范,Servlet容器是它的实现。
联动自己几天前发的文章:什么是Tomcat
1.4.1 手动实现一个简单的WEB服务器接收请求的过程
服务器端Server.java
import java.io.*;
import java.net.*;
public class Server{
public static void main(String arg[])throws Exception{
//创建一个服务器端的ServerSocket对象,监听本机的8080端口
ServerSocket ss = new ServerSocket(8080);
//这句打印类似于Tomcat的启动时间在控制台的打印,只是这句是假的
System.out.println("server startup in 0.0001ms");
//不断监听8080端口,打印客户端发来的请求信息
while(true){
//创建客户端Socket对象,接收由客户端发来的请求信息
Socket so = ss.accept();
//创建字节输入输出流对象
InputStream is = so.getInputStream();
OutputStream os = System.out;
//创建一个长度,装载取出的字节数组的长度
int length;
//创建一个足够大的字节数组,
byte[] data = new byte[1024<<3];
//将发来的请求使用字节数组读到流中
//并遍历使用字节流按数组输出,直到输出完毕
while((length = is.read(data))!=-1){
os.write(data,0,length);
}
//真正的Tomcat在初始化时会将request、response的重要参数先设定好
//HttpServletRequest request = new HttpServletRequest();
//request.setMethod(GET);
//request.setRequestURI("/index.html");
//request.setProtocol();
//request.setHeader();
//request.setHeaderNames();
//request.setDateHeader();
//..
//reqeust.setParameter()//参数
//Response
//关闭输入输出流
is.close();
os.close();
}
}
}
客户端Client.java
import java.io.*;
import java.net.*;
public class Client
{
public static void main(String artgs[])throws Exception{
//创建一个客户端Socket对象,向127.0.0.1:8080发请求
Socket so = new Socket("127.0.0.1",8080);
//创建字节输出流,输出要发送的请求
OutputStream os = so.getOutputStream();
//按字节输出请求
os.write("大家好".getBytes());
//关闭输出流
os.close();
}
}
当运行Client.java时,服务端控制台输出的和浏览器访问时服务端控制台输出的内容如图:
因为这是一个单实例单线程的服务器,所以在服务端没有返回当前响应前,服务器端无法发送接收到下一个客户端的响应。
一个Socket是一个端点,普通Socket是客户端,ServerSocket是服务器端。
在真正的Tomcat这个WEB服务器中,由它自己启动main方法,监听服务器接到请求。在初始化时new一个Request对象,并初始化关于请求的信息,这些信息被存储在new出来的Request对象中。
据Catalina实现的servlet容器调用servlet方法来管理生命周期。Jasper包实现了JSP引擎。
1.5 Servlet的核心API
1.5.1 interface javax.servlet.Servlet
- 该接口定义了所有的Servlet都必须实现的方法。
- Servlet是运行在WEB服务器端的小的Java程序,通常用来接收和响应来自客户端的请求。一般是基于HTTP协议。
- 继承javax.servlet.GenericServlet或javax.servlet.http.HttpServlet都可实现这个接口。
- 方法列表:
1):init(ServletConfig)
2):service(ServletRequest,ServletResponse)
3):destroy()
4):getServletConfig()
5):getServletInfo() - 该接口定义了生命周期方法,并且以如下顺序调用:
1):Servlet被构造,然后调用init()方法初始化
2):所有来自客户端的请求都交给service(ServletRequest,ServletResponse),并由其实现响应。
3):处理完毕使用destroy(),销毁资源然后等待GC回收 - 除了生命周期方法之外,该接口提供了getServletConfig方法,可以获得一些Servlet启动时的信息。
1.5.2 interface javax.servlet.ServletConfig
-
该接口与Servlet的关系是一一对应的,ServletConfig主要用来存放Servlet一些私有的启动信息。
-
Servlet容器创建ServletConfig的对象,在Servlet类初始化时传递启动信息给Servlet。
-
方法列表:
1):getInitParameter()
2):getInitParameterNames()
3):getServletContext()
4):getServletName() -
继承了Servlet类和ServletConfig类的抽象类GenericServlet
abstract Javax.servlet.GenericServlet implements Servlet,ServletConfig(1)init(ServletConfig)用户一般继承重写空参的init()
init(ServletConfig config){
this.config = config;
setServletConfig();
init();//调用本类的init()(一般由用户继承编写)加载资源配置文件等。。
}
(2)abstract service(ServletRequest,ServletResponse) //由用户继承编写
(3)destroy()
(4)getServletConfig()
(5)getServletInfo()
(6)getInitParameter(String)
(7)getInitParameterNames()
(8)getServletContext()
(9)getServletName()
(10)init()
(11)log(String)
(12)log(String, Throwable)
1.5.3 interface javax.servlet.ServletContext(Servlet上下文环境)
- ServletContext接口中封装了Servlet与他所在的环境通信的方法。所以叫上下文。
- 常用方法:
1):getRealPath()(获取当前路径,但是这个方法对打成的war包无用,已被遗弃)
2):getAttribute()
3):setAttribute()
4):getContextPath()(获取“/工程名字”) - 如何获得ServletContext?(ServletContext对象时在容器启动时创建的)
1):Request.getServletContext()
2):ServletConfig.getServletContext();
3):HttpSession.getServletContext();
4):FitlerConfig.getServletContext();
5):ServletContextEvent.getServletContext();
1.5.4 abstract javax.servlet.http.HttpServlet extends GenericServlet
- init(ServletConfig)用户一般继承重写空参的init()
init(ServletConfig config){
this.config = config;
setServletConfig();
init();//调用本类的init()(一般由用户继承编写)加载资源配置文件等。。
} - public service(ServletRequest req,ServletResponse resp){
HttpServletRequest request = (HttpServletReuest)req;
HttpServelResponse response = (HttpServletResponse)resp;
this.service(request,response);//调用本类的service(HttpServletRequest, HttpServletResponse)
} - protected service(HttpServletRequest request,HttpServletResponse response){
//获取用户传来的请求方式,根据请求方式调用doGet/doPost
String method = request.getMethod();
If(“GET”.equals(method)){
doGet(request,response)
} else if(“POST”.equals(method)){
doPost(request,response)
}} - destroy()
- getServletConfig()
- getServInfo()
- getInitParameter()
- getInitParameterNames()
- getServletContext()
- getServletName()
- init()
- log(String)
- log(String, Throwable)
//一般用户重写这两个方法来处理请求,不直接使用service方法 - doGet(HttpServletRequest request,HttpServletResponse response)
- doPost(HttpServletRequest request,HttpServletResponse response)
1.6 常用API的处理者
API接口名 | 创建者new | 实现者implements | 调用者 |
---|---|---|---|
Servlet | 容器(反射) | 程序员 | 容器 |
ServletConfig | 容器 | 容器 | 程序员 |
ServletContext | 容器 | 容器 | 程序员 |
Request | 容器 | 容器 | 程序员 |
RequestDispatcher | 容器 | 容器 | 程序员 |
HttpSession | 容器 | 容器 | 程序员 |
Cookie | 程序员 | 容器 | 程序员 |
Filter | 容器 | 程序员 | 容器 |
Listener | 容器 | 程序员 | 容器 |
1.7 一次请求的过程
1.7.1 服务器启动阶段
S1. 解析自身的配置文件(conf),如果有错,则服务器无法启动;如果没错,则继续解析webapps下所有的工程中的web.xml
S2. 如果web.xml解析有错,则该工程无法使用,但是服务器可以启动;没错,正常启动。到此时,web.xml中的信息已经解析完毕
1.7.2 请求发出
S1. 浏览器地址栏输入网址,向网站的域名发出GET请求(地址栏只能发送GET请求 )
S2. 浏览器把请求地址打包成标准的请求数据报(字符串),通过网络发出域名,DNS域名解析器将域名解析为IP地址后发往服务器
S3. 服务器接收到标准的请求,解析请求,将数据报信息打包成HttpServletRequest对象(字符串转为对象);根据web.xml中的部署描述信息,找到对应的 Servlet类(url-pattern–>servletname–>找到指定的类),容器通过反射构造并调用Servlet类
S4. 查看内存中是否有指定类型的Servlet的对象,如果有则使用,如果没有则创建新的Servlet对象(容器使用反射创建对象)(Servlet是单实例多线程的)
S5. 容器调用init(ServletConfig)方法初始化,我们只需要重写空参init(){初始化代码}加载需要初始化的内容,初始化过程完毕
S6. 容器调用service()完成请求的处理。(servlet类中的protected service方法,容器根据发来的请求调用执行我们重写的doXX方法)
S7. 我们在doXX方法中,通过response对象写出响应信息。
S8. 服务器根据response对象中的内容生成标准的HTTP响应信息(response对象转为字符串)发送到客户端
S9. 客户端浏览器接收到标准的HTTP响应信息,解析并显示。
2 餐厅案例
明天起进入密集的项目实训期,更新速度会慢一些,代码上传Github,这里会更新一些需求和技术介绍。
2.1 数据库表及字段
- 管理员(admin)表字段:id、name、pwd、email、phone、savepath(头像在服务器上的存储路径)、showname(头像上传前在本地的文件名)
- 餐桌(tables)表字段:id(流水号)、no(桌号)、num(就餐人数)、status(使用状态,0空闲/1使用中)
2.2 什么是数据源、数据库连接池、DBCP
2.2.1 数据源
DataSource(数据源),javax.sql.DataSource接口。数据源就是用来获得链接的工厂,是前面工厂类用到的DriverManager类的替代者。
2.2.2 数据库连接池
数据库连接池是一种实现数据源的解决方案,一种抽象的方式方法,而不是一个具体的实例。
- 它定义的工作原理
S1. 当服务器启动时,创建一些连接放在连接池中,等待客户端请求。
S2. 当客户端发来请求需要连接数据库时,容器首先查看连接池中是否有可用的连接,如果有,则返回将连接返回;如果没有则查看当前的连接数是否超过了最大的可用连接数。如果没有超过,则创建新的链接返回;如果超过了,则抛出无可用连接的异常。
S3. 当连接使用完毕后,连接再次被放回到连接池中,从而实现连接的重用。
2.2.3 DBCP
DBCP是apache-commons提供的一款采用了数据库连接池方案的数据源的具体实现产品(一个类库),类似的连接池产品还有C3P0/Druid,作用均为提供多个可循环使用的数据库连接。具体用法见后面案例的代码。
- 上面说到的三种概念的关系
数据源是标准,连接池是解决方案,DBCP是采用了连接池方案的数据源的实现。
2.2.4 Properties配置文件
Properties配置文件主要以Key-Value形式存放数据库或者系统的配置信息,在Java中使用util包中的Properties类(继承自HashTable类)可以加载写在.properties文件中的配置信息。具体用法见后面案例的代码。
2.3 代码测试
省略实体类,前面的数据库建表中有描述。
- 一般Servlet开发Java WEB工程的编写步骤:
- 建立工程 ,建包
一般包名以网站域名倒序从最外层以点分层定名;再向内层则可以是自己的项目名称;项目名称内部一般为管理员包、公共包以及其他大实体的分包、测试包;大实体包内部此阶段分为servlet层、service层、entity(po/persistence/bean)实体持久化层以及dao层,公共包一般存放util工具类包,工厂包以及与分页相关的包这样公共使用的包。 - 数据库中建立关系表
- 根据关系表在entity(po/persistence/bean)层中建立实体类
- 在公共包的工厂包中建立工厂类
- 编写dao层,实现具体对实体的增删改查基本方法
- 编写页面,通过具体的业务逻辑编写相应servlet以及service、dao层
- servlet层根据页面的需要,编写相应的Servlet接收请求回复响应
- service层根据servlet需要创建相应的service方法来封装Servlet对于实体的业务操作
- dao层具体实现service层对实体涉及到增删改查更个性化的操作
- 如图
2.3.1 使用DBCP的DataSource获取数据连接
package com.test.restaurant.test;
import org.apache.commons.dbcp.BasicDataSource;
import java.sql.Connection;
public class Test {
public static void main(String[] args) throws Exception{
//1.构造对象,实现数据源
BasicDataSource bds = new BasicDataSource();
//2.给属性赋值
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://127.0.0.1:3306/数据库名");
bds.setUsername("数据库账号(一般为root)");
bds.setPassword("数据库密码");
//3.获得连接
Connection conn = bds.getConnection();
System.out.println(conn);
}
}
获取到了连接池中的一个连接
尝试使用for循环获取十个连接但打印出来结果只得到八个,说明连接池默认最多可以得到八个连接,显然程序没有执行完。
可以通过在上面的代码中加bds.setMaxActive(10)修改为连接池允许用户获取最多十个连接。
设置最大等待时间,通过bds.setMaxWait(3000),这里等待3000ms,如果获取不到连接就抛出异常。
package com.test.restaurant.test;
import org.apache.commons.dbcp.BasicDataSource;
import java.sql.Connection;
public class Test {
public static void main(String[] args) throws Exception{
//1.构造对象,实现数据源
BasicDataSource bds = new BasicDataSource();
//2.给属性赋值
//必选项
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://127.0.0.1:3306/数据库名");
bds.setUsername("数据库账号(一般为root)");
bds.setPassword("数据库密码");
//可选项
//连接池最多允许用户获取10个连接
bds.setMaxActive(10);
//当连接不够时最多等待3000ms,超过就抛异常
bds.setMaxWait(3000);
//3.尝试获取11个连接
for(int i = 0; i < 11; i++) {
Connection conn = bds.getConnection();
System.out.println(conn);
}
}
}
2.3.2 使用properties文件作为配置文件配合DBCP重构工厂类
在src目录下建立db.properties,key值并不是规定死的写法,在Java文件中可以调取到即可
##必选项
m.driver=com.mysql.jdbc.Driver
m.url=jdbc:mysql://127.0.0.1:3306/test
##或者m.url=jdbc:mysql://localhost:3306/test
##或者当url为localhost(127.0.0.1)且端口为3306时,可写为m.url=jdbc:mysql:///:3306/test
m.user=数据库账号(一般为root)
m.pwd=数据库密码
##可选项
m.maxActive=100
m.maxWait=3000
m.maxIdle=20
- maxIdle:设置连接池中最大的空闲的连接数(默认为8,设为0表示没有限制),超过设置上限的空闲连接将被释放,如果设置为负数也表示不限制(maxIdle不能设置太小,因为假如在高负载的情况下,连接的打开时间比关闭的时间快,会引起连接池中idle的个数上升超过maxIdle,而造成频繁的连接销毁和创建)。
重构工厂类
package com.test.restaurant.common.factory;
import org.apache.commons.dbcp.BasicDataSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class ConnectionFactory {
//具体实现基础的数据源
private static BasicDataSource bds = new BasicDataSource();
private ConnectionFactory(){}
//在静态代码块中加载db.properties的配置资源文件
static{
try {
Properties properties = new Properties();
//获取输入流,将配置资源文件以流的形式加载进来
//第一种找资源文件的方式
//以ConnectionFactory.class(类文件与.java文件目录结构相同)所在目录(包的内部)为起点,"../"为转到上一级目录,
//要找到db.properties文件,需要向上转五次
//InputStream is = ConnectionFactory.class.getResourceAsStream("../../../../../db.properties");
//第二种找资源文件的方式
//通过获取classpath的路径(类加载器的路径)直接从包外目录获取文件
InputStream is = ConnectionFactory.class.getClassLoader().getResourceAsStream("db.properties");
//加载文件内容
properties.load(is);
is.close();
//设置连接池属性
//properties对象中根据key获取加载进来的value值
//(用法同map的get()通过key取value,但是map返回Object,properties返回String)
bds.setDriverClassName(properties.getProperty("m.driver"));
bds.setUrl(properties.getProperty("m.url"));
bds.setUsername(properties.getProperty("m.user"));
bds.setPassword(properties.getProperty("m.pwd"));
bds.setMaxWait(Long.parseLong(properties.getProperty("m.maxWait")));
bds.setMaxActive(Integer.parseInt(properties.getProperty("m.maxActive")));
bds.setMaxIdle(Integer.parseInt(properties.getProperty("m.maxIdle")));
} catch (Exception e) {
e.printStackTrace();
}
}
//对外提供连接
public static Connection getConnection(){
try {
return bds.getConnection();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//对外提供数据源
public static DataSource getDataSource(){
return bds;
}
}
2.4 DBUtils底层原理
DBUtils底层如何将数据库的一条条数据转换为一个个对象?
DBUtils.java
package com.test.restaurant.test.utils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DBUtils {
public DBUtils(){}
//不想每次都在使用连接时向方法中传Connection的对象,就先在构造方法中定义它
private DataSource ds;
public DBUtils(DataSource ds){
this.ds = ds;
}
/*
* 可变参数:
* 1.放在参数列表最后
* 2.用法同数组
* */
//发现方法中代码复用频繁,提取出用于填充参数的方法
private void fillStatement(PreparedStatement pst, Object...params) throws Exception{
//给参数赋值
if(params != null){
for(int i = 0; i < params.length; i++){
//数据库对应的列参数索引从1开始,数组下标从0开始
pst.setObject(i + 1, params[i]);
}
}
}
//模拟DBUtils-->QueryRunner中的update方法
public Integer update(Connection conn, String sql, Object...params) throws Exception{
//被提取到另一个方法中
/* //S1.创建statement对象
PreparedStatement pst = conn.prepareStatement(sql);
//S2.给参数赋值
if(params != null){
for(int i = 0; i < params.length; i++){
//数据库对应的列参数索引从1开始,数组下标从0开始
pst.setObject(i + 1, params[i]);
}
}*/
//S1.创建Statement对象
PreparedStatement pst = conn.prepareStatement(sql);
//S2.给参数赋值
fillStatement(pst);
//S3.执行操作
//insert/delete/update均可以使用这个方法处理
return pst.executeUpdate();
}
//模拟DBUtils-->QueryRunner中的query方法
public List<Map<String, Object>> query(Connection conn, String sql, Object...params) throws Exception{
//S1.创建Statement对象
PreparedStatement pst = conn.prepareStatement(sql);
//S2.给参数赋值
fillStatement(pst);
//S3.接收查询到的结果
//创建对象接收Map,有几条记录就有几个Map
List<Map<String, Object>> resultList = new ArrayList<>();
//通过ResultSet获得行数(查到几条记录)
ResultSet rs = pst.executeQuery();
//获取结果集的元数据(字段名/字段类型/字段长度)
ResultSetMetaData rsmd = rs.getMetaData();
//获取列数
int count = rsmd.getColumnCount();
while(rs.next()){
Map<String, Object> map = new HashMap<>();
//遍历每一列(列的索引值从1开始)
for(int i = 1; i <= count; i++){
//获取列名
String cName = rsmd.getColumnName(i);
//获取列内容
Object obj = rs.getObject(cName);
map.put(cName, obj);
}
resultList.add(map);
}
return resultList;
}
//不需要传连接的update()
//模拟DBUtils-->QueryRunner中的update方法
public Integer update(String sql, Object...params) throws Exception{
//被提取到另一个方法中
/* //S1.创建statement对象
PreparedStatement pst = conn.prepareStatement(sql);
//S2.给参数赋值
if(params != null){
for(int i = 0; i < params.length; i++){
//数据库对应的列参数索引从1开始,数组下标从0开始
pst.setObject(i + 1, params[i]);
}
}*/
//S1.创建Statement对象
PreparedStatement pst = this.ds.getConnection().prepareStatement(sql);
//S2.给参数赋值
fillStatement(pst);
//S3.执行操作
//insert/delete/update均可以使用这个方法处理
return pst.executeUpdate();
}
public <T> T query(String sql, ResultSetHandler<T> rsh, Object...params) throws Exception{
//S1.创建Statement对象
PreparedStatement pst = this.ds.getConnection().prepareStatement(sql);
//S2.给参数赋值
fillStatement(pst, params);
//S3.接收查询到的结果
//通过ResultSet获得行数(查到几条记录)
ResultSet rs = pst.executeQuery();
//直接使用ResultSetHandler的实现类处理结果集
//比如 传入BeanListHandler,这里就返回BeanListHandler实现的handle方法
return rsh.handle(rs);
}
}
ResultSetHandler.java
package com.test.restaurant.test.utils;
import java.sql.ResultSet;
public interface ResultSetHandler<T> {
public <T> T handle(ResultSet rs);
}
接口的两个实现类,在查询时根据自己需求灵活传进这个接口的实现类
BeanListHandler.java
package com.test.restaurant.test.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
public class BeanHandler<T> implements ResultSetHandler<T>{
private Class<T> cls;
public BeanHandler(Class<T> cls){
this.cls = cls;
}
//把任意的ResultSet转换为任意类型
@Override
public <T> T handle(ResultSet rs){
try {
while(rs.next()){
//通过反射的方式构造对象
//newInstance()构造对象会new这个类的空参构造方法
//很多框架底层也使用了反射
//所以实体类的空参构造方法务必要保证可用
T obj = (T)cls.newInstance();
//数据库中字段->属性
//获取这个类中所有属性对象
Field[] fs = cls.getDeclaredFields();
//f为实体类中的属性对象
for(Field f : fs) {
//属性名字
String attrName = f.getName();
//获得属性所属类的名字
Class filedType = cls.getDeclaredField(attrName).getType();
//拼出这个属性的setter方法名
String setter0 = "set" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1);
//反射获取setter方法,setter代表setter方法的名字
//最后一个参数指的是setter方法所属类的类型,因为每个setter方法的返回值类型就是其类
Method setter = cls.getDeclaredMethod(setter0, filedType);
//给属性赋值
//obj的setter这个方法被执行
setter.invoke(obj, rs.getObject(attrName));
}
return obj;
}
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
BeanHandler.java
package com.test.restaurant.test.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.ResultSet;
public class BeanHandler<T> implements ResultSetHandler<T>{
private Class<T> cls;
public BeanHandler(Class<T> cls){
this.cls = cls;
}
//把任意的ResultSet转换为任意类型
@Override
public <T> T handle(ResultSet rs){
try {
while(rs.next()){
//通过反射的方式构造对象
//newInstance()构造对象会new这个类的空参构造方法
//很多框架底层也使用了反射
//所以实体类的空参构造方法务必要保证可用
T obj = (T)cls.newInstance();
//数据库中字段->属性
//获取这个类中所有属性对象
Field[] fs = cls.getDeclaredFields();
//f为实体类中的属性对象,f的值精确到了属性的名字,getName相当于获取属性名字
for(Field f : fs) {
//属性名字
String attrName = f.getName();
//获得属性所属类的名字
Class filedType = cls.getDeclaredField(attrName).getType();
//拼出这个属性的setter方法名
String setter0 = "set" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1);
//反射获取setter方法,setter代表setter方法的名字
//最后一个参数指的是setter方法所属类的类型,因为每个setter方法的返回值类型就是其类
Method setter = cls.getDeclaredMethod(setter0, filedType);
//给属性赋值
//obj的setter这个方法被执行
setter.invoke(obj, rs.getObject(attrName));
}
return obj;
}
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}