JavaWeb学习笔记

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

JavaWeb

Tomcat

bin:存放各个平台下启动和停止Tomcat服务的脚本文件

conf:存放各种Tomcat服务器的配置文件

lib:存放Tomcat服务器所需要的jar包

logs:存放Tomcat服务运行的日志

temp:Tomcat运行时的临时文件

webapps:存放允许客户端访问的资源

work:存放Tomcat将JSP转换之后的Servlet文件

Servlet

1.servlet主要负责与客户端进行通信,创建并返回基于客户请求的动态HTML页面

2.当浏览器访问Servlet时,Tomcat会查询当前servlet的实例对象是否存在,如果不存在,则通过反射机制动态创建对象(类似字符串常量池)

3.Servlet的生命周期内的方法:无参构造器,Init(),service(),destory()

service()可以执行多次,而其他方法值执行一次

ServletConfig和ServletContest

ServletConfig作用于某个Servlet实例,每个Servlet都有对应的ServletConfig

而ServletContext作用于整个Web应用(从Tomcat运行开始到结束),多个Servlet实例对应一个ServletContext

乱码问题

  1. request乱码指的是:浏览器向服务器发送的请求参数中包含中文字符,服务器获取到的请求参数的值是乱码;

  2. response乱码指的是:服务器向浏览器发送的数据包含中文字符,浏览器中显示的是乱码;

  3. 不管是request乱码还是response乱码,其实都是由于客户端(浏览器)跟服务器端采用的编码格式不一致造成的。

一般用过滤器来解决乱码问题

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    servletRequest.setCharacterEncoding("utf-8");
    
    servletResponse.setContentType("text/html;charset=utf-8");
    filterChain.doFilter(servletRequest,servletResponse);
}

JSP

本质上是一个Servlet,JSP主要负责与用户交互,将最终的页面呈现给用户。

当服务器接收到一个后缀是.jsp的请求时,将该请求交给JSP引擎去处理,每一个JSP页面第一次被访问时,JSP引擎将它翻译为Servlet文件,再由Web容器调用Servlet完成.

我的理解:JSP主要是给用户响应页面的,所以需要用到HTML标签,但是在java文件频繁写out.write("\r\n");太麻烦了。所以有了JSP引擎,我们只需要在.jsp文件下编写HTML标签加一些EL表达式(动态响应数据),然后交给JSP引擎,让它生成.java文件就方便很多。

java语句嵌入JSP的方式:

1.JSP脚本,执行Java逻辑代码,编写在_jspService()方法体内

<%Java代码%>

2.JSP声明:定义Java方法,编写在xxx_jsp类内,_jspService()方法外

<%!
	public void test(){
    
	}    
%>

3.JSP表达式:把Java对象直接输出到HTML页面中

<%=Java变量%>

JSP内置9个对象

1.request:表示一次请求 来自于java的HttpServletRequest 客户端向服务器发送请求,产生的数据,用户看完就没用了。比如:新闻

2.response:表示一次响应 来自于java的HttpServletResponse

3.pageContext:页面上下文,获取页面信息 来自于java的PageContext

4.session:表示一次会话,保存用户信息,客户端向服务器发送请求,产生的数据,用户用完一会还有用。比如:购物车

5.application:表示当前的Web应用,保存所有用户共享的信息,相当于java的ServletContext,客户端向服务器发送请求,产生的数据,一个用户用完了,其他用户还可能使用。比如:聊天数据

6.config:来自于当前JSP对应的Servlet的ServletConfig对象,获取当前Servlet的信息

7.out:向浏览器输出数据,来自于Java的JspWrite

8.page:当前JSP对应的Servlet对象

9.exception:表示JSP页面发生的异常

final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;


pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;

常用的是request,response,session,application,pageConfig


request的常用方法

String getParameter(String key);获取客户端传来的参数
void setAttribute(String key,Object value);//通过键值对的形式保存数据

Object getAttribute(String key);//通过key取出value

这两个方法是用于获取服务器内部的页面请求信息

场景:用户在客户端login.jsp页面输入账号,密码,服务器端可以在login.jsp通过getParameter()的方式获取信息,如果想要在index.jsp页面显示账号,需要让login.jsp把数据传递给index.jsp,所以login.jsp页面需要通过setAttribute()存入数据,然后用getRequestDispatcher()请求转发,index.jsp用getAttribute()接收,再显示给用户

RequestDispatcher getRequestDispatcher(String path);//返回一个RequestDispatcher对象,该对象的forward()方法用于请求转发

void forward(ServletRequest req,ServletResponse resp);
String[] getParameterValues();//获取客户端传来的多个同名参数

如果客户端传来?name=Tom&name=Jerry,getParameter()只会获取第一个name,如果需要全部获取需要使用getParameterValues()方法

void setCharacterEncoding(String charset);//解决中文乱码
Cookie[] getCookies();//获得客户端请求中的cookies
HttpSession getSession();//获得服务器中的session

response的常用方法

void sendRedirect(String var1);//重定向

String setCharacterEncoding();//设置编码方式

String setContentType();//一般使用setContentType("text/html;charset=utf-8");来解决中文乱码问题

PrintWriter getWriter() throws IOException;//用于浏览器输出

ServletOutputStream getOutputStream() throws IOException;//将响应以流的形式输出出去

getRequestDispatcher()和forwad()请求转发 和 sendRedirect()重定向 的区别:

请求转发

将用户的同一个请求转发给另一个页面,地址栏的信息没有变

重定向

创建一个新的请求,传递给下一个页面,之前的请求结束生命周期,地址栏发送改变

场景

1.如果两个页面之间需要通过 request 来传值,则必须使用转发,不能使⽤重定向。

2.用户登录,如果⽤户名和密码正确,则跳转到首页(转发),并且展示⽤户名,否则重新回到登陆页面(重定向)


转发的路径问题

如果Tomcat Application Context设置为/test:

前端页面HTML(a标签的href、表单的action)路径一定要写上/test

在加上servlet映射的路径。

resp.sendRedirect();//路径需要包括Tomcat的Application Context,需要包括跳转的页面所在的文件夹

//代表绝对路径
resp.sendRedirect("/test/lib/test01.jsp");
//代表相对路径
resp.sendRedirect("test/test01.jsp")

注意

重定向的 / 代表绝对路径:http://localhost:8080/,意味着需要写上项目的名字,如果不写 / 代表相对路径:表示:http://localhost:8080/项目名/

请求转发的 / 代表绝对路径表示:http://localhost:8080/exer,如果不写 / 代表相对路径表示:http://localhost:8080/exer,所以请求转发的 / 可写可不写


JSP内置对象作用域

page < request < session < application

因为他们4个都有setAttribute()、getAttribute()方法,都可以作为传输数据的载体出现

主要问题是可以在什么地方把它们的值取出来

page作用域:对应的内置对象是pageContext

​ 只在当前页面有效

request作用域:对应的内置对象是request

​ 在一次请求内有效,请求转发可以取出,重定向不能取出

session作用域:对应的内置对象是session

​ 在一次会话内有效,关闭浏览器就失效了

application作用域:对应的内置对象是application

​ 服务器不关,就一直有效


EL表达式

Expression Langage表达式语言,替代JSP页面中数据访问时的复杂编码。可以方便地取出域对象(pageContext、request、session、application)中保存的数据,前提是一定要先setAttribute,EL就相当于简化的getAttribute()。在java里用不了,只能在jsp文件中使用

${变量名},变量名就是setAttribute对应的key值

  1. EL表达式对于4中域对象(同名)的默认查找顺序,没有找到继续向上查找

    pageContext > request > session > application

    越局部查找优先级越高

  2. 如果只想查找session的同名变量,也可以指定作用域查找,没有找到也不向上查找了

    pageContext:${pageScope.name}

    request:${requestScope.name}

    session:${sessionScope.name}

    application:${applicationScope.name}

<%
    Person p1 = new Person("Tom", 22);
    pageContext.setAttribute("person",p1);
%>
<tr>
    <td>${person.name}</td>
    <td>${person.age}</td>
</tr>

注意

${作用域属性名.对象成员变量},不是用属性值来调用对象的成员变量或方法。

${person.name}相当于Person p = (Person)pageContext.getAttribute(“person”);

再调用p.getName();

${person.age = 10}相当于Person p = (Person)pageContext.getAttribute(“person”);

先调用p.setAge(10),再调用p.getAge();

EL表达式主要用来读取数据,不用它设置数据

EL执行表达式

&&、||、!=、<、<=、>、>=、==

一定要写在${}之间来判断

<%
    pageContext.setAttribute("num1",10);
    pageContext.setAttribute("num2",20);
%>
做判断 false
${num1 > num2}
输出值 10 > 20
${num1} > ${num2}

JSTL

JSP Standard Tag Library:JSP标准标签库,JSP为开发者提供一系列的标签,可以完成一些逻辑处理,比如循环遍历集合,让代码更加简介,不再出现JSP脚本穿插的情况。只能在JSP中使用

实际开发中EL和JSTL结合起来使用,JSTL侧重于逻辑处理,EL负责展示数据

JSTL的使用

  1. 导入jstl和standard的jar包

  2. 在JSP页面开始的地方导入JSTL标签库

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
  3. 在需要的地方使用

    <%
    	List<Person> list = (List<Person>) request.getAttribute("list");
    %>
    <c:forEach items="${list}" var="person">
        <%--for (Person person : list)  --%>
        <%--items表示后面的参数,var表示前面的参数--%>
        <li>${person.name}</li>
        <li>${person.age}</li>
    </c:forEach>
    

JSTL的常用标签

  1. set:把数据存储在域对象中间
默认存在page中,可以用scope指定作用域
<c:set var="name" value="Tom" scope="session"></c:set>
value一般只能存字符串类型
如果想要修改对象的属性
需要先将对象存入那四个作用域之中
<%
	Person p1 = new Person("张三",50);
	request.setAttribute("person",p1);
%>
<c:set target="${person}" property="name" value="李四"></c:set>
set只能存储数字,字符串,不能存储对象
  1. out:输出域对象中的数据
<c:out value="${name}" default="未定义"></c:out>
  1. remove:删除域对象中的数据
<c:remove var="name" scope="page"></c:remove>
  1. 条件标签 if choose
<c:set var="num1" value="1"></c:set>
<c:set var="num2" value="2"></c:set>
<c:if test="${num1>num2}">ok</c:if>
<c:if test="${num1<num2}">fail</c:if>
<hr/>
<c:choose>
 <c:when test="${num1>num2}">ok</c:when>
 <c:otherwise>fail</c:otherwise>
</c:choose>

Cookie

Cookie是什么

客户端技术,服务端给客户端一个信件,客户端下次访问服务器带上信件就可以了。

Cookie是服务端在HTTP响应中附带传给客户端浏览器的一个小文本文件,一旦客户端浏览器保存了某个Cookie,在之后的请求和响应中,会将此Cookie来回传递,这样就可以通过Cookie这个载体完成客户端和服务端的数据交换。

关于Cookie的方法

Cookie[] cookies = req.getCookies();//获得客户端浏览器请求中的cookies

cookie.getName()://获得cookie中的key

cookie.getValue()://获得cookie中的value

Cookie cookie = new Cookie("key","value");//创建一个cookie,key和value都是字符串类型,注意value只能包含-+符号

cookie.setMaxAge(24 * 60 * 60);//设置cookie默认有效期为1天,默认:为一次会话,参数为int类型,单位:秒

req.getMaxAge();//默认是-1,关闭浏览器Cookie就失效

resp.addCookie(cookie);//响应给客户端一个cookie

一个cookie只能保存一个信息;

一个web站点可以给浏览器发送多个cookie,最多存放50个cookie(每个浏览器的设置不一样)

注意如果cookie.getValue()得到的中文是乱码,需要用URLDecoder.decode(cookies[index].getValue(),“UTF-8”)进行解码

Cookie的使用

Cookie[] cookies = req.getCookies();
boolean flag = false;
for(Cookie c : cookies){
    if("lastLoginTime".equals(c.getName())){
        flag = true;
    }
}
if(flag){
    out.write("您上次访问的时间是:");
    for (int i = 0; i < cookies.length; ++i) {
        Cookie c = cookies[i];
        if("lastLoginTime".equals(c.getName())){
            long lastLoginTime = Long.parseLong(c.getValue());
            Date date = new Date(lastLoginTime);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String time = sdf.format(date);
            out.write(time);
        }
    }
}else{
    out.write("第一次访问");
}
Cookie cookie = new Cookie("lastLoginTime", String.valueOf(System.currentTimeMillis()));

resp.addCookie(cookie);

Session

Session:服务器给每一个用户(浏览器)创建一个Session对象,一个Session独占一个浏览器,只要浏览器没有关闭,这个Session就一直存在。用户登录之后,这次会话中所有的页面它都可以访问保存的用户信息。
使用场景

  1. 保存一个登录用户的信息

  2. 购物车信息

  3. 整个网站中经常使用的数据

Session的方法

req.getSession();//获得这一次会话的Session

String getId();//获得SessionID

void setMaxInactiveInterval(int var1);//设置Session的失效时间,单位为秒,一般为了防止网页停留太久,信息失效
//Cookie才是可以7天免登陆的技术

int getMaxInactiveInterval();//默认是1800s,30min

void setAttribute(String key, Object value);//保存信息

Object getAttribute(String key);//读取信息

void removeAttribute(String var1);//异常信息

void invalidate();//session立即失效,一般用于退出登录

Session可以保存对象,Cookie只能保存字符串

Session的简单登录案例

当用户登录后才可以访问主页,用户没有登录则不能访问主页

  1. 首先是在login.jsp页面提交用户信息
<form action="/yin/login" method="post">
    用户名:<input type="text" name="username"><br>
    密&nbsp;&nbsp;码:<input type="password" name="password"><br>
    <input type="submit">
    <input type="reset">
</form>
<servlet>
    <servlet-name>Login</servlet-name>
    <servlet-class>com.tomorrow.servlet.Login</servlet-class>
    <init-param>
        <param-name>username</param-name>
        <param-value>yinqiming</param-value>
    </init-param>
    <init-param>
        <param-name>password</param-name>
        <param-value>123456</param-value>
    </init-param>
</servlet>

<servlet>
    <servlet-name>Logout</servlet-name>
    <servlet-class>com.tomorrow.servlet.Logout</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>Logout</servlet-name>
    <url-pattern>/logout</url-pattern>
</servlet-mapping>

<filter>
        <filter-name>IndexFilter</filter-name>
        <filter-class>com.tomorrow.filter.IndexFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>IndexFilter</filter-name>
        <url-pattern>/success/*</url-pattern>
    </filter-mapping>
  1. 用init-param替代数据库,存入信息。init-param属于当前Servlet的配置信息,需要用到当前的ServletConfig来获取,而ServletConfig在Servlet初始化时可以获取
private String username;
private String password;
@Override
public void init(ServletConfig config) throws ServletException {
    //ServletConfig:当前Servlet的信息,只有它可以取出写在web.xml中servlet
    //中的init-param
    username = config.getInitParameter("username");
    password = config.getInitParameter("password");
}
  1. 验证信息
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    if(this.username.equals(username) && this.password.equals(password)){
        HttpSession session = req.getSession();
        session.setAttribute("username",username);
        resp.sendRedirect("/yin/success/index.jsp");
    }else{
        resp.sendRedirect("/yin/error.jsp");
    }
}

验证成功,使用Session存储username信息。

<h1>Welcome</h1>
欢迎回来<%=session.getAttribute("username")%>
<a href="/yin/logout">退出</>

index.jsp单独放到一个文件夹下,使用过滤器进行保护

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) servletRequest;
    HttpServletResponse resp = (HttpServletResponse) servletResponse;
    Object username = req.getSession().getAttribute("username");
    if(username == null){
        resp.sendRedirect("/yin/error.jsp");
    }
    filterChain.doFilter(servletRequest,servletResponse);
}
  1. 注销后,要移除Session的username属性。
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    HttpSession session = req.getSession();
    Object username = session.getAttribute("username");
    if(username != null){
        session.removeAttribute("username");
        resp.sendRedirect("/yin/login.jsp");
    }else{
        resp.sendRedirect("/yin/error.jsp");
    }
}

Filter

功能

  1. ⽤来拦截传⼊的请求和传出的响应
  2. 修改或以某种⽅式处理正在客户端和服务端之间交换的数据流
  3. doFilter ⽅法中处理完业务逻辑之后,必须添加
    filterChain.doFilter(servletRequest,servletResponse);否则请求/响应⽆法向后传递,⼀直停留在过滤器中

Filter的生命周期

当 Tomcat 启动时,通过反射机制调⽤ Filter 的⽆参构造函数创建实例化对象,同时调⽤ init ⽅法实现初始化(Filter对象自始至终只存在一个),doFilter ⽅法可以调⽤多次。当 Tomcat 服务关闭的时候,调⽤ destory 来销毁 Filter 对象

同时配置多个 Filter,Filter 的调⽤顺序是由 web.xml 中的配置顺序来决定的,写在上⾯的配置先调用,因为 web.xml 是从上到下顺序读取的

实际开发中Filter的使用场景

  1. 统一处理中文乱码

  2. 屏蔽敏感词

    String name = servletRequest.getParameter("name");
    //处理的是副本,没有处理request中的name
    name = name.replaceAll("敏感词","***");
    //需要把处理完的数据存下来
    servletRequest.setAttribute("name",name);
    filterChain.doFilter(servletRequest,servletResponse);
    
  3. 控制资源的访问权限(只有登录成功才能进入主页,直接访问主页则会跳转到登录页面)

文件上传下载

文件上传

  1. input的type设置为file

  2. form表单的method设置为post,get请求会将文件名传给服务端,而不是文件本身

  3. form表单的enctype设置为multipart/form-data,以二进制流的形式把数据发送给服务器

    <form action="/myweb/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="img"><br/>
        <input type="file" name="word"><br/>
        <input type="submit" value="上传">
    </form>
    
  • fileload组件(第三方)

​ fileupload 组件可以将所有的请求信息都解析成 FileIteam 对象,可以通过对 FileItem 对象的操作完成上传,⾯向对象的思想

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>
DiskFileItemFactory dfif = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(dfif);
List<FileItem> list = sfu.parseRequest(req);
for (FileItem item : list) {
	if(item.isFormField()){//是否为普通表单项,true:普通表单项,false:文件
        String name = item.getFieldName();//input标签中的name值
        String value = item.getString("UTF-8");//输入框内的值,解码用UTF-8
        System.out.println(name + ":" + value);
    }else {
        String name = item.getName();//文件名
        long size = item.getSize();//文件大小 单位:B
        System.out.println(name + ":" + size + "B");
        InputStream is = item.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        String path = req.getServletContext().getRealPath("file/" + name);//确保该../file存在
        FileOutputStream fos = new FileOutputStream(path);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        byte[] buffer = new byte[1024];
        int len;
        while((len = bis.read(buffer)) != -1){
            bos.write(buffer,0,len);
        }
        bos.close();
        bis.close();
}

注意:由于你上传的文件路径选择在tomcat部署项目的路径(Tomcat/webapps/…)下了,虽然上传成功了,但是当你重新部署项目的时候,tomcat下的webapps文件夹下的项目会被重新部署,覆盖掉了之前的项目文件,所以文件就消失了。

文件下载

  1. 设置响应方式

    resp.setContentType("image/jpeg");
    /*
    image/jpeg
    image/png
    text/plain
    text/html
    application/msword
    */
    
  2. 设置下载后的文件名

    String filename = "背景.jpg";
    filename = URLEncoder.encode(filename,"utf-8");
    //防止下载后的文件名出现乱码
    //Content-disposition是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件
    resp.setHeader("Content-Disposition","attachment;filename=" + filename);
    
  3. 读取服务器中的资源

    //确保此路径确实存在资源
    InputStream is = req.getServletContext().getResourceAsStream("/file/" + filename);
    
  4. 将服务器中的资源下载到本地

    OutputStream os = resp.getOutputStream();
    byte[] buffer = new byte[1024];
    int len;
    while((len = is.read(buffer)) != -1){
        os.write(buffer,0,len);
    }
    
  5. 关闭资源

Listener

主要在Swing编程

JDBC

Java DataBase Connectivity,它是Java和数据库之间的桥梁,是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库(java.sql,javax.sql)使用这些类库可以以一种标准的方法,方便的访问数据库资源。它最大的优点是为访问不同的数据库提供了一种统一的途径

JDBC访问数据库的流程

  1. 加载驱动(DriverManager)
  2. 获取连接(Connection)
  3. 获取执行SQL对象(PrepareStatement)
  4. 解析结果集(ReslutSet)
  5. 释放资源(close)

数据库连接方法

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
//方式一
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "1234";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, user, password);
//方式二
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("JDBC.properties");
Properties pros = new Properties();
pros.load(is);
String url = pros.getProperty("url");
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String driverClass = pros.getProperty("driverClass");
Class.forName(driverClass);
Connection connection = DriverManager.getConnection(url, user, password);
url=jdbc:mysql://localhost:3306/数据库名
user=root
password=1234
driverClass=com.mysql.cj.jdbc.Driver

PreparedStatement

  • Statement存在拼接SQL语句繁琐和SQL注入问题,如果输入的密码包括mysql的关键字比如:密 码: aaa’ or ‘1’='1,由于 恶意用户 输入的密码被当作sql语句,编译时,1=1,返回true,所以验证通过,登陆成功。
  • PreparedStatement是Statement的子类,提供了 SQL 占位符的功能

PreparedStatement最主要的特征:预编译SQL语句

PreparedStatement ps = conn.prepareStatement(sql);
//实际上ps是已经执行过sql语句的 占位符会变,但sql语句不会变

而Statement没有预编译,改变的是整个SQL语句

相当于PreparedStatement首先确定了语法逻辑,然后填充相应的数值,而Statement连着数值里包含的非法语法一起编译,就会造成对原来语法逻辑的破坏

除了解决Statement的拼串、sql问题之外,

  •  PreparedStatement可以操作Blob的数据,而Statement做不到
    
  •  PreparedStatement可以实现更高效的批量操作 因为实现了预编译,批量操作也只是改变要输入的值,而Statement因为值不一样,所以每一条都要编译一次
    
增删改
public static int generalCUD(String sql,Object... args) {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        //1.连接数据库
        conn = JDBCUtils.getConnection();
        //2.预编译SQL语句
        ps = conn.prepareStatement(sql);
        for (int i = 0; i < args.length; ++i) {
            //3.填充占位符
            ps.setObject(i + 1,args[i]);
        }
        //4.执行
        //修改几条数据,executeUpdate()就返回几
        return  ps.executeUpdate();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    } finally {
        //5.关闭资源
        JDBCUtils.closeResource(conn,ps,null);
    }
    return 0;
}
查一条数据
public <T> T getInstance(Class<T> clazz,String sql,Object... args){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        //1.连接数据库
        conn = JDBCUtils.getConnection();
        //2.预编译SQL语句
        ps = conn.prepareStatement(sql);
        for (int i = 0; i < args.length; ++i) {
            3.填充占位符
            ps.setObject(i + 1,args[i]);
        }
        //4.执行,获取结果集
        rs = ps.executeQuery();
        //5.获取表的元数据(列数,列名)
        ResultSetMetaData rsmd = rs.getMetaData();
        //6.获取表的列数
        int columnCount = rsmd.getColumnCount();
        if(rs.next()){
            //7.通过反射创建表对应类的实例
            T t = clazz.getDeclaredConstructor().newInstance();
            for (int i = 0; i < columnCount; ++i) {
                //8.获取每个列的列值
                Object columnValue = rs.getObject(i + 1);
                //9.获取每个列的别名
                String columnLabel = rsmd.getColumnLabel(i + 1);
                //10.通过反射给实例设置属性
                Field field = clazz.getDeclaredField(columnLabel);
                field.setAccessible(true);
                field.set(t,columnValue);
            }
            //11.返回实例
            return t;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //12.关闭资源
        JDBCUtils.closeResource(conn,ps,rs);
    }
    return null;
}
查多条数据
public <T> List<T> getForList(Class<T> clazz,String sql,Object... args){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        //1.连接数据库
        conn = JDBCUtils.getConnection();
        //2.预编译SQL语句
        ps = conn.prepareStatement(sql);
        for (int i = 0; i < args.length; ++i) {
            //3.填充占位符
            ps.setObject(i + 1,args[i]);
        }
        //4.执行,获取结果集
        rs = ps.executeQuery();
        //5.获取表的元数据(列数,列名)
        ResultSetMetaData rsmd = rs.getMetaData();
        //6.获取表的列数
        int columnCount = rsmd.getColumnCount();
        //7.创建集合对象,用于存储创建好的实例对象
        ArrayList<T> list = new ArrayList<>();
        //没有数据时,返回false
        while(rs.next()){
            //8.通过反射创建表对应类的实例
            T t = clazz.getDeclaredConstructor().newInstance();
            for (int i = 0; i < columnCount; ++i) {
                //9.获取每个列的列值
                Object columnValue = rs.getObject(i + 1);
               //10.获取每个列的别名
                String columnLabel = rsmd.getColumnLabel(i + 1);
                //11.通过反射给实例设置属性
                Field field = clazz.getDeclaredField(columnLabel);
                field.setAccessible(true);
                field.set(t,columnValue);
            }
            //12.将赋好值的对象放到集合中
            list.add(t);
        }
        //13.返回集合
        return list;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //14.关闭资源
        JDBCUtils.closeResource(conn,ps,rs);
    }
    return null;
}

事务

  1. 什么是事务:

    事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态,一组逻辑操作单元:一个或多个DML操作

  2. 事务处理的原则:

    保证所有事务都作为一个工作单元来执行,即使出现故障,也不能改变
    这种执行方式。当在一个事务中执行多个操作时,要么所有事务都被提交
    (commit),那么这些修改就永久地保存下来,要么数据库管理系统放弃
    所作的所有修改,整个事务回滚(rollback)到最初状态

  3. 数据一旦提交,就不可以回滚

  4. 哪些操作会导致数据的自动提交?

    • DDL(对于数据库)操作一旦执行,都会自动提交
    • DML(对于数据表)默认情况下,一旦执行,就会自动提交,我们可以通过set autocommit = false的方式取消DML操作的自动提交
    • 默认在关闭连接时,会自动的提交数据

由于关闭连接时,也会自动提交,所以连接这一行为要受到约束,所以把connection当作参数,为的是把一系列操作用一个连接串起来

事务的ACID属性

  • 原子性(Atomicity):指事务是一个不可分割的工作单位,事务中的操作要么都发生要么都不发生
  • 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态
  • 隔离性(Isolation):指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰,与中断(关中断,开中断)类似
  • 持久性(Durability):指一个事务一旦提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

数据库的并发问题

对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题

  • 脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段,之后,若T2回滚,T1读取的内容就是无效的
  • 不可重复读:对于两个事务T1、T2,读取了一个字段后,然后T2更新了该字段,之后T1再次读取同一个字段,值就不同了
  • 幻读:对于两个事务T1、T2,T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后,如果T1再次读取同一个表,就会多出几行

一个事务与其他事务隔离的程度为隔离级别。隔离级别越高,数据一致性就越好,但并发性就越弱

4中隔离级别:

  • 读未提交数据(read uncommitted) (不用,安全性太低)
  • 读已提交数据(read committed)避免脏读,但存在不可重复读和幻读
  • 可重复读(repeatable read)(默认) 避免了脏读和不可重复读,但存在幻读
  • 串行化(serializable)(不用,并发性太低)

场景

root给一个用户user授权,可以访问user_table表

1.root、tom都取消自动提交

2.tom修改AA的工资为3000(原来是2000)

3.此时root读取后,AA的工资是2000,这是避免了脏读

4.然后user提交后,此时root读取后,AA的工资还是2000,这是避免了不可重复读

5.此时root提交后,再次查询,AA的工资为3000

数据库连接池

  • C3P0
  • DBCP
  • Druid(常用)
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
  1. 配置数据库连接池
//保证只存在一个数据库连接池,连接池中可以有多个连接
private static DataSource source = null;
static {
    try {
        Properties pros = new Properties();
        InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
        pros.load(is);
        source = DruidDataSourceFactory.createDataSource(pros);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
url=jdbc:mysql://localhost:3306/test
username=root
password=1234
driverClassName=com.mysql.cj.jdbc.Driver
  1. 连接数据库
public static Connection getConnection() throws Exception {
    Connection conn = source.getConnection();
    return conn;
}

DBUtils

commins_dbutils 是Apache组织提供的一个开源JDBC工具类库,封装了针对于数据库的增删改查操作

<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.7</version>
</dependency>
QueryRunner runner = new QueryRunner();
runner.insert(...);
runner.update(...);
runner.query(...);
<T> T insert(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException 
删、改
int update(Connection conn, boolean closeConn, String sql, Object... params) throws SQLException 
<T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException 

ResultSetHandler

ResultSetHandler接口时用来处理结果集,可以将查询到的结果集转换成Java对象,提供了4种实现类。

  • BeanHandler 将结果集(查一条数据)映射成Java对象

    QueryRunner runner = new QueryRunner();
    Connection conn = getConnection();
    String sql = "select id num,name,password,address,phone from user where id = ?";
    User user = runner.query(conn, sql, new BeanHandler<>(User.class), 2);
    DbUtils.closeQuietly(conn);
    System.out.println(user);
    
  • BeanListHandler 将结果集映射成List集合

    QueryRunner runner = new QueryRunner();
    Connection conn = getConnection();
    String sql = "select id num,name,password,address,phone from user";
    List<User> list = runner.query(conn, sql, new BeanListHandler<>(User.class));
    DbUtils.closeQuietly(conn);
    for (User user : list) {
        System.out.println(user);
    }
    
  • MapHandler 将结果集映射成Map集合

    QueryRunner runner = new QueryRunner();
    Connection conn = getConnection();
    String sql = "select id num,name,password,address,phone from user where id = ?";
    //字段名为String类型,值为Object类型
    Map<String, Object> map = runner.query(conn, sql, new MapHandler(), 5);
    DbUtils.closeQuietly(conn);
    //遍历map中的key-value对
    Set<Map.Entry<String, Object>> entries = map.entrySet();
    Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
    while (iterator.hasNext()){
        Map.Entry<String, Object> next = iterator.next();
        System.out.println(next.getKey() + ":" + next.getValue());
    }
    
  • MapListHandler 将结果集映射成MapList集合

    QueryRunner runner = new QueryRunner();
    Connection conn = getConnection();
    String sql = "select id num,name,password,address,phone from user ";
    List<Map<String, Object>> list = runner.query(conn, sql, new MapListHandler());
    DbUtils.closeQuietly(conn);
    for (Map<String, Object> map : list) {
        //有几个表项就对应几个map对象
        //有几个字段,map对象就对应几个key-value对
        System.out.println(map);
    }
    
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值