1 1.文件上传下载

1.1 文件上传

1.1.1 文件上传的作用

例如网络硬盘!就是用来上传下载文件的。

在智联招聘上填写一个完整的简历还需要上传照片呢。

1.1.2 文件上传对页面的要求

1.必须使用表单,而不能是超链接;

2.表单的method必须是POST,而不能是GET;

3.表单的enctype必须是multipart/form-data;

4.在表单中添加file表单字段,即<input type=”file”…/>

如下:

<form method="post"action="/fileupload" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" name="上传">
</form>

1.1.3 文件上传对Servlet的要求

当提交的表单是文件上传表单时,那么对Servlet也是有要求的。首先我们要肯定一点,文件上传表单的数据也是被封装到request对象中的。request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不再是字符内容,而是字节内容,所以失效。这时可以使用request的getInputStream()方法获取ServletInputStream对象,它是InputStream的子类,这个ServletInputStream对象对应整个表单的正文部分(从第一个分隔线开始,到最后),这说明我们需要的解析流中的数据。当然解析它是很麻烦的一件事情,而Apache已经帮我们提供了解析它的工具:commons-fileupload。

1.2 commons-fileupload

1.2.1 fileupload概述

fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()。

fileupload组件需要的JAR包有:

commons-fileupload.jar,核心包;

commons-io.jar,依赖包。

1.2.2 fileupload简单应用

 fileupload的核心类有:DiskFileItemFactory、ServletFileUpload、FileItem。

使用fileupload组件的步骤如下:

* 创建工厂类DiskFileItemFactory对象:DiskFileItemFactoryfactory = new DiskFileItemFactory()

* 使用工厂创建解析器对象:ServletFileUpload fileUpload = new ServletFileUpload(factory)

* 使用解析器来解析request对象:List<FileItem> list =fileUpload.parseRequest(request)

FileItem类,它才是我们最终要的结果。一个FileItem对象对应一个表单项(表单字段)。一个表单中存在文件字段和普通字段,可以使用FileItem类的isFormField()方法来判断表单字段是否为普通字段,如果不是普通字段,那么就是文件字段了。

String getName():获取文件字段的文件名称;

String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;

String getFieldName():获取字段名称,例如:<input type=”text”name=”username”/>,返回的是username;

String getContentType():获取上传的文件的类型,例如:text/plain。

int getSize():获取上传文件的大小;

boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;

InputStream getInputStream():获取上传文件对应的输入流;

voidwrite(File):把上传的文件保存到指定文件中。

1.2.3 简单上传示例

1.引入jar包

2.编写上传表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
${message}
<form method="post" action="/fileupload" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" name="上传">
</form>
</body>
</html>

3.编写Servlet

public class FileUploadServlet extends HttpServlet {

private static final String UPLOADFILE = "file";
    //设置上文文件的最大大小
    
private static final int MAX_FILE_SIZE = 1024 * 1024 * 40;
    //设置内存的临界值,超过后将产生临时文件存储于临时目录中
    
private static final int MAX_THRESHOLD = 1024 * 1024 * 20;
    private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 50;

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //首先检测是否为多媒体上传
        
if (!ServletFileUpload.isMultipartContent(req)) {
           resp.setCharacterEncoding("UTF-8");
            PrintWriterout = resp.getWriter();
            out.write("Error: 表单必须包含 enctype=multipart/form-data");
            out.flush();
            return;
        }
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //设置内存临界值,超过后将产生临时文件存储于临时目录之中
        
factory.setSizeThreshold(MAX_THRESHOLD);
        //设置临时文件夹
        
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
        ServletFileUpload upload = new ServletFileUpload(factory);
        //设置最大文件上传值
        
upload.setFileSizeMax(MAX_FILE_SIZE);
        //设置最大请求值(包含文件和表单数据)
        
upload.setSizeMax(MAX_REQUEST_SIZE);
        //处理中文
        
upload.setHeaderEncoding("UTF-8");
        //文件上传后存储的位置
        
String uploadPath = req.getServletContext().getRealPath("/") + File.separator + UPLOADFILE;
        File file = new File(uploadPath);
        if (!file.exists()) {
           file.mkdirs();
        }
        try {
           List<FileItem> fileItems = upload.parseRequest(req);
            for (FileItem fileItem : fileItems) {
                if (!fileItem.isFormField()) {
                   String fileName = new File(fileItem.getName()).getName();
                   String filePath = uploadPath + File.separator + fileName;
                   File storeFile = new File(filePath);
                   System.out.println(filePath);
                   fileItem.write(storeFile);
                   req.setAttribute("message", "文件上传成功");
               }
            }
        } catch (Exception e) {
           req.setAttribute("message", "文件上传失败");
        }
        req.getRequestDispatcher("/").forward(req, resp);
    }
}

Servlet注册:

<servlet>
    <servlet-name>fileUpload</servlet-name>
    <servlet-class>org.sang.FileUploadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>fileUpload</servlet-name>
    <url-pattern>/fileupload</url-pattern>
</servlet-mapping>

1.3 文件上传细节

1.3.1 把上传的文件放到WEB-INF目录下

如果没有把用户上传的文件存放到WEB-INF目录下,那么用户就可以通过浏览器直接访问上传的文件,这是非常危险的。

假如说用户上传了一个a.jsp文件,然后用户在通过浏览器去访问这个a.jsp文件,那么就会执行a.jsp中的内容,如果在a.jsp中有如下语句:Runtime.getRuntime().exec(“shutdown–s –t 1”);

通常我们会在WEB-INF目录下创建一个uploads目录来存放上传的文件,而在Servlet中找到这个目录需要使用ServletContext的getRealPath(String)方法,例如在我的upload1项目中有如下语句:

ServletContext servletContext =this.getServletContext();

String savepath =servletContext.getRealPath(“/WEB-INF/uploads”);

如下:

1.3.2 中文乱码问题

当上传的谁的名称中包含中文时,需要设置编码,commons-fileupload组件为我们提供了两种设置编码的方式:

request.setCharacterEncoding(String):这种方式是我们最为熟悉的方式了;

fileUpload.setHeaderEncdoing(String):这种方式的优先级高与前一种。

上传文件的文件内容包含中文:

通常我们不需关心上传文件的内容,因为我们会把上传文件保存到硬盘上!也就是说,文件原来是什么样子,到服务器这边还是什么样子!

但是如果你有这样的需求,非要在控制台显示上传的文件内容,那么你可以使用fileItem.getString(“utf-8”)来处理编码。

文本文件内容和普通表单项内容使用FileItem类的getString(“utf-8”)来处理编码。

如下:

1.3.3 上传文件同名问题

通常我们会把用户上传的文件保存到uploads目录下,但如果用户上传了同名文件呢?这会出现覆盖的现象。处理这一问题的手段是使用UUID生成唯一名称,然后再使用“_”连接文件上传的原始名称。

例如用户上传的文件是“我的一寸照片.jpg”,在通过处理后,文件名称为:“891b3881395f4175b969256a3f7b6e10_我的一寸照片.jpg”,这种手段不会使文件丢失扩展名,并且因为UUID的唯一性,上传的文件同名,但在服务器端是不会出现同名问题的。

如下:

1.3.4 一个目录不能存放过多文件

1.3.5 上传的单个文件的大小限制

限制上传文件的大小很简单,ServletFileUpload类的setFileSizeMax(long)就可以了。参数就是上传文件的上限字节数,例如servletFileUpload.setFileSizeMax(1024*10)表示上限为10KB。

一旦上传的文件超出了上限,那么就会抛出FileUploadBase.FileSizeLimitExceededException异常。我们可以在Servlet中获取这个异常,然后向页面输出“上传的文件超出限制”。

如下:

抛出异常时做如下判断:

1.3.6 上传文件的总大小限制

有时我们需要限制一个请求的大小。也就是说这个请求的最大字节数(所有表单项之和)!实现这一功能也很简单,只需要调用ServletFileUpload类的setSizeMax(long)方法即可。

例如fileUpload.setSizeMax(1024 * 10);,显示整个请求的上限为10KB。当请求大小超出10KB时,ServletFileUpload类的parseRequest()方法会抛出FileUploadBase.SizeLimitExceededException异常。

1.3.7 缓存大小与临时目录

//设置内存临界值,超过后将产生临时文件存储于临时目录之中
 
factory.setSizeThreshold(MAX_THRESHOLD);
//设置临时文件夹
 
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

默认情况下系统不会一次将所有数据全部保存到内存中然后再写到数据库中去,而是内存中保存10KB,满了之后写入缓存中。所以可以设置内存临界值和缓存目录。

1.4 文件下载

1.4.1 通过Servlet下载1

下载页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<a href="/download?path=go1.7.4.windows-amd64.msi">go1.7.4.windows-amd64.msi</a>
<a href="/download?path=QQ8.7.exe">QQ8.7.exe</a>
<a href="/download?path=只有偏执狂才能生存.pdf">只有偏执狂才能生存.pdf</a>
</body>
</html>

下载Servlet:

public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String path = req.getParameter("path");
        File file = new File(this.getServletContext().getRealPath("/WEB-INF/downloads"), path);
        if (!file.exists()) {
           resp.setContentType("text/html;charset=utf-8");
        }
        IOUtils.copy(new FileInputStream(file), resp.getOutputStream());
    }
}

上面代码有如下问题:

可以下载msi和exe文件,但在下载框中的文件名称是download;

不能下载pdf,而是在页面中显示它们。

1.4.2 通过Servlet下载2

通过添加content-disposition头来处理上面问题。当设置了content-disposition头后,浏览器就会弹出下载框。

而且还可以通过content-disposition头来指定下载文件的名称!

resp.addHeader("content-disposition", "attachment;filename=" + path);

1.4.3 通过Servlet下载3

上面的方式中文会乱码,解决方案如下:

String s = new String(path.getBytes("utf-8"), "iso-8859-1");
resp.addHeader("content-disposition", "attachment;filename=" + s);

2  2.jQuery

2.1 Day42

2.1.1 jQuery简介

什么是jQuery

其是对javascript封装的一个框架包,简化对javascript的操作。

javascript代码:获得页面节点对象、ajax元素节点对象实现、事件操作、事件对象

jquery代码:无需考虑浏览器兼容问题、代码足够少

特点

1.语法简练、语义易懂、学习快速、丰富文档。

2.jQuery 是一个轻量级的脚本,其代码非常小巧

3.jQuery 支持 CSS1~CSS3 定义的属性和选择器

4.jQuery 是跨浏览器的,它支持的浏览器包括 IE 6.0+、FF1.5+、Safari 2.0+和 Opera 9.0+。

5.能将 JavaScript 脚本与 HTML 源代码完全分离,便于后期编辑和维护。

6.插件丰富,除了 jQuery 自身带有的一些特效外,可以通过插件实现更多功能

2.1.2 jQuery选择器

为在页面上获得各种元素节点对象而使用的条件就是选择器。

document.getElementById()

document.getElementsByTagName();

document.getElementsByName();

基本选择器

1.$('#id属性值')  ----------->document.getElementById()

获取计算机基础节点并输出:

$("#id")取出的是jquery对象,这是个集合对象,要想获得dom对象,可以用$("#id").get(i),其中i是jquery对象序列号,从0开始计算。

2.$('tag标签名称')----------->document.getElementsByTagName();

3.$('.class属性值') class属性值选择器

$(".class")取出的是jquery对象,这是个集合对象,要想获得dom对象,可以用$("#id").get(i),其中i是jquery对象序列号,从0开始计算。

4.$('*') 通配符选择器

递归遍历所有元素

5.$('s1,s2,s3')联合选择器

层次选择器

1.$(s1  s2) [父子]

派生选择器:在s1内部获得全部的s2节点(不考虑层次),如下:

红线部分表示该选择器可以获取到的部分

2.$(s1 > s2) [父子]

直接子元素选择器:在s1内部获得s2的子元素节点,如下:

红线部分表示该选择器可以获取到的部分

3.$(s1 + s2) [兄弟]

直接兄弟选择器:在s1后边获得紧紧挨着的第一个兄弟关系的s2节点

4.$(s1 ~ s2) [兄弟]

后续全部兄弟关系节点选择器:在s1后边获得全部兄弟关系的s2节点

过滤选择器

:first $("p:first") 第一个 <p> 元素

:last $("p:last") 最后一个 <p> 元素

:even $("tr:even") 所有偶数 <tr> 元素

:odd $("tr:odd") 所有奇数 <tr> 元素

:eq(index) $("ul li:eq(3)") 列表中的第四个元素(index 从 0 开始)

:gt(no) $("ul li:gt(3)") 列出 index 大于 3的元素

:lt(no) $("ul li:lt(3)") 列出 index 小于 3的元素

:not(selector) $("input:not(:empty)")所有不为空的 input 元素

:header $(":header") 所有标题元素 <h1> - <h6>

内容过滤选择器

1.:contains(内容)

包含内容选择器,获得节点内部必须通过标签包含指定的内容

$("div:contains(beijing)")

<div>linken love beijing</div>

<div>jack love shanghai</div>

2.:empty

获得空元素(内部没有任何元素/文本(空) )节点对象

$("div:empty")

<div>linken love beijing</div>

<div>jack love shanghai</div>

<div></div>

<div><img /></div>

<div>      </div>

3.:has(选择器)

内部包含指定元素的选择器

$("div:has(#apple)")

<div>hello</div>

<div><p></p></div>

<div><spanid="apple"></span></div>

<div><spanclass="apple"></span></div>

4.:parent

寻找的节点必须作为父元素节点存在

$("div:parent")

<div>linken love beijing</div>

<div>jack love shanghai</div>

<div></div>

<div><img /></div>

<div>     </div>

表单域选择器

2.1.3 jQuery属性操作

$().attr(属性名称);     //获得属性信息值

$().attr(属性名称,值);   //设置属性的信息

$().removeAttr(属性名称);   //删除属性

$().attr(json对象);     //同时为多个属性设置信息值,json对象的键值对就是名称和值

$().attr(属性名称,fn);     //通过fn函数执行的return返回值对属性进行赋值

2.1.4 jQuery快捷操作

class属性值操作

$().addClass(值);    //给class属性追加信息值

$().removeClass(值);  //删除某一个class

$().toggleClass(值);  //开关效果,有就删除,没有就添加

标签包含内容操作

$().html();      //获得节点包含的信息

$().html(信息);   //设置节点包含的内容

$().text();     //获得节点包含的“文本字符串信息”内容

$().text(信息);     //设置节点包含的内容(有html标签就把“><”符号变为符号实体)

html() 和 text()方法的区别:

1.获取内容

前者可以获取html标签 和 普通字符串内容

后者只获取普通字符串内容

2.设置内容

前者可以设置html标签 和 普通字符串内容

后者只设置普通字符串内容,如果内容里边有tag标签内容,就把其中的”<”“>”符号转变为符号实体 <转&lt;  >转&gt;   空格转&nbsp;

以上两种操作(获取/设置)如果针对的操作内容是纯字符串内容,则使用效果一致。

css样式操作

css样式操作

$().css(name,value);   //设置

$().css(name);       //获取

$().css(json对象);     //同时修改多个css样式

css()样式操作特点

1.样式获取,jquery可以获取 行内、内部、外部的样式。

dom方式只能获得行内样式

2.一些jquery版本不支持复合属性操作,所以需要把复合属性样式需要拆分为"具体样式"才可以操作。

例如:  background 需要拆分为  background-color background-image 等进行操作

border: border-left-style  border-left-width  border-left-color 等

margin: margin-left  margin-top 等

value属性快捷操作

$().attr('value');

$().attr('value',信息值);

快捷操作:

$().val();      //获得value属性值

$().val(信息值);  //设置value属性的值

复选框操作

下拉列表操作

单选按钮操作

2.1.5 jQuery和DOM对象关系

jQuery和DOM不能互相调用对方的成员

jQuery对象和DOM对象的转化

jQuery->DOM

DOM->jQuery

2.2 Day43

2.2.1 jQuery加载事件

jQuery加载事件实现

jQuery加载事件与传统加载事件的区别

设置个数

在同一个请求里边,jquery的可以设置多个,而传统方式只能设置一个。传统方式加载事件是给onload事件属性赋值,多次赋值,后者会覆盖前者。

jquery方式加载事件是把每个加载事件都存入一个数组里边,成为数组的元素,执行的时候就遍历该数组执行每个元素即可,因此其可以设置多个加载事件。

执行时机

1.传统方式加载事件,是全部内容(文字、图片、样式)在浏览器显示完毕再给执行加载事件。

2.jquery方式加载事件,只要全部内容(文字、图片、样式)在内存里边对应的DOM树结构绘制完毕就给执行,有可能对应的内容在浏览器里边还没有显示。

2.2.2 jQuery普通事件操作

DOM事件处理回忆

jQuery事件处理

2.2.3 jQuery对文档的操作

节点追加

父子关系追加

案例:

兄弟关系追加

案例:

节点替换

节点删除

复制节点

2.2.4 jQuery属性选择器的使用

2.2.5 jQuery事件绑定

事件绑定

取消事件绑定

2.3 Day44

2.3.1 jQuery封装的ajax

AJAX概述

什么是AJAX

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML)。AJAX还有一个最大的特点就是,当服务器响应时,不用刷新整个浏览器页面,而是可以局部刷新。这一特点给用户的感受是在不知不觉中完成请求和响应过程。

l * 与服务器异步交互;

l * 浏览器页面局部刷新;

同步交互与异步交互

同步交互与异步交互

l 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;

l 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。

AJAX常见应用情景

当我们在百度中输入一个“传”字后,会马上出现一个下拉列表!列表中显示的是包含“传”字的10个关键字。

其实这里就使用了AJAX技术!当文件框发生了输入变化时,浏览器会使用AJAX技术向服务器发送一个请求,查询包含“传”字的前10个关键字,然后服务器会把查询到的结果响应给浏览器,最后浏览器把这10个关键字显示在下拉列表中。整个过程中页面没有刷新,只是刷新页面中的局部位置而已!当请求发出后,浏览器还可以进行其他操作,无需等待服务器的响应!

当输入用户名后,把光标移动到其他表单项上时,浏览器会使用AJAX技术向服务器发出请求,服务器会查询名为zhangSan的用户是否存在,最终服务器返回true表示名为zhangSan的用户已经存在了,浏览器在得到结果后显示“用户名已被注册!”。

l 整个过程中页面没有刷新,只是局部刷新了;

l 在请求发出后,浏览器不用等待服务器响应结果就可以进行其他操作;

AJAX优缺点

优点:

l AJAX使用Javascript技术向服务器发送异步请求;

l AJAX无须刷新整个页面;

l 因为服务器响应内容不再是整个页面,而是页面中的局部,所以AJAX性能高;

缺点:

l AJAX虽然提高了用户体验,但无形中向服务器发送的请求次数增多了,导致服务器压力增大;

l 因为AJAX是在浏览器中使用Javascript技术完成的,所以还需要处理浏览器兼容性问题;

AJAX技术

AJAX第一例

AJAX发送POST请求

2.3.2 省市县三级联动

1.创建数据库,构造数据源

http://download.csdn.net/detail/u012702547/9827293

2.C3P0提供数据连接池

所需jar包:

配置文件:

<?xml version="1.0"encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="user">root</property>
        <property name="password">123</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///pca</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
    </default-config>
</c3p0-config>

工具类:

public class DBUtils {
    public static QueryRunner getQueryRunner() {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        return new QueryRunner(dataSource);
    }
}

3.DAO实现查询操作

public class PCADao {
    public QueryRunner queryRunner = DBUtils.getQueryRunner();

public List<AreaBean> getAreaBeenByParentId(Integer parentId) {
        List<AreaBean> list = null;
        try {
            list = queryRunner.query("select * from base_area wherearea_parent_id=?", new BeanListHandler<AreaBean>(AreaBean.class), parentId);
        } catch (SQLException e) {
           e.printStackTrace();
        }
        return list;
    }
}

4.Servlet提供查询接口

public class QueryServlet extends HttpServlet {
    private PCADao pcaDao = new PCADao();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String parentId = req.getParameter("parentId");
        List<AreaBean> list = pcaDao.getAreaBeenByParentId(Integer.parseInt(parentId));
        Gson gson = new Gson();
        resp.setContentType("application/json;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.write(gson.toJson(list));
        out.flush();
        out.close();
    }
}

Servlet注册:
<servlet>
    <servlet-name>queryservlet</servlet-name>
    <servlet-class>org.sang.QueryServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>queryservlet</servlet-name>
    <url-pattern>/query</url-pattern>
</servlet-mapping>

实体类如下:

5.编写html页面 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>省市县三级联动</title>
    <script src="js/jquery-3.1.1.min.js"></script>
</head>
<body>
省<select id="province"></select>
市<select id="city"></select>
区/县<select id="area"></select>
<script>
    function initProvince(parentId){
        $.ajax({
            url: '/query',
            data: {parentId:parentId},
            success: function (msg) {
                var proStr = '';
                for (var i = 0; i < msg.length; i++) {
                    var item = msg[i];
                    proStr += '<option value="' + item.area_id + '">' + item.area_name+ '</option>';
               }
                $("#province").html(proStr);
                initCity($("#province:first").val());
            }
        });
    }
    function initCity(parentId) {
        $.ajax({
            url: '/query',
            data: {parentId:parentId},
            success: function (msg) {
                var proStr = '';
                for (var i = 0; i < msg.length; i++) {
                    var item = msg[i];
                    proStr += '<option value="' + item.area_id + '">' + item.area_name+ '</option>';
               }
                $("#city").html(proStr);
                initArea($("#city:first").val());
            }
        });
    }
    function initArea(parentId) {
        $.ajax({
            url: '/query',
            data: {parentId:parentId},
            success: function (msg) {
                var proStr = '';
                for (var i = 0; i < msg.length; i++) {
                    var item = msg[i];
                    proStr += '<option value="' + item.area_id + '">' + item.area_name+ '</option>';
               }
                $("#area").html(proStr);
            }
        });
    }
    initProvince(1);
    $("#province").change(function () {
        initCity($("#province:first").val());
    });
    $("#city").change(function () {
        initArea($("#city:first").val());
    });
</script>
</body>
</html>

2.3.3 树形菜单案例

1.构造数据源

DROP TABLE IF EXISTS `product_category`;

CREATE TABLE `product_category` (

`CATEGORY_ID` int(11) NOT NULLDEFAULT '0',

`PARENT_CATEGORY_ID` int(11) DEFAULT'0',

`CATEGORY_NAME` varchar(255) COLLATEutf8_bin DEFAULT '',

`ACTIVE_STATUS` varchar(4) COLLATEutf8_bin DEFAULT 'Y',

PRIMARY KEY (`CATEGORY_ID`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8COLLATE=utf8_bin;

/*Data for the table `product_category` */

insert into`product_category`(`CATEGORY_ID`,`PARENT_CATEGORY_ID`,`CATEGORY_NAME`,`ACTIVE_STATUS`)values (0,-1,'所有','Y'),(1,0,'图书','Y'),(2,0,'电子设备','Y'),(3,0,'家居建材','Y'),(4,1,'少儿图书','Y'),(5,1,'中学教辅','Y'),(6,1,'文艺书籍','Y'),(7,1,'历史书籍','Y'),(8,2,'手机','Y'),(9,2,'平板','Y'),(10,2,'电脑','Y'),(11,6,'先秦文学','Y'),(12,6,'两汉经学','Y'),(13,6,'魏晋文学','Y');

2.查询数据

public class TreeDao {
    private QueryRunner queryRunner = DBUtils.getQueryRunner();

public List<TreeBean> getData(Integer parentId) {
        try {
           List<TreeBean> list = queryRunner.query("select * from product_category whereparent_category_id=?", new BeanListHandler<TreeBean>(TreeBean.class), parentId);
            for (TreeBean treeBean : list) {
               List<TreeBean> query = queryRunner.query("select * from product_category whereparent_category_id=?", new BeanListHandler<TreeBean>(TreeBean.class), treeBean.getParent_category_id());
                if (query != null && query.size() > 0) {
                   treeBean.setCanExpand(true);
               }
            }
            return list;
        } catch (SQLException e) {
           e.printStackTrace();
        }
        return null;
    }
}

实体类如下:

3.渲染HTML

public class RenderTreeView {
    private StringBuffer result = new StringBuffer();
    private TreeDao treeDao = new TreeDao();

public String render(List<TreeBean> treeBeans) {
        for (TreeBean treeBean : treeBeans) {
            result.append("<li id='" + treeBean.getCategory_id() + "' style=\"list-style-type: none\">");
            if (treeBean.isCanExpand()) {
                result.append("<img src='img/plus.gif'οnclick='itemClick(" + treeBean.getCategory_id() + ");'>");
            } else {
                result.append("<img src='img/blank.gif'>");
            }
            result.append("<img src='img/folder.gif'>");
            result.append("<a>" + treeBean.getCategory_name() + "</a>");
            if (treeBean.isCanExpand()) {
               List<TreeBean> data = treeDao.getData(treeBean.getCategory_id());
                result.append("<ulstyle='padding-left:20px;display:none'>");
               render(data);
                result.append("</ul>");
            }
            result.append("</li>");
        }
        return result.toString();
    }
}

4.创建Servlet

public class TreeServlet extends HttpServlet {
    TreeDao treeDao = new TreeDao();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String parentId = req.getParameter("parentId");
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        List<TreeBean> data = treeDao.getData(Integer.parseInt(parentId));
        RenderTreeView renderTreeView = new RenderTreeView();
        String render =renderTreeView.render(data);
        out.write(render);
        out.flush();
        out.close();
    }
}

Servlet注册:

<servlet>
    <servlet-name>treeServlet</servlet-name>
    <servlet-class>org.sang.TreeServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>treeServlet</servlet-name>
    <url-pattern>/tree</url-pattern>
</servlet-mapping>

5.创建HTML

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>树形菜单</title>
    <script src="js/jquery-3.1.1.min.js"></script>
</head>
<body>
<ul style="padding-left: 0px" id="rootFile">
</ul>
<script>
    $.ajax({
        url: '/tree?parentId=0',
        success: function (msg) {
            $("#rootFile").html(msg);
        }
    });
    function itemClick(id) {
        console.log(id);
        var ele = $("#" + id);
        if (ele.has("ul").length> 0) {
            if (ele.children("ul").css("display") == "none") {
                ele.children("ul").css("display", "block");
                ele.children(":first").attr("src", "img/minus.gif");
            } else {
                ele.children("ul").css("display", "none");
                ele.children(":first").attr("src", "img/plus.gif");
            }
        }
        return false;
    }
</script>
</body>
</html>

3 3.Spring

3.1 Day51

3.1.1 初识Spring

Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当协作的对象构成的。因此,我们说这些对象间存在依赖关系。

Java语言和java平台在架构应用与建立应用方面,提供着丰富的功能。从非常基础的基本数据类型和Class(即定义新类)组成的程序块,到建立具有丰富的特性的应用服务器和web框架都有着很多的方法。一方面,可以通过抽象的显著特性让基础的程序块组成在一起成为一个连贯的整体。这样,构建一个应用(或者多个应用)的工作就可以交给架构师或者开发人员去做。因此,我们就可以清晰的知道哪些业务需要哪些Classes和对象组成,哪些设计模式可以应用在哪些业务上面。 例如:Factory、Abstract Factory、Builder、Decorator 和 Service Locator 这些模式(列举的只是少数)在软件开发行业被普遍认可和肯定(或许这就是为什么这些模式被定型的原因)。这固然是件好事,不过这些模式只是一个有名字的,有说明的,知道最好用在什么地方的,解决应用中什么问题的最佳实践而已。

Spring的IoC控件主要专注于如何利用classes、对象和服务去组成一个企业级应用,通过规范的方式,将各种不同的控件整合成一个完整的应用。Spring中使用了很多被实践证明的最佳实践和正规的设计模式,并且进行了编码实现。如果你是一个,构架师或者开发人员完全可以取出它们集成到你自己的应用之中。这对于那些使用了Spring Framework的组织和机构来说,在spring基础上实现应用不仅可以构建优秀的,可维护的应用并对Spring的设计进行验证,确实是一件好事情。

简化Java开发

Spring是一个开源框架,最早由Rod Johnson创建,并在《Expert One-on-One:J2EE Design and Development》这本著作中进行了介绍。Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。但Spring不仅仅局限于服务器端开发,任何Java应用都能在简单性、可测试性和松耦合等方面从Spring中获益。

bean的各种名称……虽然Spring用bean或者JavaBean来表示应用组件,但并不意味着Spring组

件必须要遵循JavaBean规范。一个Spring组件可以是任何形式的POJOPOJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称

Spring 可以做非常多的事情。但归根结底,支撑Spring的仅仅是少许的基本理念,所有的理念都可以追溯到Spring最根本的使命上:简化Java开发。这是一个郑重的承诺。许多框架都声称在某些方面做了简化,但Spring的目标是致力于全方位的简化Java开发。这势必引出更多的解释,Spring是如何简化Java开发的?

为了降低Java开发的复杂性,Spring采取了以下4种关键策略:

* 基于POJO的轻量级和最小侵入性编程;

* 通过依赖注入和面向接口实现松耦合;

* 基于切面和惯例进行声明式编程;

* 通过切面和模板减少样板式代码。

几乎Spring所做的任何事情都可以追溯到上述的一条或多条策略。

什么是最小侵入式

很多框架通过强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑死。Spring竭力避免因自身的API而弄乱你的应用代码。Spring不会强迫你实现Spring规范的接口或继承Spring规范的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。请参考下面的HelloWorldBean类:

可以看到,这是一个简单普通的Java类——POJO。没有任何地方表明它是一个Spring组件。Spring的非侵入编程模型意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用。

依赖注入

任何一个有实际意义的应用(肯定比Hello World示例更复杂)都会由两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务逻辑。按照传统的做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。比如下面的例子,韦小宝有多重身份:小桂子、白龙使。但他每次出场时只是使用其中一种身份,然后做这种身份该做的事,如下:

我在创建WeiXiaoBao这个类的时候,给了他一个属性叫做小桂子,这样这个韦小宝不管我怎么new都只会做小桂子做的事,显然不合理。更重要的是,这个时候我很难对xiaoGuiZi这种身份进行单独的单元测试。我希望韦小宝能够按照我的需求扮演不同的角色,如下:

改造后的韦小宝如下,DoSth是一个接口,所有的身份都实现了这个接口。这时我不在韦小宝这个类的内部来创建身份类,而是通过构造方法传进来,这就是依赖注入的一种方式,叫做构造器注入。这种方式韦小宝没有和任何身份进行绑定。这就是DI所带来的最大收益——松耦合。如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

应用切面

DI能够让相互协作的软件组件保持松散耦合,而面向切面编程(aspect-oriented programming,((AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。面向切面编程往往被定义为促使软件系统实现关注点的分离一项技术。系统由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它们会跨越系统的多个组件。

AOP能够使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。所造成的结果就是这些组件会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解涉及系统服务所带来复杂性。总之,AOP能够确保POJO的简单性。

如图所示,我们可以把切面想象为覆盖在很多组件之上的一个外壳。应用是由那些实现各自业务功能的模块组成的。借助AOP,可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活地应用到系统中,你的核心应用甚至根本不知道它们的存在。这是一个非常强大的理念,可以将安全、事务和日志关注点与核心业务逻辑相分离。

使用模板消除样板式代码

看一段JDBC代码:

只有中间的查询操作是我们每次要修改的。前面的获取连接,后面的关闭连接都是模板代码,每次重复写这些代码增加了工作量,这里只是举了一种我们常见的模板代码,还有合很多其他的,这些在Spring中都可以通过相应的工具消除。

Spring主要模块

当我们下载Spring发布版本并查看其lib目录时,会发现里面有多个JAR文件。在Spring 4.0中,Spring框架的发布版本包括了20个不同的模块,每个模块会有3个JAR文件(二进制类库、源码的JAR文件以及JavaDoc的JAR文件)。完整的库JAR文件如图所示。

这些模块依据其所属的功能可以划分为6类不同的功能,总体而言,这些模块为开发企业级应用提供了所需的一切。但是你也不必将应用建立在整个Spring框架之上,你可以自由地选择适合自身应用需求的Spring模块;当Spring不能满足需求时,完全可以考虑其他选择。事实上,Spring甚至提供了与其他第三方框架和类库的集成点,这样你就不需要自己编写这样的代码了。如下图:

Spring核心容器

容器是Spring框架最核心的部分,它管理着Spring应用中bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能。基于bean工厂,我们还会发现有多种Spring应用上下文的实现,每一种都提供了配置Spring的不同方式。除了bean工厂和应用上下文,该模块也提供了许多企业服务,例如E-mail、JNDI访问、EJB集成和调度。所有的Spring模块都构建于核心容器之上。当你配置应用时,其实你隐式地使用了这些类。

Spring的AOP模块

在AOP模块中,Spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础。与DI一样,AOP可以帮助应用对象解耦。借助于AOP,可以将遍布系统的关注点(例如事务和安全)从它们所应用的对象中解耦出来。

数据访问与集成

使用JDBC编写代码通常会导致大量的样板式代码,例如获得数据库连接、创建语句、处理结果集到最后关闭数据库连接。Spring的JDBC和DAO(Data Access Object)模块抽象了这些样板式代码,使我们的数据库代码变得简单明了,还可以避免因为关闭数据库资源失败而引发的问题。该模块在多种数据库服务的错误信息之上构建了一个语义丰富的异常层,以后我们再也不需要解释那些隐晦专有的SQL错误信息了!对于那些更喜欢ORM(Object-RelationalMapping)工具而不愿意直接使用JDBC的开发者,Spring提供了ORM模块。Spring的ORM模块建立在对DAO的支持之上,并为多个ORM框架提供了一种构建DAO的简便方式。Spring没有尝试去创建自己的ORM解决方案,而是对许多流行的ORM框架进行了集成,包括Hibernate、Java Persisternce API、Java Data Object和iBATIS SQL Maps。Spring的事务管理支持所有的ORM框架以及JDBC。本模块同样包含了在JMS(JavaMessage Service)之上构建的Spring抽象层,它会使用消息以异步的方式与其他应用集成。从Spring 3.0开始,本模块还包含对象到XML映射的特性,它最初是Spring Web Service项目的一部分。除此之外,本模块会使用SpringAOP模块为Spring应用中的对象提供事务管理服务。

Web与远程调用

MVC(Model-View-Controller)模式是一种普遍被接受的构建Web应用的方法,它可以帮助用户将界面逻辑与应用逻辑分离。Java从来不缺少MVC框架,Apache的Struts、JSF、WebWork和Tapestry都是可选的最流行的MVC框架。虽然Spring能够与多种流行的MVC框架进行集成,但它的Web和远程调用模块自带了一个强大的MVC框架,有助于在Web层提升应用的松耦合水平。除了面向用户的Web应用,该模块还提供了多种构建与其他应用交互的远程调用方案。Spring远程调用功能集成了RMI(RemoteMethod Invocation)、Hessian、Burlap、JAX-WS,同时Spring还自带了一个远程调用框架:HTTP invoker。Spring还提供了暴露和使用REST API的良好支持。

Instrumentation

Spring的Instrumentation模块提供了为JVM添加代理(agent)的功能。具体来讲,它为Tomcat提供了一个织入代理,能够为Tomcat传递类文件,就像这些文件是被类加载器加载的一样。如果这听起来有点难以理解,不必对此过于担心。这个模块所提供的Instrumentation使用场景非常有限,在实际开发中我们很少会用到。

测试

鉴于开发者自测的重要性,Spring提供了测试模块以致力于Spring应用的测试。通过该模块,你会发现Spring为使用JNDI、Servlet和Portlet编写单元测试提供了一系列的mock对象实现。对于集成测试,该模块为加载Spring应用上下文中的bean集合以及与Spring上下文中的bean进行交互提供了支持。

Spring Portfolio

当谈论Spring时,其实它远远超出我们的想象。事实上,Spring远不是Spring框架所下载的那些。如果仅仅停留在核心的Spring框架层面,我们将错过Spring Portfolio所提供的巨额财富。整个Spring Portfolio包括多个构建于核心Spring框架之上的框架和类库。概括地讲,整个SpringPortfolio几乎为每一个领域的Java开发都提供了Spring编程模型。或许需要几卷书才能覆盖Spring Portfolio所提供的所有内容,下面我们介绍Spring Portfolio中的一些项目,同样,我们将体验一下核心框架之外的另一番风景。

Spring Web Flow

Spring Web Flow建立于Spring MVC框架之上,它为基于流程的会话式Web应用(可以想一下购物车或者向导功能)提供了支持。

Spring Security

安全对于许多应用都是一个非常关键的切面。利用Spring AOP,Spring Security为Spring应用提供了声明式的安全机制。

Spring Integration

许多企业级应用都需要与其他应用进行交互。SpringIntegration提供了多种通用应用集成模式的Spring声明式风格实现。

Spring Batch

当我们需要对数据进行大量操作时,没有任何技术可以比批处理更胜任这种场景。如果需要开发一个批处理应用,你可以通过Spring Batch,使用Spring强大的面向POJO的编程模型。

Spring Data

Spring Data使得在Spring中使用任何数据库都变得非常容易。尽管关系型数据库统治企业级应用多年,但是现代化的应用正在认识到并不是所有的数据都适合放在一张表中的行和列中。一种新的数据库种类,通常被称之为NoSQL数据库,提供了使用数据的新方法,这些方法会比传统的关系型数据库更为合适。不管你使用文档数据库,如MongoDB,图数据库,如Neo4j,还是传统的关系型数据库,Spring Data都为持久化提供了一种简单的编程模型。这包括为多种数据库类型提供了一种自动化的Repository机制,它负责为你创建Repository的实现。

Spring Social

社交网络是互联网领域中新兴的一种潮流,越来越多的应用正在融入社交网络网站,例如Facebook或者Twitter。SpringSocial,这是Spring的一个社交网络扩展模块。

Spring Mobile

移动应用是另一个引人瞩目的软件开发领域。智能手机和平板设备已成为许多用户首选的客户端。Spring Mobile是Spring MVC新的扩展模块,用于支持移动Web应用开发。

Spring for Android

与Spring Mobile相关的是Spring Android项目。这个新项目,旨在通过Spring框架为开发基于Android设备的本地应用提供某些简单的支持。最初,这个项目提供了SpringRestTemplate的一个可以用于Android应用之中的版本。它还能与Spring Social协作,使得原生应用可以通过REST API进行社交网络的连接。

Spring Boot

Spring极大地简化了众多的编程任务,减少甚至消除了很多样板式代码,如果没有Spring的话,在日常工作中你不得不编写这样的样板代码。Spring Boot是一个崭新的令人兴奋的项目,它以Spring的视角,致力于简化Spring本身。Spring Boot大量依赖于自动配置技术,它能够消除大部分(在很多场景中,甚至是全部)Spring配置。它还提供了多个Starter项目,不管你使用Maven还是Gradle,这都能减少Spring工程构建文件的大小。

3.1.2 Bean的注入

Spring提供了三种不同的注入方式:

* 在XML中进行显式配置。

* 在Java中进行显式配置。

* 隐式的bean发现机制和自动装配。

XMl配置

使用xml配置来实例化bean共分为三种方式,分别是:

* 普通构造方法创建

* 静态工厂创建

* 实例工厂创建

在这三种创建方式中,最常用的为第一种。

普通构造方法创建

1.创建Spring配置文件

2.创建实体类

3.配置实体类

4.调用

Spring自带了多种类型的应用上下文。下面罗列的几个是你最有可能遇到的。

* AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文

* AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文

* ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源

* FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义

* XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。

现在我们先简单地使用FileSystemXmlApplicationContext从文件系统中加载应用上下文或者使

用ClassPathXmlApplicationContext从类路径中加载应用上下文。无论是从文件系统中装载应用上下文还是从类路径下装载应用上下文,将bean加载到bean工厂的过程都是相似的。

使用FileSystemXmlApplicationContext和使用ClassPathXmlApplicationContext的区别在于:FileSystemXmlApplicationContext在指定的文件系统路径下查找配置文件;而ClassPathXmlApplicationContext是在所有的类路径(包含JAR文件)下查找配置文件。如果你想从Java配置中加载应用上下文,那么可以使用AnnotationConfigApplicationContext:在这里没有指定加载Spring应用上下文所需的XML文件,AnnotationConfigApplicationContext通过一个配置类加载bean。

应用上下文准备就绪之后,我们就可以调用上下文的getBean()方法从Spring容器中获取

bean。

打印结果如下:

静态工厂创建

1.创建静态工厂方法

2.配置xml文件

3.测试:

实例工厂创建

1.创建普通工厂方法

2.xml中配置

先配置工厂实例,然后在这个实例中根据getUser方法来获取User实例。

3.测试

属性注入方式

属性注入方式有多种,最常用的是set方法注入

构造方法注入

1.新建User类如下:

2.在xml中配置时传入构造方法的参数:

set方法注入

1.新建User类如下:

2.xml文件中配置:

p名称空间注入

1.导入p名称空间

2.配置bean

注意:p名称空间也是通过set方法注入属性值的。

对象的注入

1.创建Music类如下:

2.创建播放类如下:

3.我们需要在Play类中注入Music,注入方式如下:

数组注入

1.创建User类如下:

2.xml中配置如下:

List集合注入

1.创建User类如下

注意,我们在List集合中放的是对象哦。

2.xml文件中进行配置

Map注入

1.创建实体类如下:

2.xml配置如下:

Properties注入

1.创建User实体类如下:

2.xml配置如下

Java配置

在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。

1.创建两个bean,如下:

第二个实体类中引用了第一个。

2.创建JavaConfig配置类,如下:

创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解,@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是getSayHelloFunction和getUseSayHelloFunction。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字。

3.测试

自动配置

Spring从两个角度来实现自动化装配:

* 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。

* 自动装配(autowiring):Spring自动满足bean之间的依赖。

组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。

通过Java代码实现

1.创建可被Spring容器发现的Bean

创建可被Spring容器发现的Bean我们有多种注解可用,如下:

@Component注解,没有明确的角色

@Service注解,在业务逻辑层(service层)使用

@Repository注解,在数据访问层(dao层使用)

@Controller注解,在展现层使用

2.将一个Bean注入到另一个Bean中

Bean的注入有三种方式:

@Autowired:Spring提供的注解

@Inject:JSR-330提供的注解

@Resource:JSR-250提供的注解

这三个注解都可以直接用在属性上,也可以用在set方法中。

3.创建Java配置,扫描所有Bean

首先使用@Configuration注解说明这个一个配置类,然后通过@ComponentScan注解说明要扫描的包,这里我们配置了org.sang包,表示凡是这个包以及这个包的子包以及这个包的子包的子包的子包....中,所有声明为Bean的组件都会被注册到。

4.测试

通过XML配置文件实现

基本和Java代码实现一致,唯一不同的是,XML配置文件实现时,不需要Java配置,直接在Spring的配置文件中开启包扫描即可,如下:

混合配置

1.在Java配置中引用XML配置:

Profile

Java配置

1.创建DataSource类

2.创建Java配置文件

3.测试

XML配置

1.创建DataSource

2.创建prod.db.properties和dev.db.properties两个数据库配置文件

3.创建Spring的xml配置

4.测试

条件注解

类似于Profile,但是比Profile更灵活。

1.创建Windows和Linux的判断条件

2.创建命令显示接口及其实现类

3.创建Java配置文件

4.测试

Bean的作用域

单例(Singleton):在整个应用中,只创建bean的一个实例。

原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

会话(Session):在Web应用中,为每个会话创建一个bean实例。

请求(Rquest):在Web应用中,为每个请求创建一个bean实例。

其他问题

(1)id属性:bean的名称,id属性值名称任意命名

* id属性值,不能包含特殊符号

* 根据id值得到配置对象

(2)class属性:创建对象所在类的全路径

(3)name属性:功能和id属性一样的

(4)name和id的细微区别

3.2 Day52

3.2.1 AOP简介

面向切面编程(AOP是AspectOriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。      但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。    也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。一般而言,我们把切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。

3.2.2 AOP几个关键概念

Joinpoint(连接点): 类里面可以被增强的方法,这些方法称为连接点

Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Aspect(切面): 是切入点和通知(引介)的结合

Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

Target(目标对象):代理的目标对象(要增强的类)

Weaving(织入):是把增强应用到目标的过程.把advice 应用到 target的过程

Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类

3.2.3 AOP原理

AOP通过Java中的动态代理来实现那你。

1.创建计算接口

2.创建计算接口实现类

3.创建接口动态代理类

4.测试

3.2.4 AOP实现

通过Java代码实现

使用注解拦截

前置、后置通知

1.创建注解

@Target一行表示该注解将作用在方法上

@Retention一行表示该注解将一直保存到运行时

@Documented表示该注解将保存在javadoc中

2.创建计算类,并添加注解

凡是使用了@Action注解的方法都是一个切点。

3.编写日志类

@Aspect注解表明该类是一个切面,@Pointcut表示定义一个切点。@After表示该切面会在方法执行之后执行,@Before表示该方法会在方法执行之前执行。

4.创建配置类

@EnableAspectJAutoProxy表示开启Spring对AspectJ代理的支持

5.测试

6.结果

返回通知

环绕通知

异常通知

按照方法规则拦截

前置、后置通知

除了按照注解拦截,也可以按照方法的命名规则拦截。

1.定义计算类如下

2.定义切面,按照方法名称拦截处理

3.定义配置类

4.测试

5.测试结果

返回通知

无论连接点正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。在返回通知中,只需要在@AfterReturning注解中添加returning属性,就可以访问连接点的返回值。必须在通知方法的签名中添加一个同名参数,在运行时spring AOP会通过这个参数传递给返回值。

环绕通知

Spring 的环绕通知和前置通知,后置通知有着很大的区别,主要有两个重要的区别:

1.目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知   是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。

2.环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用

异常通知

之前演示的两种案例都属于前置通知和后置通知,除了这两种通知之外,还有异常通知、返回通知以及环绕通知。

只在连接点抛出异常时才执行异常通知,将 throwing 属性添加到 @AfterThrowing 注解中, 也可以访问连接点抛出的异常. Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.

通过XML配置实现

使用注解拦截

按照方法规则拦截

前置通知

1.创建切面

2.创建XML配置文件

返回通知

1.创建方法

2.XML中配置

环绕通知

1.创建方法

2.XML文件中配置

异常通知

1.创建方法

2.XML中配置

后置通知

3.3 Day53

3.3.1 JdbcTemplate

增删改查

1.增

2.删

3.改

4.查

4.1查询总记录数

4.2按条件查询对象

4.3查询列表

配合数据库连接池

1.XML文件中配置bean

2.创建UserDao

3.创建UserService

4.测试

3.3.2 Spring事务管理

回忆事务

原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。

一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。

隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

XML配置实现

1.创建UserDao

2.创建UserService,模拟转账

3.XML配置

3.1配置事务管理器

3.2配置增强/通知

3.3配置AOP

4.测试

Java配置实现

1.创建UserDao

2.创建UserService,模拟转账

3.Java配置

3.1配置事务管理器

3.2配置增强/通知

3.3在需要处理的类上添加注解

4.测试

4 4.SpringMVC基础

4.1 Day54

4.1.1 简介

1.Spring Web MVC是什么

Spring Web MVC是一种基于Java的实现了WebMVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。

Spring Web MVC也是服务到工作者模式的实现,但进行可优化。前端控制器是DispatcherServlet;应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;页面控制器/动作/处理器为Controller接口(仅包含ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的POJO类);支持本地化(Locale)解析、主题(Theme)解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定机制;提供了强大的约定大于配置(惯例优先原则)的契约式编程支持。

2.Spring Web MVC能帮我们做什么

* 让我们能非常简单的设计出干净的Web层和薄薄的Web层;

* 进行更简洁的Web层的开发;

* 天生与Spring框架集成(如IoC容器、AOP等);

* 提供强大的约定大于配置的契约式编程支持;

* 能简单的进行Web层的单元测试;

* 支持灵活的URL到页面控制器的映射;

* 非常容易与其他视图技术集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用);

* 非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API;

* 提供一套强大的JSP标签库,简化JSP开发;

* 支持灵活的本地化、主题等解析;

* 更加简单的异常处理;

* 对静态资源的支持;

* 支持Restful风格。

SpringMVC工作流程

1.工作流程图

2.详述

1.用户发送请求至前端控制器DispatcherServlet

2.DispatcherServlet收到请求调用HandlerMapping处理器映射器

3.处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet

4.DispatcherServlet通过HandlerAdapter处理器适配器调用处理器

5.执行处理器(Controller,也叫后端控制器)

6.Controller执行完成返回ModelAndView

7.HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet

8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器

9.ViewReslover解析后返回具体View

10.DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)

11.DispatcherServlet响应用户

SpringMVC相关组件

1.DispatcherServlet:前端控制器

用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。

2.HandlerMapping:处理器映射器

HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3.Handler:处理器

Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。

4.HandlAdapter:处理器适配器

通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

5.View Resolver:视图解析器

View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

HelloWorld

1.导入相关的jar包

2.web.xml文件中配置SpringMVC

contextConfigLocation:指定springmvc配置的加载位置,如果不指定则默认加载WEB-INF/[DispatcherServlet 的Servlet 名字]-servlet.xml。以上面的配置为例,如果不指定SpringMVC配置文件的位置,将默认加载WEB-INF/springMVC-servlet.xml作为配置文件。

3.在SpringMVC的配置文件中配置HandlerMapping和HandlerAdapter

BeanNameUrlHandlerMapping:表示将请求的URL和Bean名字映射(即Bean的name属性),如URL为"XXX/hello",则Spring配置文件必须有一个名字为"/hello"的Bean,项目名称默认忽略。

SimpleControllerHandlerAdapter:表示所有实现了org.springframework.web.servlet.mvc.Controller接口的Bean可以作为Spring Web MVC中的处理器。如果需要其他类型的处理器可以通过实现HandlerAdapter来解决。

4.配置视图解析器

prefix和suffix:查找视图页面的前缀和后缀(前缀[逻辑视图名]后缀),比如传进来的逻辑视图名为hello,则该该jsp视图页面应该存放在“WEB-INF/jsp/hello.jsp”;

5.编写处理器

*org.springframework.web.servlet.mvc.Controller:页面控制器/处理器必须实现Controller接口。

* public ModelAndViewhandleRequest(HttpServletRequest req, HttpServletResponse resp) :功能处理方法,实现相应的功能处理,比如收集参数、验证参数、绑定参数到命令对象、将命令对象传入业务对象进行业务处理、最后返回ModelAndView对象;

* ModelAndView:包含了视图要实现的模型数据和逻辑视图名;“mv.addObject("users","list");”表示添加模型数据,此处可以是任意POJO对象;“mv.setViewName("userlist");”表示设置逻辑视图名为"userlist",视图解析器会将其解析为具体的视图,用前边的视图解析器InternalResourceViewResolver会将其解析为“WEB-INF/jsp/userlist.jsp”。

然后在SpringMVC的配置文件中注册该bean

注意这里的name="/userList.do"表示当浏览器中的访问地址为/userList.do的时候,就会请求到这个Bean中来。这个主要是由于我们前边配置了BeanNameUrlHandlerMapping这个东西。

6.编写userlist.jsp页面,注意页面位置

7.运行流程

1.首先用户发送请求http://localhost:8080/userList.do到web容器,web容器根据"/userList.do"路径映射到DispatcherServlet(url-pattern为/)进行处理;

2、  DispatcherServlet——>BeanNameUrlHandlerMapping进行请求到处理的映射,BeanNameUrlHandlerMapping将"/userList.do"路径直接映射到名字为"/userList.do"的Bean进行处理,即UserList,BeanNameUrlHandlerMapping将其包装为HandlerExecutionChain(只包括UserList处理器,没有拦截器);

3.DispatcherServlet——>SimpleControllerHandlerAdapter,SimpleControllerHandlerAdapter将HandlerExecutionChain中的处理器(UserList)适配为SimpleControllerHandlerAdapter;

4.SimpleControllerHandlerAdapter——>UserList处理器功能处理方法的调用,SimpleControllerHandlerAdapter将会调用处理器的handleRequest方法进行功能处理,该处理方法返回一个ModelAndView给DispatcherServlet;

5.userlist(ModelAndView的逻辑视图名)——>InternalResourceViewResolver, InternalResourceViewResolver找到具体视图页面在/WEB-INF/jsp/userlist.jsp;

6.JstlView(/WEB-INF/jsp/hello.jsp)——>渲染,将在处理器传入的模型数据(message=HelloWorld!)在视图中展示出来;

7.返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

到此userList.do请求就完成了,步骤是不是有点多?而且回忆下我们主要进行了如下配置:

1.前端控制器DispatcherServlet;

2.HandlerMapping

3.HandlerAdapter

4.ViewResolver

5.处理器/页面控制器

6.视图

4.1.2 组件介绍

DispatcherServlet

1.DispatcherServlet作用

DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:

1.文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;

2.通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);

3.通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);

4.通过ViewResolver解析逻辑视图名到具体视图实现;

5.本地化解析;

6.渲染具体的视图等;

7.如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。

2.DispathcherServlet配置详解

load-on-startup:表示启动容器时初始化该Servlet;

url-pattern:表示哪些请求交给Spring Web MVC处理,"/" 是用来定义默认servlet映射的。也可以如“*.html”表示拦截所有以html为扩展名的请求

contextConfigLocation:表示SpringMVC配置文件的路径

其他的参数配置:

参数

描述

contextClass

实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定, 默认使用XmlWebApplicationContext

contextConfigLocation

传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符) 来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)。

namespace

WebApplicationContext命名空间。默认值是[server-name]-servlet。

OK,以上是关于SpringMVC的配置,我们再来看看Spring的一个通用配置,如下:

如上配置是Spring集成Web环境的通用配置;一般用于加载除Web层的Bean(如DAO、Service等),以便于与其他任何Web框架集成。

* contextConfigLocation:表示用于加载Bean的配置文件;

* contextClass:表示用于加载Bean的ApplicationContext实现类,默认WebApplicationContext。

创建完毕后会将该上下文放在ServletContext:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.context);

ContextLoaderListener初始化的上下文和DispatcherServlet初始化的上下文关系,如下图:

从图中可以看出:

ContextLoaderListener初始化的上下文加载的Bean是对于整个应用程序共享的,不管是使用什么表现层技术,一般如DAO层、Service层Bean;

DispatcherServlet初始化的上下文加载的Bean是只对Spring Web MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,该初始化上下文应该只加载Web相关组件。

Controller

1.@RequestMapping

通过@RequestMapping注解可以定义不同的处理器映射规则。

1.1 URL路径映射

@RequestMapping(value="/item")或@RequestMapping("/item)

value的值是数组,可以将多个url映射到同一个方法

1.2 窄化请求映射

在class上添加@RequestMapping(url)指定通用请求前缀,限制此类下的所有方法请求url必须以请求前缀开头,通过此方法对url进行分类管理。

如下:

@RequestMapping放在类名上边,设置请求前缀

@Controller

@RequestMapping("/item")

方法名上边设置请求映射url:

@RequestMapping放在方法名上边,如下:

@RequestMapping("/queryItem ")

访问地址为:/item/queryItem

1.3 请求方法限定

* 限定GET方法

@RequestMapping(method = RequestMethod.GET)

如果通过Post访问则报错:

HTTP Status 405 - Request method 'POST' notsupported

例如:

@RequestMapping(value="/editItem",method=RequestMethod.GET)

* 限定POST方法

@RequestMapping(method =RequestMethod.POST)

如果通过GET访问则报错:

HTTP Status 405 - Request method 'GET' notsupported

* GET和POST都可以

@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})

2.controller方法返回值

2.1 返回ModelAndView

controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view。

2.2 返回void

在controller方法形参上可以定义request和response,使用request或response指定响应结果:

2.2.1 使用request转向页面,如下:

request.getRequestDispatcher("页面路径").forward(request,response);

2.2.2 也可以通过response页面重定向:

response.sendRedirect("url")

2.2.3 也可以通过response指定响应结果,例如响应json数据如下:

response.setCharacterEncoding("utf-8");

response.setContentType("application/json;charset=utf-8");

response.getWriter().write("json串");

2.3 返回字符串

2.3.1 逻辑视图名

controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。

//指定逻辑视图名,经过视图解析器解析为jsp物理路径:/WEB-INF/jsp/item/editItem.jsp

return "item/editItem";

2.3.2 Redirect重定向

Contrller方法返回结果重定向到一个url地址,如下商品修改提交后重定向到商品查询方法,参数无法带到商品查询方法中。

//重定向到queryItem.action地址,request无法带过去

return"redirect:queryItem.action";

redirect方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的request和response。

由于新发起一个request原来的参数在转发时就不能传递到下一个url,如果要传参数可以/item/queryItem.action后边加参数,如下:

/item/queryItem?...&…..

2.3.3 forward转发

controller方法执行后继续执行另一个controller方法,如下商品修改提交后转向到商品修改页面,修改商品的id参数可以带到商品修改方法中。

//结果转发到editItem.action,request可以带过去

return "forward:editItem.action";

forward方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。

3.参数绑定

处理器适配器在执行Handler之前需要把http请求的key/value数据绑定到Handler方法形参数上。

3.1 默认支持的参数类型

处理器形参中添加如下类型的参数处理适配器会默认识别并进行赋值。

3.1.1 HttpServletRequest

通过request对象获取请求信息

3.1.2 HttpServletResponse

通过response处理响应信息

3.1.3 HttpSession

通过session对象得到session中存放的对象

3.1.4 Model/ModelMap

ModelMap是Model接口的实现类,通过Model或ModelMap向页面传递数据,如下:

//调用service查询商品信息

Items item = itemService.findItemById(id);

model.addAttribute("item", item);

页面通过${item.XXXX}获取item对象的属性值。

使用Model和ModelMap的效果一样,如果直接使用Model,springmvc会实例化ModelMap。

3.2 参数绑定介绍

注解适配器对RequestMapping标记的方法进行适配,对方法中的形参会进行参数绑定,早期springmvc采用PropertyEditor(属性编辑器)进行参数绑定将request请求的参数绑定到方法形参上,3.X之后springmvc就开始使用Converter进行参数绑定。

3.2.1 简单类型

当请求的参数名称和处理器形参名称一致时会将请求参数与形参进行绑定。

3.2.2 整型

public String editItem(Model model,Integerid) throws Exception{

3.2.3 字符串、单精度/双精度

3.2.4 布尔型

处理器方法:

public String editItem(Model model,Integerid,Boolean status) throws Exception

3.2.5 @RequestParam

使用@RequestParam常用于处理简单类型的绑定。

value:参数名字,即入参的请求参数名字,如value=“item_id”表示请求的参数区中的名字为item_id的参数的值将传入;

required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报;

TTP Status 400 - Required Integer parameter'XXXX' is not present

defaultValue:默认值,表示如果请求中没有同名参数时的默认值

定义如下:

public StringeditItem(@RequestParam(value="item_id",required=true) String id) {

}

形参名称为id,但是这里使用value=" item_id"限定请求的参数名为item_id,所以页面传递参数的名必须为item_id。

注意:如果请求参数中没有item_id将跑出异常:

HTTP Status 500 - Required Integer parameter'item_id' is not present

这里通过required=true限定item_id参数为必需传递,如果不传递则报400错误,可以使用defaultvalue设置默认值,即使required=true也可以不传item_id参数值

3.3 pojo

3.3.1 简单pojo

将pojo对象中的属性名与传递进来的属性名对应,如果传进来的参数名称和对象中的属性名称一致则将参数值设置在pojo对象中

页面定义如下;

<input type="text"name="name"/>

<input type="text"name="price"/>

Contrller方法定义如下:

@RequestMapping("/editItemSubmit")

public StringeditItemSubmit(Items items)throws Exception{

System.out.println(items);

请求的参数名称和pojo的属性名称一致,会自动将请求参数赋值给pojo的属性。

3.3.2 包装pojo

如果采用类似struts中对象.属性的方式命名,需要将pojo对象作为一个包装对象的属性,action中以该包装对象作为形参。

包装对象定义如下:

Public class QueryVo {

private Items items;

}

页面定义:

<input type="text"name="items.name" />

<input type="text"name="items.price" />

Controller方法定义如下:

public String useraddsubmit(Modelmodel,QueryVo queryVo)throws Exception{

System.out.println(queryVo.getItems());

3.4 自定义参数绑定

3.4.1 需求

根据业务需求自定义日期格式进行参数绑定。

自定义Converter

public class CustomDateConverter implementsConverter<String, Date> {

@Override

public Date convert(Stringsource) {

try {

SimpleDateFormatsimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

returnsimpleDateFormat.parse(source);

} catch(Exception e) {

e.printStackTrace();

}

return null;

}

}

配置方式1

<mvc:annotation-drivenconversion-service="conversionService">

</mvc:annotation-driven>

<!-- conversionService -->

<beanid="conversionService"

class="org.springframework.format.support.FormattingConversionServiceFactoryBean">

<!-- 转换器 -->

<propertyname="converters">

<list>

<beanclass="cn.sang.controller.converter.CustomDateConverter"/>

</list>

</property>

</bean>

配置方式2

<!--注解适配器 -->

<bean

class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">

<propertyname="webBindingInitializer"ref="customBinder"></property>

</bean>

<!-- 自定义webBinder -->

<beanid="customBinder"

class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">

<propertyname="conversionService" ref="conversionService" />

</bean>

<!-- conversionService-->

<beanid="conversionService"

class="org.springframework.format.support.FormattingConversionServiceFactoryBean">

<!-- 转换器 -->

<property name="converters">

<list>

<beanclass="cn.itcast.ssm.controller.converter.CustomDateConverter"/>

</list>

</property>

</bean>

3.5 集合类

3.5.1 字符串数组

页面定义如下:

页面选中多个checkbox向controller方法传递

<input type="checkbox"name="item_id" value="001"/>

<input type="checkbox"name="item_id" value="002"/>

<input type="checkbox"name="item_id" value="002"/>

传递到controller方法中的格式是:001,002,003

Controller方法中可以用String[]接收,定义如下:

public String deleteitem(String[]item_id)throws Exception{

System.out.println(item_id);

3.5.2 List

List中存放对象,并将定义的List放在包装类中,action使用包装对象接收。

List中对象:

成绩对象

Public class QueryVo {

Private List<Items> itemList;//商品列表

//get/set方法..

}

包装类中定义List对象,并添加get/set方法如下:

页面定义如下:

<tr>

<td>

<input type="text" name="itemsList[0].id" value="${item.id}"/>

</td>

<td>

<input type="text" name="itemsList[0].name" value="${item.name }"/>

</td>

<td>

<input type="text" name="itemsList[0].price" value="${item.price}"/>

</td>

</tr>

<tr>

<td>

<input type="text" name="itemsList[1].id" value="${item.id}"/>

</td>

<td>

<input type="text" name="itemsList[1].name" value="${item.name }"/>

</td>

<td>

<input type="text" name="itemsList[1].price" value="${item.price}"/>

</td>

</tr>

上边的静态代码改为动态jsp代码如下:

<c:forEach items="${itemsList}" var="item" varStatus="s">

<tr>

<td><inputtype="text" name="itemsList[${s.index }].name"value="${item.name }"/></td>

<td><inputtype="text" name="itemsList[${s.index }].price"value="${item.price }"/></td>

.....

.....

</tr>

</c:forEach>

Contrller方法定义如下:

public String useraddsubmit(Modelmodel,QueryVo queryVo)throws Exception{

System.out.println(queryVo.getItemList());

}

3.5.3 Map

在包装类中定义Map对象,并添加get/set方法,action使用包装对象接收。

包装类中定义Map对象如下:

Public class QueryVo {

private Map<String, Object> itemInfo= new HashMap<String, Object>();

//get/set方法..

}

页面定义如下:

<tr>

<td>学生信息:</td>

<td>

姓名:<inputtype="text"name="itemInfo['name']"/>

年龄:<inputtype="text"name="itemInfo['price']"/>

.. .. ..

</td>

</tr>

Contrller方法定义如下:

public String useraddsubmit(Model model,QueryVoqueryVo)throws Exception{

System.out.println(queryVo.getStudentinfo());

}

4. 问题总结

4.1  404

页面找不到,视图找不到。

HandlerMapping根据url没有找到Handler。

4.2 Post时中文乱码

在web.xml中加入:

<filter>

<filter-name>CharacterEncodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>utf-8</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>CharacterEncodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

以上可以解决post请求乱码问题。

对于get请求中文参数出现乱码解决方法有两个:

* 修改tomcat配置文件添加编码与工程编码一致,如下:

<Connector URIEncoding="utf-8"connectionTimeout="20000" port="8080"protocol="HTTP/1.1" redirectPort="8443"/>

* 另外一种方法对参数进行重新编码:

String userName new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8"),ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码

@RequestMapping

通过@RequestMapping注解可以定义不同的处理器映射规则。

URL路径映射

@RequestMapping(value="/item")或@RequestMapping("/item)

value的值是数组,可以将多个url映射到同一个方法

窄化请求映射

在class上添加@RequestMapping(url)指定通用请求前缀,限制此类下的所有方法请求url必须以请求前缀开头,通过此方法对url进行分类管理。

如下:

@RequestMapping放在类名上边,设置请求前缀

@Controller

@RequestMapping("/item")

方法名上边设置请求映射url:

@RequestMapping放在方法名上边,如下:

@RequestMapping("/queryItem ")

访问地址为:/item/queryItem

请求方法限定

* 限定GET方法

@RequestMapping(method = RequestMethod.GET)

如果通过Post访问则报错:

例如:

@RequestMapping(value="/editItem",method=RequestMethod.GET)

* 限定POST方法

@RequestMapping(method =RequestMethod.POST)

如果通过GET访问则报错:

* GET和POST都可以

@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})

Controller方法返回值

返回ModelAndView

Controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view。

返回void

在Controller方法形参上可以定义request和response,使用request或response指定响应结果

使用request转向页面

request.getRequestDispatcher("页面路径").forward(request,response);

通过response页面重定向

response.sendRedirect("url")

通过response指定响应结果

response.setCharacterEncoding("utf-8");

response.setContentType("application/json;charset=utf-8");

response.getWriter().write("json串");

返回字符串

逻辑视图名

controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。

如指定逻辑视图名,经过视图解析器解析为jsp物理路径:/WEB-INF/jsp/item/editItem.jsp

return"item/editItem";

Redirect重定向

Contrller方法返回结果重定向到一个url地址,如下商品修改提交后重定向到商品查询方法,参数无法带到商品查询方法中。

如下:重定向到queryItem.action地址,request无法带过去

return "redirect:queryItem.action";

redirect方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的request和response。

由于新发起一个request原来的参数在转发时就不能传递到下一个url,如果要传参数可以/item/queryItem.action后边加参数,如下:

/item/queryItem?...&…..

forward转发

controller方法执行后继续执行另一个controller方法,如下商品修改提交后转向到商品修改页面,修改商品的id参数可以带到商品修改方法中。

如下:结果转发到editItem.action,request可以带过去

return "forward:editItem.action";

forward方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。

参数绑定

处理器适配器在执行Handler之前需要把http请求的key/value数据绑定到Handler方法形参数上。

默认支持的参数类型

处理器形参中添加如下类型的参数处理适配器会默认识别并进行赋值。

1.HttpServletRequest

通过request对象获取请求信息

2.HttpServletResponse

通过response处理响应信息

3.HttpSession

通过session对象得到session中存放的对象

4.Model/ModelMap

ModelMap是Model接口的实现类,通过Model或ModelMap向页面传递数据,如下:

//调用service查询商品信息

Items item = itemService.findItemById(id);

model.addAttribute("item", item);

页面通过${item.XXXX}获取item对象的属性值。

使用Model和ModelMap的效果一样,如果直接使用Model,springmvc会实例化ModelMap。

简单数据类型

注解适配器对RequestMapping标记的方法进行适配,对方法中的形参会进行参数绑定,早期springmvc采用PropertyEditor(属性编辑器)进行参数绑定将request请求的参数绑定到方法形参上,3.X之后springmvc就开始使用Converter进行参数绑定。

简单类型

当请求的参数名称和处理器形参名称一致时会将请求参数与形参进行绑定。

1.整型

public String editItem(Model model,Integerid) throws Exception{

2.字符串、单精度/双精度

3.布尔型

处理器方法:

public StringeditItem(Model model,Integer id,Boolean status) throws Exception

@RequestParam

使用@RequestParam常用于处理简单类型的绑定。

value:参数名字,即入参的请求参数名字,如value=“item_id”表示请求的参数区中的名字为item_id的参数的值将传入;

required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报;

TTP Status 400 - Required Integer parameter'XXXX' is not present

defaultValue:默认值,表示如果请求中没有同名参数时的默认值

定义如下:

public StringeditItem(@RequestParam(value="item_id",required=true) String id) {

}

形参名称为id,但是这里使用value=" item_id"限定请求的参数名为item_id,所以页面传递参数的名必须为item_id。

注意:如果请求参数中没有item_id将跑出异常:

HTTP Status 500 - Required Integerparameter 'item_id' is not present

这里通过required=true限定item_id参数为必需传递,如果不传递则报400错误,可以使用defaultvalue设置默认值,即使required=true也可以不传item_id参数值

POJO

简单pojo

将pojo对象中的属性名与传递进来的属性名对应,如果传进来的参数名称和对象中的属性名称一致则将参数值设置在pojo对象中

页面定义如下;

<input type="text"name="name"/>

<input type="text"name="price"/>

Contrller方法定义如下:

@RequestMapping("/editItemSubmit")

public StringeditItemSubmit(Items items)throws Exception{

System.out.println(items);

请求的参数名称和pojo的属性名称一致,会自动将请求参数赋值给pojo的属性。

包装pojo

如果采用类似struts中对象.属性的方式命名,需要将pojo对象作为一个包装对象的属性,action中以该包装对象作为形参。

包装对象定义如下:

public class MBook {
    private Book book;

public void setBook(Book book) {
        this.book= book;
    }

public Book getBook() {
        return book;
    }
}

页面定义:

<input type="text"name="book.name" />

<input type="text"name="book.price" />

Controller方法定义如下:

@RequestMapping("/t4")
@ResponseBody
public void t4(MBook book) {
    System.out.println(book.getBook());
}

自定义参数绑定

根据业务需求自定义日期格式进行参数绑定。

1.自定义Converter

public class CustomDateConverter implementsConverter<String, Date> {

@Override

public Date convert(Stringsource) {

try {

SimpleDateFormatsimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

returnsimpleDateFormat.parse(source);

} catch(Exception e) {

e.printStackTrace();

}

return null;

}

}

2.配置方式1

3.配置方式2

集合类

字符串数组

页面定义如下:

页面选中多个checkbox向controller方法传递

<input type="checkbox"name="item_id" value="001"/>

<input type="checkbox"name="item_id" value="002"/>

<input type="checkbox"name="item_id" value="002"/>

传递到controller方法中的格式是:001,002,003

Controller方法中可以用String[]接收,定义如下:

public String deleteitem(String[] item_id)throwsException{

System.out.println(item_id);

List

List中存放对象,并将定义的List放在包装类中,action使用包装对象接收。

List中对象:

成绩对象

Public class QueryVo {

Private List<Items> itemList;//商品列表

//get/set方法..

}

包装类中定义List对象,并添加get/set方法如下:

页面定义如下:

<tr>

<td>

<input type="text" name="itemsList[0].id" value="${item.id}"/>

</td>

<td>

<input type="text" name="itemsList[0].name" value="${item.name }"/>

</td>

<td>

<input type="text" name="itemsList[0].price" value="${item.price}"/>

</td>

</tr>

<tr>

<td>

<input type="text" name="itemsList[1].id" value="${item.id}"/>

</td>

<td>

<input type="text" name="itemsList[1].name" value="${item.name }"/>

</td>

<td>

<input type="text" name="itemsList[1].price" value="${item.price}"/>

</td>

</tr>

上边的静态代码改为动态jsp代码如下:

<c:forEach items="${itemsList}" var="item" varStatus="s">

<tr>

<td><inputtype="text" name="itemsList[${s.index }].name"value="${item.name }"/></td>

<td><inputtype="text" name="itemsList[${s.index }].price"value="${item.price }"/></td>

.....

.....

</tr>

</c:forEach>

Contrller方法定义如下:

public String useraddsubmit(Modelmodel,QueryVo queryVo)throws Exception{

System.out.println(queryVo.getItemList());

}

Map

在包装类中定义Map对象,并添加get/set方法,action使用包装对象接收。

包装类中定义Map对象如下:

Public class QueryVo {

private Map<String, Object> itemInfo= new HashMap<String, Object>();

//get/set方法..

}

页面定义如下:

<tr>

<td>学生信息:</td>

<td>

姓名:<inputtype="text"name="itemInfo['name']"/>

年龄:<inputtype="text"name="itemInfo['price']"/>

.. .. ..

</td>

</tr>

Contrller方法定义如下:

public String useraddsubmit(Modelmodel,QueryVo queryVo)throws Exception{

System.out.println(queryVo.getStudentinfo());

}

问题总结

404

页面找不到,视图找不到。

HandlerMapping根据url没有找到Handler。

Post时中文乱码

在web.xml中加入:

<filter>

<filter-name>CharacterEncodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>utf-8</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>CharacterEncodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

以上可以解决post请求乱码问题。

对于get请求中文参数出现乱码解决方法有两个:

* 修改tomcat配置文件添加编码与工程编码一致,如下:

<Connector URIEncoding="utf-8"connectionTimeout="20000" port="8080"protocol="HTTP/1.1" redirectPort="8443"/>

* 另外一种方法对参数进行重新编码:

String userName newString(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8"),ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码

其他组件

1.HandlerMapping处理器映射器

HandlerMapping负责根据request请求找到对应的Handler处理器及Interceptor拦截器,将它们封装在HandlerExecutionChain 对象中返回给前端控制器。

2.BeanNameUrlHandlerMapping

BeanNameUrl处理器映射器,根据请求的url与spring容器中定义的bean的name进行匹配,从而从spring容器中找到bean实例。

<beanclass="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

2.1.SimpleUrlHandlerMapping

SimpleUrlHandlerMapping是BeanNameUrlHandlerMapping的增强版本,它可以将url和处理器bean的id进行统一映射配置。

<beanclass="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<propertyname="mappings">

<props>

<propkey="/u1.do">controller的bean id</prop>

<propkey="/u2.do">controller的bean id</prop>

</props>

</property>

</bean>

3.HandlerAdapter处理器适配器

HandlerAdapter会根据适配器接口对后端控制器进行包装(适配),包装后即可对处理器进行执行,通过扩展处理器适配器可以执行多种类型的处理器,这里使用了适配器设计模式。

3.1.SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter简单控制器处理器适配器,所有实现了org.springframework.web.servlet.mvc.Controller接口的Bean通过此适配器进行适配、执行。

适配器配置如下:

<beanclass="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

3.2.HttpRequestHandlerAdapter

HttpRequestHandlerAdapter,http请求处理器适配器,所有实现了org.springframework.web.HttpRequestHandler 接口的Bean通过此适配器进行适配、执行。

适配器配置如下:

<beanclass="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>

4.注解映射器和适配器

4.1.组件扫描器

使用组件扫描器省去在spring容器配置每个controller类的繁琐。使用context:component-scan自动扫描标记@controller的控制器类,配置如下<!-- 扫描controller注解,多个包中间使用半角逗号分隔 -->

<context:component-scanbase-package="org.sang"/>

4.2.RequestMappingHandlerMapping

注解式处理器映射器,对类中标记@ResquestMapping的方法进行映射,根据ResquestMapping定义的url匹配ResquestMapping标记的方法,匹配成功返回HandlerMethod对象给前端控制器,HandlerMethod对象中封装url对应的方法Method。

配置如下:

<!--注解映射器 -->

<beanclass="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

注解描述:

@RequestMapping:定义请求url到处理器功能方法的映射

4.3.RequestMappingHandlerAdapter

注解式处理器适配器,对标记@ResquestMapping的方法进行适配。

配置如下:

<!--注解适配器 -->

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

4.4.<mvc:annotation-driven>

springmvc使用<mvc:annotation-driven>自动加载RequestMappingHandlerMapping和RequestMappingHandlerAdapter,可用在springmvc.xml配置文件中使用<mvc:annotation-driven>替代注解处理器和适配器的配置。

5 5.SpringMVC高级应用

5.1 服务端校验

5.1.1 普通校验

b/s系统中对http请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺少的,Spring支持JSR-303验证框架,JSR-303是JAVA EE 6 中的一项子规范,叫做BeanValidation,官方参考实现是Hibernate Validator(与Hibernate ORM 没有关系),JSR 303 用于对Java Bean 中的字段的值进行验证。

使用步骤:

1.添加相关jar

2.配置validator

3.将validator加到处理器适配器

4.添加验证规则

5.编写错误信息文件MyValidationMessage.properties

6.捕获错误

添加@Validated表示在对book参数绑定时进行校验,校验信息写入BindingResult中,在要校验的pojo后边添加BingdingResult, 一个BindingResult对应一个pojo,且BingdingResult放在pojo的后边。

7.编辑页面

5.1.2 分组校验

如果两处校验使用同一个Book类则可以设定校验分组,通过分组校验可以对每处的校验个性化。比如我在Book类中设置了两个校验条件,但是有的时候我只需要其中一个校验即可,这个时候就可以使用分组校验。

1.定义分组

2.指定分组校验

3.定义Controller

在@Validated中添加value={ValidGroup1.class}表示商品修改使用了ValidGroup1分组校验规则,也可以指定多个分组中间用逗号分隔,@Validated(value={ValidGroup1.class,ValidGroup2.class})

5.1.3 校验注解

其他一些常见校验注解如下:

@Null 被注解的元素必须为 null

@NotNull 被注解的元素必须不为 null

@AssertTrue 被注解的元素必须为 true

@AssertFalse 被注解的元素必须为 false

@Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值

@DecimalMax(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max=, min=) 被注解的元素的大小必须在指定的范围内

@Digits (integer, fraction) 被注解的元素必须是一个数字,其值必须在可接受的范围内

@Past 被注解的元素必须是一个过去的日期

@Future 被注解的元素必须是一个将来的日期

@Pattern(regex=,flag=) 被注解的元素必须符合指定的正则表达式

@NotBlank(message =) 验证字符串非null,且长度必须大于0

@Email 被注解的元素必须是电子邮箱地址

@Length(min=,max=) 被注解的字符串的大小必须在指定的范围内

@NotEmpty 被注解的字符串的必须非空

@Range(min=,max=,message=)被注解的元素必须在合适的范围内

5.2 数据回显

表单提交失败需要再回到表单页面重新填写,原来提交的数据需要重新在页面上显示。

5.2.1 默认方式

对于简单数据类型,如:Integer、String、Float等使用Model将传入的参数再放到request域实现显示。

如下:

@RequestMapping(value="/editItems",method={RequestMethod.GET})

public String editItems(Modelmodel,String haha)throws Exception{

//传入的id重新放到request域

model.addAttribute("haha",haha);

jsp页面相关字段如下:

5.2.2 Model方式

SpringMVC默认支持pojo数据回显,SpringMVC自动将形参中的pojo重新放回request域中,request的key为pojo的类名(首字母小写),如下:

Controller方法:

相当于调用了

model.addAttribute("book", book);

然后jsp页面显示如下:

5.2.3 @ModelAttribute

解决数据回显问题

如果key不是pojo的类名(首字母小写),可以使用@ModelAttribute完成数据回显。@ModelAttribute可以绑定请求参数到pojo并且暴露为模型数据传到视图页面,如下:

这个相当于 model.addAttribute("b",book);

方法返回值暴露为模型数据

被@ModelAttribute注解的方法表示这个方法的目的是增加一个或多个模型(model)属性。这个方法和被@RequestMapping注解的方法一样也支持@RequestParam参数,但是它不能直接被请求映射。实际上,控制器中的@ModelAttribute方法是在同一控制器中的@RequestMapping方法被调用之前调用的。被@ModelAttribute注解的方法用于填充model属性,例如,为下拉菜单填充内容,或检索一个command对象(如,Account),用它来表示一个HTML表单中的数据。一个控制器可以有任意数量的@ModelAttribute方法。所有这些方法都在@RequestMapping方法被调用之前调用。有两种类型的@ModelAttribute方法。

JSP 页面如下:

5.3 异常处理

SpringMVC在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。

1.异常处理思路

系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。系统的dao、service、controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理,如下图:

2.自定义异常类

3.自定义异常处理器

4.错误页面

5.SpringMVC配置文件中配置异常处理器

6.测试

5.4 文件上传

1.引入jar包

2.配置解析器

3.Controller

4.文件上传页面

5.5 JSON数据交互

5.5.1 @RequestBody

@RequestBody注解用于读取http请求的内容(字符串),通过SpringMVC提供的HttpMessageConverter接口将读到的内容转换为json、xml等格式的数据并绑定到controller方法的参数上。如下案例:

如果客户端的数据不是以键值对的形式上传到服务端,则可以使用这种方式来接收。

客户端测试如下:

data的值会自动解析为字符串然后赋值给Controller中的req1的方法user上。

5.5.2 @ResponseBody

该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端。

使用方式很简单,想要转为json数据直接导入如下jar包:

当然也可以画蛇添足的增加如下配置:

没什么卵用,因为默认就是如此。

正常情况下,添加完jar包之后,直接写Controller中的方法,如下:

这样返回的就是json对象。

5.6 RESTful支持

5.6.1 RESTful简介

越来越多的人开始意识到,网站即软件,而且是一种新型的软件。这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。

RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

但是,到底什么是RESTful架构,并不是一个容易说清楚的问题。下面,我就谈谈我理解的RESTful架构。

一、起源

REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。

Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。

他这样介绍论文的写作目的:

"本文研究计算机科学两大前沿----软件和网络----的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。"

二、名称

Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。

如果一个架构符合REST原则,就称它为RESTful架构。

要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。

三、资源(Resources)

REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。

所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。

四、表现层(Representation)

"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。

比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。

URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

五、状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(StateTransfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

六、综述

综合上面的解释,我们总结一下什么是RESTful架构:

  (1)每一个URI代表一种资源;

  (2)客户端和服务器之间,传递这种资源的某种表现层;

  (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

七、误区

RESTful架构有一些典型的设计误区。

最常见的一种设计错误,就是URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。

举例来说,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。

如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户1向账户2汇款500元,错误的URI是:

  POST /accounts/1/transfer/500/to/2

正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:

  POST /transaction HTTP/1.1

  Host: 127.0.0.1

  from=1&to=2&amount=500.00

另一个设计误区,就是在URI中加入版本号:

  http://www.example.com/app/1.0/foo

  http://www.example.com/app/1.1/foo

  http://www.example.com/app/2.0/foo

因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在HTTP请求头信息的Accept字段中进行区分(参见Versioning REST Services):

  Accept: vnd.example-com.foo+json;version=1.0

  Accept: vnd.example-com.foo+json;version=1.1

  Accept: vnd.example-com.foo+json;version=2.0

5.6.2 SpringMVC对RESTful的支持

@RequestMapping(value="/viewItems/{id}"):{×××}占位符,请求的URL可以是“/viewItems/1”或“/viewItems/2”,通过在方法中使用@PathVariable获取{×××}中的×××变量。@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。如果RequestMapping中表示为"/viewItems/{id}",id和形参名称一致,@PathVariable不用指定名称。

5.7 静态资源访问

如果在DispatcherServlet中设置url-pattern为 /则必须对静态资源进行访问处理。spring mvc 的<mvc:resourcesmapping="" location="">实现对静态资源进行映射访问。

如下是对js文件访问配置:

<mvc:resources location="/js/"mapping="/js/**"/>

5.8 拦截器

Spring Web MVC 的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理。

1.创建拦截器

2.配置拦截器

3.拦截器拦截规则

preHandle按拦截器定义顺序调用

postHandler按拦截器定义逆序调用

afterCompletion按拦截器定义逆序调用

postHandler在拦截器链内所有拦截器返成功调用

afterCompletion只有preHandle返回true才调用

6 6.freemarker

6.1 Freemarker

6.1.1 Freemarker介绍

Freemarker简介

FreeMarker是一个基于Java的模板引擎,最初专注于使用MVC软件架构生成动态网页。但是,它是一个通用的模板引擎,不依赖于servlets或HTTP或HTML,因此它通常用于生成源代码,配置文件或电子邮件。FreeMarker是自由软件。

使用引入

1.引入freemarker相关jar

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.23</version>
</dependency>

2.创建freemarker变量文件

resoures目录下创建文件,如下:

文件内容如下:

root=m3

3.spring-mvc配置文件中配置freemarker

配置分三步:

3.1引入变量文件

<bean id="freemarkerProperties"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:freemarker-variable.properties</value>
        </list>
    </property>
</bean>

3.2freemarker配置

<bean id="freemarkerConfigurer"class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/"/>
    <property name="defaultEncoding" value="UTF-8"/>
    <property name="freemarkerSettings">
        <props>
            <!--用来指定更新模版文件的间隔时间.0表示每次都重新加载,否则为多少秒检查是否更新-->
            
<prop key="template_update_delay">10</prop>
            <prop key="locale">zh_CN</prop>
            <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
            <prop key="date_format">yyyy-MM-dd</prop>
            <prop key="time_format">HH:mm:ss</prop>
            <prop key="number_format">#.####</prop>
        </props>
    </property>
    <property name="freemarkerVariables">
        <map>
            <entry key="root" value="${root}"/>
        </map>
    </property>
</bean>

3.3视图解析器配置

<!--freemarker等非JSP模板没有request、session等概念,只有模型的概念-->
<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="contentType" value="text/html;charset=UTF-8"/>
    <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/>
    <property name="suffix" value=".ftl"/>
    <property name="cache" value="true"/>
    <!--设置是否所有的session属性在与模板进行合并之前添加到model中-->
    
<property name="exposeSessionAttributes" value="true"/>
    <!--设置是否所有的request属性在与模板进行合并之前添加到model中-->
    
<property name="exposeRequestAttributes" value="true"/>
    <!--allowSessionOverride表示是否允许session属性覆盖模型数据,默认是false,即不允许,如果重名则抛异常-->
    
<property name="allowSessionOverride" value="true"/>
    <!--在JSP和Freemarker的配置项中都有一个order property,上面例子是把freemarker的order设置为0,jsp为1,意思是找view时,先找ftl文件,再找jsp文件做为视图。这样Freemarker视图解析器就能与JSP视图解析器并存。-->
    
<property name="order" value="0" />
</bean>

6.1.2 ftl基本语法

插值规则

通用插值

对于通用插值,又可以分为以下4种情况:

1,插值结果为字符串值:直接输出表达式结果

2,插值结果为数字值:根据默认格式(由#setting指令设置)将表达式结果转换成文本输出.可以使用内建的字符串函数格式化单个插值

3,插值结果为日期值:根据默认格式(由#setting指令设置)将表达式结果转换成文本输出.可以使用内建的字符串函数格式化单个插值

4,插值结果为布尔值:根据默认格式(由#setting指令设置)将表达式结果转换成文本输出.可以使用内建的字符串函数格式化单个插值

代码如下:

${user}<br/>
<#setting number_format="currency"/>
<#assign foo
=true/>
${foo
?string("yes", "no")}<br/>
<#assign answer=42/>
<#assign lastupdate
=2011-01-01/>
${answer}
<br/>
${answer?string} <br/><#-- the same as ${answer} -->
${answer?string.number}<br/>
${answer?string.currency}<br/>
${answer?string.percent}<br/>
${answer}<br/>
${d?string("yyyy-MM-dd HH:mm:ss")}<br/>

显示效果如下:

数字格式化插值

数字格式化插值可采用#{expr;format}形式来格式化数字,其中format可以是:

mX:小数部分最小X位

MX:小数部分最大X位

如下面的例子:

<#assign x=2.582/><br/>
<#assign y=4/><br/>
#{x; M2} <#-- 输出2.58 --><br/>
#{y; M2} <#-- 输出4 --><br/>
#{x; m1} <#-- 输出2.6 --><br/>
#{y; m2} <#-- 输出4.00 --><br/>
#{x; m1M2} <#-- 输出2.58 --><br/>
#{y; m1M2} <#-- 输出4.0 --><br/>

表达式

直接指定值

使用直接指定值语法让FreeMarker直接输出插值中的值,而不是输出变量值.直接指定值可以是字符串,数值,布尔值,集合和MAP对象.

1,字符串

直接指定字符串值使用单引号或双引号限定,如果字符串值中包含特殊字符需要转义,看下面的例子:

${"我的文件保存在C:\\盘"}

${'我名字是\"annlee\"'}

输出结果是:

我的文件保存在C:\盘

我名字是"annlee"

FreeMarker支持如下转义字符:

\";双引号(u0022)

\';单引号(u0027)

\\;反斜杠(u005C)

\n;换行(u000A)

\r;回车(u000D)

\t;Tab(u0009)

\b;退格键(u0008)

\f;Form feed(u000C)

\l;<

\g;>

\a;&

\{;{

\xCode;直接通过4位的16进制数来指定Unicode码,输出该unicode码对应的字符.

如果某段文本中包含大量的特殊符号,FreeMarker提供了另一种特殊格式:可以在指定字符串内容的引号前增加r标记,在r标记后的文件将会直接输出.看如下代码:

${r"${foo}"}

${r"C:\foo\bar"}

输出结果是:

${foo}

C:\foo\bar

2,数值

表达式中的数值直接输出,不需要引号.小数点使用"."分隔,不能使用分组","符号.FreeMarker目前还不支持科学计数法,所以"1E3"是错误的.在FreeMarker表达式中使用数值需要注意以下几点:

1,数值不能省略小数点前面的0,所以".5"是错误的写法

2,数值8 , +8 , 8.00都是相同的

3,布尔值

直接使用true和false,不使用引号.

4,集合

集合以方括号包括,各集合元素之间以英文逗号","分隔,看如下的例子:

<#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as x>

${x}

</#list>

输出结果是:

星期一

星期二

星期三

星期四

星期五

星期六

星期天

除此之外,集合元素也可以是表达式,例子如下:

[2 + 2, [1, 2, 3, 4], "whatnot"]

还可以使用数字范围定义数字集合,如2..5等同于[2, 3, 4, 5],但是更有效率.注意,使用数字范围来定义集合时无需使用方括号,数字范围也支持反递增的数字范围,如5..2

5,Map对象

Map对象使用花括号包括,Map中的key-value对之间以英文冒号":"分隔,多组key-value对之间以英文逗号","分隔.下面是一个例子:

{"语文":78, "数学":80}

Map对象的key和value都是表达式,但是key必须是字符串

输出变量

FreeMarker的表达式输出变量时,这些变量可以是顶层变量,也可以是Map对象中的变量,还可以是集合中的变量,并可以使用点(.)语法来访问Java对象的属性.下面分别讨论这些情况

1,顶层变量

所谓顶层变量就是直接放在数据模型中的值,例如有如下数据模型:

Map root = new HashMap();   //创建数据模型

root.put("name","annlee");  //name是一个顶层变量

对于顶层变量,直接使用${variableName}来输出变量值,变量名只能是字母,数字,下划线,$,@和#的组合,且不能以数字开头号.为了输出上面的name的值,可以使用如下语法:

${name}

2,输出集合元素

如果需要输出集合元素,则可以根据集合元素的索引来输出集合元素,集合元素的索引以方括号指定.假设有索引:

["星期一","星期二","星期三","星期四","星期五","星期六","星期天"].该索引名为week,如果需要输出星期三,则可以使用如下语法:

${week[2]}   //输出第三个集合元素

此外,FreeMarker还支持返回集合的子集合,如果需要返回集合的子集合,则可以使用如下语法:

week[3..5]   //返回week集合的子集合,子集合中的元素是week集合中的第4-6个元素

3,输出Map元素

这里的Map对象可以是直接HashMap的实例,甚至包括JavaBean实例,对于JavaBean实例而言,我们一样可以把其当成属性为key,属性值为value的Map实例.为了输出Map元素的值,可以使用点语法或方括号语法.假如有下面的数据模型:

Map root = new HashMap();

Book book = new Book();

Author author = new Author();

author.setName("annlee");

author.setAddress("gz");

book.setName("struts2");

book.setAuthor(author);

root.put("info","struts");

root.put("book", book);

为了访问数据模型中名为struts2的书的作者的名字,可以使用如下语法:

book.author.name    //全部使用点语法

book["author"].name

book.author["name"]   //混合使用点语法和方括号语法

book["author"]["name"]  //全部使用方括号语法

使用点语法时,变量名字有顶层变量一样的限制,但方括号语法没有该限制,因为名字可以是任意表达式的结果.

字符串操作

FreeMarker的表达式对字符串操作非常灵活,可以将字符串常量和变量连接起来,也可以返回字符串的子串等.

字符串连接有两种语法:

1,使用${..}或#{..}在字符串常量部分插入表达式的值,从而完成字符串连接.

2,直接使用连接运算符+来连接字符串

例如有如下数据模型:

Map root = new HashMap();root.put("user","annlee");

下面将user变量和常量连接起来:

${"hello, ${user}!"}   //使用第一种语法来连接

${"hello, " + user +"!"} //使用+号来连接

上面的输出字符串都是hello,annlee!,可以看出这两种语法的效果完全一样.

值得注意的是,${..}只能用于文本部分,不能用于表达式,下面的代码是错误的:

<#if ${isBig}>Wow!</#if>

<#if"${isBig}">Wow!</#if>

应该写成:<#ifisBig>Wow!</#if>

截取子串可以根据字符串的索引来进行,截取子串时如果只指定了一个索引值,则用于取得字符串中指定索引所对应的字符;如果指定两个索引值,则返回两个索引中间的字符串子串.假如有如下数据模型:

Map root = new HashMap();root.put("book","struts2,freemarker");

可以通过如下语法来截取子串:

${book[0]}${book[4]}   //结果是su

${book[1..4]}    //结果是tru

集合连接运算符

这里所说的集合运算符是将两个集合连接成一个新的集合,连接集合的运算符是+,看如下的例子:

<#list ["星期一","星期二","星期三"] + ["星期四","星期五","星期六","星期天"] as x>

${x}

</#list>

输出结果是:星期一星期二 星期三 星期四 星期五 星期六 星期天

Map连接运算符

Map对象的连接运算符也是将两个Map对象连接成一个新的Map对象,Map对象的连接运算符是+,如果两个Map对象具有相同的key,则右边的值替代左边的值.看如下的例子:

<#assign scores = {"语文":86,"数学":78} + {"数学":87,"Java":93}>

语文成绩是${scores.语文}

数学成绩是${scores.数学}

Java成绩是${scores.Java}

输出结果是:

语文成绩是86

数学成绩是87

Java成绩是93

算术运算符

FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:+, - , * , / , % 看如下的代码:

<#assign x=5>

${ x * x - 100 }

${ x /2 }

${ 12 %10 }

输出结果是:

-75   2.5   2

在表达式中使用算术运算符时要注意以下几点:

1,运算符两边的运算数字必须是数字

2,使用+运算符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符串再连接,如:${3+ "5"},结果是:35

使用内建的int函数可对数值取整,如:

<#assign x=5>

${ (x/2)?int }

${ 1.1?int }

${ 1.999?int }

${ -1.1?int }

${ -1.999?int }

结果是:2 1 1 -1-1

比较运算符

表达式中支持的比较运算符有如下几个:

1,=或者==:判断两个值是否相等.

2,!=:判断两个值是否不等.

3,>或者gt:判断左边值是否大于右边值

4,>=或者gte:判断左边值是否大于等于右边值

5,<或者lt:判断左边值是否小于右边值

6,<=或者lte:判断左边值是否小于等于右边值

注意:=和!=可以用于字符串,数值和日期来比较是否相等,但=和!=两边必须是相同类型的值,否则会产生错误,而且FreeMarker是精确比较,"x","x","X"是不等的.其它的运行符可以作用于数字和日期,但不能作用于字符串,大部分的时候,使用gt等字母运算符代替>会有更好的效果,因为FreeMarker会把>解释成FTL标签的结束字符,当然,也可以使用括号来避免这种情况,如:<#if (x>y)>

逻辑运算符

逻辑运算符有如下几个:

逻辑与:&&

逻辑或:||

逻辑非:!

逻辑运算符只能作用于布尔值,否则将产生错误

内置函数

FreeMarker还提供了一些内建函数来转换输出,可以在任何变量后紧跟?,?后紧跟内建函数,就可以通过内建函数来轮换输出变量.下面是常用的内建的字符串函数:

html:对字符串进行HTML编码

cap_first:使字符串第一个字母大写

lower_case:将字符串转换成小写

upper_case:将字符串转换成大写

trim:去掉字符串前后的空白字符

下面是集合的常用内建函数

size:获取序列中元素的个数

下面是数字值的常用内建函数

int:取得数字的整数部分,结果带符号

例如:

<#assign test="Tom &Jerry">

${test?html}

${test?upper_case?html}

结果是:Tom&amp; Jerry   TOM &amp; JERRY

空值处理运算符

FreeMarker对空值的处理非常严格,FreeMarker的变量必须有值,没有被赋值的变量就会抛出异常,因为FreeMarker未赋值的变量强制出错可以杜绝很多潜在的错误,如缺失潜在的变量命名,或者其他变量错误.这里所说的空值,实际上也包括那些并不存在的变量,对于一个Java的null值而言,我们认为这个变量是存在的,只是它的值为null,但对于FreeMarker模板而言,它无法理解null值,null值和不存在的变量完全相同.

为了处理缺失变量,FreeMarker提供了两个运算符:

!:指定缺失变量的默认值

??:判断某个变量是否存在

其中,!运算符的用法有如下两种:

variable!或variable!defaultValue,第一种用法不给缺失的变量指定默认值,表明默认值是空字符串,长度为0的集合,或者长度为0的Map对象.

使用!指定默认值时,并不要求默认值的类型和变量类型相同.使用??运算符非常简单,它总是返回一个布尔值,用法为:variable??,如果该变量存在,返回true,否则返回false

运算符的优先级

FreeMarker中的运算符优先级如下(由高到低排列):

1,一元运算符:!

2,内建函数:?

3,乘除法:*, / , %

4,加减法:- , +

5,比较:> , < , >= , <= (lt , lte , gt , gte)

6,相等:== , = , !=

7,逻辑与:&&

8,逻辑或:||

9,数字范围:..

实际上,我们在开发过程中应该使用括号来严格区分,这样的可读性好,出错少

freemarker常用指令

if

这是一个典型的分支控制指令,该指令的作用完全类似于Java语言中的if,if指令的语法格式如下:

<#if condition>...

<#elseif condition>...

<#elseif condition>...

<#else> ...

</#if>

例子如下:

<#assign age=23>

<#if (age>60)>老年人

<#elseif (age>40)>中年人

<#elseif (age>20)>青年人

<#else> 少年人

</#if>

输出结果是:青年人

上面的代码中的逻辑表达式用括号括起来主要是因为里面有>符号,由于FreeMarker会将>符号当成标签的结束字符,可能导致程序出错,为了避免这种情况,我们应该在凡是出现这些符号的地方都使用括号.

switch、case、default、break

这些指令显然是分支指令,作用类似于Java的switch语句,switch指令的语法结构如下:

<#switch value>

<#case refValue>...<#break>

<#case refValue>...<#break>

<#default>...

</#switch>

list、break

list指令是一个迭代输出指令,用于迭代输出数据模型中的集合,list指令的语法格式如下:

<#list sequence as item>

...

</#list>

上面的语法格式中,sequence就是一个集合对象,也可以是一个表达式,但该表达式将返回一个集合对象,而item是一个任意的名字,就是被迭代输出的集合元素.此外,迭代集合对象时,还包含两个特殊的循环变量:

item_index:当前变量的索引值

item_has_next:是否存在下一个对象

也可以使用<#break>指令跳出迭代

例子如下:

<#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as x>

${x_index + 1}.${x}<#ifx_has_next>,</if>

<#if x="星期四"><#break></#if>

</#list>

include

include指令的作用类似于JSP的包含指令,用于包含指定页.include指令的语法格式如下:

<#include filename [options]>

在上面的语法格式中,两个参数的解释如下:

filename:该参数指定被包含的模板文件

options:该参数可以省略,指定包含时的选项,包含encoding和parse两个选项,其中encoding指定包含页面时所用的解码集,而parse指定被包含文件是否作为FTL文件来解析,如果省略了parse选项值,则该选项默认是true.

import

该指令用于导入FreeMarker模板中的所有变量,并将该变量放置在指定的Map对象中,import指令的语法格式如下:

<#import "/lib/common.ftl" ascom>

上面的代码将导入/lib/common.ftl模板文件中的所有变量,交将这些变量放置在一个名为com的Map对象中.

noparse

noparse指令指定FreeMarker不处理该指定里包含的内容,该指令的语法格式如下:

<#noparse>...</#noparse>

看如下的例子:

<#noparse>

<#list books as book>

<tr><td>${book.name}<td>作者:${book.author}

</#list>

</#noparse>

输出如下:

<#list books as book>

<tr><td>${book.name}<td>作者:${book.author}

</#list>

escape

escape指令导致body区的插值都会被自动加上escape表达式,但不会影响字符串内的插值,只会影响到body内出现的插值,使用escape指令的语法格式如下:

<#escape identifier as expression>...

<#noescape>...</#noescape>

</#escape>

看如下的代码:

<#escape x as x?html>

First name:${firstName}

Last name:${lastName}

Maiden name:${maidenName}

</#escape>

上面的代码等同于:

First name:${firstName?html}

Last name:${lastName?html}

Maiden name:${maidenName?html}

escape指令在解析模板时起作用而不是在运行时起作用,除此之外,escape指令也嵌套使用,子escape继承父escape的规则,如下例子:

<#escape x as x?html>

Customer Name:${customerName}

Items to ship;

<#escape x as itemCodeToNameMap[x]>

${itemCode1}

${itemCode2}

${itemCode3}

${itemCode4}

</#escape>

</#escape>

上面的代码类似于:

Customer Name:${customerName?html}

Items to ship;

${itemCodeToNameMap[itemCode1]?html}

${itemCodeToNameMap[itemCode2]?html}

${itemCodeToNameMap[itemCode3]?html}

${itemCodeToNameMap[itemCode4]?html}

对于放在escape指令中所有的插值而言,这此插值将被自动加上escape表达式,如果需要指定escape指令中某些插值无需添加escape表达式,则应该使用noescape指令,放在noescape指令中的插值将不会添加escape表达式.

assign指令

assign指令在前面已经使用了多次,它用于为该模板页面创建或替换一个顶层变量,assign指令的用法有多种,包含创建或替换一个顶层变量,或者创建或替换多个变量等,它的最简单的语法如下:<#assign name=value [innamespacehash]>,这个用法用于指定一个名为name的变量,该变量的值为value,此外,FreeMarker允许在使用assign指令里增加in子句,in子句用于将创建的name变量放入namespacehash命名空间中.

assign指令还有如下用法:<#assign name1=value1 name2=value2 ... nameN=valueN [innamespacehash]>,这个语法可以同时创建或替换多个顶层变量,此外,还有一种复杂的用法,如果需要创建或替换的变量值是一个复杂的表达式,则可以使用如下语法格式:<#assign name [innamespacehash]>capture this</#assign>,在这个语法中,是指将assign指令的内容赋值给name变量.如下例子:

<#assign x>

<#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as n>

${n}

</#list>

</#assign>

${x}

上面的代码将产生如下输出:星期一 星期二 星期三 星期四 星期五星期六 星期天

虽然assign指定了这种复杂变量值的用法,但是我们也不要滥用这种用法,如下例子:<#assignx>Hello ${user}!</#assign>,以上代码改为如下写法更合适:<#assignx="Hello ${user}!">

setting指令

该指令用于设置FreeMarker的运行环境,该指令的语法格式如下:<#setting name=value>,在这个格式中,name的取值范围包含如下几个:

locale:该选项指定该模板所用的国家/语言选项

number_format:指定格式化输出数字的格式

boolean_format:指定两个布尔值的语法格式,默认值是true,false

date_format,time_format,datetime_format:指定格式化输出日期的格式

time_zone:设置格式化输出日期时所使用的时区

macro , nested , return指令

macro可以用于实现自定义指令,通过使用自定义指令,可以将一段模板片段定义成一个用户指令,使用macro指令的语法格式如下:

<#macro name param1 param2 ...paramN>

...

<#nested loopvar1, loopvar2, ...,loopvarN>

...

<#return>

...

</#macro>

在上面的格式片段中,包含了如下几个部分:

name:name属性指定的是该自定义指令的名字,使用自定义指令时可以传入多个参数

paramX:该属性就是指定使用自定义指令时报参数,使用该自定义指令时,必须为这些参数传入值

nested指令:nested标签输出使用自定义指令时的中间部分

nested指令中的循环变量:这此循环变量将由macro定义部分指定,传给使用标签的模板

return指令:该指令可用于随时结束该自定义指令.

看如下的例子:

<#macro book>   //定义一个自定义指令

j2ee

</#macro>

<@book />    //使用刚才定义的指令

上面的代码输出结果为:j2ee

在上面的代码中,可能很难看出自定义标签的用处,因为我们定义的book指令所包含的内容非常简单,实际上,自定义标签可包含非常多的内容,从而可以实现更好的代码复用.此外,还可以在定义自定义指令时,为自定义指令指定参数,看如下代码:

<#macro bookbooklist>     //定义一个自定义指令booklist是参数

<#list booklist as book>

${book}

</#list>

</#macro>

<@bookbooklist=["spring","j2ee"] />   //使用刚刚定义的指令

上面的代码为book指令传入了一个参数值,上面的代码的输出结果为:spring j2ee

不仅如此,还可以在自定义指令时使用nested指令来输出自定义指令的中间部分,看如下例子:

<#macro page title>

<html>

<head>

<title>FreeMarker示例页面 - ${title?html}</title>

</head>

<body>

<h1>${title?html}</h1>

<#nested>      //用于引入用户自定义指令的标签体

</body>

</html>

</#macro>

上面的代码将一个HTML页面模板定义成一个page指令,则可以在其他页面中如此page指令:

<#import "/common.ftl" ascom>     //假设上面的模板页面名为common.ftl,导入页面

<@com.page title="booklist">

<u1>

<li>spring</li>

<li>j2ee</li>

</ul>

</@com.page>

从上面的例子可以看出,使用macro和nested指令可以非常容易地实现页面装饰效果,此外,还可以在使用nested指令时,指定一个或多个循环变量,看如下代码:

<#macro book>

<#nested1>      //使用book指令时指定了一个循环变量值

<#nested 2>

</#macro>

<@book ;x> ${x} .图书</@book>

当使用nested指令传入变量值时,在使用该自定义指令时,就需要使用一个占位符(如book指令后的;x).上面的代码输出文本如下:

1 .图书    2 .图书

在nested指令中使用循环变量时,可以使用多个循环变量,看如下代码:

<#macro repeat count>

<#list 1..count asx>     //使用nested指令时指定了三个循环变量

<#nested x, x/2,x==count>

</#list>

</#macro>

<@repeat count=4 ; c halfc last>

${c}. ${halfc}<#if last> Last!</#if>

</@repeat>

上面的输出结果为:

1. 0.5   2. 1   3. 1.5   4.2 Last;

return指令用于结束macro指令,一旦在macro指令中执行了return指令,则FreeMarker不会继续处理macro指令里的内容,看如下代码:

<#macro book>

spring

<#return>

j2ee

</#macro>

<@book />

上面的代码输出:spring,而j2ee位于return指令之后,不会输出.

7 7.MyBatis

7.1 MyBatis入门

7.1.1 单独使用JDBC编程问题总结

JDBC程序

Connection connection = null;

PreparedStatement preparedStatement = null;

ResultSet resultSet = null;

try {

//加载数据库驱动

Class.forName("com.mysql.jdbc.Driver");

//通过驱动管理类获取数据库链接

connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "mysql");

//定义sql语句 ?表示占位符

String sql = "select *from user where username = ?";

//获取预处理statement

preparedStatement =connection.prepareStatement(sql);

//设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值

preparedStatement.setString(1,"王五");

//向数据库发出sql执行查询,查询出结果集

resultSet =  preparedStatement.executeQuery();

//遍历查询结果集

while(resultSet.next()){

System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));

}

} catch (Exception e) {

e.printStackTrace();

}finally{

//释放资源

if(resultSet!=null){

try {

resultSet.close();

} catch (SQLExceptione) {

//TODO Auto-generated catch block

e.printStackTrace();

}

}

if(preparedStatement!=null){

try {

preparedStatement.close();

} catch(SQLException e) {

//TODO Auto-generated catch block

e.printStackTrace();

}

}

if(connection!=null){

try {

connection.close();

} catch(SQLException e) {

//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

上边使用jdbc的原始方法(未经封装)实现了查询数据库表记录的操作。

JDBC编程步骤

1.加载数据库驱动

2.创建并获取数据库链接

3.创建jdbc statement对象

4.设置sql语句

5.设置sql语句中的参数(使用preparedStatement)

6.通过statement执行sql并获取结果

7.对sql执行结果进行解析处理

8.释放资源(resultSet、preparedstatement、connection)

JDBC问题总结如下

1.数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。

2.Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

3.使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。

4.对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。

7.1.2 MyBatis介绍

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。M         ybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

与其他的对象关系映射框架不同,MyBatis并没有将Java对象与数据库表关联起来,而是将Java方法与SQL语句关联。MyBatis允许用户充分利用数据库的各种功能,例如存储过程、视图、各种复杂的查询以及某数据库的专有特性。如果要对遗留数据库、不规范的数据库进行操作,或者要完全控制SQL的执行,MyBatis是一个不错的选择。

7.1.3 MyBatis架构

1.mybatis配置

* SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。

* mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

2.通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

3.由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

4.mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

5.Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。

6.Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

7.Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

7.1.4 MyBatis下载

https://github.com/mybatis/mybatis-3

7.1.5 创建数据库

CREATE DATABASE `usermana` DEFAULTCHARACTER SET utf8 COLLATE utf8_bin;

USE `usermana`;

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(11) NOT NULLAUTO_INCREMENT,

`username` varchar(255) COLLATE utf8_binDEFAULT NULL,

`nickname` varchar(255) COLLATEutf8_bin DEFAULT NULL,

`address` varchar(255) COLLATEutf8_bin DEFAULT NULL,

`favorites` varchar(255) COLLATEutf8_bin DEFAULT NULL,

`birthday` varchar(255) COLLATEutf8_bin DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDBDEFAULT CHARSET=utf8 COLLATE=utf8_bin;

7.1.6 MyBatis入门程序

需求

实现用户的增删改查

第一步:创建Java工程

创建一个普通的Java工程,如下所示:

第二步:加入jar包

1.将mybatis相关的jar包导入到项目中

2.导入数据库驱动jar

第三步:log4j.properties

classpath下创建log4j.properties文件,该文件用来输出mybatis的日志信息:

log4j.rootLogger=DEBUG,stdout

log4j.logger.org.mybatis=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%5p%d %C: %m%n

第四步:mybatis-conf.xml

在classpath下创建mybatis-conf.xml文件:

<?xml version="1.0"encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--引入数据库配置文件-->
    
<properties resource="db.properties"/>
    <!--当前环境-->
    
<environments default="development">
        <environment id="development">
            <!--使用JDBC事务-->
            
<transactionManager type="JDBC"/>
            <!--使用数据库连接池-->
            
<dataSource type="POOLED">
                <!--配置数据库-->
                
<property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username"value="${db.username}"/>
                <property name="password"value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--配置Mapper文件位置-->
        
<mapper resource="org/sang/mapping/*.xml"/>
    </mappers>
</configuration>

第五步:POJO类

创建User类:

第六步:程序编写

添加

1.在mapping包下创建user-insert.xml文件,文件内容如下:

<?xml version="1.0"encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="inserttest">
    <select id="insert" resultType="int" parameterType="org.sang.model.User">
        INSERT INTOuser(username,nickname,address,favorites,birthday) VALUES(#{username},#{nickname},#{address},#{favorites},#{birthday});
    </select>
</mapper>

2.加载MyBatis全局配置文件并开启SqlSession:

public class DBUtils {
    private static final Class CLASS_LOCK = DBUtils.class;
    private static SqlSessionFactory factory;

public static SqlSessionFactory sqlSessionFactory() {
        InputStream is = null;
        try {
            is =Resources.getResourceAsStream("mybatis-conf.xml");
        } catch (IOException e) {
           e.printStackTrace();
        }
        synchronized (CLASS_LOCK) {
            if (factory== null) {
                factory = new SqlSessionFactoryBuilder().build(is);
            }
        }
        return factory;
    }
    public static SqlSession openSession() {
        if (factory== null) {
            sqlSessionFactory();
        }
        return factory.openSession();
    }
}

3.测试

@Test
public void test1() {
    SqlSession sqlSession = DBUtils.openSession();
    User user = new User();
    user.setAddress("深圳");
    user.setBirthday("2017-05-01");
    user.setFavorites("足球");
    user.setNickname("君子剑");
    user.setUsername("岳不群");
    int insert= sqlSession.insert("inserttest.insert", user);
    sqlSession.commit();
    System.out.println(insert);
}

主键自增长

可在数据插入成功之后获取到主键的值:

<insert id="insert" parameterType="org.sang.model.User">
    <selectKey keyProperty="id" resultType="Long" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTOuser(username,nickname,address,favorites,birthday) VALUES(#{username},#{nickname},#{address},#{favorites},#{birthday});
</insert>

注意:

添加selectKey实现将主键返回

keyProperty:返回的主键存储在pojo中的哪个属性

order:selectKey的执行顺序,是相对与insert语句来说,由于mysql的自增原理执行完insert语句之后才将主键生成,所以这里selectKey的执行顺序为after

resultType:返回的主键是什么类型

LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录id值。

测试:

@Test
public void test1() {
    SqlSession sqlSession = DBUtils.openSession();
    User user = new User();
    user.setAddress("深圳");
    user.setBirthday("2017-05-01");
    user.setFavorites("足球");
    user.setNickname("君子剑");
    user.setUsername("岳不群");
    int insert= sqlSession.insert("inserttest.insert", user);
    sqlSession.commit();
    System.out.println(user.getId());
    System.out.println(insert);
}

UUID设置主键

<insert id="insert2" parameterType="org.sang.model.User">
    <selectKey keyProperty="id" resultType="String" order="BEFORE">
        SELECT uuid()
    </selectKey>
    INSERT INTOuser(id,username,nickname,address,favorites,birthday) VALUES
   (#{id},#{username},#{nickname},#{address},#{favorites},#{birthday});
</insert>

注意这里使用的order是“BEFORE”,即先生成UUID,再执行插入操作。

查询

1.添加映射文件

<select id="select" resultType="org.sang.model.User"parameterType="String">
    SELECT * FROM user WHERE id=#{id}
</select>

parameterType:定义输入到sql中的映射类型,#{id}表示使用preparedstatement设置占位符号并将输入变量id传到sql。

resultType:定义结果映射类型。

2.测试

@Test
public void test3() {
    SqlSession sqlSession = DBUtils.openSession();
    User user = (User) sqlSession.selectOne("inserttest.select", "3");
    sqlSession.commit();
    System.out.println(user);
}

删除

1.添加映射文件

<delete id="delete" parameterType="String">
    DELETE FROM user WHERE id=#{id}
</delete>

2.测试

@Test
public void test4() {
    SqlSession sqlSession = DBUtils.openSession();
    int delete= sqlSession.delete("inserttest.delete", "3");
    sqlSession.commit();
    System.out.println(delete);
}

修改

1.添加映射文件

<update id="update" parameterType="org.sang.model.User">
    UPDATE user setusername=#{username},nickname=#{nickname},address=#{address},favorites=#{favorites},birthday=#{birthday}WHERE id=#{id}
</update>

2.测试

@Test
public void test5() {
    SqlSession sqlSession = DBUtils.openSession();
    User user = new User();
    user.setUsername("左冷禅");
    user.setNickname("嵩山掌门");
    user.setFavorites("寒冰指");
    user.setAddress("河南");
    user.setBirthday("0101");
    user.setId("4");
    int update= sqlSession.update("inserttest.update", user);
    sqlSession.commit();
    System.out.println(update);
}

MyBatis解决JDBC编程的问题

1.数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。

2.Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

3.向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

4.对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

MyBatis与Hibernate比较

Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。

总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

8 8.MyBatis配置

8.1 Day58

8.1.1 mybaits-conf.xml文件

配置内容

properties(属性)

settings(全局配置参数)

typeAliases(类型别名)

typeHandlers(类型处理器)

objectFactory(对象工厂)

plugins(插件)

environments(环境集合属性对象)

environment(环境子属性对象)

transactionManager(事务管理)

dataSource(数据源)

mappers(映射器)

properties属性

在classpath下定义db.properties文件,内容如下:

db.driver=com.mysql.jdbc.Driver
db.url
=jdbc:mysql:///usermana?useUnicode=true&characterEncoding=utf-8
db.username
=root
db.password
=123

在mybatis-conf.xml中引用db.properties文件,内容如下:

settings配置

Setting(设置)

Description(描述)

Valid Values(验证值组)

Default(默认值)

cacheEnabled

在全局范围内启用或禁用缓存配置任何映射器在此配置下。

true | false

TRUE

lazyLoadingEnabled

在全局范围内启用或禁用延迟加载。禁用时,所有协会将热加载。

true | false

TRUE

aggressiveLazyLoading

启用时,有延迟加载属性的对象将被完全加载后调用懒惰的任何属性。否则,每一个属性是按需加载。

true | false

TRUE

multipleResultSetsEnabled

允许或不允许从一个单独的语句(需要兼容的驱动程序)要返回多个结果集。

true | false

TRUE

useColumnLabel

使用列标签,而不是列名。在这方面,不同的驱动有不同的行为。参考驱动文档或测试两种方法来决定你的驱动程序的行为如何。

true | false

TRUE

useGeneratedKeys

允许JDBC支持生成的密钥。兼容的驱动程序是必需的。此设置强制生成的键被使用,如果设置为true,一些驱动会不兼容性,但仍然可以工作。

true | false

FALSE

autoMappingBehavior

指定MyBatis的应如何自动映射列到字段/属性。NONE自动映射。 PARTIAL只会自动映射结果没有嵌套结果映射定义里面。 FULL会自动映射的结果映射任何复杂的(包含嵌套或其他)。

NONE, PARTIAL, FULL

PARTIAL

defaultExecutorType

配置默认执行人。SIMPLE执行人确实没有什么特别的。 REUSE执行器重用准备好的语句。 BATCH执行器重用语句和批处理更新。

SIMPLE REUSE BATCH

SIMPLE

defaultStatementTimeout

设置驱动程序等待一个数据库响应的秒数。

Any positive integer

Not Set (null)

safeRowBoundsEnabled

允许使用嵌套的语句RowBounds。

true | false

FALSE

mapUnderscoreToCamelCase

从经典的数据库列名A_COLUMN启用自动映射到骆驼标识的经典的Java属性名aColumn。

true | false

FALSE

localCacheScope

MyBatis的使用本地缓存,以防止循环引用,并加快反复嵌套查询。默认情况下(SESSION)会话期间执行的所有查询缓存。如果localCacheScope=STATMENT本地会话将被用于语句的执行,只是没有将数据共享之间的两个不同的调用相同的SqlSession。

SESSION | STATEMENT

SESSION

dbcTypeForNull

指定为空值时,没有特定的JDBC类型的参数的JDBC类型。有些驱动需要指定列的JDBC类型,但其他像NULL,VARCHAR或OTHER的工作与通用值。

JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER

OTHER

lazyLoadTriggerMethods

指定触发延迟加载的对象的方法。

A method name list separated by commas

equals,clone,hashCode,toString

defaultScriptingLanguage

指定所使用的语言默认为动态SQL生成。

A type alias or fully qualified class name.

org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver

callSettersOnNulls

指定如果setter方法​​或地图的put方法时,将调用检索到的值是null。它是有用的,当你依靠Map.keySet()或null初始化。注意原语(如整型,布尔等)不会被设置为null。

true | false

FALSE

logPrefix

指定的前缀字串,MyBatis将会增加记录器的名称。

Any String

Not set

logImpl

指定MyBatis的日志实现使用。如果此设置是不存在的记录的实施将自动查找。

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

Not set

proxyFactory

指定代理工具,MyBatis将会使用创建懒加载能力的对象。

CGLIB | JAVASSIST

typeAliases

mybatis支持别名

别名

映射的类型

_byte

byte

_long

long

_short

short

_int

int

_integer

int

_double

double

_float

float

_boolean

boolean

string

String

byte

Byte

long

Long

short

Short

int

Integer

integer

Integer

double

Double

float

Float

boolean

Boolean

date

Date

decimal

BigDecimal

bigdecimal

BigDecimal

自定义别名

typeHandler

类型处理器用于java类型和jdbc类型映射

mybatis支持类型处理器:

类型处理器

Java类型

JDBC类型

BooleanTypeHandler

Boolean,boolean

任何兼容的布尔值

ByteTypeHandler

Byte,byte

任何兼容的数字或字节类型

ShortTypeHandler

Short,short

任何兼容的数字或短整型

IntegerTypeHandler

Integer,int

任何兼容的数字和整型

LongTypeHandler

Long,long

任何兼容的数字或长整型

FloatTypeHandler

Float,float

任何兼容的数字或单精度浮点型

DoubleTypeHandler

Double,double

任何兼容的数字或双精度浮点型

BigDecimalTypeHandler

BigDecimal

任何兼容的数字或十进制小数类型

StringTypeHandler

String

CHAR和VARCHAR类型

ClobTypeHandler

String

CLOB和LONGVARCHAR类型

NStringTypeHandler

String

NVARCHAR和NCHAR类型

NClobTypeHandler

String

NCLOB类型

ByteArrayTypeHandler

byte[]

任何兼容的字节流类型

BlobTypeHandler

byte[]

BLOB和LONGVARBINARY类型

DateTypeHandler

Date(java.util)

TIMESTAMP类型

DateOnlyTypeHandler

Date(java.util)

DATE类型

TimeOnlyTypeHandler

Date(java.util)

TIME类型

SqlTimestampTypeHandler

Timestamp(java.sql)

TIMESTAMP类型

SqlDateTypeHandler

Date(java.sql)

DATE类型

SqlTimeTypeHandler

Time(java.sql)

TIME类型

ObjectTypeHandler

任意

其他或未指定类型

EnumTypeHandler

Enumeration类型

VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。

mappers

mappers(映射器)

Mapper配置的几种方法:

1. <mapper resource=" " />

使用相对于类路径的资源

如:<mapperresource="mapping/User.xml" />

2. <mapper url="" />

使用完全限定路径

如:<mapperurl="file:///D:\mybatis_01\config\sqlmap\User.xml" />

3. <mapper class=" " />

使用mapper接口类路径

如:<mapperclass="org.sang.mapper.UserMapper"/>

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

4. <package name=""/>

注册指定包下的所有mapper接口

如:<packagename="org.sang.mybatis.mapper"/>

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

8.1.2 Mapper.xml映射文件

parameterType

输入类型

#{}与${}

#{}实现的是向prepareStatement中的预处理语句中设置参数值,sql语句中#{}表示一个占位符即?。使用占位符#{}可以有效防止sql注入,在使用时不需要关心参数值的类型,mybatis会自动进行java类型和jdbc类型的转换。#{}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

${}和#{}不同,通过${}可以将parameterType传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。使用${}不能防止sql注入,但是有时用${}会非常方便,如下的例子:

<!-- 根据名称模糊查询用户信息 -->

<selectid="selectUserByName" parameterType="string"resultType="user">

select * from userwhere username like '%${value}%'

</select>

如果本例子使用#{}则传入的字符串中必须有%号,而%是人为拼接在参数中,显然有点麻烦,如果采用${}在sql中拼接为%的方式则在调用mapper接口传递参数就方便很多。

不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

ORDER BY ${columnName}

此时MyBatis 不会修改或转义字符串。

这种方式类似于:

Statement st =conn.createStatement();

ResultSet rs = st.executeQuery(sql);

这种方式的缺点是: 以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

使用#{}格式的语法在mybatis中使用Preparement语句来安全的设置值,执行sql类似下面的:

PreparedStatement ps =conn.prepareStatement(sql);

ps.setInt(1,id);

这样做的好处是:更安全,更迅速,通常也是首选做法。

传递简单类型

传递POJO对象

username和nickname均表示user中的属性。

传递POJO包装对象

1.定义包装对象

2.mapper.xml映射文件

<select id="getOne" parameterType="org.sang.model.QueryUser"resultType="org.sang.model.User">
    SELECT * FROM user WHERE id=#{user.id}
</select>

传递HashMap

UserMapper.xml中做如下定义:

<select id="getOne2" parameterType="hashmap"resultType="org.sang.model.User">
    SELECT * FROM user WHERE id=#{id}
</select>

resultType

输出简单类型

输出POJO对象

输出POJO集合

resultType总结

输出pojo对象和输出pojo列表在sql中定义的resultType是一样的。

返回单个pojo对象要保证sql查询出来的结果集为单条,内部使用session.selectOne方法调用,mapper接口使用pojo对象作为方法返回值。返回pojo列表表示查询出来的结果集可能为多条,内部使用session.selectList方法,mapper接口使用List<pojo>对象作为方法返回值。

输出HashMap

输出pojo对象可以改用hashmap输出类型,将输出的字段名称作为map的key,value为字段值。

resultMap

resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还需要将查询结果映射到pojo对象中。resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

<resultMap id="BaseResultMap"type="org.sang.model.User2">
    <id property="id" column="id"/>
    <result property="u" column="username"/>
    <result property="n" column="nickname"/>
    <result property="a" column="address"/>
</resultMap>
<select id="getOne4" parameterType="Long" resultMap="BaseResultMap">
    SELECT * FROM user WHERE id=#{id}
</select>

<id />:此属性表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个<id />。

property:表示person类的属性。

column:表示sql查询出来的字段名。

column和property放在一块儿表示将sql查询出来的字段映射到指定的pojo类属性上。

<result />:普通结果,即pojo的属性。

动态SQL

通过mybatis提供的各种标签方法实现动态拼接sql。

if

<select id="getUser" parameterType="org.sang.model.User"resultType="org.sang.model.User">
    select * from user WHERE 1=1
    <if test="id!=null and id!=''">
        AND id=#{id}
    </if>
</select>

where

<select id="getUser" parameterType="org.sang.model.User"resultType="org.sang.model.User">
    select * from user
    <where>
    <if test="id!=null and id!=''">
        AND id=#{id}
    </if>
    </where>
</select>

<where />可以自动处理第一个and

foreach

通过POJO传递List

传递单个List

<select id="getUser2" parameterType="Long" resultType="org.sang.model.User">
    SELECT * FROM USER
    <where>
        <if test="ids!=null andids.size>0">
            <foreach collection="ids"open="and id in(" close=")"item="id" separator=",">
               #{id}
            </foreach>
        </if>
    </where>
</select>

查询sql

注意Mapper中的写法:

public List<User> getUser2(@Param("ids") List<Long> ids);

只有一个List时,参数名默认为list

传递单个数组(数组中是POJO)

<select id="getUser4" parameterType="Object[]" resultType="org.sang.model.User">
    SELECT * FROM USER
    <where>
        <if test="array!=nulland array.length>0">
            <foreach collection="array"open="and id in(" close=")"item="user" separator=",">
               #{user.id}
            </foreach>
        </if>
    </where>
</select>

注意Mapper中的写法:

public List<User> getUser4(User[] users);

传递单个数组(数组中是字符串类型)

<select id="getUser3"parameterType="Long[]" resultType="org.sang.model.User">
    SELECT * FROM USER
    <where>
        <if test="array!=nulland array.length>0">
            <foreach collection="array"open="and id in(" close=")"item="id" separator=",">
               #{id}
            </foreach>
        </if>
    </where>
</select>

注意默认名字是array。可以通过@Param来修改。Mapper如下:

public List<User> getUser3(Long[] ids);

sql片段

Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。

<sql id="BaseColumn">
    username,nickname,address,birthday,id,favorites
</sql>
<select id="getUser4" parameterType="Object[]" resultType="org.sang.model.User">
    SELECT <include refid="BaseColumn"></include> FROM USER
    <where>
        <if test="array!=nulland array.length>0">
            <foreach collection="array"open="and id in(" close=")"item="user" separator=",">
               #{user.id}
            </foreach>
        </if>
    </where>
</select>

9 9.MyBatis高级知识

9.1 关联查询

9.1.1 一对一查询

方法一

1.定义实体类

2.Mapper.xml中定义相关方法

<select id="getFood" parameterType="Long"resultType="org.sang.model.Food">
    SELECT f.*,s.windowName FROM food f,seller s WHERE f.sellerId=s.idAND f.id=#{id}
</select>

3.测试接口

public Food getFood(Long id);

4.测试

@Test
public void test15() {
    SqlSession sqlSession = DBUtils.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Food food = mapper.getFood(27l);
    System.out.println(food.toString());
}

5.总结

定义专门的pojo类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。

方法二

1.定义POJO对象,直接在Food类中定义:

2.Mapper.xml文件中首先定义ResultMap:

<resultMap id="BaseResultMap2"type="org.sang.model.Food2">
    <id column="id" property="id"/>
    <result column="foodImg" property="foodImg"/>
    <result column="foodPrice" property="foodPrice"/>
    <result column="foodName" property="foodName"/>
    <result column="priceType" property="priceType"/>
    <association property="seller" javaType="org.sang.model.Seller">
        <result column="windowName"property="windowName"/>
    </association>
</resultMap>

association:表示进行关联查询单条记录

property:表示关联查询的结果存储在org.sang.model.Food2的seller属性中

javaType:表示关联查询的结果类型

<result property="windowName"column="windowName"/>:查询结果的windowName列对应关联对象的windowName属性。

3.定义查询

<select id="getFood2"parameterType="Long" resultMap="BaseResultMap2">
    SELECT f.*,s.windowName FROM food f,seller s WHEREf.sellerId=s.id AND f.id=#{id}
</select>

4.接口中定义相关方法

public Food2 getFood2(Long id);

5.测试

@Test
public void test16() {
    SqlSession sqlSession = DBUtils.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Food2 food = mapper.getFood2(33l);
    System.out.println(food.toString());
}

9.1.2 一对多查询

Sql语句

查询某一个商户下的所有食物:

SELECT f.*,s.windowName FROM sellers,food f WHERE f.sellerId=s.id AND s.id=#{id}

定义POJO类

定义Seller类,添加List<Food>属性,如下:

定义resultMap

<resultMap id="BaseResultMap3"type="org.sang.model.Seller">
    <result column="windowName" property="windowName"/>
    <collection property="food2s" ofType="org.sang.model.Food2">
        <id column="id"property="id"/>
        <result column="foodImg"property="foodImg"/>
        <result column="foodPrice"property="foodPrice"/>
        <result column="foodName"property="foodName"/>
        <result column="priceType"property="priceType"/>
    </collection>
</resultMap>

collection部分定义了查询订单明细信息。

collection:表示关联查询结果集

property="food2s":关联查询的结果集存储在org.sang.model.Seller上哪个属性。

ofType="org.sang.model.Food2":指定关联查询的结果集中的对象类型即List中的对象类型。

<id />及<result/>的意义同一对一查询。

Mapper.xml

<select id="getSeller"parameterType="Long" resultMap="BaseResultMap3">
    SELECT f.*,s.windowName FROM seller s,food f WHEREf.sellerId=s.id AND s.id=#{id}
</select>

Mapper接口

public Seller getSeller(Long id);

测试

@Test
public void test17() {
    SqlSession sqlSession = DBUtils.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Seller seller = mapper.getSeller(13l);
    List<Food2> food2s = seller.getFood2s();
    System.out.println(food2s.size());
    for (Food2food2 : food2s) {
        System.out.println(food2.toString());
    }
}

小结

使用collection完成关联查询,将关联查询信息映射到集合对象。

9.1.3 多对多查询

9.1.4 resultMap小结

resultType:

作用:将查询结果按照sql列名pojo属性名一致性映射到pojo中。

使用场景:常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。

resultMap:使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。

association:

作用:将关联查询信息映射到一个pojo对象中。

场合:为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。

collection:

作用:将关联查询信息映射到一个list集合中。

场合:为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。如果使用resultType无法将查询结果映射到list集合中。

9.1.5 延迟加载

设置项

描述

允许值

默认值

lazyLoadingEnabled

全局性设置懒加载。如果设为?false?,则所有相关联的都会被初始化加载。

true | false

false

aggressiveLazyLoading

当设置为?true?的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。

true | false

true

开启延迟加载

<settings>

<settingname="lazyLoadingEnabled" value="true"/>

<settingname="aggressiveLazyLoading" value="false"/>

</settings>

一对一查询延迟加载

需求

Sql语句

定义POJO类

Mapper.xml

定义resultMap

Mapper接口

测试

fetchType属性

延迟加载优缺点分析

不使用mybatis提供的延迟加载功能是否可以实现延迟加载?

一对多查询延迟加载

一对多延迟加载的方法同一对一延迟加载,在collection标签中配置select内容。本部分内容自学。

延迟加载小结

作用:

当需要查询关联信息时再去数据库查询,默认不去关联查询,提高数据库性能。

只有使用resultMap支持延迟加载设置。

场合:

当只有部分记录需要关联查询其它信息时,此时可按需延迟加载,需要关联查询时再向数据库发出sql,以提高数据库性能。

当全部需要关联查询信息时,此时不用延迟加载,直接将关联查询信息全部返回即可,可使用resultType或resultMap完成映射。

9.2 查询缓存

9.2.1 MyBatis缓存介绍

Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。

Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。

9.2.2 一级缓存

原理

测试

10 11.OGNL和值栈

10.1 OGNL和值栈

10.1.1 OGNL概述

1 之前web阶段,学习过EL表达式,EL表达式在jsp中获取域对象里面的值

2 OGNL是一种表达式,这个表达式功能更加强大,主要体现在如下方面:

和struts2标签一起使用操作值栈

3 OGNL不是struts2的一部分,单独的项目,经常和struts2一起使用

(1)使用ognl时候首先导入jar包,struts2提供jar包

10.1.2 OGNL入门案例

1 使用ognl+struts2标签实现计算字符串长度

(1)在java代码中,调用字符串.length();

2 使用struts2标签

(1)使用jstl时候,导入jar包之外,在jsp页面中引入标签库

(2)使用struts2标签实现操作

10.1.3 什么是值栈

1 之前在web阶段,在servlet里面进行操作,把数据放到域对象里面,在页面中使用el表达式获取到,域对象在一定范围内,存值和取值

2 在struts2里面提供本身一种存储机制,类似于域对象,是值栈,可以存值和取值,在action里面把数据放到值栈里面,在页面中获取到值栈数据

3 servlet和action区别

(1)Servlet:默认在第一次访问时候创建,创建一次,单实例对象

(2)Action:访问时候创建,每次访问action时候,都会创建action对象,创建多次,多实例对象

4 值栈存储位置

(1)每次访问action时候,都会创建action对象,

(2)在每个action对象里面都会有一个值栈对象(只有一个)

10.1.4 获取值栈对象

使用ActionContext类里面的方法得到值栈对象

10.1.5 值栈内部结构

1 值栈分为两部分:

第一部分 root,结构是list集合,一般操作都是root里面数据

第二部分 context,结构map集合

2 struts2里面标签 s:debug,使用这个标签可以查看值栈结构和存储值

在action没有做任何操作,栈顶元素是 action引用

- action对象里面有值栈对象

- 值栈对象里面有action引用

10.1.6 向值栈存放数据

基本类型数据

1.获取值栈对象,调用值栈对象里面的 set 方法

ValueStack valueStack =ctx.getValueStack();
valueStack.set("username", "张三");

2.获取值栈对象,调用值栈对象里面的  push方法

ValueStack valueStack =ctx.getValueStack();
valueStack.push("李四");

3.在action定义变量,生成变量的get方法(重点)

private String address;

public String getAddress() {
    return address;
}

@Override
public String execute() throws Exception {
    ActionContext ctx = ActionContext.getContext();
    ValueStack valueStack = ctx.getValueStack();
    address = "老王";
    return SUCCESS;
}

存放对象

1.定义对象变量

private User user = new User();

2.生成变量的get方法

public User getUser() {
    return user;
}

3.在执行的方法里面向对象中设置值

@Override
public String execute() throws Exception {
    user.setUsername("张三");
    user.setPassword("1111");
    return SUCCESS;
}

存放List集合

1.定义list集合变量

private List<User> list = new ArrayList<>();

2.生成变量的get方法

public List<User> getList() {
    return list;
}

3.在执行的方法里面向list集合设置值

@Override
public String execute() throws Exception {
    list.add(new User("张三", "zs"));
    list.add(new User("李四", "ls"));
    return SUCCESS;
}

10.1.7 从值栈获取数据

获取字符串

在jsp使用struts2标签+ognl表达式获取

存:

private String address;

public String getAddress() {
    return address;
}
@Override
public String execute() throws Exception {
    list.add(new User("张三", "zs"));
    list.add(new User("李四", "ls"));
    ActionContext ctx = ActionContext.getContext();
    ValueStack valueStack = ctx.getValueStack();
    valueStack.set("username", "王五");
    address = "深圳";
    return SUCCESS;
}

取:

<s:property value="username"/>
<s:property value="address"/>

获取对象

存:

取:

<s:property value="user.username"/><br/>
<s:property value="user.password"/><br/>

获取list集合

存:

取:

方式1:

<s:property value="list[0].username"/><br/>
<s:property value="list[1].username"/><br/>

方式2:

<s:iterator value="list">
    <s:property value="username"></s:property><br/>
    <s:property value="password"></s:property><br/>
</s:iterator>

方式3:

<s:iterator value="list" var="user">
    <s:property value="#user.username"/>
    <s:property value="#user.password"/>
</s:iterator>

这种方式是遍历值栈list集合,得到每个User对象,然后把每次遍历出来的User对象放到Context里面,然后获取Context中的数据。注意前面添加#

其他操作

push中存值:

valueStack.push("风起");
valueStack.push("马坝");

取出push中的值:

<s:property value="[0].top"/>
<s:property value="[1].top"/>

使用push方法设置值,没有名称,只有设置的值,向值栈放数据,把向值栈放数据存到数组里面,数组名称 top,根据数组获取值

10.1.8 EL表达式获取值栈数据

首先从request域获取值,如果获取到,直接返回

如果从request域获取不到值,到值栈中把值获取出来,把值放到域对象里面

10.1.9 OGNL的#、%的使用

#

1.使用#获取context里面数据

参考遍历list集合中的第三种方式

2.获取request域中的值

<s:property value="#request.username"/>

效果类似于

<%=request.getAttribute("username")%><br/>

%

在struts2标签里面使用ognl表达式,如果直接在struts2表单标签里面使用ognl表达式不识别,只有%之后才会识别。

<s:textfield name="username" value="%{#request.username}"></s:textfield>

11 12.Hibernate初级

11.1  Hibernate概述

11.1.1 什么是框架

写程序,使用框架之后,帮我们实现一部分功能,使用框架好处,少写一部分代码实现功能

11.1.2 什么是ORM思想

什么是ORM

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和收费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。

ORM思想在Hibernate中的应用

在ORM思想中,让实体类和数据库表进行一一对应,让实体类首先和数据库表对应,让实体类属性和 表里面字段对应,同时不需要直接操作数据库表,而操作表对应实体类对象

---------------------------------------------------------------------------------------------------

Hibernate是一个数据持久化层的ORM框架.

Object:对象,java对象,此处特指JavaBean

Relational:关系,二维表,数据库中的表。

Mapping:映射|映射元数据:对象中属性,与表的字段,存在对应关系。

11.1.3 什么是hibernate框架

Hibernate是一种Java语言下的对象关系映射解决方案。它是使用GNU宽通用公共许可证发行的自由、开源的软件。它为面向对象的领域模型到传统的关系型数据库的映射,提供了一个使用方便的框架。它的设计目标是将软件开发人员从大量相同的数据持久层相关编程工作中解放出来。无论是从设计草案还是从一个遗留数据库开始,开发人员都可以采用Hibernate。Hibernate不仅负责从Java类到数据库表的映射(还包括从Java数据类型到SQL数据类型的映射),还提供了面向对象的数据查询检索机制,从而极大地缩短的手动处理SQL和JDBC上的开发时间。

1 hibernate框架应用在javaee三层结构中 dao层框架

2 在dao层里面做对数据库crud操作,使用hibernate实现crud操作,hibernate底层代码就是jdbc,hibernate对jdbc进行封装,使用hibernate好处,不需要写复杂jdbc代码了,不需要写sql语句实现

3 hibernate开源的轻量级的框架

4 hibernate版本

Hibernate3.x

Hibernate4.x

Hibernate5.x(学习)

--------------------------------------------------------------------------------------------------------

1.hibernate框架应用在javaee三层结构中 dao层框架

2.在dao层里面做对数据库crud操作,使用hibernate实现crud操作,hibernate底层代码就是jdbc,hibernate对jdbc进行封装,使用hibernate好处,不需要写复杂jdbc代码了,不需要写sql语句实现

3.hibernate开源的轻量级的框架

11.2  Hibernate入门

11.2.1 搭建Hibernate环境

一、jar包导入

1.下载Hibernate的jar文件

http://hibernate.org/orm/

2.将下载的文件解压,如下:

3.将lib文件中required文件夹下的jar包拷贝到项目中,如下:

4.导入日志输出jar包和MySql驱动jar

二、实体类创建

注意:

1.使用Hibernate时不需要我们手动自己创建表,Hibernate会自动帮我们创建

2.实体类中需要有一个属性用来描述表的id

三、配置实体类和数据库表的映射关系

1.创建xml格式的配置文件,文件位置和名称并无严格要求,但是一般建议放在实体类所在的包路径下,名称建议取为:

实体类名.hbm.xml,如下:

2.xml文件中首先引入约束文件,引入方式:

搜到后将该文件的头信息拷贝到我们的user.hbm.xml中,如下:

3.配置映射关系

四.创建hibernate的核心配置文件

1.核心配置文件的名称和位置是固定的,名称必须为hibernate.cfg.xml,文件位置必须在src下,如下:

2.引入约束文件,引入方式和上文类似,还是在project中搜索,搜索文件为hibernate.cfg.xml,要引入的文件如下:

3.配置核心文件,核心文件共分为三大块,分别是数据库信息、hibernate信息和映射文件信息

3.1数据库信息配置

3.2配置Hibernate信息(模板文件在hibernate.properties.template中),注意这里的方言问题,不同版本对应的方言有差异

3.3配置映射文件路径

3.4MySql中创建相应的数据库:

五、测试

Hibernate在调用的过程中,整体上可以分为七个步骤,如下:

1.加载Hibernate核心配置文件

2.创建SessionFactory对象

3.使用SessionFactory对象创建Session对象

4.开启事务

5.开始CRUD操作

6.提交事务

7.关闭资源

11.2.2 实现添加操作

11.3  Hibernate配置文件详解

11.3.1 Hibernate映射配置文件

1 映射配置文件名称和位置没有固定要求

2 映射配置文件中,标签name属性值为实体类属性名称

(1)class标签name属性值为实体类全路径

(2)id标签和property标签name属性值为实体类属性名称

3 id标签和property标签,column属性可以省略的

(1)不写值和name属性值一样的

4 property标签type属性,设置生成表字段的类型,默认自动对应类型

11.3.2 Hibernate核心配置文件

1 配置文件位置要求

(1)配置文件名必须为hibernate.cfg.xml

(2)配置文件的位置必须是src目录下

2 配置三部分要求

(1)数据库部分必须的

(2)hibernate部分可选的

(3)映射文件必须的

3 映射文件可以在xml中配置,也可以在java代码中添加,一般建议直接写在xml中。

通过Java代码添加,方式如下:

还可以这样:

第二种方式要求映射文件的命名必须为实体类名.hbm.xml,否则会报如下异常:

11.4  Hibernate核心API

11.4.1 Configuration

下面代码含义表示去src目录下查找hibernate.cfg.xml文件

11.4.2 SessionFactory

1.当调用Configuration的buildSessionFactory方法时,Hibernate会根据实际情况将表创建出来。

2.SessionFactory在创建的过程中要解析xml文件,耗费资源,所以一般建议对SessionFactory提供工具类,一个项目中创建一个SessionFactory即可,so,SessionFactory不用关闭,如下:

11.4.3 Session

(1)Session是单线程对象,只能有一个操作时候,不能同时多个操作使用,不要把Session变量定义成成员变量,每次使用都创建新对象,相当于JDBC的Connection。

(2)Session是应用程序与数据库之间交互操作的一个单线程对象,是 Hibernate 运作的中心;Session是线程不安全的。

(3)所有持久化对象必须在Session 的管理下才可以进行持久化操作。

(4)Session 对象有一个一级缓存,显式执行 flush 之前,所有的持久化操作的数据都缓存在 session 对象处。

(5)持久化类与 Session 关联起来后就具有了持久化的能力。

Session常用方法:

方法

作用

save()/persist()、update()、saveOrUpdate()

添加和修改对象

delete()

删除对象

get()/load()

根据主键查询

createQuery()、createSQLQuery()

数据库操作对象

createCriteria()

条件查询

11.4.4 Transaction

(1)hibernate对数据库的操作都是封装在事务transaction当中,并且默认是非自动提交的方式,所以用session保存对象时,若不开启事务,并手工提交事务,对象是不会保存到数据库当中的。

(2)如果想让Hibernate像jdbc那样自动提交事务,可以不开启事务,用session的doWork()方法,并设置自动提交事务,在保存对象之后,可以用session.flush();来输出SQL语句。但一般是不建议用这种方法,都是用事务来封装对数据库的操作。如下:

11.4.5 openSession与getCurrentSession

(1)openSession 每次使用都是创建一个新的session;getCurrentSession 是获取当前session对象,连续使用多次时,得到的session都是同一个对象。

(2)getCurrentSession在事务提交或者回滚之后会自动关闭;而openSession需要手动关闭,如果使用openSession而没有手动关闭,多次使用之后会导致连接池溢出。

(3)一般在实际开发中,往往使用getCurrentSession多,因为一般是处理同一个事务,所以在一般情况下比较少使用openSession。

注意,使用getCurrentSession要添加如下配置:

这个配置的含义表示将Session和本地线程(ThreadLocal)绑定在一起

11.5  Hibernate主键生成策略

Hibernate主键生成策略

* native 主键自增长

* uuid 随机生成UUID(注意此时id字段的类型为String)

其他策略:

11.6  实体类创建规则

* 严格遵守Java中的封装思想,属性全部都是私有的,通过get/set方法进行操作

* 实体类中要有属性提供唯一值,一般情况下就是id

* 实体类中建议使用包装数据类型,而不是基本数据类型,因为基本数据类型都有默认值,不能充分满足我们的需求,比如该字段值为null,如果使用基本数据类则无法实现

12 13.Hibernate高级

12.1  实体类操作

12.1.1 添加

SessionFactory sessionFactory= SessionFactoryUtils.getSessionFactory();
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
session.save(new Book(null, "三国演义", "罗贯中"));
tx.commit();
session.close();

12.1.2 根据id查询

(1)get()/load()方法

通过get方法结合id查询,如下:

通过load方法结合id查询,如下:

(2)get()/load()方法的区别

get() / load() 会首先从一级缓存中取,如果没有,两者才会有不同的操作:get 会立即向数据库发请求,而load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求

12.1.3 修改操作

修改的步骤是先查询,再修改。

12.1.4 删除操作

先查询,再删除

12.2  实体类对象状态

12.2.1 三种状态介绍

(1)瞬时态(Transient Object):对象里没有id值,对象和session没有关联,处于瞬时态的实体类对象可以用来做添加操作。

(2)托管态(Detached Object):对象里有id值,对象和session没有关联,处于托管态的实体类对象可以用来做修改操作

也可以调用saveOrUpdate方法,该方法会自动判断该实例是瞬时态还是托管态,然后根据判断结果执行添加或者修改操作。

(3)持久态(Persistent Object):对象里有id值,对象和session有关联,持久态的对象可以用来做修改操作

//持久态的对象具有自动更新数据库的能力

12.2.2 三种状态之间的转换

* 获得瞬时态的对象

User user = new User();

创建了持久化类的对象,该对象还没有 OID,也和 Session 对象无关,所以是瞬时态。

* 瞬时态对象转换持久态

session.save(user); 或者 session.saveOrUpdate(user);

user对象进入缓存,且自动生成了 OID,故为持久态。

* 瞬时态对象转换成托管态

user.setId(1);

手动设置了 OID,但没有和 Session 对象发生关系,故为托管态。

*获得持久态的对象

get()/load();

* 持久态转为托管态

*session的close()/evict()/clear();

Session 对象被销毁,所有持久化类的对象没有被 session 管理,所以为托管态。

*获得托管态对象:不建议直接获得脱管态的对象.

User user = new User();

user.setId(1);

*脱管态对象转换成持久态对象

update();/saveOrUpdate()/lock();

*脱管态对象转换成瞬时态对象

user.setId(null);

12.3  Hibernate的一级缓存

12.3.1 什么是缓存

数据存到数据库里面,数据库本身是文件系统,使用流方式操作文件效率不是很高。

(1)把数据存到内存里面,不需要使用流方式,可以直接读取内存中数据

(2)把数据放到内存中,提供读取效率

12.3.2 Hibernate缓存

1 hibernate框架中提供很多优化方式,hibernate的缓存就是一个优化方式

2 hibernate缓存特点:

第一类 hibernate的一级缓存

(1)hibernate的一级缓存默认打开的

(2)hibernate的一级缓存使用范围,是session范围,从session创建到session关闭范围

(3)hibernate的一级缓存中,存储数据必须是持久态数据

第二类 hibernate的二级缓存

(1)目前已经不使用了,替代技术 redis

(2)二级缓存默认不是打开的,需要配置

(3)二级缓存使用范围,是sessionFactory范围

12.3.3 验证缓存

每次都从数据库中查询数据效率略低,so,可以使用Hibernate中提供的一级缓存,Hibernate中,默认情况下一级缓存是打开的,一级缓存的范围是从Session创建到Session关闭的范围。二级缓存目前不再使用,不做了解。

(1)基本验证

如下,连续查询同一条数据,第二次不再发送SQL语句,每次查询时都先去缓存中查到,缓存中没有才去数据库中查找:

(2)持久态对象有自动更新数据库的能力

代码如下:

不用手动更新,Hibernate会自动更新。

12.3.4 Hibernate一级缓存执行过程

12.3.5 Hibernate一级缓存特性

持久态自动更新数据库

12.4  Hibernate事务操作

12.4.1 事务相关概念

概要

一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:

* 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。

* 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。

当事务被提交给了DBMS(数据库管理系统),则DBMS(数据库管理系统)需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

但在现实情况下,失败的风险很高。在一个数据库事务的执行过程中,有可能会遇上事务操作失败、数据库系统/操作系统失败,甚至是存储介质失败等情况。这便需要DBMS对一个执行失败的事务执行恢复操作,将其数据库状态恢复到一致状态(数据的一致性得到保证的状态)。为了实现将数据库状态恢复到一致状态的功能,DBMS通常需要维护事务日志以追踪事务中所有影响数据库数据的操作。

ACID性质

并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为ACID特性。

原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行

一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。

隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

12.4.2 Hibernate事务代码规范写法

12.4.3 Hibernate绑定session

参考openSession与getCurrentSession一节

12.5  Hibernate的API的使用

1 使用query对象,不需要写sql语句,需要写hql语句

(1)hql:hibernate query language,hibernate提供查询语言,这个hql语句和普通sql语句很相似

(2)hql和sql语句区别:

使用sql操作表和表字段

使用hql操作实体类和属性

2 查询所有hql语句:

(1)from 实体类名称

3 Query对象使用

(1)创建Query对象

(2)调用query对象里面的方法得到结果

12.5.1 Query对象

使用createQuery查询(底层是SQL查询)

12.5.2 Criteria对象

(1)使用这个对象查询操作,但是使用这个对象时候,不需要写语句,直接调用方法实现

(2)实现过程

*创建criteria对象

*调用对象里面的方法得到结果

如下:

12.5.3 SQLQuery对象

(1)使用hibernate时候,调用底层sql实现

(2)实现过程

*创建对象

*调用对象的方法得到结果

12.6  Hibernate一对多操作

案例:每个省份都有n个市县,省和市县就是一对多的关系

1.创建省份和市县实体类,如下:

一个城市属于一个省,一个省中有多个城市。

2.配置两个实体类的映射文件

city.hbm.xml

province.hbm.xml

向hibernate核心配置文件中添加映射文件:

3.测试:向两张表中添加数据:

4.上面这种操作略麻烦,我希望当我存储Province的时候能够自动更新City表,这时可做如下配置:

然后存储操作可以简化为如下方式:

同时,我们使用了级联,此时不能直接删除Province表,也不能直接删除Province表中的数据,直接删除会有如下异常:

为了维护数据的完整,当我们删除一个Province的时候,也要删除该Province下的所有City,因此,我们可以做如下配置

再执行删除动作,就正常了,如下:

12.7  Hibernate查询

12.7.1 对象导航查询

12.7.2 OID查询

get()/load()方法实现

12.7.3 HQL查询

1 hql:hibernate query language

hibernate提供的一种查询语言,hql语言和普通sql很相似,区别:普通sql操作数据库表和字段,hql操作实体类和属性

2 常用的hql语句

(1)查询所有: from实体类名称

(2)条件查询: from实体类名称 where 属性名称=?

(3)排序查询: from实体类名称 order by 实体类属性名称 asc/desc

3 使用hql查询操作时候,使用Query对象

(1)创建Query对象,写hql语句

(2)调用query对象里面的方法得到结果

查询所有

查询所有: from 实体类名称

条件查询

from 实体类名称 where 实体类属性名称=? and实体类属性名称=?

from 实体类名称 where 实体类属性名称 like ?

排序查询

from 实体类名称 order by 实体类属性名称 asc/desc

分页查询

在hql操作中,在语句里面不能写limit,hibernate的Query对象封装两个方法实现分页操作

投影查询

1 投影查询:查询不是所有字段值,而是部分字段的值

2 投影查询hql语句写法:

(1)select 实体类属性名称1, 实体类属性名称2 from 实体类名称

(2)select 后面不能写 * ,不支持的

聚合函数使用

1 常用的聚合函数

(1)count、sum、avg、max、min

2 hql聚合函数语句写法

(1)查询表记录数

- select count(*) from 实体类名称

12.7.4 QBC查询

1 使用hql查询需要写hql语句实现,但是使用qbc时候,不需要写语句了,使用方法实现

2 使用qbc时候,操作实体类和属性

3 使用qbc,使用Criteria对象实现

查询所有

1 创建Criteria对象

2 调用方法得到结果

条件查询

没有语句,使用封装的方法实现

排序查询

分页查询

开始位置计算公式: (当前页-1)*每页记录数

统计查询

Subtopic

13 14.SpringBoot

13.1 SpringBoot

13.1.1 初识

初识Spring Boot

使用Spring Boot可以让我们快速创建一个基于Spring的项目,而让这个Spring项目跑起来我们只需要很少的配置就可以了。Spring Boot主要有如下核心功能:

1.独立运行的Spring项目

2.内嵌Servlet容器

3.提供starter简化Maven配置

4.自动配置Spring

5.准生产的应用监控

6.无代码生成和xml配置

创建一个Spring Boot工程

1.在IDEA中选择Spring Boot选项

2.填入Maven坐标和工程名(Spring Boot是一个Maven工程)

3.选择要添加的依赖,Web作为最基本的依赖。

4.选择工程的位置,点击finish 按钮

5.创建成功后,会有一个artifactId+Application命名规则的入口类,如下:

6.@SpringBootApplication注解,这是整个Spring Boot的核心注解,它的目的就是开启Spring Boot的自动配置。OK,那么我在这个类上再添加一个@RestController注解,使之变为一个Controller,然后里边提供一个地址转换方法,如下:

@SpringBootApplication
@RestController
public class Sb2Application {

public static void main(String[] args) {
         SpringApplication.run(Sb2Application.class, args);
    }
    @RequestMapping(value = "/",produces = "text/html;charset=utf-8")
    String index() {
         return "hello Spring Boot!";
    }
}

@SpringBootApplication详解

如上,是@SpringBootApplication源码:我们可以看到它组合了@SpringBootConfiguration、@EnableAutoConfiguration以及@ComponentScan,我们在开发的过程中如果不使用@SpringBootApplication,则可以组合使用这三个注解。这三个注解中,@SpringBootConfiguration实际上就是@Configuration注解,表明这个类是一个配置类,@EnableAutoConfiguration则表示让Spring Boot根据类路径中的jar包依赖为当前项目进行自动配置,最后一个@ComponentScan的作用就是包扫描,唯一要注意的是如果我们使用了@SpringBootApplication注解的话,系统会去入口类的同级包以及下级包中去扫描实体类,因此我们建议入口类的位置在groupId+arctifactID组合的包名下。

Banner操作

定制Banner

1.在src/main/resources下新建一个banner.txt文档

2.通过http://patorjk.com/software/taag网站生成需要的字符,将字符拷贝到步骤1所创建的txt文档中,比如我这里为HelloSang!生成字符,如下:

关闭Banner

public static void main(String[] args) {
    SpringApplicationBuilder builder = new SpringApplicationBuilder(Sb2Application.class);
    builder.bannerMode(Banner.Mode.OFF).run(args);
}

server配置

Spring Boot使用一个全局的配置文件application.properties或者application.yml,配置文件放在src/main/resources目录下。properties是我们常见的一种配置文件,Spring Boot不仅支持properties这种类型的配置文件,也支持yaml语言的配置文件。

1.修改Tomcat默认端口和默认访问路径

Tomcat默认端口是8080,将之改为8081,默认访问路径是http://localhost:8080,我将之改为http://localhost:8081/helloboot,

在application.properties文件中添加如下代码

server.context-path=/helloboot

server.port=8081

---------------------------------------------------------

其他配置:

server.port=8081#配置服务器端口,默认为8080

server.session-timeout=1000000#用户回话session过期时间,以秒为单位

server.context-path=/index#配置访问路径,默认为/

server.tomcat.uri-encoding=UTF-8#配置Tomcat编码,默认为UTF-8

server.tomcat.compression=on#Tomcat是否开启压缩,默认为关闭

Profile配置问题

全局Profile配置我们使用application-{profile}.properties来定义,然后在application.properties中通过spring.profiles.active来指定使用哪个Profile。

在src/main/resources文件夹下定义不同环境下的Profile配置文件,文件名分别为application-prod.properties和application-dev.properties,这两个前者表示生产环境下的配置,后者表示开发环境下的配置,如下:

13.1.2 使用SpringBoot开发web工程

Spring Boot 提供了spring-boot-starter-web来为Web开发予以支持,spring-boot-starter-web为我们提供了嵌入的Tomcat以及SpringMVC的依赖,用起来很方便。另外,我们这里还要用到模板引擎,我们做web开发可选的模板引擎还是挺多的,这里我主要使用Thymeleaf作为模板引擎,事实上,Spring Boot提供了大量的模板引擎,包括FreeMarker、Groovy、Thymeleaf、Velocity和Mustache,在 提供的这么多中它推荐使用Thymeleaf。Thymeleaf在使用的过程中通过ThymeleafAutoConfiguration类对集成所需要的Bean进行自动配置,通过ThymeleafProperties来配置Thymeleaf,包括前缀后缀什么的,我们可以查看ThymeleafProperties一段源码:

从这一段源码中我们可以看到默认的页面后缀名为.html,前缀为classpath:/templates/,实际上也就是我们需要把html页面放到resources文件夹下的templates文件夹中。同时我们也看到了要如何修改这个配置,在application.properties文件中以spring.thymeleaf为前缀来配置相关属性。

1.创建Web工程时注意选择Thymeleaf依赖,如下:

2.创建一个JavaBean:

public class Person {
    private String name;
    private Integer age;

public Person() {
    }

public Person(String name, Integer age) {
        this.name= name;
        this.age= age;
    }

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;
    }
}

3.构造映射器

@Controller
public class HelloController {
    @RequestMapping("/")
    public String index(Model model) {
        Person single = new Person("aa", 11);
        List<Person> people = new ArrayList<>();
        Person p1 = new Person("zhangsan", 11);
        Person p2 = new Person("lisi", 22);
        Person p3 = new Person("wangwu", 33);
        people.add(p1);
        people.add(p2);
        people.add(p3);
        model.addAttribute("singlePerson", single);
        model.addAttribute("people", people);
        return "index";
    }
}

4.默认情况下前台页面要放在src/main/resources/templates目录下,so,我们在该目录下新建文件就叫index.html,如下<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>首页</title>
</head>
<body>
<table border="1">
    <tr>
        <td>
            <span th:text="${singlePerson.name}"></span></td>
    </tr>
    <tr>
        <td>
            <table border="1"th:each="person:${people}">
                <tr><td th:οnclick="'getName(\''+${person.name}+'\');'"><span th:text="${person.name}"></span></td><td><span th:text="${person.age}"></span></td></tr>
            </table>
        </td>
    </tr>
</table>
<script th:inline="javascript">
    var single =[[${singlePerson}]];
    console.log(single.name+"/"+single.age);
    function getName(name) {
        alert(name);
    }
</script>
</body>
</html>

首先通过xmlns:th="http://www.thymeleaf.org"导入命名空间,在后期时候的时候,由于html本身是静态视图,在使用相关属性的时候加上th:前缀可以使之变为动态视图。th:href="@{bootstrap/css/bootstrap.min.css}"表示引用Web静态资源。

1.th:text="${singlePerson.name}"表示访问model中singlePerson的name属性

2.th:if="${not#lists.isEmpty(people)}"表示判断model中的people集合是否为空

3.th:each="person:${people}"表示遍历people中的元素,这个和Java里的foreach差不多,person表示迭代元素

4.th:οnclick="'getName(\''+${person.name}+'\');'"表示添加点击事件,点击事件由JavaScript来处理。th:inline="javascript"这样添加到的script标签可以通过[[${singlePerson}]]访问model中的属性。

14 15.JPA基础知识

14.1 JPA简介

14.1.1 JPA是什么

1.JDBC是什么?

2.JPA是什么?

(1).Java Persistence API:用于对象持久化的 API

(2).Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层

14.1.2 JPA和Hibernate的关系

1.JPA 是 hibernate 的一个抽象(就像JDBC和JDBC驱动的关系):

2.JPA 是规范:JPA 本质上就是一种  ORM 规范,不是ORM 框架 —— 因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现

3.Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现

4.从功能上来说, JPA 是Hibernate 功能的一个子集

14.1.3 JPA的供应商

JPA 的目标之一是制定一个可以由很多供应商实现的 API,Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的实现

1.Hibernate

JPA 的始作俑者就是Hibernate 的作者

2.Hibernate 从 3.2 开始兼容 JPA

3.OpenJPA

OpenJPA 是 Apache 组织提供的开源项目

4.TopLink

TopLink 以前需要收费,如今开源了

14.1.4 JPA的优势

1.标准化:  提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。

2.简单易用,集成方便:  JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java  类一样简单,只需要使用 javax.persistence.Entity进行注释;JPA 的框架和接口也都非常简单,

3.可媲美JDBC的查询能力: JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。

4.支持面向对象的高级特性: JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,最大限度的使用面向对象的模型

14.1.5 JPA包含的技术

1.ORM 映射元数据:JPA 支持 XML 和  JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。

2.JPA 的 API:用来操作实体对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的 JDBC和 SQL代码中解脱出来。

3.查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的  SQL 紧密耦合。

14.2 JPAHelloWorld

14.2.1 使用JPA持久化对象的步骤

1.创建 persistence.xml, 在这个文件中配置持久化单元

需要指定跟哪个数据库进行交互;

需要指定 JPA 使用哪个持久化的框架以及配置该框架的基本属性

2.创建实体类, 使用 annotation 来描述实体类跟数据库表之间的映射关系.

3.使用 JPA API 完成数据增加、删除、修改和查询操作

创建 EntityManagerFactory (对应 Hibernate 中的 SessionFactory);

创建EntityManager (对应 Hibernate 中的Session);

14.2.2 工程创建

14.2.3 开发JPA依赖的jar文件

14.2.4 实体类

@Entity(name = "t_book")
public class Book {
    private Long id;
    private String name;
    private String author;

@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }

public void setId(Long id) {
        this.id = id;
    }

public String getName() {
        return name;
    }

public void setName(String name) {
        this.name= name;
    }

public String getAuthor() {
        return author;
    }

public void setAuthor(String author) {
        this.author= author;
    }
}

14.2.5 persistence.xml

JPA 规范要求在类路径的 META-INF 目录下放置persistence.xml,文件的名称是固定的

<?xml version="1.0"encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">

<persistence-unit name="NewPersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>org.sang.Book</class>
        <properties>
            <property name="hibernate.connection.url"
                      value="jdbc:mysql:///jpa01?useUnicode=true&amp;characterEncoding=UTF-8"
/>
            <property name="hibernate.connection.driver_class"value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.username"value="root"/>
            <property name="hibernate.connection.password"value="123"/>
            <property name="hibernate.archive.autodetection"value="class"/>
            <property name="hibernate.show_sql"value="true"/>
            <property name="hibernate.format_sql"value="true"/>
            <property name="hibernate.hbm2ddl.auto"value="update"/>
        </properties>
    </persistence-unit>
</persistence>

注意:

1.persistence-unit  的name属性用于定义持久化单元的名字, 必选

2.transaction-type:指定 JPA  的事务处理策略。RESOURCE_LOCAL:默认值,数据库级别的事务,只能针对一种数据库,不支持分布式事务。如果需要支持分布式事务,使用JTA:transaction-type="JTA"

3.class节点表示显式的列出实体类

4.properties中的配置分为两部分:数据库连接信息以及Hibernate信息

5.

14.2.6 执行持久化操作

EntityManagerFactory entityManagerFactory= Persistence.createEntityManagerFactory("NewPersistenceUnit");
EntityManager manager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
Book book = new Book();
book.setAuthor("罗贯中");
book.setName("三国演义");
manager.persist(book);
transaction.commit();
manager.close();
entityManagerFactory.close();

15 16.JPA高级

15.1 JPA API

15.1.1 JPAEntityManager

1.JPA相关接口/类:Persistence:

1.Persistence  类是用于获取 EntityManagerFactory 实例。该类包含一个名为createEntityManagerFactory 的 静态方法 。

2.createEntityManagerFactory 方法有如下两个重载版本。

带有一个参数的方法以 JPA 配置文件 persistence.xml 中的持久化单元名为参数

带有两个参数的方法:前一个参数含义相同,后一个参数 Map类型,用于设置 JPA 的相关属性,这时将忽略其它地方设置的属性。Map 对象的key必须是 JPA 实现库提供商的名字空间约定的属性名。

2.EntityManagerFactory 接口主要用来创建 EntityManager 实例。该接口约定了如下4个方法:

createEntityManager():用于创建实体管理器对象实例。

createEntityManager(Map map):用于创建实体管理器对象实例的重载方法,Map 参数用于提供 EntityManager 的属性。

isOpen():检查 EntityManagerFactory 是否处于打开状态。实体管理器工厂创建后一直处于打开状态,除非调用close()方法将其关闭。

close():关闭 EntityManagerFactory 。 EntityManagerFactory关闭后将释放所有资源,isOpen()方法测试将返回false,其它方法将不能调用,否则将导致IllegalStateException异常。

3.EntityManager

1.在 JPA规范中, EntityManager 是完成持久化操作的核心对象。实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。

2.实体的状态:

(1).新建状态:   新创建的对象,尚未拥有持久性主键。

(2).持久化状态:已经拥有持久性主键并和持久化建立了上下文环境

(3).游离状态:拥有持久化主键,但是没有与持久化建立上下文环境

(4).删除状态:  拥有持久化主键,已经和持久化建立上下文环境,但是从数据库中删除。

3.find(Class<T> entityClass,Object primaryKey):返回指定的 OID 对应的实体类对象,如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值。

4.getReference(Class<T> entityClass,Object primaryKey):与find()方法类似,不同的是:如果缓存中不存在指定的Entity, EntityManager 会创建一个 Entity 类的代理,但是不会立即加载数据库中的信息,只有第一次真正使用此 Entity 的属性才加载,所以如果此 OID 在数据库不存在,getReference() 不会返回 null 值, 而是抛出EntityNotFoundException。

5.persist(Object entity):用于将新创建的Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态。

如果传入 persist() 方法的 Entity 对象已经处于持久化状态,则 persist() 方法什么都不做。

如果对删除状态的 Entity 进行 persist() 操作,会转换为持久化状态。

如果对游离状态的实体执行 persist() 操作,可能会在 persist() 方法抛出 EntityExistException(也有可能是在flush或事务提交后抛出)。

6.remove(Object entity):删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录。

7.merge (Tentity):merge() 用于处理 Entity 的同步。即数据库的插入和更新操作

8.flush ():同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。

9.setFlushMode(FlushModeType flushMode):设置持久上下文环境的Flush模式。参数可以取2个枚举

FlushModeType.AUTO 为自动更新数据库实体,

FlushModeType.COMMIT 为直到提交事务时才更新数据库记录。

getFlushMode ():获取持久上下文环境的Flush模式。返回FlushModeType类的枚举值。

10.refresh(Object entity):用数据库实体记录的值更新实体对象的状态,即更新实例的属性值。

11.clear ():清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。

12.contains(Object entity):判断一个实例是否属于当前持久上下文环境管理的实体。

13.isOpen ():判断当前的实体管理器是否是打开状态。

14.getTransaction():返回资源层的事务对象。EntityTransaction实例可以用于开始和提交多个事务。

15.close ():关闭实体管理器。之后若调用实体管理器实例的方法或其派生的查询对象的方法都将抛出 IllegalstateException 异常,除了getTransaction 和 isOpen方法(返回 false)。不过,当与实体管理器关联的事务处于活动状态时,调用 close 方法后持久上下文将仍处于被管理状态,直到事务完成。

16.createQuery(String qlString):创建一个查询对象。

17.createNamedQuery(String name):根据命名的查询语句块创建查询对象。参数为命名的查询语句。

18.createNativeQuery(String sqlString):使用标准SQL语句创建查询对象。参数为标准SQL语句字符串。

19.createNativeQuery(String sqls, String resultSetMapping):使用标准SQL语句创建查询对象,并指定返回结果集 Map的 名称。

15.1.2 JPAEntityTransaction

EntityTransaction 接口用来管理资源层实体管理器的事务操作。通过调用实体管理器的getTransaction方法获得其实例。

1.begin ()用于启动一个事务,此后的多个数据库操作将作为整体被提交或撤消。若这时事务已启动则会抛出IllegalStateException 异常。

2.commit ()用于提交当前事务。即将事务启动以后的所有数据库更新操作持久化至数据库中。

3.rollback ()撤消(回滚)当前事务。即撤消事务启动后的所有数据库更新操作,从而不对数据库产生影响。

4.setRollbackOnly ()使当前事务只能被撤消。

5.getRollbackOnly ()查看当前事务是否设置了只能撤消标志。

6.isActive ()查看当前事务是否是活动的。如果返回true则不能调用begin方法,否则将抛出 IllegalStateException 异常;如果返回 false 则不能调用 commit、rollback、setRollbackOnly及 getRollbackOnly 方法,否则将抛出IllegalStateException 异常。

15.1.3 JPA 映射单向多对一关联关系

15.1.4 JPA 映射单向一对多关联关系

15.1.5 JPA 映射双向一对多的关联关系

15.2 JPQL

1.JPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL查询,从而屏蔽不同数据库的差异。JPQL语言的语句可以是select 语句、update 语句或delete语句,它们都通过 Query 接口封装执行

2.Query接口封装了执行数据库查询的相关方法。调用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。

3.Query接口的主要方法

int executeUpdate()

用于执行update或delete语句。

List getResultList()

用于执行select语句并返回结果集实体列表。

Object getSingleResult()

用于执行只返回单个结果实体的select语句。

Query setFirstResult(int startPosition)

用于设置从哪个实体记录开始返回查询结果。

Query setMaxResults(int maxResult)

用于设置返回结果实体的最大数。与setFirstResult结合使用可实现分页查询。

Query setFlushMode(FlushModeType flushMode)

设置查询对象的Flush模式。参数可以取2个枚举值:FlushModeType.AUTO 为自动更新数据库记录,FlushMode Type.COMMIT 为直到提交事务时才更新数据库记录。

setHint(String hintName, Object value)

设置与查询对象相关的特定供应商参数或提示信息。参数名及其取值需要参考特定JPA 实现库提供商的文档。如果第二个参数无效将抛出IllegalArgumentException异常。

setParameter(int position, Object value)

为查询语句的指定位置参数赋值。Position 指定参数序号,value 为赋给参数的值。

setParameter(int position, Date d, TemporalType type)

为查询语句的指定位置参数赋 Date 值。Position 指定参数序号,value 为赋给参数的值,temporalType 取 TemporalType 的枚举常量,包括 DATE、TIME 及TIMESTAMP 三个,,用于将 Java 的 Date 型值临时转换为数据库支持的日期时间类型(java.sql.Date、java.sql.Time及java.sql.Timestamp)。

setParameter(int position, Calendar c, TemporalType type)

为查询语句的指定位置参数赋 Calenda r值。position 指定参数序号,value 为赋给参数的值,temporalType 的含义及取舍同前。

setParameter(String name, Object value)

为查询语句的指定名称参数赋值。

setParameter(String name, Date d, TemporalType type)

为查询语句的指定名称参数赋 Date 值。用法同前。

setParameter(String name, Calendar c, TemporalType type)

为查询语句的指定名称参数设置Calendar值。name为参数名,其它同前。该方法调用时如果参数位置或参数名不正确,或者所赋的参数值类型不匹配,将抛出 IllegalArgumentException 异常。

15.2.1 Select语句

select语句用于执行查询。其语法可表示为:

select_clause

form_clause

[where_clause]

[groupby_clause]

[having_clause]

[orderby_clause]

------------------------------------------------------------

from 子句是查询语句的必选子句。

Select 用来指定查询返回的结果实体或实体的某些属性

From 子句声明查询源实体类,并指定标识符变量(相当于SQL表的别名)。

如果不希望返回重复实体,可使用关键字 distinct 修饰。select、from 都是 JPQL 的关键字,通常全大写或全小写,建议不要大小写混用。

------------------------------------------------------

查询所有数据:

查询所有实体的 JPQL 查询字串很简单,例如:

select o from Order o 或  select o from Order as o

关键字 as 可以省去。

标识符变量的命名规范与 Java 标识符相同,且区分大小写。

调用 EntityManager 的 createQuery() 方法可创建查询对象,接着调用 Query 接口的 getResultList() 方法就可获得查询结果集。例如:

Query query = entityManager.createQuery("select o from Order o");

List orders = query.getResultList();

Iterator iterator = orders.iterator();

while( iterator.hasNext() ) {

// 处理Order

}

15.2.2 Where子句

where子句用于指定查询条件,where跟条件表达式。例:

select o from Orders o whereo.id = 1

select o from Orders o whereo.id > 3 and o.confirm = 'true'

select o from Orders o whereo.address.streetNumber >= 123

JPQL也支持包含参数的查询,例如:

select o from Orders o whereo.id = :myId

select o from Orders o whereo.id = :myId and o.customer = :customerName

 注意:参数名前必须冠以冒号(:),执行查询前须使用Query.setParameter(name, value)方法给参数赋值。

---------------------------------------------------------------------、

也可以不使用参数名而使用参数的序号,例如:

select o from Order o where o.id = ?1 ando.customer = ?2

其中 ?1 代表第一个参数,?2 代表第一个参数。在执行查询之前需要使用重载方法Query.setParameter(pos,value) 提供参数值。

Query query = entityManager.createQuery("select o from    Orderso where o.id = ?1 and o.customer = ?2" );

query.setParameter( 1, 2 );

query.setParameter( 2, "John" );

List orders = query.getResultList();

… …

----------------------------------------------------------------------

where条件表达式中可用的运算符基本上与SQL一致,包括:

算术运算符:+ - * / +(正) -(负)

关系运算符:== <> > >= < <= between…and like in is null 等

逻辑运算符: and or not

------------------------------------------------------------------------

下面是一些常见查询表达式示例:

// 以下语句查询 Id 介于 100 至 200 之间的订单。

select o from Orders o where o.id between100 and 200

// 以下语句查询国籍为的 'US'、'CN'或'JP' 的客户。

select c from Customers c where c.county in('US','CN','JP')

// 以下语句查询手机号以139开头的客户。%表示任意多个字符序列,包括0个。

select c from Customers c where c.phonelike '139%'

// 以下语句查询名字包含4个字符,且234位为ose的客户。_表示任意单个字符。

select c from Customers c where c.lnamelike '_ose'

// 以下语句查询电话号码未知的客户。Nul l用于测试单值是否为空。

select c from Customers c where c.phone isnull

// 以下语句查询尚未输入订单项的订单。empty用于测试集合是否为空。

select o from Orders o where o.orderItemsis empty

15.2.3 查询部分属性

如果只须查询实体的部分属性而不需要返回整个实体。例如:

select o.id, o.customerName,o.address.streetNumber from Order o order by o.id

执行该查询返回的不再是Orders实体集合,而是一个对象数组的集合(Object[]),集合的每个成员为一个对象数组,可通过数组元素访问各个属性。

15.2.4 OrderBy子句

order by子句用于对查询结果集进行排序。和SQL的用法类似,可以用 “asc“ 和"desc“ 指定升降序。如果不显式注明,默认为升序。

select o from Orders o order by o.id

select o from Orders o order byo.address.streetNumber desc

select o from Orderso order by o.customer asc, o.id desc

15.2.5 groupBy与聚合查询

group by 子句用于对查询结果分组统计,通常需要使用聚合函数。常用的聚合函数主要有 AVG、SUM、COUNT、MAX、MIN 等,它们的含义与SQL相同。例如:

select max(o.id) from Orders o

没有 group by 子句的查询是基于整个实体类的,使用聚合函数将返回单个结果值,可以使用Query.getSingleResult()得到查询结果。例如:

Query query = entityManager.createQuery(

"selectmax(o.id) from Orders o");

Object result = query.getSingleResult();

Long max = (Long)result;

15.2.6 having子句

Having 子句用于对 group by 分组设置约束条件,用法与where 子句基本相同,不同是 where 子句作用于基表或视图,以便从中选择满足条件的记录;having 子句则作用于分组,用于选择满足条件的组,其条件表达式中通常会使用聚合函数。

例如,以下语句用于查询订购总数大于100的商家所售商品及数量:

select o.seller, o.goodId, sum(o.amount)from V_Orders o group by

o.seller, o.goodId having sum(o.amount)> 100

having子句与where子句一样都可以使用参数。

15.2.7 关联查询

在JPQL中,很多时候都是通过在实体类中配置实体关联的类属性来实现隐含的关联(join)查询。例如:

select o from Orders o whereo.address.streetNumber=2000

上述JPQL语句编译成以下SQL时就会自动包含关联,默认为左关联。

在某些情况下可能仍然需要对关联做精确的控制。为此,JPQL 也支持和 SQL 中类似的关联语法。如:

left out join / left join

inner join

left join / inner join fetch

其中,left join和leftout join等义,都是允许符合条件的右边表达式中的实体为空。

---------------------------------------------------------------------------------

例如,以下外关联查询可以找出所有客户实体记录,即使它未曾订货:

select c from Customers c left joinc.orders o

以下内关联查询只找出所有曾订过商品的客户实体记录:

select c from Customers c inner joinc.orders o

如果001号客户下过5次订单的话,以下fetch关联查询将得到 5个客户实体的引用,并且执行了 5 个订单的查询:

select c from Customers c left join fetchc.orders o where c.id=001

15.2.8 子查询

JPQL也支持子查询,在 where 或 having 子句中可以包含另一个查询。当子查询返回多于 1 个结果集时,它常出现在 any、all、exist s表达式中用于集合匹配查询。它们的用法与SQL语句基本相同。

15.2.9 JPQL函数

JPQL提供了以下一些内建函数,包括字符串处理函数、算术函数和日期函数。

字符串处理函数主要有:

concat(String s1, String s2):字符串合并/连接函数。

substring(String s, int start, int length):取字串函数。

trim([leading|trailing|both,] [char c,]String s):从字符串中去掉首/尾指定的字符或空格。

lower(String s):将字符串转换成小写形式。

upper(String s):将字符串转换成大写形式。

length(String s):求字符串的长度。

locate(String s1, String s2[, int start]):从第一个字符串中查找第二个字符串(子串)出现的位置。若未找到则返回0。

算术函数主要有 abs、mod、sqrt、size 等。Size 用于求集合的元素个数。

日期函数主要为三个,即 current_date、current_time、current_timestamp,它们不需要参数,返回服务器上的当前日期、时间和时戳。

15.2.10 update语句

update语句用于执行数据更新操作。主要用于针对单个实体类的批量更新

以下语句将帐户余额不足万元的客户状态设置为未偿付:

update Customers c set c.status = '未偿付' where c.balance <

10000

15.2.11 delete语句

delete语句用于执行数据更新操作。

以下语句删除不活跃的、没有订单的客户:

delete from Customers c where c.status =

'inactive' and c.orders is empty

16 17.SpringData

16.1 SpringData

16.1.1 什么是SpringData

Spring Data是Spring 的一个子项目。用于简化数据库访问,支持NoSQL和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。Spring Data具有如下特点:

1.SpringData 项目支持 NoSQL 存储:

MongoDB (文档数据库)

Neo4j(图形数据库)

Redis(键/值存储)

Hbase(列族数据库)

2.SpringData 项目所支持的关系数据存储技术:

JDBC

JPA

3.JPA Spring Data : 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA来帮你完成!

4.框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById()  这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User  对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。

16.1.2 HelloWorld

开发步骤

使用 Spring Data JPA 进行持久层开发需要的四个步骤:

1.配置Spring 整合 JPA

2.在Spring 配置文件中配置 Spring Data,让Spring 为声明的接口创建代理对象。配置了 <jpa:repositories> 后,Spring 初始化容器时将会扫描 base-package  指定的包目录及其子目录,为继承Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。

3.声明持久层的接口,该接口继承  Repository,Repository 是一个标记型接口,它不包含任何方法,如必要,Spring Data 可实现 Repository 其他子接口,其中定义了一些常用的增删改查,以及分页相关的方法。

4.在接口中声明需要的方法。Spring Data 将根据给定的策略(具体策略稍后讲解)来为其生成实现代码。

环境搭建

1.创建普通javase工程

2.导入相关jar

(1).Spring的jar包

(2).JPA的jar包(Hibernate)

(3).数据库驱动、数据库连接池

(4).Spring Data的jar包

(5).日志jar

3.创建applicationContext.xml文件,整合Spring和JPA,如下:

<context:property-placeholder location="classpath:db.properties"/>
<context:component-scanbase-package="org.sang"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
    <property name="driverClassName" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
</bean>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"id="entityManagerFactory">
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    </property>
    <property name="packagesToScan" value="org.sang.model"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</prop>
        </props>
    </property>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driventransaction-manager="transactionManager"/>

4.配置jpa

<jpa:repositories base-package="org.sang.dao"
                  entity-manager-factory-ref="entityManagerFactory"
/>

5. 创建UserDao:

public interface UserDao extends Repository<User, Long> {
    User getUserById(Long id);
}

两个泛型数据,第一个表示实体类名,第二个表示主键类型。

6.创建UserService:

@Service
@Transactional
public class UserService {
    @Resource
    UserDao userDao;

public User getUserById(Long id) {
        return userDao.getUserById(id);
    }
}

7.测试:

public void test1() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = ctx.getBean(UserService.class);
    User user = userService.getUserById(1l);
    System.out.println(user);
}

16.1.3 Repository

1.Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法

public interfaceRepository<T, ID extends Serializable> { }

2.若我们定义的接口继承了 Repository, 则该接口会被 IOC 容器识别为一个 Repository Bean.纳入到 IOC 容器中. 进而可以在该接口中定义满足一定规范的方法.

3.Spring Data可以让我们只定义接口,只要遵循 Spring Data的规范,就无需写实现类。

4.与继承 Repository 等价的一种方式,就是在持久层接口上使用@RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的

@RepositoryDefinition(domainClass = User.class, idClass = Long.class)
public interface UserDao
//       extends Repository<User,Long>
{
    User findById(Long id);

List<User> findAll();
}

------------------------------------------------------------------------------------------------------

基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:

1.Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类

2.CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法

3.PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法

4.JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法

5.自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。

6.JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

16.1.4 SpringData方法定义规范

简单条件查询

1.简单条件查询: 查询某一个实体类或者集合

2.按照 Spring Data 的规范,查询方法以 find | read | get 开头

3.涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。

例如:定义一个 Entity 实体类:

class User{

private String firstName;

private String lastName;

使用And条件连接时,应这样写:

findByLastNameAndFirstName(StringlastName,String firstName);

条件的属性名称与个数要与参数的位置与个数一一对应

4.支持属性的级联查询. 若当前类有符合条件的属性, 则优先使用, 而不使用级联属性. 若需要使用级联属性, 则属性之间使用 _ 进行连接.

查询举例:

1.按照id查询

User getUserById(Long id);

User getById(Long id);

2.查询所有年龄小于90岁的人:

List<User> findByAgeLessThan(Long age);

3.查询所有姓赵的人:

List<User> findByUsernameStartingWith(String u)

4.查询所有姓赵的、并且id大于50的人:

List<User> findByUsernameStartingWithAndIdGreaterThan(String name,Long id)

5.查询所有姓名中包含"上"字的人:

List<User> findByUsernameContaining(String name)

6.查询所有姓赵的或者年龄大于90岁的:

List<User> findByUsernameStartingWithOrAgeGreaterThan(String name,Long age)

7.查询所有角色为1的用户:

List<User> findByRole_Id(Long id);

支持的关键字

直接在接口中定义查询方法,如果是符合规范的,可以不用写实现,目前支持的关键字写法如下:

查询方法流程解析

假如创建如下的查询:findByUserDepUuid(),框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc:

1.先判断 userDepUuid (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;

2.从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为查询实体的一个属性;

3.接着处理剩下部分(DepUuid),先判断 user 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 “Doc.user.depUuid” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “Doc.user.dep.uuid” 的值进行查询。

4.可能会存在一种特殊情况,比如 Doc包含一个 user 的属性,也有一个 userDep 属性,此时会存在混淆。可以明确在属性之间加上"_" 以显式表达意图,比如"findByUser_DepUuid()" 或者"findByUserDep_uuid()"

特殊的参数: 还可以直接在方法的参数上加入分页或排序的参数,比如:

1.Page<UserModel> findByName(Stringname, Pageable pageable);

2.List<UserModel>findByName(String name, Sort sort);

16.1.5 使用@Query注解

使用@Query自定义查询

这种查询可以声明在 Repository 方法中,摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰,这是 Spring data 的特有实现。

比如:查询Id最大的User:

@Query("selectu from t_user u where id=(select max(id) from t_user)")
User getMaxIdUser();

索引参数与命名参数

1.索引参数如下所示,索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致

@Query("selectu from t_user u where id>?1 and username like ?2")
List<User> selectUserByParam(Long id, String name);

2.命名参数(推荐使用这种方式):可以定义好参数名,赋值时采用@Param("参数名"),而不用管顺序。

@Query("selectu from t_user u where id>:id and username like :name")
List<User> selectUserByParam2(@Param("name") String name, @Param("id") Long id);

---------------------------------------------------------------------------------------

3.如果是 @Query 中有 LIKE 关键字,后面的参数需要前面或者后面加 %,这样在传递参数值的时候就可以不加 %:

@Query("select o from UserModel owhere o.name like ?1%")

publicList<UserModel> findByUuidOrAge(String name);

@Query("select o from UserModel owhere o.name like %?1")

publicList<UserModel> findByUuidOrAge(String name);

@Query("select o from UserModel owhere o.name like %?1%")

public List<UserModel> findByUuidOrAge(String name);

用@Query来指定本地查询

还可以使用@Query来指定本地查询,只要设置nativeQuery为true,比如:

@Query(value = "select * from t_user",nativeQuery = true)
List<User> selectAll();

16.1.6 @Modifying注解和事务

@Query和@Modifying执行更新操作

@Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下:

@Modifying
@Query("update t_user set age=:age whereid>:id")
int updateUserById(@Param("age") Long age, @Param("id") Long id);

注意:

1.可以通过自定义的 JPQL 完成 UPDATE 和DELETE 操作. 注意: JPQL 不支持使用 INSERT

2.方法的返回值应该是 int,表示更新语句所影响的行数

3.在调用的地方必须加事务,没有事务不能正常执行

4.默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作

Spring Data中的事务问题

1.Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。

2.对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解@Transactional 声明

3.进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。

16.1.7 CrudRepository

CrudRepository 接口提供了最基本的对实体类的添删改查操作

1.T save(T entity);//保存单个实体

2.Iterable<T> save(Iterable<?extends T> entities);//保存集合

3.T findOne(ID id);//根据id查找实体

4.boolean exists(ID id);//根据id判断实体是否存在

5.Iterable<T> findAll();//查询所有实体,不用或慎用!

6.long count();//查询实体数量

7.void delete(ID id);//根据Id删除实体

8.void delete(T entity);//删除一个实体

9.void delete(Iterable<? extends T>entities);//删除一个实体的集合

10.voiddeleteAll();//删除所有实体,不用或慎用!

16.1.8 PagingAndSortingRepository

该接口提供了分页与排序功能

Iterable<T> findAll(Sort sort); //排序

Page<T> findAll(Pageable pageable);//分页查询(含排序功能)

int page = 3;
int size = 10;
PageRequest pageRequest = new PageRequest(page,size);
Page<User> all = userService.findAll(pageRequest);
System.out.println("总页数:"+all.getTotalPages());
System.out.println("当前记录数:"+all.getNumberOfElements());
System.out.println("当前页数:"+all.getNumber());
System.out.println("总记录数:"+all.getTotalElements());
System.out.println("查询结果集:"+all.getContent());

注意:

页数的记录是从0开始的。

加上排序功能如下:

int page = 3;
int size = 10;
Sort.Order order2 = new Sort.Order(Sort.Direction.DESC,"id");
Sort.Order order1 = new Sort.Order(Sort.Direction.DESC,"username");
Sort sort = new Sort(order2,order1);
PageRequest pageRequest = new PageRequest(page, size,sort);
Page<User> all = userService.findAll(pageRequest);
System.out.println("总页数:"+all.getTotalPages());
System.out.println("当前记录数:"+all.getNumberOfElements());
System.out.println("当前页数:"+all.getNumber());
System.out.println("总记录数:"+all.getTotalElements());
System.out.println("查询结果集:"+all.getContent());
for (User user : all.getContent()) {
    System.out.println(user.getId());
}

16.1.9 JPARepository接口

该接口提供了JPA的相关功能

1.List<T> findAll(); //查找所有实体

2.List<T> findAll(Sort sort); //排序、查找所有实体

3.List<T> save(Iterable<? extendsT> entities);//保存集合

4.void flush();//执行缓存与数据库同步

5.T saveAndFlush(T entity);//强制执行持久化 /保存或者更新

6.voiddeleteInBatch(Iterable<T> entities);//删除一个实体集合

16.1.10 JpaSpecificationExecutor

不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

Specification:封装  JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象:

举例:

        int page = 0;
        int size = 10;
        
PageRequest pageRequest = new PageRequest(page, size);
        Specification specification = new Specification() {
            /**
             * @param
root  代表查询的实体类.
             * @param
criteriaQuery  可以从中可到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
             * @param
criteriaBuilder  CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
             * @return Predicate 类型, 代表一个查询条件.
             */
            
@Override
            public Predicate toPredicate(Root root, CriteriaQuerycriteriaQuery, CriteriaBuilder criteriaBuilder) {
               Path path = root.get("id");
               Predicate predicate = criteriaBuilder.gt(path, 101l);
                return predicate;
            }
        };
        Page<User> all = userService.findAll(specification,pageRequest);
        System.out.println("总页数:"+all.getTotalPages());
        System.out.println("当前记录数:"+all.getNumberOfElements());
        System.out.println("当前页数:"+all.getNumber());
        System.out.println("总记录数:"+all.getTotalElements());
        System.out.println("查询结果集:"+all.getContent());
        for (User user : all.getContent()) {
            System.out.println(user.getId());
        }

16.1.11 自定义Repository方法

步骤

步骤

1.定义一个接口: 声明要添加的, 并自实现的方法

2.提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法

3.声明 Repository 接口, 并继承1) 声明的接口

4.使用.

注意:

默认情况下, Spring Data 会在 base-package 中查找 "接口名Impl" 作为实现类. 也可以通过 repository-impl-postfix 声明后缀.

举例

1.定义PersonDao接口

public interface PersonDao {
    void test();
}

2.定义PersonRepositoryImpl实现该接口

public class PersonRepositoryImpl implements PersonDao {
    @PersistenceContext
    EntityManager entityManager;
    @Override
    public void test() {
        User user = entityManager.find(User.class, 1l);
        System.out.println(user);
    }
}

3.在PersonRepository中继承2中定义的接口

17 18.Shiro基础

17.1 Shiro简介

17.1.1 Shiro简介

什么是Shiro:

1.ApacheShiro是Java 的一个安全(权限)框架。

2.Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。

3.Shiro可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。

4.下载:http://shiro.apache.org/

17.1.2 功能简介

基本功能点如下图所示:

Shiro具备如下功能:

1.Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

2.Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

3.Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的;

4.Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

5.Web Support:Web 支持,可以非常容易的集成到Web 环境;

6.Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

7.Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

8.Testing:提供测试支持;

9.Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

10.Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

17.1.3 Shiro架构

从外部看

从外部来看Shiro,即从应用程序角度的来观察如何使用Shiro完成工作:

1.Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API 核心就是Subject。Subject代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;与Subject 的所有交互都会委托给SecurityManager;Subject 其实是一个门面,SecurityManager才是实际的执行者;

2.SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且其管理着所有Subject;可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色

3.Realm:Shiro从Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm 看成DataSource

从内部看

1.Subject:任何可以与应用交互的“用户”;

2.SecurityManager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证、授权、会话及缓存的管理。

3.Authenticator:负责Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

4.Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

5.Realm:可以有1 个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的Realm;

6.SessionManager:管理Session 生命周期的组件;而Shiro并不仅仅可以用在Web 环境,也可以用在如普通的JavaSE环境

7.CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能

8.Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密。

17.2 HelloWorld

1.环境搭建,创建JavaSE项目,添加jar包:

2.参考\shiro-root-1.3.2-source-release\shiro-root-1.3.2\samples\quickstart\项目

3.解释如下:

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 获取当前的 Subject. 调用 SecurityUtils.getSubject();
Subject currentUser = SecurityUtils.getSubject();
// 测试使用 Session
// 获取 Session: Subject#getSession()
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
    log.info("---> Retrievedthe correct value! [" + value + "]");
}
// 测试当前的用户是否已经被认证. 即是否已经登录.
// 调动 Subject 的 isAuthenticated()
if (!currentUser.isAuthenticated()) {
    // 把用户名和密码封装为 UsernamePasswordToken 对象
    
UsernamePasswordTokentoken = new UsernamePasswordToken("lonestarr", "vespa");
    //rememberme
    
token.setRememberMe(true);
    try {
        // 执行登录.
        
currentUser.login(token);
    }
    // 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常.
    
catch (UnknownAccountException uae) {
        log.info("----> There is no user with username of " + token.getPrincipal());
        return;
    }
    // 若账户存在, 但密码不匹配, 则 shiro 会抛出IncorrectCredentialsException 异常。
    
catch (IncorrectCredentialsException ice) {
        log.info("----> Password for account " + token.getPrincipal() + " wasincorrect!");
        return;
    }
    // 用户被锁定的异常 LockedAccountException
    
catch (LockedAccountException lae) {
        log.info("The account for username " + token.getPrincipal() + " islocked.  " +
                "Please contact your administrator to unlock it.");
    }
    // 所有认证时异常的父类.
    
catch (AuthenticationException ae) {
    }
}
log.info("----> User[" + currentUser.getPrincipal() + "] logged in successfully.");
// 测试是否有某一个角色. 调用 Subject 的 hasRole 方法.
if (currentUser.hasRole("schwartz")) {
    log.info("----> May theSchwartz be with you!");
} else {
    log.info("----> Hello,mere mortal.");
    return;
}
// 测试用户是否具备某一个行为. 调用 Subject 的 isPermitted() 方法。
if (currentUser.isPermitted("lightsaber:weild")) {
    log.info("----> You mayuse a lightsaber ring.  Use itwisely.");
} else {
    log.info("Sorry, lightsaberrings are for schwartz masters only.");
}
// 测试用户是否具备某一个行为.
if (currentUser.isPermitted("user:delete:zhangsan")) {
    log.info("----> You arepermitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
            "Here are the keys - have fun!");
} else {
    log.info("Sorry, you aren'tallowed to drive the 'eagle5' winnebago!");
}
// 执行登出. 调用 Subject 的 Logout() 方法.
System.out.println("---->" +currentUser.isAuthenticated());
currentUser.logout();
System.out.println("---->" + currentUser.isAuthenticated());
System.exit(0);

17.3 Shiro集成Spring

17.3.1 集成Spring

1.创建Web工程,导入jar包:

(1).Spring的jar包

(2).Shiro的jar包,共四个:

2.配置Spring+SpringMVC环境,并测试

3.配置Shiro过滤器

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

4.配置ShiroBean

<!-- 1. 配置 SecurityManager!-->
 
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
     <property name="cacheManager" ref="cacheManager"/>
     <property name="sessionMode" value="native"/>
     <property name="realm" ref="jdbcRealm"/>
 </bean>
 <!--
     2. 配置 CacheManager.
     2.1 需要加入 ehcache 的 jar 包及配置文件.
-->
 
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
     <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
 </bean>
 <!--
    3. 配置 Realm
    3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
 -->
 
<bean id="jdbcRealm" class="org.sang.MyRealm">
 </bean>
 <!--
     4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
     -->
 
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
 <!--
     5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用.
     -->
 
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
       depends-on="lifecycleBeanPostProcessor"
/>
 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
     <property name="securityManager" ref="securityManager"/>
 </bean>
 <!--
 6. 配置 ShiroFilter.
 6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.
                  若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的            filter bean.
 -->
 
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
     <property name="securityManager" ref="securityManager"/>
     <property name="loginUrl" value="/login.jsp"/>
     <property name="successUrl" value="/index.jsp"/>
     <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
     <!--
         配置哪些页面需要受保护.
         以及访问这些页面需要的权限.
         1). anon 可以被匿名访问
         2). authc 必须认证(即登录)后才可能访问的页面.
         3). logout 登出.
         4). roles 角色过滤器
     -->
     
<property name="filterChainDefinitions">
         <value>
            /login.jsp=anon
            /**=authc
         </value>
     </property>
 </bean>

17.3.2 集成Web

1.Shiro提供了与Web 集成的支持,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制。

2.ShiroFilter类似于如Strut2/SpringMVC这种web 框架的前端控制器,是安全控制的入口点,其负责读取配置(如ini配置文件),然后判断URL 是否需要登录/权限等工作。

17.3.3 ShiroFilter工作原理

DelegatingFilterProxy作用是自动到Spring 容器查找名字为shiroFilter(filter-name)的bean并把所有Filter 的操作委托给它:

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

部分细节:

1.[urls] 部分的配置,其格式是:“url=拦截器[参数],拦截器[参数]”;

2.如果当前请求的url匹配[urls] 部分的某个url模式,将会执行其配置的拦截器。

3.anon(anonymous)拦截器表示匿名访问(即不需要登录即可访问)

4.authc(authentication)拦截器表示需要身份认证通过后才能访问

17.3.4 Shiro中默认的过滤器

17.3.5 URL匹配模式

1.url模式使用Ant 风格模式

2.Ant 路径通配符支持?、*、**,注意通配符匹配不包括目录分隔符“/”:

–?:匹配一个字符,如/admin? 将匹配/admin1,但不匹配/admin 或/admin/;

–*:匹配零个或多个字符串,如/admin 将匹配/admin、/admin123,但不匹配/admin/1;

–**:匹配路径中的零个或多个路径,如/admin/** 将匹配/admin/a 或/admin/a/b

17.3.6 URL匹配顺序

1.URL 权限采取第一次匹配优先的方式,即从头开始使用第一个匹配的url模式对应的拦截器链。

2.如:

–/bb/**=filter1

–/bb/aa=filter2

–/**=filter3

–如果请求的url是“/bb/aa”,因为按照声明顺序进行匹配,那么将使用filter1 进行拦截。

17.4 认证

17.4.1 回忆Shiro架构

17.4.2 身份验证

1.身份验证:一般需要提供如身份ID 等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明。

2.在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:

3.principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。

4.credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

5.最常见的principals 和credentials 组合就是用户名/密码了

17.4.3 身份验证基本流程

1、收集用户身份/凭证,即如用户名/密码

2、调用Subject.login进行登录,如果失败将得到相应的AuthenticationException异常,根据异常提示用户错误信息;否则登录成功

3、创建自定义的Realm 类,继承org.apache.shiro.realm.AuthorizingRealm类,实现doGetAuthenticationInfo() 方法

17.4.4 身份验证示例

// 调动Subject 的 isAuthenticated()
if (!currentUser.isAuthenticated()) {
    // 把用户名和密码封装为 UsernamePasswordToken 对象
    
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
    // rememberme
    
token.setRememberMe(true);
    try {
        // 执行登录.
        
currentUser.login(token);
    }
    // 若没有指定的账户, 则 shiro将会抛出 UnknownAccountException 异常.
    
catch (UnknownAccountException uae) {
        log.info("----> There is no user with username of" + token.getPrincipal());
        return;
    }
    // 若账户存在, 但密码不匹配, 则 shiro 会抛出IncorrectCredentialsException 异常。
    
catch (IncorrectCredentialsException ice) {
        log.info("----> Password for account " + token.getPrincipal() + " was incorrect!");
        return;
    }
    // 用户被锁定的异常 LockedAccountException
    
catch (LockedAccountException lae) {
        log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                "Please contactyour administrator to unlock it.");
    }
    // 所有认证时异常的父类.
    
catch (AuthenticationException ae) {
    }
}

17.4.5 AuthenticationException

1.如果身份验证失败请捕获AuthenticationException或其子类

2.最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;

17.4.6 身份认证流程

1、首先调用Subject.login(token) 进行登录,其会自动委托给SecurityManager

2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;

3、Authenticator 才是真正的身份验证者,ShiroAPI 中核心的身份认证入口点,此处可以自定义插入自己的实现;

4、Authenticator 可能会委托给相应的AuthenticationStrategy进行多Realm 身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm 身份验证;

5、Authenticator 会把相应的token 传入Realm,从Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

17.4.7 Realm

1.Realm:Shiro从Realm 获取安全数据(如用户、角色、权限),即SecurityManager要验证用户身份,那么它需要从Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作

2.Realm接口如下:

3.一般继承AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

4.Realm 的继承关系:

17.4.8 Authenticator

1.Authenticator 的职责是验证用户帐号,是ShiroAPI 中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException异常

2.SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行验证,验证规则通过AuthenticationStrategy接口指定

17.4.9 AuthenticationStrategy

1.AuthenticationStrategy接口的默认实现:

2.FirstSuccessfulStrategy:只要有一个Realm 验证成功即可,只返回第一个Realm 身份验证成功的认证信息,其他的忽略;

3.AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,将返回所有Realm身份验证成功的认证信息;

4.AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

5.ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略

18 19.Redis基础

18.1 NoSQL概述

18.1.1 入门概述

为什么用NoSQL

在90年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。

在那个时候,更多的都是静态网页,动态交互类型的网站不多。

上述架构下,我们来看看数据存储的瓶颈是什么?

1.数据量的总大小一个机器放不下时

2.数据的索引(B+ Tree)一个机器的内存放不下时

3.访问量(读写混合)一个实例不能承受

------------------------------------------------------------------------------------

后来,随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web程序不再仅仅专注在功能上,同时也在追求性能。程序员们开始大量的使用缓存技术来缓解数据库的压力,优化数据库的结构和索引。开始比较流行的是通过文件缓存来缓解数据库压力,但是当访问量继续增大的时候,多台web机器通过文件缓存不能共享,大量的小文件缓存也带了了比较高的IO压力。在这个时候,Memcached就自然的成为一个非常时尚的技术产品。

Memcached作为一个独立的分布式的缓存服务器,为多个web服务器提供了一个共享的高性能缓存服务,在Memcached服务器上,又发展了根据hash算法来进行多台Memcached缓存服务的扩展,然后又出现了一致性hash来解决增加或减少缓存服务器导致重新hash带来的大量缓存失效的弊端

------------------------------------------------------------------------------------

由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力。读写集中在一个数据库上让数据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展性。Mysql的master-slave模式成为这个时候的网站标配了。

------------------------------------------------------------------------------------

在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用InnoDB引擎代替MyISAM。

同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题。这个时候,分表分库成了一个热门技术,是业界讨论的热门技术问题。也就在这个时候,MySQL推出了还不太稳定的表分区,这也给技术实力一般的公司带来了希望。虽然MySQL推出了MySQL Cluster集群,但性能也不能很好满足互联网的要求,只是在高可靠性上提供了非常大的保证。

------------------------------------------------------------------------------------

MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。

------------------------------------------------------------------------------------

当下常见架构:

------------------------------------------------------------------------------------

为什么使用NoSQL ?

今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。

什么是NoSQL

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

NoSQL能干什么

易扩展

1.NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。

2.数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。

大数据量高性能

1.NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。

2.一般MySQL使用Query Cache,每次表的更新Cache就失效,是一种大粒度的Cache,在针对web2.0的交互频繁的应用,Cache性能不高。而NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说就要性能高很多了

多样灵活的数据模型

NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦

传统RDBMS VS NOSQL

RDBMS

1.高度组织化结构化数据

2.结构化查询语言(SQL)

3.数据和关系都存储在单独的表中。

4.数据操纵语言,数据定义语言

5.严格的一致性

6.基础事务

NoSQL

1.代表着不仅仅是SQL

2.没有声明性查询语言

3.没有预定义的模式

4.键 - 值对存储,列存储,文档存储,图形数据库

5.最终一致性,而非ACID属性

6.非结构化和不可预知的数据

7.CAP定理

8.高性能,高可用性和可伸缩性

18.1.2 3V+3高

大数据时代的3V

1.海量Volume

2.多样Variety

3.实时Velocity

互联网需求的3高

1.高并发

2.高可扩

3.高性能

18.1.3 当下的NoSQL经典应用(以阿里巴巴为例)

架构发展史

阿里巴巴中文站商品信息存放

商品基本信息

1.名称、价格,出厂日期,生产厂商等

2.关系型数据库:mysql/oracle目前淘宝在去O化(也即拿掉Oracle), 注意,淘宝内部用的Mysql是里面的大牛自己改造过的

为什么去IOE

2008年,王坚加盟阿里巴巴成为集团首席架构师,即现在的首席技术官。这位前微软亚洲研究院常务副院长被马云定位为:将帮助阿里巴巴集团建立世界级的技术团队,并负责集团技术架构以及基础技术平台搭建。

在加入阿里后,带着技术基因和学者风范的王坚就在阿里巴巴集团提出了被称为“去IOE”(在IT建设过程中,去除IBM小型机、Oracle数据库及EMC存储设备)的想法,并开始把云计算的本质,植入阿里IT基因。

王坚这样概括“去IOE”运动和阿里云之间的关系:“去IOE”彻底改变了阿里集团IT架构的基础,是阿里拥抱云计算,产出计算服务的基础。“去IOE”的本质是分布化,让随处可以买到的Commodity PC架构成为可能,使云计算能够落地的首要条件。

商品描述、详情、评价信息(多文字类)

1.多文字信息描述类,IO读写性能变差

2.文档数据库MongDB中

商品的图片

分布式的文件系统中

淘宝自己的TFS

Google的GFS

Hadoop的HDFS

商品关键字

搜索引擎,淘宝内用

ISearch

商品的波段性的热点高频信息

1.内存数据库

2.tair、Redis、Memcache

商品的交易、价格计算、积分累计

1.外部系统,外部第3方支付接口

2.支付宝

大型互联网应用(大数据、高并发、多样数据类型)的难点和解决方案

难点

1.数据类型多样性

2.数据源多样性和变化重构

3.数据源改造而数据服务平台不需要大面积重构

解决办法UDSL

18.1.4 NoSQL数据模型简介

传统数据库模型

1:1/N:N/1:N

NoSQL数据模型

BSON()是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象:

{

"customer":{

"id":1136,

"name":"Z3",

"billingAddress":[{"city":"beijing"}],

"orders":[

{

"id":17,

"customerId":1136,

"orderItems":[{"productId":27,"price":77.5,"productName":"thinkingin java"}],

"shippingAddress":[{"city":"beijing"}]

"orderPayment":[{"ccinfo":"111-222-333","txnid":"asdfadcd334","billingAddress":{"city":"beijing"}}],

}

]

}

}

为什么要这样设计:

1.高并发的操作是不太建议有关联查询的,互联网公司用冗余数据来避免关联查询

2.分布式事务是支持不了太多的并发的

聚合模型

1.KV键值

2.Bson

3.列族

顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。

4.图形

18.1.5 NoSQL数据库的四大分类

KV键值

新浪:BerkeleyDB+redis

美团:redis+tair

阿里、百度:memcache+redis

文档型数据库(bson格式比较多)

CouchDB

MongoDB

MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

列存储数据库

Cassandra, HBase

分布式文件系统

图关系数据库

1.它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统

2.社交网络,推荐系统等。专注于构建关系图谱

3.Neo4J, InfoGrid

四者对比

18.1.6 分布式数据库中CAP原理

一.CAP起源

  CAP原本是一个猜想,2000年PODC大会的时候大牛Brewer提出的,他认为在设计一个大规模可扩放的网络服务时候会遇到三个特性:一致性(consistency)、可用性(Availability)、分区容错(partition-tolerance)都需要的情景,然而这是不可能都实现的。之后在2003年的时候,Mit的Gilbert和Lynch就正式的证明了这三个特征确实是不可以兼得的。该理论是NoSQL数据库管理系统构建的基础。。

  Consistency、Availability、Partition-tolerance的提法是由Brewer提出的,而Gilbert和Lynch在证明的过程中改变了Consistency的概念,将其转化为Atomic。Gilbert认为这里所说的Consistency其实就是数据库系统中提到的ACID的另一种表述:

  一个用户请求要么成功、要么失败,不能处于中间状态(Atomic);

  一旦一个事务完成,将来的所有事务都必须基于这个完成后的状态(Consistent);

  未完成的事务不会互相影响(Isolated);

  一旦一个事务完成,就是持久的(Durable)。

  对于Availability,其概念没有变化,指的是对于一个系统而言,所有的请求都应该‘成功’并且收到‘返回’。

  对于Partition-tolerance,所指就是分布式系统的容错性。节点crash或者网络分片都不应该导致一个分布式系统停止服务。

二.CAP简介

  CAP定律说的是在一个分布式计算机系统中,一致性,可用性和分区容错性这三种保证无法同时得到满足,最多满足两个。

2.1 强一致性(C) 

  强一致性:系统在执行过某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读到最新的值,这样的系统被认为是具有强一致性的。等同于所有节点访问同一份最新的数据副本;

  All clients always have the sameview of the data。

2.2 可用性(A)

  可用性:每一个操作总是能够在一定的时间内返回结果,这里需要注意的是"一定时间内"和"返回结果"。一定时间指的是,在可以容忍的范围内返回结果,结果可以是成功或者失败。对数据更新具备高可用性(A);

  Each client can alwa read and write。

2.3 分区容错性(P)

  分区容错性:理解为在存在网络分区的情况下,仍然可以接受请求(满足一致性和可用性)。这里的网络分区是指由于某种原因,网络被分成若干个孤立的区域,而区域之间互不相通。还有一些人将分区容错性理解为系统对节点动态加入和离开的能力,因为节点的加入和离开可以认为是集群内部的网络分区。

  Partition Tolerance的意思是,在网络中断,消息丢失的情况下,系统照样能够工作。以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择

2.4 放弃C.A.P

  放弃P:如果想避免分区容错性问题的发生,一种做法是将所有的数据(与事务相关的)都放在一台机器上。虽然无法100%保证系统不会出错,但不会碰到由分区带来的负面效果。当然这个选择会严重的影响系统的扩展性。

  放弃A:相对于放弃“分区容错性“来说,其反面就是放弃可用性。一旦遇到分区容错故障,那么受到影响的服务需要等待一定的时间,因此在等待期间系统无法对外提供服务。

  放弃C:这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性,而保留数据的最终一致性。以网络购物为例,对只剩下一件库存的商品,如果同时接受到了两份订单,那么较晚的订单将被告知商品告罄。

  一致性与可用性的决择: 而CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用 性之间进行权衡。

三.基本CAP的证明思路

  CAP的证明基于异步网络,异步网络也是反映了真实网络中情况的模型。真实的网络系统中,节点之间不可能保持同步,即便是时钟也不可能保持同步,所有的节点依靠获得的消息来进行本地计算和通讯。这个概念其实是相当强的,意味着任何超时判断也是不可能的,因为没有共同的时间标准。之后我们会扩展CAP的证明到弱一点的异步网络中,这个网络中时钟不完全一致,但是时钟运行的步调是一致的,这种系统是允许节点做超时判断的。

  CAP的证明很简单,假设两个节点集{G1, G2},由于网络分片导致G1和G2之间所有的通讯都断开了,如果在G1中写,在G2中读刚写的数据, G2中返回的值不可能G1中的写值。由于A的要求,G2一定要返回这次读请求,由于P的存在,导致C一定是不可满足的。

四.CAP的理解

4.1 流行解释

  目前流行的、对CAP理论解释的情形是从同一数据在网络环境中存在多个副本出发为前提的。为了保证数据不会丢失,同时也是为了增加并发访问量(读写分离),在企业级的数据管理方案中,一般必须考虑数据的冗余存储问题,而这应该是通过在网络上的其他独立物理存储节点上保留另一份、或多份数据副本来实现的(如附图所示)。因为在同一个存储节点上的数据冗余明显不能解决单点故障问题,这与通过多节点集群来提供更好的计算可用性的道理是相同的。

  如上图的情况,数据在节点A、B、C上保留了三份,如果对节点A上的数据进行了修改,然后再让客户端通过网络对该数据进行读取。那么,客户端的读取操作什么时候返回呢?

  一种情况是要求节点A、B、C的三份数据完全一致后返回。也就是说,这时从任何一个网络节点读取的数据都是一样的,这就是所谓的强一致性读。很明显,这时数据读取的Latency(延迟)要高一些(因为要等数据在网络中的复制),同时A、B、C三个节点中任何一个宕机,都会导致数据不可用。也就是说,要保证强一致性,网络中的副本越多,数据的可用性就越差。

  另一种情况是,允许读操作立即返回,容忍B节点的读取与A节点的读取不一致的情况发生。这样一来,可用性显然得到了提高,网络中的副本也可以多一些,唯一得不到保证的是数据一致性。当然,对写操作同样也有多个节点一致性的情况,在此不再赘述。

  可以看出,上述对CAP理论的解释主要是从网络上多个节点之间的读写一致性出发考虑问题的。而这一点,对于关系型数据库意味着什么呢?当然主要是指通常所说的Standby(备用)(关于分布式事务,涉及到更多考虑,随后讨论)情况。对此,在实践中我们大多已经采取了弱一致性的异步延时同步方案,以提高可用性。这种情况并不存在关系型数据库为保证C、A而放弃P的情况;而对海量数据管理的需求,关系型数据库扩展过程中所遇到的性能瓶颈,似乎也并不是CAP理论中所描述的那种原因造成的。那么,上述流行的说法中所描述的关系型数据库为保证C、A而牺牲P到底是在指什么呢? 如果只将CAP当作分布式系统中多个数据副本之间的读写一致性问题的通用理论对待,那么就可以得出结论:CAP既适用于NoSQL数据库,也适用于关系型数据库。它是NoSQL数据库、关系型数据库,乃至一切分布式系统在设计数据多个副本之间读写一致性问题时需要遵循的共同原则。

4.2 形式化描述

  要真正理解 CAP 理论必须要读懂它的形式化描述。 形式化描述中最重要的莫过于对 Consistency, Availability, Partition-tolerance 的准确定义。

  Consistency (一致性) 实际上等同于系统领域的 before-or-after atomicity 这个术语,或者等同于 linearizable (可串行化) 这个术语。具体来说,系统中对一个数据的读和写虽然包含多个子步骤并且会持续一段时间才能执行完,但是在调用者看来,读操作和写操作都必须是单个的即时完成的操作,不存在重叠。对一个写操作,如果系统返回了成功,那么之后到达的读请求都必须读到这个新的数据;如果系统返回失败,那么所有的读,无论是之后发起的,还是和写同时发起的,都不能读到这个数据。

  要说清楚 Availability 和 Partition-tolerance 必须要定义好系统的故障模型。在形式化证明中,系统包含多个节点,每个节点可以接收读和写的请求,返回成功或失败,对读还要返回一个数据。和调用者之间的连接是不会中断的,系统的节点也不会失效,唯一的故障就是报文的丢失。 Partition-tolerance 指系统中会任意的丢失报文(这和“最终会有一个报文会到达”是相对的)。 Availability 是指所有的读和写都必须要能终止。

  注: “Availability 是指所有的读和写都必须要能终止” 这句话听上去很奇怪,为什么不是“Availability 是指所有的写和读都必须成功”? 要回答这个问题,我们可以仔细思考下“什么是成功”。“成功”必须要相对于某个参照而言,这里的参照就是 Consistency。

4.3 两种重要的分布式场景

  关于对CAP理论中一致性C的理解,除了上述数据副本之间的读写一致性以外,分布式环境中还有两种非常重要的场景,如果不对它们进行认识与讨论,就永远无法全面地理解CAP,当然也就无法根据CAP做出正确的解释。

  1.分布式环境中的事务场景

  我们知道,在关系型数据库的事务操作遵循ACID原则,其中的一致性C,主要是指一个事务中相关联的数据在事务操作结束后是一致的。所谓ACID原则,是指在写入/异动资料的过程中,为保证交易正确可靠所必须具备的四个特性:即原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)和持久性(Durability)。

  例如银行的一个存款交易事务,将导致交易流水表增加一条记录。同时,必须导致账户表余额发生变化,这两个操作必须是一个事务中全部完成,保证相关数据的一致性。而前文解释的CAP理论中的C是指对一个数据多个备份的读写一致性。表面上看,这两者不是一回事,但实际上,却是本质基本相同的事物:数据请求会等待多个相关数据操作全部完成才返回。对分布式系统来讲,这就是我们通常所说的分布式事务问题。

  众所周知,分布式事务一般采用两阶段提交策略来实现,这是一个非常耗时的复杂过程,会严重影响系统效率,在实践中我们尽量避免使用它。在实践过程中,如果我们为了扩展数据容量将数据分布式存储,而事务的要求又完全不能降低。那么,系统的可用性一定会大大降低,在现实中我们一般都采用对这些数据不分散存储的策略。

  当然,我们也可以说,最常使用的关系型数据库,因为这个原因,扩展性(分区可容忍性P)受到了限制,这是完全符合CAP理论的。但同时我们应该意识到,这对NoSQL数据库也是一样的。如果NoSQL数据库也要求严格的分布式事务功能,情况并不会比关系型数据库好多少。只是在NoSQL的设计中,我们往往会弱化甚至去除事务的功能,该问题才表现得不那么明显而已。

  因此,在扩展性问题上,如果要说关系型数据库是为了保证C、A而牺牲P,在尽量避免分布式事务这一点上来看,应该是正确的。也就是说:关系型数据库应该具有强大的事务功能,如果分区扩展,可用性就会降低;而NoSQL数据库干脆弱化甚至去除了事务功能,因此,分区的可扩展性就大大增加了。

  2.分布式环境中的关联场景

  初看起来,关系型数据库中常用的多表关联操作与CAP理论就更加不沾边了。但仔细考虑,也可以用它来解释数据库分区扩展对关联所带来的影响。对一个数据库来讲,采用了分区扩展策略来扩充容量,数据分散存储了,很显然多表关联的性能就会下降,因为我们必须在网络上进行大量的数据迁移操作,这与CAP理论中数据副本之间的同步操作本质上也是相同的。

  因此,如果要保证系统的高可用性,需要同时实现强大的多表关系操作的关系型数据库在分区可扩展性上就遇到了极大的限制(即使是那些采用了各种优秀解决方案的MPP架构的关系型数据库,如TeraData,Netezza等,其水平可扩展性也是远远不如NoSQL数据库的),而NoSQL数据库则干脆在设计上弱化甚至去除了多表关联操作。那么,从这一点上来理解"NoSQL数据库是为了保证A与P,而牺牲C"的说法,也是可以讲得通的。当然,我们应该理解,关联问题在很多情况下不是并行处理的优点所在,这在很大程度上与Amdahl定律相符合。

  所以,从事务与关联的角度来看关系型数据库的分区可扩展性为什么受限的原因是最为清楚的。而NoSQL数据库也正是因为弱化,甚至去除了像事务与关联(全面地讲,其实还有索引等特性)等在分布式环境中会严重影响系统可用性的功能,才获得了更好的水平可扩展性。

  那么,如果将事务与关联也纳入CAP理论中一致性C的范畴的话,问题就很清楚了:关于“关系型数据库为了保证一致性C与可用性A,而不得不牺牲分区可容忍性P”的说法便是正确的了。但关于"NoSQL选择了C与P,或者A与P"的说法则是错误的,所有的NoSQL数据库在设计策略的大方向上都是选择了A与P(虽然对同一数据多个副本的读写一致性问题的设计各有不同),从来没有完全选择C与P的情况存在。

  现在看来,如果理解CAP理论只是指多个数据副本之间读写一致性的问题,那么它对关系型数据库与NoSQL数据库来讲是完全一样的,它只是运行在分布式环境中的数据管理设施在设计读写一致性问题时需要遵循的一个原则而已,却并不是NoSQL数据库具有优秀的水平可扩展性的真正原因。而如果将CAP理论中的一致性C理解为读写一致性、事务与关联操作的综合,则可以认为关系型数据库选择了C与A,而NoSQL数据库则全都是选择了A与P,但并没有选择C与P的情况存在。

五.一致性分类

  对于分布式数据系统,分区容忍性是基本要求,否则就失去了价值。因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。对于大多数WEB应用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是多数分布式数据库产品的方向。

  当然,牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值。牺牲一致性,只是不再要求关系型数据库中的强一致性,而是只要系统能达到最终一致性即可,考虑到客户体验,这个最终一致的时间窗口,要尽可能的对用户透明,也就是需要保障“用户感知到的一致性”。通常是通过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性”的时间窗口则取决于数据复制到一致状态的时间。

  对于一致性,可以分为从客户端和服务端两个不同的视角。从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。

5.1 客户端角度

  从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。

  在MongoDB中可以通过配置让复制集成员内部支持强一致性,这时可以设置一个写成功数,只有写操作成功树满足设定的值时才会向客户端返回结果。

  最终一致性根据更新数据后各进程访问到数据的时间和方式的不同,又可以区分为:因果一致性(CAUSAL CONSISTENCY),如果进程A通知进程B它已更新了一个数据项,那么进程B的后续访问将返回更新后的值,且一次写入将保证取代前一次写入。与进程A无因果关系的进程C的访问遵守一般的最终一致性规则。读己之所写(READ-YOUR-WRITES)一致性,当进程A自己更新一个数据项之后,它总是访问到更新过的值,绝不会看到旧值。这是因果一致性模型的一个特例。会话(SESSION)一致性,这是上一个模型的实用版本,它把访问存储系统的进程放到会话的上下文中。只要会话还存在,系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止,就要建立新的会话,而且系统的保证不会延续到新的会话。单调(MONOTONIC)读一致性,如果进程已经看到过数据对象的某个值,那么任何后续访问都不会返回在那个值之前的值。单调写一致性,系统保证来自同一个进程的写操作顺序执行。要是系统不能保证这种程度的一致性,就非常难以编程了。

  上述最终一致性的不同方式可以进行组合,例如单调读一致性和读己之所写一致性就可以组合实现。并且从实践的角度来看,这两者的组合,读取自己更新的数据,和一旦读取到最新的版本不会再读取旧版本,对于此架构上的程序开发来说,会少很多额外的烦恼。

5.2 服务端角度

  从服务端角度,如何尽快将更新后的数据分布到整个系统,降低达到最终一致性的时间窗口,是提高系统的可用度和用户体验非常重要的方面。对于分布式数据系统:N — 数据复制的份数,W — 更新数据是需要保证写完成的节点数,R — 读取数据的时候需要读取的节点数,如果W+R>N,写的节点和读的节点重叠,则是强一致性。例如对于典型的一主一备同步复制的关系型数据库,N=2,W=2,R=1,则不管读的是主库还是备库的数据,都是一致的。如果W+R<=N,则是弱一致性。例如对于一主一备异步复制的关系型数据库,N=2,W=1,R=1,则如果读的是备库,就可能无法读取主库已经更新过的数据,所以是弱一致性。

  对于分布式系统,为了保证高可用性,一般设置N>=3。不同的N,W,R组合,是在可用性和一致性之间取一个平衡,以适应不同的应用场景。如果N=W,R=1,任何一个写节点失效,都会导致写失败,因此可用性会降低,但是由于数据分布的N个节点是同步写入的,因此可以保证强一致性。如果N=R,W=1,只需要一个节点写入成功即可,写性能和可用性都比较高。但是读取其他节点的进程可能不能获取更新后的数据,因此是弱一致性。这种情况下,如果W<(N+1)/2,并且写入的节点不重叠的话,则会存在写冲突。

六.传统数据库与NoSQL数据库

  传统的关系型数据库在功能支持上通常很宽泛,从简单的键值查询,到复杂的多表联合查询再到事务机制的支持。而与之不同的是,NoSQL系统通常注重性能和扩展性,而非事务机制(事务就是强一致性的体现)。

  传统的SQL数据库的事务通常都是支持ACID的强事务机制。A代表原子性,即在事务中执行多个操作是原子性的,要么事务中的操作全部执行,要么一个都不执行;C代表一致性,即保证进行事务的过程中整个数据加的状态是一致的,不会出现数据花掉的情况;I代表隔离性,即两个事务不会相互影响,覆盖彼此数据等;D表示持久化,即事务一量完成,那么数据应该是被写到安全的,持久化存储的设备上(比如磁盘)。

  NoSQL系统仅提供对行级别的原子性保证,也就是说同时对同一个Key下的数据进行的两个操作,在实际执行的时候是会串行的执行,保证了每一个Key-Value对不会被破坏。例如MongoDB数据库,它是不支持事务机制的,同时也不提倡多表关联的复杂模式设计,它只保证对单个文档(相当于关系数据库中的记录)读写的原子性。

  补充: MPP架构介绍 MPP (Massively Parallel Processing),大规模并行处理系统,这样的系统是由许多松耦合的处理单元组成的,要注意的是这里指的是处理单元而不是处理器。每个单元内的CPU都有自己私有的资源,如总线,内存,硬盘等。在每个单元内都有操作系统和管理数据库的实例复本。这种结构最大的特点在于不共享资源。

七.战胜CAP

  核心内容就是放松Gilbert和Lynch证明中的限制:“系统必须同时达到CAP三个属性”,放松到“系统可以不同时达到CAP,而是分时达到”。

  CAP理论被很多人拿来作为分布式系统设计的金律,然而感觉大家对CAP这三个属性的认识却存在不少误区。从CAP的证明中可以看出来,这个理论的成立是需要很明确的对C、A、P三个概念进行界定的前提下的。在本文中笔者希望可以对论文和一些参考资料进行总结并附带一些思考

  CAP理论的表述很好地服务了它的目的,即开阔设计师的思路,在多样化的取舍方案下设计出多样化的系统。在过去的十几年里确实涌现了不计其数的新系统,也随之在数据一致性和可用性的相对关系上产生了相当多的争论。“三选二”的公式一直存在着误导性,它会过分简单化各性质之间的相互关系。现在我们有必要辨析其中的细节。实际上只有“在分区存在的前提下呈现完美的数据一致性和可用性”这种很少见的情况是CAP理论不允许出现的。

  虽然设计师仍然需要在分区的前提下对数据一致性和可用性做取舍,但具体如何处理分区和恢复一致性,这里面有不计其数的变通方案和灵活度。当代CAP实践应将目标定为针对具体的应用,在合理范围内最大化数据一致性和可用性的“合力”。这样的思路延伸为如何规划分区期间的操作和分区之后的恢复,从而启发设计师加深对CAP的认识,突破过去由于CAP理论的表述而产生的思维局限。

7.1 为什么“三选二”公式有误导性

  理解CAP理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致,即丧失了C性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了A性质。除非两个节点可以互相通信,才能既保证C又保证A,这又会导致丧失P性质。一般来说跨区域的系统,设计师无法舍弃P性质,那么就只能在数据一致性和可用性上做一个艰难选择。不确切地说,NoSQL运动的主题其实是创造各种可用性优先、数据一致性其次的方案;而传统数据库坚守ACID特性(原子性、一致性、隔离性、持久性),做的是相反的事情。下文“ACID、BASE、CAP”小节详细说明了它们的差异。

  “三选二”的观点在几个方面起了误导作用,详见下文“CAP之惑”小节的解释。首先,由于分区很少发生,那么在系统不存在分区的情况下没什么理由牺牲C或A。其次,C与A之间的取舍可以在同一系统内以非常细小的粒度反复发生,而每一次的决策可能因为具体的操作,乃至因为牵涉到特定的数据或用户而有所不同。最后,这三种性质都可以在程度上衡量,并不是非黑即白的有或无。可用性显然是在0%到100%之间连续变化的,一致性分很多级别,连分区也可以细分为不同含义,如系统内的不同部分对于是否存在分区可以有不一样的认知。

  要探索这些细微的差别,就要突破传统的分区处理方式,而这是一项根本性的挑战。因为分区很少出现,CAP在大多数时候允许完美的C和A。但当分区存在或可感知其影响的情况下,就要预备一种策略去探知分区并显式处理其影响。这样的策略应分为三个步骤:探知分区发生,进入显式的分区模式以限制某些操作,启动恢复过程以恢复数据一致性并补偿分区期间发生的错误。

7.2 解决CAP

  根据一些专家的分析,CAP并不是一个严谨的定律,并不是牺牲了Consistency,就一定能同时获得Availability和Partition Tolerance。还有一个很重要的因素是Latency,在CAP中并没有体现。在现在NoSQL以及其他一些大规模设计时,A和P并不是牺牲C或部分牺牲C的借口,因为即使牺牲了C,也不一定A和P,并且C不一定必须要牺牲。

  淘宝一天就处理了1亿零580万,而12306一天处理的交易仅仅166万条 ,如果从并发性上来说,淘宝的并发量远比12306大,但天猫的商品信息,促销数据都可以做缓存,做CDN,而12306的“商品”是一个个座位,这些座位必须通过后端数据库即时查询出来,状态的一致性要求很高。

  从这点上看,12306的商品信息很难利用到缓存,因此12306查看“商品”的代价是比较大的,涉及到一系列的后端数据库操作,从这个角度讲,12306的复杂度是高于天猫的。 淘宝的商品相对独立,而12306商品之间的关联性很大,由于CAP定律限制,如果其商品的一致性要求过高,必然对可用性和分区容错性造成影响。

  因此,业务设计上,如果找到一条降低一致性要求时,还能保证业务的正确性的业务分拆之路。举个例子,火车票查询时,不要显示多少张,而是显示“有”或“无”,或者显示>100张,50~100,小于50等,这样就可以减小状态的更新频率,充分使用缓存数据。

  CAP 理论说在一个系统中对某个数据不存在一个算法同时满足 Consistency, Availability, Partition-tolerance。注意,这里边最重要和最容易被人忽视的是限定词“对某个数据不存在一个算法”。这就是说在一个系统中,可以对某些数据做到 CP, 对另一些数据做到 AP,就算是对同一个数据,调用者可以指定不同的算法,某些算法可以做到 CP,某些算法可以做到 AP。

7.3 做到两项

  要做到 CP, 系统可以把这个数据只放在一个节点上,其他节点收到请求后向这个节点读或写数据,并返回结果。很显然,串行化是保证的。但是如果报文可以任意丢失的话,接受请求的节点就可能永远不返回结果。

  要做到 CA, 一个现实的例子就是单点的数据库。你可能会疑惑“数据库也不是 100% 可用的呀?” 要回答这个疑惑,注意上面说的故障模型和 availability 的定义就可以了。

  要做到 AP, 系统只要每次对写都返回成功,对读都返回固定的某个值就可以了。

如果我们到这里就觉得已近掌握好 CAP 理论了,那么就相当于刚把橘子剥开,就把它扔了。

  CAP 理论更重要的一个结果是, 在 Partial Synchronous System (半同步系统) 中,一个弱化的 CAP 是能达到的:对所有的数据访问,总返回一个结果 * 如果期间没有报文丢失,那么返回一个满足 consistency 要求的结果。

  这里的半同步系统指每个节点存在一个时钟,这些时钟不需要同步,但是按照相同的速率流逝。更通俗的来说,就是一个能够实现超时机制的系统。

  举个例子,系统可以把这个数据只放在一个节点上,其他节点收到请求后向这个节点读或写数据,并设置一个定时器,如果超时前得到结果,那么返回这个结果,否则返回失败。更进一步的,也是最重要的,实现一个满足最终一致性 (Eventually Consistency) 和 AP 的系统是可行的。现实中的一个例子是 Cassandra 系统。

  而对于分布式数据系统,分区容忍性是基本要求,否则就失去了价值。因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。对于大多数WEB应用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是多数分布式数据库产品的方向。 当然,牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值。牺牲一致性,只是不再要求关系型数据库中的强一致性,而是只要系统能达到最终一致性即可,考虑到客户体验,这个最终一致的时间窗口,要尽可能的对用户透明,也就是需要保障“用户感知到的一致性”。通常是通过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性”的时间窗口则取决于数据复制到一致状态的时间。

  最终一致性(EVENTUALLY CONSISTENT) 对于一致性,可以分为从客户端和服务端两个不同的视角。从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。最终一致性根据更新数据后各进程访问到数据的时间和方式的不同,又可以区分为: 因果一致性(CAUSALCONSISTENCY)

  如果进程A通知进程B它已更新了一个数据项,那么进程B的后续访问将返回更新后的值,且一次写入将保证取代前一次写入。与进程A无因果关系的进程C的访问遵守一般的最终一致性规则。“读己之所写(READ-YOUR-WRITES)”一致性。当进程A自己更新一个数据项之后,它总是访问到更新过的值,绝不会看到旧值。这是因果一致性模型的一个特例。会话(SESSION)一致性。这是上一个模型的实用版本,它把访问存储系统的进程放到会话的上下文中。只要会话还存在,系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止,就要建立新的会话,而且系统的保证不会延续到新的会话。单调(MONOTONIC)读一致性。如果进程已经看到过数据对象的某个值,那么任何后续访问都不会返回在那个值之前的值。单调写一致性。系统保证来自同一个进程的写操作顺序执行。要是系统不能保证这种程度的一致性,就非常难以编程了。上述最终一致性的不同方式可以进行组合,例如单调读一致性和读己之所写一致性就可以组合实现。并且从实践的角度来看,这两者的组合,读取自己更新的数据,和一旦读取到最新的版本不会再读取旧版本,对于此架构上的程序开发来说,会少很多额外的烦恼。从服务端角度,如何尽快将更新后的数据分布到整个系统,降低达到最终一致性的时间窗口,是提高系统的可用度和用户体验非常重要的方面。

  对于分布式数据系统: N — 数据复制的份数,W — 更新数据是需要保证写完成的节点数,R — 读取数据的时候需要读取的节点数如果W+R>N,写的节点和读的节点重叠,则是强一致性。例如对于典型的一主一备同步复制的关系型数据库,N=2,W=2,R=1,则不管读的是主库还是备库的数据,都是一致的。 如果W+R<=N,则是弱一致性。例如对于一主一备异步复制的关系型数据库,N=2,W=1,R=1,则如果读的是备库,就可能无法读取主库已经更新过的数据,所以是弱一致性。 对于分布式系统,为了保证高可用性,一般设置N>=3。不同的N,W,R组合,是在可用性和一致性之间取一个平衡,以适应不同的应用场景。如果N=W,R=1,任何一个写节点失效,都会导致写失败,因此可用性会降低,但是由于数据分布的N个节点是同步写入的,因此可以保证强一致性。如果N=R,W=1,只需要一个节点写入成功即可,写性能和可用性都比较高。但是读取其他节点的进程可能不能获取更新后的数据,因此是弱一致性。这种情况下,如果W<(N+1)/2,并且写入的节点不重叠的话,则会存在写冲突。

http://xjsunjie.blog.51cto.com/999372/1885452

http://zheming.wang/blog/2014/09/12/D730A966-E221-4221-9314-61DA513CBE31/

传统的ACID分别是什么

关系型数据库遵循ACID规则

事务在英文中是transaction,和现实世界中的交易很类似,它有如下四个特性:

1、A (Atomicity) 原子性

原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。比如银行转账,从A账户转100元至B账户,分为两个步骤:1)从A账户取100元;2)存入100元至B账户。这两步要么一起完成,要么一起不完成,如果只完成第一步,第二步失败,钱会莫名其妙少了100元。

2、C (Consistency) 一致性

一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。

3、I (Isolation) 独立性

所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。比如现有有个交易是从A账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加的100元的

4、D (Durability) 持久性

持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。

CAP

C:Consistency(强一致性)

A:Availability(可用性)

P:Partition tolerance(分区容错性)

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

因此,根据 CAP 原理将NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。

CP - 满足一致性,分区容错性的系统,通常性能不是特别高。

AP - 满足可用性,分区容错性的系统,通常可能对一致性要求低一些。

CAP的3选2

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。

而由于当前的网络硬件肯定会出现延迟丢包等问题,所以

分区容错性是我们必须需要实现的。

所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。

---------------------------------------------------------------------------------------------

C:强一致性 A:高可用性 P:分布式容错性

CA 传统Oracle数据库

AP 大多数网站架构的选择

CP Redis、Mongodb

注意:分布式架构的时候必须做出取舍。

一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。

因此牺牲C换取P,这是目前分布式数据库产品的方向

---------------------------------------------------------------------------------------------

一致性与可用性的决择

对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地

数据库事务一致性需求

  很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求并不高。允许实现最终一致性。

数据库的写实时性和读实时性需求

  对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。

对复杂的SQL查询,特别是多表关联查询的需求

  任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角 度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。

BASE是什么

BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。

BASE其实是下面三个术语的缩写:

基本可用(Basically Available)

软状态(Soft state)

最终一致(Eventually consistent)

它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法

分布式和集群

分布式系统(distributed system)

由多台计算机和通信的软件组件通过计算机网络连接(本地网络或广域网)组成。分布式系统是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。分布式系统可以应用在在不同的平台上如:Pc、工作站、局域网和广域网上等。

简单来讲:

1.分布式:不同的多台服务器上面部署不同的服务模块(工程),他们之间通过Rpc/Rmi之间通信和调用,对外提供服务和组内协作。

2.集群:不同的多台服务器上面部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和访问。

18.2 Redis入门介绍

18.2.1 入门概述

Redis:REmote DIctionary Server(远程字典服务器),它是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器。

Redis具有如下特点:

1.Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用

2.Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

3.Redis支持数据的备份,即master-slave模式的数据备份

Redis具有如下功能:

1.内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务

2.取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面

3.模拟类似于HttpSession这种需要设定过期时间的功能

4.发布、订阅消息系统

5.定时器、计数器

下载地址:

http://redis.io/

http://www.redis.cn/

18.2.2 VMWare上安装Linux

VMWare虚拟机的安装

CentOS或者RedHad5的安装

如何查看自己的linux是32位还是64位:

getconf LONG_BIT返回是多少就是几位

VMTools的安装

设置共享目录

上述环境都OK后开始进行Redis的服务器安装配置

18.2.3 Redis的安装

Windows版安装

Window下安装

下载地址:https://github.com/dmajkic/redis/downloads

下载到的Redis支持32bit和64bit。根据自己实际情况选择,将64bit的内容cp到自定义盘符安装目录取名redis。 如C:\reids

打开一个cmd窗口 使用cd命令切换目录到 C:\redis 运行 redis-server.exe redis.conf 。

如果想方便的话,可以把redis的路径加到系统的环境变量里,这样就省得再输路径了,后面的那个redis.conf可以省略,

如果省略,会启用默认的。输入之后,会显示如下界面:

这时候另启一个cmd窗口,原来的不要关闭,不然就无法访问服务端了。

切换到redis目录下运行 redis-cli.exe -h127.0.0.1 -p 6379 。

设置键值对 set myKey abc

取出键值对 get myKey

重要提示:

由于企业里面做Redis开发,99%都是Linux版的运用和安装,几乎不会涉及到Windows版,上一步的讲解只是为了知识的完整性,Windows版不作为重点,企业实战就认一个版:Linux

Linux版安装

1.下载获得redis-3.0.4.tar.gz后将它放入我们的Linux目录/opt

2./opt目录下,解压命令:tar -zxvf redis-3.0.4.tar.gz

3.解压完成后出现文件夹:redis-3.0.4,进入目录:cd redis-3.0.4

4.在redis-3.0.4目录下执行make命令

5.如果make完成后继续执行make install

6.查看默认安装目录:usr/local/bin

redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何,服务启动起来后执行

redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲

redis-check-dump:修复有问题的dump.rdb文件

redis-cli:客户端,操作入口

redis-sentinel:redis集群使用

redis-server:Redis服务器启动命令

7.启动

修改redis.conf文件将里面的daemonize no 改成 yes,让服务在后台启动

启动

连通测试

/usr/local/bin目录下运行redis-server,运行拷贝出存放了自定义conf文件目录下的redis.conf文件

8.永远的helloworld

9.关闭

单实例关闭:redis-cli shutdown

多实例关闭,指定端口关闭:redis-cli -p 6379shutdown

18.2.4 Redis启动后杂项基础知识讲解

1.单进程

单进程模型来处理客户端的请求。对读写等事件的响应 是通过对epoll函数的包装来做到的。Redis的实际处理速度完全依靠主进程的执行效率

epoll是Linux内核为处理大批量文件描述符而作了改进的epoll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

2.默认16个数据库,类似数组下表从零开始,初始默认使用零号库

设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

databases 16

3.select命令切换数据库

4.dbsize查看当前数据库的key的数量

5.flushdb:清空当前库

6.Flushall;通杀全部库

7.统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上

8.Redis索引都是从零开始

9.为什么默认端口是6379

19 20.Redis的数据类型

19.1 Redis的数据类型

http://redisdoc.com/

19.1.1 Redis的五大数据类型

string(字符串)

String(字符串)

string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

hash(哈希,类似java里的Map)

Hash(哈希)

Redis hash 是一个键值对集合。

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

类似Java里面的Map<String,Object>

list(列表)

List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。

它的底层实际是个链表

set(集合)

Set(集合)

Redis的Set是string类型的无序集合。它是通过HashTable实现实现的

zset(sorted set:有序集合)

zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。

redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

19.1.2 Redis 键(key)

常用

案例

keys *

exists key的名字,判断某个key是否存在

move key db   --->当前库就没有了,被移除了

expire key 秒钟:为给定的key设置过期时间

ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期

type key 查看你的key是什么类型

示例:

19.1.3 Redis字符串(String)

常用

案例

1.set/get/del/append/strlen

2.Incr/decr/incrby/decrby,一定要是数字才能进行加减

3.getrange/setrange

getrange:获取指定区间范围内的值,类似between......and的关系

4.setex(set with expire)键秒值/setnx(setif not exist)

5.mset/mget/msetnx

6.getset(先get再set)

getset:将给定 key 的值设为 value ,并返回key 的旧值(old value)。

简单一句话,先get然后立即set

19.1.4 Redis列表(List)

常用

案例

lpush/rpush/lrange

lpop/rpop

lindex,按照索引下标获得元素(从上到下)

通过索引获取列表中的元素 lindex key index

llen

获取集合的大小:

lremkey 删N个value

1.从left往right删除2个值等于v1的元素,返回的值为实际删除的数量

2.LREM list3 0 值,表示删除全部给定的值。零个就是全部值

ltrim

ltrim key 开始index 结束index,截取指定范围的值后再赋值给key

rpoplpush 源列表 目的列表

移除列表的最后一个元素,并将该元素添加到另一个列表并返回

lsetkey index value

linsert key before/after 值1 值2

在list某个已有值的前后再添加具体值

性能总结

它是一个字符串链表,left、right都可以插入添加;

如果键不存在,创建新的链表;

如果键已存在,新增内容;

如果值全移除,对应的键也就消失了。

链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很慢。

19.1.5 Redis集合(Set)

常用

案例

sadd/smembers/sismember

scard,获取集合里面的元素个数

获取集合里面的元素个数

sremkey value 删除集合中元素

srandmember key 某个整数(随机出几个数)

*从set集合里面随机取出2个

*如果超过最大数量就全部取出,

*如果写的值是负数,比如-3 ,表示需要取出3个,但是可能会有重复值。

spopkey 随机出栈

smovekey1 key2 在key1里某个值

smove key1 key2 ke1中的某个值

作用是将key1里的某个值赋给key2

数学集合类

差集:sdiff

在第一个set里面而不在后面任何一个set里面的项

交集:sinter

并集:sunion

19.1.6 Redis哈希(Hash)

KV模式不变,但V是一个键值对

常用

案例

hset/hget/hmset/hmget/hgetall/hdel

hlen

hexists key key里面的某个值的key

hkeys/hvals

hincrby/hincrbyfloat

hsetnx

19.1.7 Redis有序集合Zset(sorted set)

与set比起来多了一个score

常用

案例

zadd/zrange

zrangebyscore key 开始score结束score

zremkey 某score下对应的value值

zrem key 某score下对应的value值,作用是删除元素,项的值可以是多个

zcard/zcount/zrank /zscore

1.zcard :获取集合中元素个数

2.zcount :获取分数区间内元素个数,zcount key 开始分数区间 结束分数区间

这里是闭区间

3.zrank:获取value在zset中的下标位置

4.zscore:按照值获得对应的分数

zrevrank

正序、逆序获得下标索引值

zrevrange

zrevrangebyscore  key 结束score 开始score

zrevrangebyscore zset1 90 60 withscores   分数是反着来的

20 21.Redis配置文件和持久化

20.1 解析配置文件redis.conf

#redis.conf

# Redis configuration file example.

# ./redis-server /path/to/redis.conf

################################## INCLUDES###################################

#这在你有标准配置模板但是每个redis服务器又需要个性设置的时候很有用。

# include /path/to/local.conf

# include /path/to/other.conf

################################ GENERAL#####################################

#是否在后台执行,yes:后台运行;no:不是后台运行(老版本默认)

daemonize yes

#3.2里的参数,是否开启保护模式,默认开启。要是配置里没有指定bind和密码。开启该参数后,redis只会本地进行访问,拒绝外部访问。要是开启了密码   和bind,可以开启。否   则最好关闭,设置为no。

protected-mode yes

#redis的进程文件

pidfile /var/run/redis/redis-server.pid

#redis监听的端口号。

port 6379

#此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度,当然此值必须不大于Linux系统定义的/proc/sys/net/core/somaxconn值,默认是511,而Linux的默认参数值是128。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。该内核参数默认值一般是128,对于负载很大的服务程序来说大大的不够。一般会将它修改为2048或者更大。在/etc/sysctl.conf中添加:net.core.somaxconn =2048,然后在终端中执行sysctl -p。

tcp-backlog 511

#指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求

bind 127.0.0.1

#配置unix socket来让redis支持监听本地连接。

# unixsocket /var/run/redis/redis.sock

#配置unix socket使用文件的权限

# unixsocketperm 700

# 此参数为设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0。

timeout 0

#tcp keepalive参数。如果设置不为0,就使用配置tcp的SO_KEEPALIVE值,使用keepalive有两个好处:检测挂掉的对端。降低中间设备出问题而导致网络看似连接却已经与对端端口的问题。在Linux内核中,设置了keepalive,redis会定时给对端发送ack。检测到对端关闭需要两倍的设置值。

tcp-keepalive 0

#指定了服务端日志的级别。级别包括:debug(很多信息,方便开发、测试),verbose(许多有用的信息,但是没有debug级别信息多),notice(适当的日志级别,适合生产环境),warn(只有非常重要的信息)

loglevel notice

#指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。

logfile /var/log/redis/redis-server.log

#是否打开记录syslog功能

# syslog-enabled no

#syslog的标识符。

# syslog-ident redis

#日志的来源、设备

# syslog-facility local0

#数据库的数量,默认使用的数据库是DB 0。可以通过”SELECT “命令选择一个db

databases 16

################################SNAPSHOTTING ################################

# 快照配置

# 注释掉“save”这一行配置项就可以让保存数据库功能失效

# 设置sedis进行数据库镜像的频率。

# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)

# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)

# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)

save 900 1

save 300 10

save 60 10000

#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误

stop-writes-on-bgsave-error yes

#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间

rdbcompression yes

#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。

rdbchecksum yes

#rdb文件的名称

dbfilename dump.rdb

#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录

dir /var/lib/redis

#################################REPLICATION #################################

#复制选项,slave复制对应的master。

# slaveof <masterip><masterport>

#如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证。

# masterauth <master-password>

#当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。

slave-serve-stale-data yes

#作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议)。

slave-read-only yes

#是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。有2种方式:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式。

repl-diskless-sync no

#diskless复制的延迟时间,防止设置为0。一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好等待一段时间,等更多的slave连上来。

repl-diskless-sync-delay 5

#slave根据指定的时间间隔向服务器发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒。

# repl-ping-slave-period 10

#复制连接超时时间。master和slave都有超时时间的设置。master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时。

# repl-timeout 60

#是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。默认是no,即使用tcp nodelay。如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。但是这也可能带来数据的延迟。默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes。

repl-disable-tcp-nodelay no

#复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m。

# repl-backlog-size 5mb

#master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。

# repl-backlog-ttl 3600

#当master不可用,Sentinel会根据slave的优先级选举一个master。最低的优先级的slave,当选master。而配置成0,永远不会被选举。

slave-priority 100

#redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能。

# min-slaves-to-write 3

#延迟小于min-slaves-max-lag秒的slave才认为是健康的slave。

# min-slaves-max-lag 10

# 设置1或另一个设置为0禁用这个特性。

# Setting one or the other to 0 disablesthe feature.

# By default min-slaves-to-write is set to0 (feature disabled) and

# min-slaves-max-lag is set to 10.

################################## SECURITY###################################

#requirepass配置可以让用户使用AUTH命令来认证密码,才能使用其他命令。这让redis可以使用在不受信任的网络中。为了保持向后的兼容性,可以注释该命令,因为大部分用户也不需要认证。使用requirepass的时候需要注意,因为redis太快了,每秒可以认证15w次密码,简单的密码很容易被攻破,所以最好使用一个更复杂的密码。

# requirepass foobared

#把危险的命令给修改成其他名称。比如CONFIG命令可以重命名为一个很难被猜到的命令,这样用户不能使用,而内部工具还能接着使用。

# rename-command CONFIGb840fc02d524045429941cc15f59e41cb7be6c52

#设置成一个空的值,可以禁止一个命令

# rename-command CONFIG ""

################################### LIMITS####################################

# 设置能连上redis的最大客户端连接数量。默认是10000个客户端连接。由于redis不区分连接是客户端连接还是内部打开文件或者和slave连接等,所以maxclients最小建议设置到32。如果超过了maxclients,redis会给新的连接发送’max number of clients reached’,并关闭连接。

# maxclients 10000

#redis配置的最大内存容量。当内存满了,需要配合maxmemory-policy策略进行处理。注意slave的输出缓冲区是不计算在maxmemory内的。所以为了防止主机内存使用完,建议设置的maxmemory需要更小一些。

# maxmemory <bytes>

#内存容量超过maxmemory后的处理策略。

#volatile-lru:利用LRU算法移除设置过过期时间的key。

#volatile-random:随机移除设置过过期时间的key。

#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)

#allkeys-lru:利用LRU算法移除任何key。

#allkeys-random:随机移除任何key。

#noeviction:不移除任何key,只是返回一个写错误。

#上面的这些驱逐策略,如果redis没有合适的key驱逐,对于写命令,还是会返回错误。redis将不再接收写请求,只接收get请求。写命令包括:set setnx setex append incr decr rpush lpush rpushx lpushx linsertlset rpoplpush sadd sinter sinterstore sunion sunionstore sdiff sdiffstore zaddzincrby zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby getsetmset msetnx exec sort。

# maxmemory-policy noeviction

#lru检测的样本数。使用lru或者ttl淘汰算法,从需要淘汰的列表中随机选择sample个key,选出闲置时间最长的key移除。

# maxmemory-samples 5

############################## APPEND ONLYMODE ###############################

#默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。

appendonly no

#aof文件名

appendfilename "appendonly.aof"

#aof持久化策略的配置

#no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。

#always表示每次写入都执行fsync,以保证数据同步到磁盘。

#everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。

appendfsync everysec

# 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。

no-appendfsync-on-rewrite no

#aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。

auto-aof-rewrite-percentage 100

#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写

auto-aof-rewrite-min-size 64mb

#aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。

aof-load-truncated yes

################################ LUASCRIPTING ###############################

# 如果达到最大时间限制(毫秒),redis会记个log,然后返回error。当一个脚本超过了最大时限。只有SCRIPT KILL和SHUTDOWN NOSAVE可以用。第一个可以杀没有调write命令的东西。要是已经调用了write,只能用第二个命令杀。

lua-time-limit 5000

################################ REDISCLUSTER ###############################

#集群开关,默认是不开启集群模式。

# cluster-enabled yes

#集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系统中配置文件名称不冲突

# cluster-config-file nodes-6379.conf

#节点互连超时的阀值。集群节点超时毫秒数

# cluster-node-timeout 15000

#在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是:

#比较slave断开连接的时间和(node-timeout * slave-validity-factor)+ repl-ping-slave-period

#如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移

# cluster-slave-validity-factor 10

#master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移。

# cluster-migration-barrier 1

#默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致。

# cluster-require-full-coverage yes

################################## SLOW LOG###################################

###slog log是用来记录redis运行中执行比较慢的命令耗时。当命令的执行超过了指定时间,就记录在slow log中,slog log保存在内存中,所以没有IO操作。

#执行时间比slowlog-log-slower-than大的请求记录到slowlog里面,单位是微秒,所以1000000就是1秒。注意,负数时间会禁用慢查询日志,而0则会强制记录所有命令。

slowlog-log-slower-than 10000

#慢查询日志长度。当一个新的命令被写进日志的时候,最老的那个记录会被删掉。这个长度没有限制。只要有足够的内存就行。你可以通过 SLOWLOG RESET 来释放内存。

slowlog-max-len 128

################################ LATENCYMONITOR ##############################

#延迟监控功能是用来监控redis中执行比较缓慢的一些操作,用LATENCY打印redis实例在跑命令时的耗时图表。只记录大于等于下边设置的值的操作。0的话,就是关闭监视。默认延迟监控功能是关闭的,如果你需要打开,也可以通过CONFIG SET命令动态设置。

latency-monitor-threshold 0

############################# EVENTNOTIFICATION ##############################

#键空间通知使得客户端可以通过订阅频道或模式,来接收那些以某种方式改动了 Redis 数据集的事件。因为开启键空间通知功能需要消耗一些 CPU ,所以在默认配置下,该功能处于关闭状态。

#notify-keyspace-events 的参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知:

##K 键空间通知,所有通知以 __keyspace@__ 为前缀

##E 键事件通知,所有通知以 __keyevent@__ 为前缀

##g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知

##$ 字符串命令的通知

##l 列表命令的通知

##s 集合命令的通知

##h 哈希命令的通知

##z 有序集合命令的通知

##x 过期事件:每当有过期键被删除时发送

##e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送

##A 参数 g$lshzxe 的别名

#输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何 通知被分发。详细使用可以参考http://redis.io/topics/notifications

notify-keyspace-events ""

############################### ADVANCEDCONFIG ###############################

#数据量小于等于hash-max-ziplist-entries的用ziplist,大于hash-max-ziplist-entries用hash

hash-max-ziplist-entries 512

#value大小小于等于hash-max-ziplist-value的用ziplist,大于hash-max-ziplist-value用hash。

hash-max-ziplist-value 64

#数据量小于等于list-max-ziplist-entries用ziplist,大于list-max-ziplist-entries用list。

list-max-ziplist-entries 512

#value大小小于等于list-max-ziplist-value的用ziplist,大于list-max-ziplist-value用list。

list-max-ziplist-value 64

#数据量小于等于set-max-intset-entries用iniset,大于set-max-intset-entries用set。

set-max-intset-entries 512

#数据量小于等于zset-max-ziplist-entries用ziplist,大于zset-max-ziplist-entries用zset。

zset-max-ziplist-entries 128

#value大小小于等于zset-max-ziplist-value用ziplist,大于zset-max-ziplist-value用zset。

zset-max-ziplist-value 64

#value大小小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse),大于hll-sparse-max-bytes使用稠密的数据结构(dense)。一个比16000大的value是几乎没用的,建议的value大概为3000。如果对CPU要求不高,对空间要求较高的,建议设置到10000左右。

hll-sparse-max-bytes 3000

#Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存。

activerehashing yes

##对客户端输出缓冲进行限制可以强迫那些不从服务器读取数据的客户端断开连接,用来强制关闭传输缓慢的客户端。

#对于normal client,第一个0表示取消hard limit,第二个0和第三个0表示取消soft limit,normal client默认取消限制,因为如果没有寻问,他们是不会接收数据的。

client-output-buffer-limit normal 0 0 0

#对于slave client和MONITER client,如果client-output-buffer一旦超过256mb,又或者超过64mb持续60秒,那么服务器就会立即断开客户端连接。

client-output-buffer-limit slave 256mb 64mb60

#对于pubsub client,如果client-output-buffer一旦超过32mb,又或者超过8mb持续60秒,那么服务器就会立即断开客户端连接。

client-output-buffer-limit pubsub 32mb 8mb60

#redis执行任务的频率为1s除以hz。

hz 10

#在aof重写的时候,如果打开了aof-rewrite-incremental-fsync开关,系统会每32MB执行一次fsync。这对于把文件写入磁盘是有帮助的,可以避免过大的延迟峰值。

aof-rewrite-incremental-fsync yes

---------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------------

-----------------------------------

-----------------------------------

-----------------------------------

---------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------------

# redis 配置文件示例

# 当你需要为某个配置项指定内存大小的时候,必须要带上单位,

# 通常的格式就是 1k 5gb 4m 等酱紫:

#

# 1k => 1000 bytes

# 1kb => 1024 bytes

# 1m => 1000000 bytes

# 1mb => 1024*1024 bytes

# 1g => 1000000000 bytes

# 1gb => 1024*1024*1024 bytes

#

# 单位是不区分大小写的,你写 1K 5GB 4M 也行

################################## INCLUDES###################################

# 假如说你有一个可用于所有的 redis server 的标准配置模板,

# 但针对某些 server 又需要一些个性化的设置,

# 你可以使用 include 来包含一些其他的配置文件,这对你来说是非常有用的。

#

# 但是要注意哦,include 是不能被 config rewrite 命令改写的

# 由于 redis 总是以最后的加工线作为一个配置指令值,所以你最好是把 include 放在这个文件的最前面,

# 以避免在运行时覆盖配置的改变,相反,你就把它放在后面(外国人真啰嗦)。

#

# include /path/to/local.conf

# include /path/to/other.conf

################################ 常用#####################################

# 默认情况下 redis 不是作为守护进程运行的,如果你想让它在后台运行,你就把它改成 yes。

# 当redis作为守护进程运行的时候,它会写一个 pid 到 /var/run/redis.pid 文件里面。

daemonize no

# 当redis作为守护进程运行的时候,它会把 pid 默认写到 /var/run/redis.pid 文件里面,

# 但是你可以在这里自己制定它的文件位置。

pidfile /var/run/redis.pid

# 监听端口号,默认为 6379,如果你设为 0 ,redis将不在 socket 上监听任何客户端连接。

port 6379

# TCP 监听的最大容纳数量

#

# 在高并发的环境下,你需要把这个值调高以避免客户端连接缓慢的问题。

# Linux 内核会一声不响的把这个值缩小成 /proc/sys/net/core/somaxconn 对应的值,

# 所以你要修改这两个值才能达到你的预期。

tcp-backlog 511

# 默认情况下,redis 在 server 上所有有效的网络接口上监听客户端连接。

# 你如果只想让它在一个网络接口上监听,那你就绑定一个IP或者多个IP。

#

# 示例,多个IP用空格隔开:

#

# bind 192.168.1.100 10.0.0.1

# bind 127.0.0.1

# 指定 unix socket 的路径。

#

# unixsocket /tmp/redis.sock

# unixsocketperm 755

# 指定在一个 client 空闲多少秒之后关闭连接(0 就是不管它)

timeout 0

# tcp 心跳包。

#

# 如果设置为非零,则在与客户端缺乏通讯的时候使用 SO_KEEPALIVE 发送 tcp acks 给客户端。

# 这个之所有有用,主要由两个原因:

#

# 1) 防止死的 peers

# 2) Take the connection alive from thepoint of view of network

#    equipment in the middle.

#

# On Linux, the specified value (inseconds) is the period used to send ACKs.

# Note that to close the connection thedouble of the time is needed.

# On other kernels the period depends onthe kernel configuration.

#

# A reasonable value for this option is 60seconds.

# 推荐一个合理的值就是60秒

tcp-keepalive 0

# 定义日志级别。

# 可以是下面的这些值:

# debug (适用于开发或测试阶段)

# verbose (many rarely useful info, but nota mess like the debug level)

# notice (适用于生产环境)

# warning (仅仅一些重要的消息被记录)

loglevel notice

# 指定日志文件的位置

logfile ""

# 要想把日志记录到系统日志,就把它改成 yes,

# 也可以可选择性的更新其他的syslog 参数以达到你的要求

# syslog-enabled no

# 设置 syslog 的 identity。

# syslog-ident redis

# 设置 syslog 的 facility,必须是 USER 或者是 LOCAL0-LOCAL7 之间的值。

# syslog-facility local0

# 设置数据库的数目。

# 默认数据库是 DB 0,你可以在每个连接上使用 select <dbid> 命令选择一个不同的数据库,

# 但是 dbid 必须是一个介于 0 到databasees - 1 之间的值

databases 16

################################ 快照 ################################

#

# 存 DB 到磁盘:

#

#   格式:save <间隔时间(秒)> <写入次数>

#

#   根据给定的时间间隔和写入次数将数据保存到磁盘

#

#   下面的例子的意思是:

#   900 秒内如果至少有 1 个 key 的值变化,则保存

#   300 秒内如果至少有 10 个 key 的值变化,则保存

#   60 秒内如果至少有 10000 个 key 的值变化,则保存

#  

#   注意:你可以注释掉所有的 save 行来停用保存功能。

#   也可以直接一个空字符串来实现停用:

#   save ""

save 900 1

save 300 10

save 60 10000

# 默认情况下,如果 redis 最后一次的后台保存失败,redis 将停止接受写操作,

# 这样以一种强硬的方式让用户知道数据不能正确的持久化到磁盘,

# 否则就会没人注意到灾难的发生。

#

# 如果后台保存进程重新启动工作了,redis 也将自动的允许写操作。

#

# 然而你要是安装了靠谱的监控,你可能不希望 redis 这样做,那你就改成 no 好了。

stop-writes-on-bgsave-error yes

# 是否在 dump .rdb 数据库的时候使用 LZF 压缩字符串

# 默认都设为 yes

# 如果你希望保存子进程节省点 cpu ,你就设置它为 no ,

# 不过这个数据集可能就会比较大

rdbcompression yes

# 是否校验rdb文件

rdbchecksum yes

# 设置 dump 的文件位置

dbfilename dump.rdb

# 工作目录

# 例如上面的 dbfilename 只指定了文件名,

# 但是它会写入到这个目录下。这个配置项一定是个目录,而不能是文件名。

dir ./

################################# 主从复制#################################

# 主从复制。使用 slaveof 来让一个 redis 实例成为另一个reids 实例的副本。

# 注意这个只需要在 slave 上配置。

#

# slaveof <masterip><masterport>

# 如果 master 需要密码认证,就在这里设置

# masterauth <master-password>

# 当一个 slave 与 master 失去联系,或者复制正在进行的时候,

# slave 可能会有两种表现:

#

# 1) 如果为 yes ,slave 仍然会应答客户端请求,但返回的数据可能是过时,

#    或者数据可能是空的在第一次同步的时候

#

# 2) 如果为 no ,在你执行除了 info he salveof 之外的其他命令时,

#    slave 都将返回一个 "SYNC with master inprogress" 的错误,

#

slave-serve-stale-data yes

# 你可以配置一个slave 实体是否接受写入操作。

# 通过写入操作来存储一些短暂的数据对于一个 slave 实例来说可能是有用的,

# 因为相对从 master 重新同步数而言,据数据写入到 slave 会更容易被删除。

# 但是如果客户端因为一个错误的配置写入,也可能会导致一些问题。

#

# 从 redis 2.6 版起,默认 slaves 都是只读的。

#

# Note: read only slaves are not designedto be exposed to untrusted clients

# on the internet. It's just a protectionlayer against misuse of the instance.

# Still a read only slave exports bydefault all the administrative commands

# such as CONFIG, DEBUG, and so forth. To alimited extent you can improve

# security of read only slaves using'rename-command' to shadow all the

# administrative / dangerous commands.

# 注意:只读的 slaves 没有被设计成在 internet 上暴露给不受信任的客户端。

# 它仅仅是一个针对误用实例的一个保护层。

slave-read-only yes

# Slaves 在一个预定义的时间间隔内发送 ping 命令到 server。

# 你可以改变这个时间间隔。默认为 10 秒。

#

# repl-ping-slave-period 10

# The following option sets the replicationtimeout for:

# 设置主从复制过期时间

#

# 1) Bulk transfer I/O during SYNC, fromthe point of view of slave.

# 2) Master timeout from the point of viewof slaves (data, pings).

# 3) Slave timeout from the point of viewof masters (REPLCONF ACK pings).

#

# It is important to make sure that thisvalue is greater than the value

# specified for repl-ping-slave-periodotherwise a timeout will be detected

# every time there is low traffic betweenthe master and the slave.

# 这个值一定要比 repl-ping-slave-period 大

#

# repl-timeout 60

# Disable TCP_NODELAY on the slave socketafter SYNC?

#

# If you select "yes" Redis willuse a smaller number of TCP packets and

# less bandwidth to send data to slaves.But this can add a delay for

# the data to appear on the slave side, upto 40 milliseconds with

# Linux kernels using a defaultconfiguration.

#

# If you select "no" the delayfor data to appear on the slave side will

# be reduced but more bandwidth will beused for replication.

#

# By default we optimize for low latency,but in very high traffic conditions

# or when the master and slaves are manyhops away, turning this to "yes" may

# be a good idea.

repl-disable-tcp-nodelay no

# 设置主从复制容量大小。这个 backlog 是一个用来在 slaves 被断开连接时

# 存放 slave 数据的 buffer,所以当一个 slave 想要重新连接,通常不希望全部重新同步,

# 只是部分同步就够了,仅仅传递 slave 在断开连接时丢失的这部分数据。

#

# The biggest the replication backlog, thelonger the time the slave can be

# disconnected and later be able to performa partial resynchronization.

# 这个值越大,salve 可以断开连接的时间就越长。

#

# The backlog is only allocated once thereis at least a slave connected.

#

# repl-backlog-size 1mb

# After a master has no longer connectedslaves for some time, the backlog

# will be freed. The following optionconfigures the amount of seconds that

# need to elapse, starting from the timethe last slave disconnected, for

# the backlog buffer to be freed.

# 在某些时候,master 不再连接 slaves,backlog将被释放。

#

# A value of 0 means to never release thebacklog.

# 如果设置为 0 ,意味着绝不释放 backlog 。

#

# repl-backlog-ttl 3600

# 当 master 不能正常工作的时候,Redis Sentinel 会从 slaves 中选出一个新的 master,

# 这个值越小,就越会被优先选中,但是如果是 0 , 那是意味着这个 slave 不可能被选中。

#

# 默认优先级为 100。

slave-priority 100

# It is possible for a master to stopaccepting writes if there are less than

# N slaves connected, having a lag less orequal than M seconds.

#

# The N slaves need to be in"online" state.

#

# The lag in seconds, that must be <=the specified value, is calculated from

# the last ping received from the slave,that is usually sent every second.

#

# This option does not GUARANTEES that Nreplicas will accept the write, but

# will limit the window of exposure forlost writes in case not enough slaves

# are available, to the specified number ofseconds.

#

# For example to require at least 3 slaveswith a lag <= 10 seconds use:

#

# min-slaves-to-write 3

# min-slaves-max-lag 10

#

# Setting one or the other to 0 disablesthe feature.

#

# By default min-slaves-to-write is set to0 (feature disabled) and

# min-slaves-max-lag is set to 10.

################################## 安全###################################

# Require clients to issue AUTH<PASSWORD> before processing any other

# commands. This might be useful in environments in which you do not trust

# others with access to the host runningredis-server.

#

# This should stay commented out forbackward compatibility and because most

# people do not need auth (e.g. they runtheir own servers).

#

# Warning: since Redis is pretty fast anoutside user can try up to

# 150k passwords per second against a goodbox. This means that you should

# use a very strong password otherwise itwill be very easy to break.

#

# 设置认证密码

# requirepass foobared

# Command renaming.

#

# It is possible to change the name ofdangerous commands in a shared

# environment. For instance the CONFIGcommand may be renamed into something

# hard to guess so that it will still beavailable for internal-use tools

# but not available for general clients.

#

# Example:

#

# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52

#

# It is also possible to completely kill acommand by renaming it into

# an empty string:

#

# rename-command CONFIG ""

#

# Please note that changing the name ofcommands that are logged into the

# AOF file or transmitted to slaves maycause problems.

################################### 限制####################################

# Set the max number of connected clientsat the same time. By default

# this limit is set to 10000 clients,however if the Redis server is not

# able to configure the process file limitto allow for the specified limit

# the max number of allowed clients is setto the current file limit

# minus 32 (as Redis reserves a few filedescriptors for internal uses).

#

# 一旦达到最大限制,redis 将关闭所有的新连接

# 并发送一个‘max number of clients reached’的错误。

#

# maxclients 10000

# 如果你设置了这个值,当缓存的数据容量达到这个值, redis 将根据你选择的

# eviction 策略来移除一些 keys。

#

# 如果 redis 不能根据策略移除 keys ,或者是策略被设置为 ‘noeviction’,

# redis 将开始响应错误给命令,如 set,lpush 等等,

# 并继续响应只读的命令,如 get

#

# This option is usually useful when usingRedis as an LRU cache, or to set

# a hard memory limit for an instance(using the 'noeviction' policy).

#

# WARNING: If you have slaves attached toan instance with maxmemory on,

# the size of the output buffers needed tofeed the slaves are subtracted

# from the used memory count, so thatnetwork problems / resyncs will

# not trigger a loop where keys areevicted, and in turn the output

# buffer of slaves is full with DELs ofkeys evicted triggering the deletion

# of more keys, and so forth until thedatabase is completely emptied.

#

# In short... if you have slaves attachedit is suggested that you set a lower

# limit for maxmemory so that there is somefree RAM on the system for slave

# output buffers (but this is not needed ifthe policy is 'noeviction').

#

# 最大使用内存

# maxmemory <bytes>

# 最大内存策略,你有 5 个选择。

#

# volatile-lru -> remove the key with anexpire set using an LRU algorithm

# volatile-lru -> 使用 LRU 算法移除包含过期设置的 key 。

# allkeys-lru -> remove any keyaccordingly to the LRU algorithm

# allkeys-lru -> 根据 LRU 算法移除所有的 key 。

# volatile-random -> remove a random keywith an expire set

# allkeys-random -> remove a random key,any key

# volatile-ttl -> remove the key withthe nearest expire time (minor TTL)

# noeviction -> don't expire at all,just return an error on write operations

# noeviction -> 不让任何 key 过期,只是给写入操作返回一个错误

#

# Note: with any of the above policies,Redis will return an error on write

#      operations, when there are not suitable keys for eviction.

#

#       At thedate of writing this commands are: set setnx setex append

#       incr decrrpush lpush rpushx lpushx linsert lset rpoplpush sadd

#       sintersinterstore sunion sunionstore sdiff sdiffstore zadd zincrby

#      zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby

#       getsetmset msetnx exec sort

#

# The default is:

#

# maxmemory-policy noeviction

# LRU and minimal TTL algorithms are notprecise algorithms but approximated

# algorithms (in order to save memory), soyou can tune it for speed or

# accuracy. For default Redis will checkfive keys and pick the one that was

# used less recently, you can change thesample size using the following

# configuration directive.

#

# The default of 5 produces good enoughresults. 10 Approximates very closely

# true LRU but costs a bit more CPU. 3 isvery fast but not very accurate.

#

# maxmemory-samples 5

############################## APPEND ONLYMODE ###############################

# By default Redis asynchronously dumps thedataset on disk. This mode is

# good enough in many applications, but anissue with the Redis process or

# a power outage may result into a fewminutes of writes lost (depending on

# the configured save points).

#

# The Append Only File is an alternativepersistence mode that provides

# much better durability. For instanceusing the default data fsync policy

# (see later in the config file) Redis canlose just one second of writes in a

# dramatic event like a server poweroutage, or a single write if something

# wrong with the Redis process itselfhappens, but the operating system is

# still running correctly.

#

# AOF and RDB persistence can be enabled atthe same time without problems.

# If the AOF is enabled on startup Rediswill load the AOF, that is the file

# with the better durability guarantees.

#

# Please checkhttp://redis.io/topics/persistence for more information.

appendonly no

# The name of the append only file(default: "appendonly.aof")

appendfilename "appendonly.aof"

# The fsync() call tells the OperatingSystem to actually write data on disk

# instead to wait for more data in theoutput buffer. Some OS will really flush

# data on disk, some other OS will just tryto do it ASAP.

#

# Redis supports three different modes:

#

# no: don't fsync, just let the OS flushthe data when it wants. Faster.

# always: fsync after every write to theappend only log . Slow, Safest.

# everysec: fsync only one time everysecond. Compromise.

#

# The default is "everysec", asthat's usually the right compromise between

# speed and data safety. It's up to you tounderstand if you can relax this to

# "no" that will let theoperating system flush the output buffer when

# it wants, for better performances (but ifyou can live with the idea of

# some data loss consider the defaultpersistence mode that's snapshotting),

# or on the contrary, use"always" that's very slow but a bit safer than

# everysec.

#

# More details please check the followingarticle:

#http://antirez.com/post/redis-persistence-demystified.html

#

# If unsure, use "everysec".

# appendfsync always

appendfsync everysec

# appendfsync no

# When the AOF fsync policy is set toalways or everysec, and a background

# saving process (a background save or AOFlog background rewriting) is

# performing a lot of I/O against the disk,in some Linux configurations

# Redis may block too long on the fsync()call. Note that there is no fix for

# this currently, as even performing fsyncin a different thread will block

# our synchronous write(2) call.

#

# In order to mitigate this problem it'spossible to use the following option

# that will prevent fsync() from beingcalled in the main process while a

# BGSAVE or BGREWRITEAOF is in progress.

#

# This means that while another child issaving, the durability of Redis is

# the same as "appendfsync none".In practical terms, this means that it is

# possible to lose up to 30 seconds of login the worst scenario (with the

# default Linux settings).

#

# If you have latency problems turn this to"yes". Otherwise leave it as

# "no" that is the safest pickfrom the point of view of durability.

no-appendfsync-on-rewrite no

# Automatic rewrite of the append onlyfile.

# Redis is able to automatically rewritethe log file implicitly calling

# BGREWRITEAOF when the AOF log size growsby the specified percentage.

#

# This is how it works: Redis remembers thesize of the AOF file after the

# latest rewrite (if no rewrite hashappened since the restart, the size of

# the AOF at startup is used).

#

# This base size is compared to the currentsize. If the current size is

# bigger than the specified percentage, therewrite is triggered. Also

# you need to specify a minimal size forthe AOF file to be rewritten, this

# is useful to avoid rewriting the AOF fileeven if the percentage increase

# is reached but it is still pretty small.

#

# Specify a percentage of zero in order todisable the automatic AOF

# rewrite feature.

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

################################ LUASCRIPTING  ###############################

# Max execution time of a Lua script inmilliseconds.

#

# If the maximum execution time is reachedRedis will log that a script is

# still in execution after the maximumallowed time and will start to

# reply to queries with an error.

#

# When a long running script exceed themaximum execution time only the

# SCRIPT KILL and SHUTDOWN NOSAVE commandsare available. The first can be

# used to stop a script that did not yetcalled write commands. The second

# is the only way to shut down the serverin the case a write commands was

# already issue by the script but the userdon't want to wait for the natural

# termination of the script.

#

# Set it to 0 or a negative value forunlimited execution without warnings.

lua-time-limit 5000

################################ REDIS 集群 ###############################

#

# 启用或停用集群

# cluster-enabled yes

# Every cluster node has a clusterconfiguration file. This file is not

# intended to be edited by hand. It iscreated and updated by Redis nodes.

# Every Redis Cluster node requires adifferent cluster configuration file.

# Make sure that instances running in thesame system does not have

# overlapping cluster configuration filenames.

#

# cluster-config-file nodes-6379.conf

# Cluster node timeout is the amount ofmilliseconds a node must be unreachable

# for it to be considered in failure state.

# Most other internal time limits aremultiple of the node timeout.

#

# cluster-node-timeout 15000

# A slave of a failing master will avoid tostart a failover if its data

# looks too old.

#

# There is no simple way for a slave toactually have a exact measure of

# its "data age", so thefollowing two checks are performed:

#

# 1) If there are multiple slaves able tofailover, they exchange messages

#    in order to try to give anadvantage to the slave with the best

#    replication offset (moredata from the master processed).

#    Slaves will try to get theirrank by offset, and apply to the start

#    of the failover a delayproportional to their rank.

#

# 2) Every single slave computes the timeof the last interaction with

#    its master. This can be thelast ping or command received (if the master

#    is still in the"connected" state), or the time that elapsed since the

#    disconnection with themaster (if the replication link is currently down).

#    If the last interaction istoo old, the slave will not try to failover

#    at all.

#

# The point "2" can be tuned byuser. Specifically a slave will not perform

# the failover if, since the lastinteraction with the master, the time

# elapsed is greater than:

#

#   (node-timeout *slave-validity-factor) + repl-ping-slave-period

#

# So for example if node-timeout is 30seconds, and the slave-validity-factor

# is 10, and assuming a default repl-ping-slave-periodof 10 seconds, the

# slave will not try to failover if it wasnot able to talk with the master

# for longer than 310 seconds.

#

# A large slave-validity-factor may allowslaves with too old data to failover

# a master, while a too small value mayprevent the cluster from being able to

# elect a slave at all.

#

# For maximum availability, it is possibleto set the slave-validity-factor

# to a value of 0, which means, that slaveswill always try to failover the

# master regardless of the last time theyinteracted with the master.

# (However they'll always try to apply adelay proportional to their

# offset rank).

#

# Zero is the only value able to guaranteethat when all the partitions heal

# the cluster will always be able tocontinue.

#

# cluster-slave-validity-factor 10

# Cluster slaves are able to migrate toorphaned masters, that are masters

# that are left without working slaves.This improves the cluster ability

# to resist to failures as otherwise anorphaned master can't be failed over

# in case of failure if it has no workingslaves.

#

# Slaves migrate to orphaned masters onlyif there are still at least a

# given number of other working slaves fortheir old master. This number

# is the "migration barrier". Amigration barrier of 1 means that a slave

# will migrate only if there is at least 1other working slave for its master

# and so forth. It usually reflects thenumber of slaves you want for every

# master in your cluster.

#

# Default is 1 (slaves migrate only iftheir masters remain with at least

# one slave). To disable migration just setit to a very large value.

# A value of 0 can be set but is usefulonly for debugging and dangerous

# in production.

#

# cluster-migration-barrier 1

# In order to setup your cluster make sureto read the documentation

# available at http://redis.io web site.

################################## SLOW LOG###################################

# The Redis Slow Log is a system to logqueries that exceeded a specified

# execution time. The execution time doesnot include the I/O operations

# like talking with the client, sending thereply and so forth,

# but just the time needed to actuallyexecute the command (this is the only

# stage of command execution where thethread is blocked and can not serve

# other requests in the meantime).

#

# You can configure the slow log with twoparameters: one tells Redis

# what is the execution time, inmicroseconds, to exceed in order for the

# command to get logged, and the otherparameter is the length of the

# slow log. When a new command is loggedthe oldest one is removed from the

# queue of logged commands.

# The following time is expressed inmicroseconds, so 1000000 is equivalent

# to one second. Note that a negativenumber disables the slow log, while

# a value of zero forces the logging ofevery command.

slowlog-log-slower-than 10000

# There is no limit to this length. Just beaware that it will consume memory.

# You can reclaim memory used by the slowlog with SLOWLOG RESET.

slowlog-max-len 128

############################# Eventnotification ##############################

# Redis can notify Pub/Sub clients aboutevents happening in the key space.

# This feature is documented athttp://redis.io/topics/keyspace-events

#

# For instance if keyspace eventsnotification is enabled, and a client

# performs a DEL operation on key"foo" stored in the Database 0, two

# messages will be published via Pub/Sub:

#

# PUBLISH __keyspace@0__:foo del

# PUBLISH __keyevent@0__:del foo

#

# It is possible to select the events thatRedis will notify among a set

# of classes. Every class is identified bya single character:

#

#  K    Keyspace events, published with __keyspace@<db>__prefix.

#  E    Keyevent events, published with __keyevent@<db>__prefix.

#  g    Generic commands (non-type specific) like DEL, EXPIRE,RENAME, ...

#  $    String commands

#  l    List commands

#  s    Set commands

#  h    Hash commands

#  z    Sorted set commands

#  x    Expired events (events generated every time a key expires)

#  e    Evicted events (events generated when a key is evicted formaxmemory)

#  A    Alias for g$lshzxe, so that the "AKE" string meansall the events.

#

# The "notify-keyspace-events" takes as argument a string thatis composed

#  byzero or multiple characters. The empty string means that notifications

# are disabled at all.

#

# Example: to enable list and generic events, from the point of view ofthe

#          event name, use:

#

# notify-keyspace-events Elg

#

# Example 2: to get the stream of the expired keys subscribing to channel

#            name__keyevent@0__:expired use:

#

# notify-keyspace-events Ex

#

#  Bydefault all notifications are disabled because most users don't need

# this feature and the feature has some overhead. Note that if you don't

# specify at least one of K or E, no events will be delivered.

notify-keyspace-events ""

############################### ADVANCEDCONFIG ###############################

# Hashes are encoded using a memoryefficient data structure when they have a

# small number of entries, and the biggestentry does not exceed a given

# threshold. These thresholds can beconfigured using the following directives.

hash-max-ziplist-entries 512

hash-max-ziplist-value 64

# Similarly to hashes, small lists are alsoencoded in a special way in order

# to save a lot of space. The specialrepresentation is only used when

# you are under the following limits:

list-max-ziplist-entries 512

list-max-ziplist-value 64

# Sets have a special encoding in just onecase: when a set is composed

# of just strings that happens to beintegers in radix 10 in the range

# of 64 bit signed integers.

# The following configuration setting setsthe limit in the size of the

# set in order to use this special memorysaving encoding.

set-max-intset-entries 512

# Similarly to hashes and lists, sortedsets are also specially encoded in

# order to save a lot of space. Thisencoding is only used when the length and

# elements of a sorted set are below thefollowing limits:

zset-max-ziplist-entries 128

zset-max-ziplist-value 64

# HyperLogLog sparse representation byteslimit. The limit includes the

# 16 bytes header. When an HyperLogLogusing the sparse representation crosses

# this limit, it is converted into thedense representation.

#

# A value greater than 16000 is totallyuseless, since at that point the

# dense representation is more memoryefficient.

#

# The suggested value is ~ 3000 in order tohave the benefits of

# the space efficient encoding withoutslowing down too much PFADD,

# which is O(N) with the sparse encoding.The value can be raised to

# ~ 10000 when CPU is not a concern, butspace is, and the data set is

# composed of many HyperLogLogs withcardinality in the 0 - 15000 range.

hll-sparse-max-bytes 3000

# Active rehashing uses 1 millisecond every100 milliseconds of CPU time in

# order to help rehashing the main Redishash table (the one mapping top-level

# keys to values). The hash tableimplementation Redis uses (see dict.c)

# performs a lazy rehashing: the moreoperation you run into a hash table

# that is rehashing, the more rehashing"steps" are performed, so if the

# server is idle the rehashing is nevercomplete and some more memory is used

# by the hash table.

#

# The default is to use this millisecond 10times every second in order to

# active rehashing the main dictionaries,freeing memory when possible.

#

# If unsure:

# use "activerehashing no" if youhave hard latency requirements and it is

# not a good thing in your environment thatRedis can reply form time to time

# to queries with 2 milliseconds delay.

#

# use "activerehashing yes" ifyou don't have such hard requirements but

# want to free memory asap when possible.

activerehashing yes

# The client output buffer limits can beused to force disconnection of clients

# that are not reading data from the serverfast enough for some reason (a

# common reason is that a Pub/Sub clientcan't consume messages as fast as the

# publisher can produce them).

#

# The limit can be set differently for thethree different classes of clients:

#

# normal -> normal clients

# slave -> slave clients and MONITOR clients

# pubsub -> clients subscribed to atleast one pubsub channel or pattern

#

# The syntax of everyclient-output-buffer-limit directive is the following:

#

# client-output-buffer-limit <class><hard limit> <soft limit> <soft seconds>

#

# A client is immediately disconnected oncethe hard limit is reached, or if

# the soft limit is reached and remainsreached for the specified number of

# seconds (continuously).

# So for instance if the hard limit is 32megabytes and the soft limit is

# 16 megabytes / 10 seconds, the clientwill get disconnected immediately

# if the size of the output buffers reach32 megabytes, but will also get

# disconnected if the client reaches 16megabytes and continuously overcomes

# the limit for 10 seconds.

#

# By default normal clients are not limitedbecause they don't receive data

# without asking (in a push way), but justafter a request, so only

# asynchronous clients may create ascenario where data is requested faster

# than it can read.

#

# Instead there is a default limit forpubsub and slave clients, since

# subscribers and slaves receive data in apush fashion.

#

# Both the hard or the soft limit can bedisabled by setting them to zero.

client-output-buffer-limit normal 0 0 0

client-output-buffer-limit slave 256mb 64mb60

client-output-buffer-limit pubsub 32mb 8mb60

# Redis calls an internal function toperform many background tasks, like

# closing connections of clients intimeout, purging expired keys that are

# never requested, and so forth.

#

# Not all tasks are performed with the samefrequency, but Redis checks for

# tasks to perform accordingly to thespecified "hz" value.

#

# By default "hz" is set to 10.Raising the value will use more CPU when

# Redis is idle, but at the same time willmake Redis more responsive when

# there are many keys expiring at the sametime, and timeouts may be

# handled with more precision.

#

# The range is between 1 and 500, however avalue over 100 is usually not

# a good idea. Most users should use thedefault of 10 and raise this up to

# 100 only in environments where very lowlatency is required.

hz 10

# When a child rewrites the AOF file, ifthe following option is enabled

# the file will be fsync-ed every 32 MB ofdata generated. This is useful

# in order to commit the file to the diskmore incrementally and avoid

# big latency spikes.

aof-rewrite-incremental-fsyncyes

20.1.1 它在哪

20.1.2 units单位

1  配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit

2 对大小写不敏感

20.1.3 INCLUDES包含

和我们的Struts2配置文件类似,可以通过includes包含,redis.conf可以作为总闸,包含其他

20.1.4 GENERAL通用

daemonize

pidfile

port

tcp-backlog

tcp-backlog

设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。

在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值

来达到想要的效果

timeout

bind

tcp-keepalive

单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60

loglevel

logfile

syslog-enabled

是否把日志输出到syslog中

syslog-ident

指定syslog里的日志标志

syslog-facility

指定syslog设备,值可以是USER或LOCAL0-LOCAL7

databases

20.1.5 SNAPSHOTTING快照

Save

save 秒钟写操作次数

RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,

默认

是1分钟内改了1万次,

或5分钟内改了10次,

或15分钟内改了1次。

禁用

如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以

stop-writes-on-bgsave-error

如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制

rdbcompression

rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能

rdbchecksum

rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能

dbfilename

dir

20.1.6 REPLICATION复制

20.1.7 SECURITY安全

访问密码的查看、设置和取消

20.1.8 LIMITS限制

maxclients

设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端。当你无法设置进程文件句柄限制时,redis会设置为当前的文件句柄限制值减去32,因为redis会为自身内部处理逻辑留一些句柄出来。如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number ofclients reached”以作回应。

maxmemory

设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,

那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。

但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素

maxmemory-policy

(1)volatile-lru:使用LRU算法移除key,只对设置了过期时间的键

(2)allkeys-lru:使用LRU算法移除key

(3)volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键

(4)allkeys-random:移除随机的key

(5)volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key

(6)noeviction:不进行移除。针对写操作,只是返回错误信息

maxmemory-samples

设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个

20.1.9 APPENDONLY MODE追加

1.appendonly

2.appendfilename

3.appendfsync

(1).always:同步持久化 每次发生数据变更会被立即记录到磁盘  性能较差但数据完整性比较好

(2).everysec:出厂默认推荐,异步操作,每秒记录   如果一秒内宕机,有数据丢失

(3).no

4.no-appendfsync-on-rewrite:重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。

5.auto-aof-rewrite-min-size:设置重写的基准值

6.auto-aof-rewrite-percentage:设置重写的基准值

20.1.10 常见配置redis.conf介绍

参数说明

redis.conf 配置项说明如下:

1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

daemonize no

2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

pidfile /var/run/redis.pid

3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

port 6379

4. 绑定的主机地址

bind 127.0.0.1

5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

timeout 300

6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

loglevel verbose

7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

logfile stdout

8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

databases 16

9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

save <seconds> <changes>

Redis默认配置文件中提供了三个条件:

save 900 1

save 300 10

save 60 10000

分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

rdbcompression yes

11. 指定本地数据库文件名,默认值为dump.rdb

dbfilename dump.rdb

12. 指定本地数据库存放目录

dir ./

13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

slaveof <masterip><masterport>

14. 当master服务设置了密码保护时,slav服务连接master的密码

masterauth <master-password>

15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

requirepass foobared

16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

maxclients 128

17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

maxmemory <bytes>

18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

appendonly no

19. 指定更新日志文件名,默认为appendonly.aof

appendfilename appendonly.aof

20. 指定更新日志条件,共有3个可选值:

no:表示等操作系统进行数据缓存同步到磁盘(快)

always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)

everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

vm-enabled no

22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

vm-swap-file /tmp/redis.swap

23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

vm-max-memory 0

24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

vm-page-size 32

25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

vm-pages 134217728

26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

vm-max-threads 4

27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

glueoutputbuf yes

28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

hash-max-zipmap-entries 64

hash-max-zipmap-value 512

29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

activerehashing yes

30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

include /path/to/local.conf

20.2 redis的持久化

20.2.1 RDB(Redis DataBase)

简介

rdb方式的持久化是通过快照完成的,当符合一定条件时redis会自动将内存中的所有数据执行快照操作并存储到硬盘上。默认存储在redis根目录的dump.rdb文件中。(文件名在配置文件中dbfilename)

redis进行快照的时机(在配置文件redis.conf中)

save 900 1:表示900秒内至少一个键被更改则进行快照。

save 300 10 表示300秒内至少10个键被更改则进行快照。

save 60 10000 表示60秒内至少10000个键被更改则进行快照。

redis自动实现快照的过程

1:redis使用fork函数复制一份当前进程的副本(子进程)

2:父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件

3:当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此,一次快照操作完成。

注意:redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。这就使得我们可以通过定时备份RDB文件来实现redis数据库的备份, RDB文件是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。

手动执行save或者bgsave命令让redis执行快照。

两个命令的区别在于,save是由主进程进行快照操作,会阻塞其它请求。bgsave是由redis执行fork函数复制出一个子进程来进行快照操作。

文件修复:redis-check-dump

rdb的优缺点

RDB的优点是:

1.RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。

2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。

3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

RDB缺点:

1.如果你需要尽量避免在服务器故障时丢失数据,那么RDB 不适合你。 虽然Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少5 分钟才保存一次 RDB 文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。

2.每次保存 RDB 的时候,Redis 都要fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端;如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。

Fork

fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

配置位置

如何触发RDB快照

1.配置文件中默认的快照配置

2.save命令或者是bgsave命令

Save:save时只管保存,其它不管,全部阻塞

BGSAVE:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。可以通过lastsave 命令获取最后一次成功执行快照的时间

3.执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

如何恢复

1.将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可

2.CONFIG GET dir获取目录

优势

1.适合大规模的数据恢复

2.对数据完整性和一致性要求不高

劣势

1.在一定间隔时间做一次备份,所以如果redis意外down掉的话,就 会丢失最后一次快照后的所有修改

2.fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑

如何停止

动态所有停止RDB保存规则的方法:redis-cli config set save ""

小总结

20.2.2 AOF(Append Only File)

简介

aof方式的持久化是通过日志文件的方式。默认情况下redis没有开启aof,可以通过参数appendonly参数开启。

appendonly yes

aof文件的保存位置和rdb文件的位置相同,都是dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改

appendfilename appendonly.aof

redis写命令同步的时机

a ppendfsync always 每次都会执行

appendfsync everysec 默认 每秒执行一次同步操作(推荐,默认)

appendfsync no不主动进行同步,由操作系统来做,30秒一次

aof日志文件重写

auto-aof-rewrite-percentage 100(当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的aof文件大小为依据)auto-aof-rewrite-min-size 64mb

手动执行bgrewriteaof进行重写

重写的过程只和内存中的数据有关,和之前的aof文件无关。所谓的“重写”其实是一个有歧义的词语,实际上, AOF 重写并不需要对原有的AOF 文件进行任何写入和读取,它针对的是数据库中键的当前值。

文件修复:redis-check-aof

动态切换redis持久方式,从 RDB 切换到 AOF(支持Redis2.2及以上)

CONFIG SET appendonlyyes

CONFIG SET save""(可选)

注意:

1.当redis启动时,如果rdb持久化和aof持久化都打开了,那么程序会优先使用aof方式来恢复数据集,因为aof方式所保存的数据通常是最完整的。如果aof文件丢失了,则启动之后数据库内容为空。

2.如果想把正在运行的redis数据库,从RDB切换到AOF,建议先使用动态切换方式,再修改配置文件,重启数据库。(不能直接修改配置文件,重启数据库,否则数据库中数据就为空了) 。

概述

以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

AOF同步过程:

1、Redis 执行 fork() ,现在同时拥有父进程和子进程。

2、子进程开始将新 AOF 文件的内容写入到临时文件。

3、对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾: 这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。

4、当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。

5、搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

配置位置

AOF启动/修复/恢复

正常恢复

1.启动:设置Yes

修改默认的appendonly no,改为yes

2.将有数据的aof文件复制一份保存到对应目录(config get dir)

3.恢复:重启redis然后重新加载

异常恢复

1.启动:设置Yes

修改默认的appendonly no,改为yes

2.备份被写坏的AOF文件

3.修复:

redis-check-aof --fix进行修复

4.恢复:重启redis然后重新加载

rewrite

是什么

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

重写原理

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似

触发机制

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

优势

1.每修改同步:appendfsync always   同步持久化 每次发生数据变更会被立即记录到磁盘  性能较差但数据完整性比较好

2.每秒同步:appendfsync everysec    异步操作,每秒记录  如果一秒内宕机,有数据丢失

3.不同步:appendfsync no   从不同步

劣势

1.相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb

2.aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同

小总结

20.2.3 总结

1.RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储

2.AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些 命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾. Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大

3.只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.

4.同时开启两种持久化方式

(1)在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

(2)RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。

5.性能建议

因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。

如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。

如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构

21 22.Redis高级

21.1 Redis的事务

相信学过MySQL等其他数据库的同学对事务这个词都不陌生,事务表示的是一组动作,这组动作要么全部执行,要么全部不执行。为什么会有这样的需求呢?看看下面的场景:

微博是一个弱关系型社交网络,用户之间有关注和被关注两种关系,比如两个用户A和B,如果A关注B,则B的粉丝中就应该有A。关注这个动作需要两个步骤完成:在A的关注者中添加B;在B的粉丝中添加A。 这两个动作要么都执行成功,要么都不执行。否则就可能会出现A关注了B,但是B的粉丝中没有A的不可容忍的情况。

转账汇款,假设现在有两个账户A和B,现在需要将A中的一万块大洋转到B的账户中,这个动作也需要两个步骤完成:从A的账户中划走一万块;在B的账户中增加一万块。这两个动作要么全部执行成功,要么全部不执行,否则自会有人问候你的!!!

Redis作为一种高效的分布式数据库,同样支持事务。

21.1.1 概述

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。

除了保证事务中的所有命令要么全执行要么全不执行外,Redis的事务还能保证一个事务中的命令依次执行而不会被其他命令插入。试想一个客户端A需要执行几条命令,同时客户端B发送了几条命令,如果不使用事务,则客户端B的命令有可能会插入到客户端A的几条命令中,如果想避免这种情况发生,也可以使用事务。

21.1.2 用法

常见用法

正常执行:

放弃事务:

全体连坐:

冤头债主:

Redis事务错误处理

如果一个事务中的某个命令执行出错,Redis会怎样处理呢?要回答这个问题,首先要搞清楚是什么原因导致命令执行出错:

1.语法错误就像上面的例子一样,语法错误表示命令不存在或者参数错误

这种情况需要区分Redis的版本,Redis 2.6.5之前的版本会忽略错误的命令,执行其他正确的命令,2.6.5之后的版本会忽略这个事务中的所有命令,都不执行。

2.运行错误运行错误表示命令在执行过程中出现错误,比如用GET命令获取一个散列表类型的键值。

这种错误在命令执行之前Redis是无法发现的,所以在事务里这样的命令会被Redis接受并执行。如果事务里有一条命令执行错误,其他命令依旧会执行(包括出错之后的命令)。比如下例:

watch监控

悲观锁/乐观锁/CAS(CheckAnd Set)

1.悲观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁

2.乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,

乐观锁策略:提交版本必须大于记录当前版本才能执行更新

3.CAS

初始化信用卡可用余额和欠额

无加塞篡改,先监控再开启multi, 保证两笔金额变动在同一个事务内

有加塞篡改

监控了key,如果key被修改了,后面一个事务的执行失效

unwatch

一旦执行了exec,之前加的监控锁都会被取消掉了

小结

1.Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行

2.通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败

21.1.3 3阶段

1.开启:以MULTI开始一个事务

2.入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面

3.执行:由EXEC命令触发事务

21.1.4 3特性

1.单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

2.没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题

3.不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

21.2 Redis的发布订阅

21.2.1 概述

1.进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

2.订阅/发布消息图

21.2.2 命令

21.2.3 案列

先订阅后发布后才能收到消息,

1 可以一次性订阅多个,SUBSCRIBE c1 c2 c3

2 消息发布,PUBLISH c2 hello-redis

--------------------------------------------------

3 订阅多个,通配符*, PSUBSCRIBE new*

4 收取消息, PUBLISH new1 redis2015

21.3 Redis的复制(Master/Slave)

21.3.1 是什么

就是我们所说的主从复制,主机数据更新后根据配置和策略,

自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

-------------------------------------------------------------------

作用:

1.读写分离

2.容灾恢复

21.3.2 配置

配从(库)不配主(库)

从库配置:slaveof 主库IP 主库端口

1.每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件

2.info replication

修改配置文件细节操作

1.拷贝多个redis.conf文件

2.开启daemonize yes

3.pid文件名字

4.指定端口

5.log文件名字

6.dump.rdb名字

常用3招

一主二仆

Init

一个Master两个Slave

日志查看

1.主机日志

2.备机日志

3.info replication

主从问题演示

1.切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制

2.从机是否可以写?set可否?

3.主机shutdown后情况如何?从机是上位还是原地待命

4.主机又回来了后,主机新增记录,从机还能否顺利复制?

5.其中一台从机down后情况如何?依照原有它能跟上大部队吗?

薪火相传

1.上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力

2.中途变更转向:会清除之前的数据,重新建立拷贝最新的

3.slaveof 新主库IP 新主库端口

反客为主

SLAVEOF no one

使当前数据库停止与其他数据库的同步,转成主数据库

21.3.3 复制原理

1.slave启动成功连接到master后会发送一个sync命令

2.Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,

3.在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步

4.全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。

5.增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

6.但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

21.3.4 哨兵模式(sentinel)

是什么

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

使用步骤

1.调整结构,6379带着80、81

2.自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错

3.配置哨兵,填写内容

(1).sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1

(2).上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机

4.启动哨兵

(1).redis-sentinel/myredis/sentinel.conf

(2).上述目录依照各自的实际情况配置,可能目录不同

5.正常主从演示

6.原有的master挂了

7.投票新选

8.重新主从继续开工,inforeplication查查看

9.问题:如果之前的master重启回来,会不会双master冲突?

一组sentinel能同时监控多个Master

21.3.5 复制的缺点

复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

22 23.Jedis

22.1 Redis的Java客户端Jedis

Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等,推荐使用Jedis。

22.1.1 搭建环境

1.安装JDK

(1).tar -zxvf jdk-7u67-linux-i586.tar.gz

(2).vi /etc/profile

(3).重启一次Centos

(4).编码验证

2.安装开发工具IntelliJ IDEA/Eclipse

3.Jedis所需要的jar包

jedis-2.1.0.jar

commons-pool2-2.4.2.jar

4.关闭Linux防火墙

直接关闭防火墙

systemctl stop firewalld.service

禁止firewall开机启动

systemctl disable firewalld.service

5.注释掉redis.conf中的bind:127.0.0.1

6.配置redis.conf中的protected-mode为no

22.1.2 Jedis常用操作

测试连通性

public class Main {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.248.128",6379);
        System.out.println(jedis.ping());
    }
}

5+1

Jedis jedis = new Jedis("192.168.248.128",6379);
System.out.println(jedis.ping());
Set<String> keys = jedis.keys("*");
Iterator<String> iterator = keys.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
Boolean k3 = jedis.exists("k3");
System.out.println(k3);
String k2 = jedis.get("k2");
System.out.println(k2);
String mset = jedis.mset("name", "zs", "age", "100");
System.out.println(mset);
List<String> mget = jedis.mget("name", "age", "k5");
System.out.println(mget);
Long lpush = jedis.lpush("l1", "v1", "v2", "v3");
System.out.println(lpush);
List<String> l1 = jedis.lrange("l1", 0, -1);
System.out.println(l1);
Long rpush = jedis.rpush("l2", "v1", "v2", "v3");
System.out.println(rpush);
List<String> l2 = jedis.lrange("l2", 0, -1);
System.out.println(l2);
Long sadd = jedis.sadd("s1", "v1", "v2", "v3");
System.out.println(sadd);
Set<String> s1 = jedis.smembers("s1");
Iterator<String> iterator1 = s1.iterator();
while (iterator1.hasNext()) {
    System.out.println(iterator1.next());
}
Long srem = jedis.srem("s1", "v1");
System.out.println(srem);
int s11 = jedis.smembers("s1").size();
System.out.println(s11);
Long hset = jedis.hset("h1", "name", "zs");
System.out.println(hset);
String hget = jedis.hget("h1", "name");
System.out.println(hget);
Map<String, String> map = new HashMap<>();
map.put("name", "ls");
map.put("age", "101");
String h2 = jedis.hmset("h2", map);
System.out.println(h2);
List<String> hmget = jedis.hmget("h2", "name", "age");
System.out.println(hmget);
jedis.zadd("z1", 60, "v1");
jedis.zadd("z1", 70, "v2");
Set<String> z1 = jedis.zrange("z1", 0, -1);
System.out.println(z1);

事务提交

日常

public static void main(String[] args) {
    Jedis jedis = new Jedis("192.168.248.128",6379);
    System.out.println(jedis.ping());
    Transaction transaction = jedis.multi();
    transaction.set("name", "ww");
    transaction.set("age", "999");
    Response<String> hget = transaction.hget("h2", "name");
    List<Object> exec = transaction.exec();
    System.out.println(exec);
    System.out.println(hget.get());
}

加锁

public static void main(String[] args) {
    Jedis jedis = new Jedis("192.168.248.128",6379);
    String money = jedis.set("money", "100");
    jedis.watch("money");
    jedis.set("money", "300");
    Transaction tx = jedis.multi();
    tx.set("money", "200");
    List<Object> exec = tx.exec();
    System.out.println(exec);
    String money1 = jedis.get("money");
    System.out.println(money1);
}

主从复制

public static void main(String[] args) {
    Jedis jedis = new Jedis("192.168.248.128",6379);
    jedis.set("name", "zl");
    Jedis jedis2 = new Jedis("192.168.248.128",6380);
    jedis2.slaveof("192.168.248.128", 6379);
    String name = jedis2.get("name");
    System.out.println(name);
}

6379,6380启动,先各自先独立

主写

从读

22.1.3 JedisPool

获取Jedis实例需要从JedisPool中获取

用完Jedis实例需要返还给JedisPool

如果Jedis在使用过程中出错,则也需要还给JedisPool

案例见代码

JedisPoolUtil

public class JedisPoolUtils {
    public static volatile JedisPool jedisPool = null;
    public static JedisPool getInstance() {
        if (jedisPool== null) {
            synchronized (JedisPoolUtils.class) {
                if (jedisPool== null) {
                   GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
                   poolConfig.setMaxTotal(1000);
                   poolConfig.setMaxIdle(32);
                   poolConfig.setMaxWaitMillis(100*1000);
                   poolConfig.setTestOnBorrow(true);
                    jedisPool = new JedisPool(poolConfig,"192.168.248.128",6379);
               }
            }
        }
        return jedisPool;
    }

public static void release(JedisPool jedisPool, Jedis jedis) {
        if (jedis != null) {
            jedisPool.returnResourceObject(jedis);
        }
    }
}

调用

public static void main(String[] args) {
    JedisPool jedisPool = JedisPoolUtils.getInstance();
    Jedis jedis = null;
    try {
        jedis = jedisPool.getResource();
        jedis.set("name", "aaa");
        String name = jedis.get("name");
        System.out.println(name);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JedisPoolUtils.release(jedisPool,jedis);
    }
}

配置总结all

JedisPool的配置参数大部分是由JedisPoolConfig的对应项来赋值的。

maxActive:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。

maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;

whenExhaustedAction:表示当pool中的jedis实例都被allocated完时,pool要采取的操作;默认有三种。

WHEN_EXHAUSTED_FAIL --> 表示无jedis实例时,直接抛出NoSuchElementException;

WHEN_EXHAUSTED_BLOCK --> 则表示阻塞住,或者达到maxWait时抛出JedisConnectionException;

WHEN_EXHAUSTED_GROW --> 则表示新建一个jedis实例,也就说设置的maxActive无用;

maxWait:表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException;

testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;

testOnReturn:return 一个jedis实例给pool时,是否检查连接可用性(ping());

testWhileIdle:如果为true,表示有一个idle objectevitor线程对idle object进行扫描,如果validate失败,此object会被从pool中drop掉;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;

timeBetweenEvictionRunsMillis:表示idle object evitor两次扫描之间要sleep的毫秒数;

numTestsPerEvictionRun:表示idle object evitor每次扫描的最多的对象数;

minEvictableIdleTimeMillis:表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;

softMinEvictableIdleTimeMillis:在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。如果为-1,evicted不会根据idletime驱逐任何对象。如果minEvictableIdleTimeMillis>0,则此项设置无意义,且只有在timeBetweenEvictionRunsMillis大于0时才有意义;

lifo:borrowObject返回对象时,是采用DEFAULT_LIFO(last in first out,即类似cache的最频繁使用队列),如果为False,则表示FIFO队列;

---------------------------------------------------------------------------------------------------------------

其中JedisPoolConfig对一些参数的默认设置如下:

testWhileIdle=true

minEvictableIdleTimeMills=60000

timeBetweenEvictionRunsMillis=30000

numTestsPerEvictionRun=-1

22.2 SpringData Redis

22.2.1 简介

Redis

  Redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写。

Jedis

  Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等,推荐使用Jedis。

Spring Data Redis

  SDR是Spring官方推出,可以算是Spring框架集成Redis操作的一个子框架,封装了Redis的很多命令,可以很方便的使用Spring操作Redis数据库,Spring对很多工具都提供了类似的集成,如Spring Data MongDB、Spring Data JPA等

  这三个究竟有什么区别呢?可以简单的这么理解,Redis是用ANSI C写的一个基于内存的Key-Value数据库,而Jedis是Redis官方推出的面向Java的Client,提供了很多接口和方法,可以让Java操作使用Redis,而Spring Data Redis是对Jedis进行了封装,集成了Jedis的一些命令和方法,可以与Spring整合。在后面的配置文件(redis-context.xml)中可以看到,Spring是通过Jedis类来初始化connectionFactory的。

22.2.2 案例

1.创建Maven项目,添加依赖

<dependencies>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>1.8.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.4.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring-framework.version}</version>
    </dependency>
</dependencies>

要加入的依赖可以分为三类:

jedis客户端

spring-data-redis

spring

2.搭建Spring+SpringMVC环境

创建applicationContext.xml

创建spring-servlet.xml

配置web.xml

3.配置Redis

首先在redis.properties中配置基本的连接信息:

redis.host=192.168.248.128
redis.port
=6379
redis.maxIdle
=300
redis.maxActive
=600
redis.maxWait
=1000
redis.testOnBorrow
=true

创建redisConfig.xml文件来配置redis:

<!--引入redis.properties文件-->
<context:property-placeholderlocation="classpath:redis.properties"/>
<!--配置连接池信息-->
<bean class="redis.clients.jedis.JedisPoolConfig" id="poolConfig">
    <property name="maxIdle" value="${redis.maxIdle}"/>
    <property name="maxTotal" value="${redis.maxActive}"/>
    <property name="maxWaitMillis" value="${redis.maxWait}"/>
    <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<!--配置基本连接信息-->
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"id="connectionFactory">
    <property name="hostName" value="${redis.host}"/>
    <property name="port" value="${redis.port}"/>
    <property name="poolConfig" ref="poolConfig"/>
</bean>
<!--配置RedisTemplate-->
<bean class="org.springframework.data.redis.core.StringRedisTemplate" id="redisTemplate">
    <property name="connectionFactory" ref="connectionFactory"/>
    <!--key和value要进行序列化,否则存储对象时会出错-->
    
<property name="keySerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    </property>
    <property name="valueSerializer">
        <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    </property>
</bean>

然后在applicationContext.xml中引入Redis的配置文件:

<import resource="classpath:redisConfig.xml"/>

SDR官方文档中对Redistemplate的介绍,通过Redistemplate可以调用ValueOperations和ListOperations等等方法,分别是对Redis命令的高级封装。但是ValueOperations等等这些命令最终是要转化成为RedisCallback来执行的。也就是说通过使用RedisCallback可以实现更强的功能。

4.创建实体类

public class User implements Serializable{
    private String username;
    private String password;
    private String id;

//get/set省略

}

注意:实体类一定要可序列化。

5.编写Controller、Service、Dao进行测试

DAO:

@Repository
public class HelloDao {
    @Autowired
    RedisTemplate redisTemplate;

public void set(String key, String value) {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set(key, value);
    }

public String get(String key) {
        ValueOperations ops = redisTemplate.opsForValue();
        return ops.get(key).toString();
    }

public void setuser(User user) {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set(user.getId(), user);
    }

public User getuser(String id) {
        ValueOperations<String, User>ops = redisTemplate.opsForValue();
        User user = ops.get(id);
        System.out.println(user);
        return user;
    }
}

Service:

@Service
public class HelloService {
    @Autowired
    HelloDao helloDao;
    public void set(String key, String value) {
        helloDao.set(key,value);
    }

public String get(String key) {
        return helloDao.get(key);
    }

public void setuser(User user) {
        helloDao.setuser(user);
    }

public String getuser(String id) {
        String s = helloDao.getuser(id).toString();
        return s;
    }
}

Controller:

@Controller
public class HelloController {
    @Autowired
    HelloService helloService;

@RequestMapping("/set")
    @ResponseBody
    public void set(String key, String value) {
        helloService.set(key, value);
    }

@RequestMapping("/get")
    @ResponseBody
    public String get(String key) {
        return helloService.get(key);
    }

@RequestMapping("/setuser")
    @ResponseBody
    public void setUser() {
        User user = new User();
        user.setId("1");
        user.setUsername("深圳");
        user.setPassword("sang");
        helloService.setuser(user);
    }

@RequestMapping(value = "/getuser",produces = "text/html;charset=UTF-8")
    @ResponseBody
    public String getUser() {
        return helloService.getuser("1");
    }
}

6.浏览器中进行测试

23 24.负载均衡+集群

23.1  传统服务器工作模式

1.apache三种工作模式

我们都知道Apache有三种工作模块,分别为prefork、worker、event。

prefork:多进程,每个请求用一个进程响应,这个过程会用到select机制来通知。

worker:多线程,一个进程可以生成多个线程,每个线程响应一个请求,但通知机制还是select不过可以接受更多的请求。

event:基于异步I/O模型,一个进程或线程,每个进程或线程响应多个用户请求,它是基于事件驱动(也就是epoll机制)实现的。

2.prefork的工作原理

如果不用“--with-mpm”显式指定某种MPM,prefork就是Unix平台上缺省的MPM.它所采用的预派生子进程方式也是 Apache1.3中采用的模式.prefork本身并没有使用到线程,2.0版使用它是为了与1.3版保持兼容性;另一方面,prefork用单独的子进程来处理不同的请求,进程之间是彼此独立的,这也使其成为最稳定的MPM之一。

3.worker的工作原理

相对于prefork,worker是2.0版中全新的支持多线程和多进程混合模型的MPM.由于使用线程来处理,所以可以处理相对海量的请求,而 系统资源的开销要小于基于进程的服务器.但是,worker也使用了多进程,每个进程又生成多个线程,以获得基于进程服务器的稳定性.这种MPM的工作方式将是Apache2.0的发展趋势。

4.event 基于事件机制的特性

一个进程响应多个用户请求,利用callback机制,让套接字复用,请求过来后进程并不处理请求,而是直接交由其他机制来处理,通过epoll机制来通知请求是否完成;在这个过程中,进程本身一直处于空闲状态,可以一直接收用户请求。可以实现一个进程程响应多个用户请求。支持持海量并发连接数,消耗更少的资源。

23.2  支持高并发的Web服务器

支持高并发的Web服务器有几个基本条件:

1.基于线程,即一个进程生成多个线程,每个线程响应用户的每个请求。

2.基于事件的模型,一个进程处理多个请求,并且通过epoll机制来通知用户请求完成。

3.基于磁盘的AIO(异步I/O)

4.支持mmap内存映射,mmap传统的web服务器,进行页面输入时,都是将磁盘的页面先输入到内核缓存中,再由内核缓存中复制一份到web服务器上,mmap机制就是让内核缓存与磁盘进行映射,web服务器,直接复制页面内容即可。不需要先把磁盘的上的页面先输入到内核缓存去。

刚好,Nginx 支持以上所有特性。所以Nginx官网上说,Nginx支持50000并发,是有依据的。好了,基础知识就说到这边下面我们来谈谈我们今天讲解的重点Nginx。

23.3  反向代理

A同学在大众创业、万众创新的大时代背景下开启他的创业之路,目前他遇到的最大的一个问题就是启动资金,于是他决定去找马云爸爸借钱,可想而知,最后碰一鼻子灰回来了,情急之下,他想到一个办法,找关系开后门,经过一番消息打探,原来A同学的大学老师王老师是马云的同学,于是A同学找到王老师,托王老师帮忙去马云那借500万过来,当然最后事成了。不过马云并不知道这钱是A同学借的,马云是借给王老师的,最后由王老师转交给A同学。这里的王老师在这个过程中扮演了一个非常关键的角色,就是代理,也可以说是正向代理,王老师代替A同学办这件事,这个过程中,真正借钱的人是谁,马云是不知道的,这点非常关键。我们常说的代理也就是只正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求,某些科学上网工具扮演的就是典型的正向代理角色。用浏览器访问 google.com 时,被残忍的block,于是你可以在国外搭建一台代理服务器,让代理帮我去请求google.com,代理把请求返回的相应结构再返回给我。

反向代理

大家都有过这样的经历,拨打10086客服电话,可能一个地区的10086客服有几个或者几十个,你永远都不需要关心在电话那头的是哪一个,叫什么,男的,还是女的,漂亮的还是帅气的,你都不关心,你关心的是你的问题能不能得到专业的解答,你只需要拨通了10086的总机号码,电话那头总会有人会回答你,只是有时慢有时快而已。那么这里的10086总机号码就是我们说的反向代理。客户不知道真正提供服务人的是谁。反向代理隐藏了真实的服务端,当我们请求 ww.baidu.com 的时候,就像拨打10086一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了,ww.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。

两者的区别在于代理的对象不一样:

正向代理代理的对象是客户端,反向代理代理的对象是服务端。

23.4  Nginx详解

23.4.1 简介

传统上基于进程或线程模型架构的web服务通过每进程或每线程处理并发连接请求,这势必会在网络和I/O操作时产生阻塞,其另一个必然结果则是对内存或CPU的利用率低下。生成一个新的进程/线程需要事先备好其运行时环境,这包括为其分配堆内存和栈内存,以及为其创建新的执行上下文等。这些操作都需要占用CPU,而且过多的进程/线程还会带来线程抖动或频繁的上下文切换,系统性能也会由此进一步下降。另一种高性能web服务器/web服务器反向代理:Nginx(Engine X),nginx的主要着眼点就是其高性能以及对物理计算资源的高密度利用,因此其采用了不同的架构模型。受启发于多种操作系统设计中基于“事件”的高级处理机制,nginx采用了模块化、事件驱动、异步、单线程及非阻塞的架构,并大量采用了多路复用及事件通知机制。在nginx中,连接请求由为数不多的几个仅包含一个线程的进程worker以高效的回环(run-loop)机制进行处理,而每个worker可以并行处理数千个的并发连接及请求。

23.4.2 Nginx工作原理

Nginx会按需同时运行多个进程:一个主进程(master)和几个工作进程(worker),配置了缓存时还会有缓存加载器进程(cache loader)和缓存管理器进程(cache manager)等。所有进程均是仅含有一个线程,并主要通过“共享内存”的机制实现进程间通信。主进程以root用户身份运行,而worker、cacheloader和cache manager均应以非特权用户身份运行。

主进程主要完成如下工作:

* 读取并验正配置信息;

* 创建、绑定及关闭套接字;

* 启动、终止及维护worker进程的个数;

* 无须中止服务而重新配置工作特性;

* 控制非中断式程序升级,启用新的二进制程序并在需要时回滚至老版本;

* 重新打开日志文件;

* 编译嵌入式perl脚本;

worker进程主要完成的任务包括:

* 接收、传入并处理来自客户端的连接;

* 提供反向代理及过滤功能;

* nginx任何能完成的其它任务;

注意,如果负载以CPU密集型应用为主,如SSL或压缩应用,则worker数应与CPU数相同;如果负载以IO密集型为主,如响应大量内容给客户端,则worker数应该为CPU个数的1.5或2倍。

23.4.3 Nginx架构

Nginx的代码是由一个核心和一系列的模块组成, 核心主要用于提供Web Server的基本功能,以及Web和Mail反向代理的功能;还用于启用网络协议,创建必要的运行时环境以及确保不同的模块之间平滑地进行交互。不过,大多跟协议相关的功能和某应用特有的功能都是由nginx的模块实现的。这些功能模块大致可以分为事件模块、阶段性处理器、输出过滤器、变量处理器、协议、upstream和负载均衡几个类别,这些共同组成了nginx的http功能。事件模块主要用于提供OS独立的(不同操作系统的事件机制有所不同)事件通知机制如kqueue或epoll等。协议模块则负责实现nginx通过http、tls/ssl、smtp、pop3以及imap与对应的客户端建立会话。在Nginx内部,进程间的通信是通过模块的pipeline或chain实现的;换句话说,每一个功能或操作都由一个模块来实现。例如,压缩、通过FastCGI或uwsgi协议与upstream服务器通信,以及与memcached建立会话等。

23.4.4 Nginx基础功能

1.处理静态文件,索引文件以及自动索引;

2.反向代理加速(无缓存),简单的负载均衡和容错;

3.FastCGI,简单的负载均衡和容错;

4.模块化的结构。过滤器包括gzipping, byte ranges, chunked responses, 以及SSI-filter 。在SSI过滤器中,到同一个 proxy或者 FastCGI 的多个子请求并发处理;

5.SSL 和 TLS SNI 支持;

23.4.5 NginxIMAP/POP3 代理服务功能

1.使用外部 HTTP 认证服务器重定向用户到 IMAP/POP3 后端;

2.使用外部 HTTP 认证服务器认证用户后连接重定向到内部的 SMTP 后端;

认证方法:

1.POP3: POP3 USER/PASS, APOP, AUTH LOGINPLAIN CRAM-MD5;

2.IMAP: IMAP LOGIN;

3.SMTP: AUTH LOGIN PLAIN CRAM-MD5;

SSL 支持;

在 IMAP 和 POP3 模式下的 STARTTLS 和 STLS 支持;

23.4.6 Nginx支持的操作系统

1.FreeBSD 3.x, 4.x, 5.x, 6.x i386; FreeBSD5.x, 6.x amd64;

2.Linux 2.2, 2.4, 2.6 i386; Linux 2.6 amd64;

3.Solaris 8 i386; Solaris 9 i386 and sun4u;Solaris 10 i386;

4.MacOS X (10.4) PPC;

5.Windows 编译版本支持 windows 系列操作系统;

23.4.7 Nginx结构与扩展

1.一个主进程和多个工作进程,工作进程运行于非特权用户;

2.kqueue (FreeBSD 4.1+), epoll (Linux2.6+), rt signals (Linux 2.2.19+), /dev/poll (Solaris 7 11/99+), select, 以及 poll 支持;

3.kqueue支持的不同功能包括 EV_CLEAR, EV_DISABLE (临时禁止事件), NOTE_LOWAT,EV_EOF, 有效数据的数目,错误代码;

4.sendfile (FreeBSD 3.1+), sendfile (Linux2.2+), sendfile64 (Linux 2.4.21+), 和 sendfilev (Solaris 8 7/01+) 支持;

5.输入过滤 (FreeBSD 4.1+) 以及 TCP_DEFER_ACCEPT (Linux2.4+) 支持;

6.10000 非活动的 HTTP keep-alive 连接仅需要 2.5M 内存。

7.最小化的数据拷贝操作;

23.4.8 Nginx其他HTTP功能

1.基于IP 和名称的虚拟主机服务;

2.Memcached 的 GET 接口;

3.支持 keep-alive 和管道连接;

4.灵活简单的配置;

5.重新配置和在线升级而无须中断客户的工作进程;

6.可定制的访问日志,日志写入缓存,以及快捷的日志回卷;

7.4xx-5xx 错误代码重定向;

8.基于 PCRE 的 rewrite 重写模块;

9.基于客户端 IP 地址和 HTTP 基本认证的访问控制;

10.PUT, DELETE, 和 MKCOL 方法;

11.支持 FLV (Flash 视频);

12.带宽限制;

23.4.9 为什么选择Nginx

在高连接并发的情况下,Nginx是Apache服务器不错的替代品: Nginx在美国是做虚拟主机生意的老板们经常选择的软件平台之一. 能够支持高达 50,000 个并发连接数的响应, 感谢Nginx为我们选择了epoll and kqueue 作为开发模型。

Nginx作为负载均衡服务器: Nginx 既可以在内部直接支持 Rails 和 PHP 程序对外进行服务, 也可以支持作为 HTTP代理 服务器对外进行服务. Nginx采用C进行编写, 不论是系统资源开销还是CPU使用效率都比 Perlbal 要好很多。

作为邮件代理服务器: Nginx 同时也是一个非常优秀的邮件代理服务器(最早开发这个产品的目的之一也是作为邮件代理服务器), Last.fm 描述了成功并且美妙的使用经验.

Nginx 是一个 [#installation 安装] 非常的简单 , 配置文件 非常简洁(还能够支持perl语法), Bugs 非常少的服务器: Nginx 启动特别容易, 并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动. 你还能够 不间断服务的情况下进行软件版本的升级 。

Nginx 的诞生主要解决C10K问题

23.5  Nginx安装与应用

23.5.1 安装

23.5.2 动静分离

什么是动静分离

动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。

动静分离简单的概括是:动态文件与静态文件的分离。

在我们的软件开发中,有些请求是需要后台处理的(如:.jsp,.do等等),有些请求是不需要经过后台处理的(如:css、html、jpg、js等等文件),这些不需要经过后台处理的文件称为静态文件,否则称为动态文件,因此我们后台处理忽略静态文件。这会有人又说那我后台忽略静态文件不就完了吗。当然这是可以的,但是这样后台的请求次数就明显增多了。在我们对资源的响应速度有要求的时候,我们应该使用这种动静分离的策略去解决。

动静分离将网站静态资源(HTML,JavaScript,CSS,img等文件)与后台应用分开部署,提高用户访问静态代码的速度,降低对后台应用访问。这里我们将静态资源放到nginx中,动态资源转发到tomcat服务器中。

因此,动态资源转发到tomcat服务器我们就使用到了前面讲到的反向代理了。

动静分离的原理很简单,通过location对请求url进行匹配即可

实现

23.6  Nginx+Tomcat集群

23.6.1 SpringSession+Redis共享Session

web.xml中添加过滤器:

<!--注意,filter的name必须为springSessionRepositoryFilter-->
<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

applicationContext.xml中添加Bean:

<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="192.168.248.128"/>
    <property name="port" value="6379"/>
    <property name="database" value="0"/>
</bean>

23.6.2 项目部署

1.工具

nginx-1.12.0

apache-tomcat-8.5.12

2.网络拓扑图

3.配置两台Tomcat

配置两台Tomcat的端口:分别改为18080和28080,如下,一共需要修改三个地方:

4.启动两个Tomcat,检查配置是否正常

5.修改Tomcat启动页,区分不同的请求

6. 解压下载好的Nginx,做如下配置:

24 25.SVN&Git

24.1 SVN使用

24.2 Git使用

See attachment(s): GitHub.pptx

安装完成之后首先配置用户名和邮箱地址

git config --global user.name"username"

git config --global user.email"example@qq.com"

创建仓库 mkdir git2

仓库初始化 git init

创建文件 touch "文件名"

提交过程:

提交文件到缓存区 git add 文件名

提交文件到版本仓库 git commit -m"mesage"

当前的版本HEAD,上一个版本号HEAD^,上上一个版本HEAD^^,往前100个HEAD~100

版本回退方式1:

回退到上一个版本git reset --hard HEAD^

版本回退方式2:

git reset --hard 版本号(通过git log 命令可以查看版本号)

查看当前版本控制状态:git status

查看工作区文件和仓库文件的异同:git diff

注意区分管理修改

撤销修改:

1.只在工作区修改

git checkout -- 文件名

2.在工作区修改后提交到缓存区

i git reset HEAD 文件名   使文件的状态恢复到1

ii 参考1

3.已经提交到版本仓库,参考版本回退

删除文件

1.真删:

i rm 文件名(删除工作区中该文件)

ii git rm "文件名"(删除缓存区中该文件)

iii git commit -m "deletefile"(删除版本仓库中该文件)

2.误删恢复

git checkout -- 文件名

关联远程仓库

1.登录GitHub,点击右上角的New Repository创建一个新的仓库(创建仓库时不要勾选Initialize this repository with a README )

2.创建完成之后使用命令git remote add origin https://github.com/lenve/t3.git将本地仓库和远程仓库关联起来

3.然后使用命令git push -u origin master将本地代码提交到远程仓库(只有第一次提交时需要-u,以后都不需要)

生成SSH指纹:

1.点击GitHub右上角的Settings,在新打开的页面中点击左边的SSH and GPG keys

2.点击右边的generating SSH keys 生成SSH指纹

3.使用命令ls -al ~/.ssh来检查本地是否有SSH指纹,如果有ssh,跳转到第六步,否则第四步

4.使用命令ssh-keygen -t rsa -b 4096 -C "你的邮箱地址"来生成SSH指纹

5.添加ssh到ssh-agent中:eval "$(ssh-agent-s)"

6.点击https://github.com/settings/keys页面的New SSHKey,title任意填,key的值为

当前用户目录/.ssh/id_rsa.pub文件中的值

25 26.创建Maven工程

25.1 创建Maven工程

25.1.1 不使用模板创建

25.1.2 使用模板创建

JavaEE知识体系相关推荐

  1. JavaEE知识体系梳理

    目录 JavaEE介绍: JavaEE的13种核心技术规范: 1.JDBC(Java Database)数据库连接 2.JNDI(Java Naming and Directory Interface ...

  2. Java Web学习总结(36)——JavaEE知识体系及项目开发过程的总结

    一.代码优化 代码结构层次的优化(目的:更加方便代码的维护--可维护性,可读性) 1.代码注释(代码规范) 2.工具类的封装(方便代码的维护,使代码结构更加清晰不臃肿,保证团队里代码 质量一致性) 3 ...

  3. javaee, javaweb和javase的区别以及各自的知识体系

    javaee, javaweb和javase的区别以及各自的知识体系 来源 https://blog.csdn.net/weixin_39297312/article/details/79454642 ...

  4. java和javaweb的区别_javaee, javaweb和javase的区别以及各自的知识体系

    javaee, javaweb和javase的区别以及各自的知识体系 来源 https://blog.csdn.net/weixin_39297312/article/details/79454642 ...

  5. 从懵逼到再入门——JavaEE完整体系架构

     java 理想的建筑师应该既是文学家又是数字家,他还应通晓历史,热衷于哲学研究,精通音乐,懂得医药知识,具有法学造诣,深谙天文学及天文计算. --Vitruvius(古罗马建筑师) 约公元前25年 ...

  6. 2021-07-23Linux知识体系总结(2021版)

    前言 Linux是我多年的心结,主要是因为工作中没有接触过,作为一个程序员,真的是奇葩,在我心里,Linux,有一种神秘感,总觉得熟练掌握Linux的都是大神,遥想当年,2019年11月份,我与公司的 ...

  7. 大型网站架构演变和知识体系

    存爱好,作为收藏,原地址:http://www.blogjava.net/BlueDavy/archive/2008/09/03/226749.html ,同时向原创致敬 之前也有一些介绍大型网站架构 ...

  8. 谷歌编程语言年度榜NO.1:知识体系总结(2021版)

    本文专注整理一些有关Python学习的知识体系. 整理的Python知识体系主要包括基础知识,Python热门的应用方向,推荐书籍,FAQ以及一些常见面试题目,包含了作为一个Python全栈工程师以及 ...

  9. 从0到1构建数据科学竞赛知识体系,有夕,鱼佬,茂霖等竞赛大咖将特邀分享...

    从0到1构建数据科学竞赛知识体系 这是怎样的数据竞赛知识体系 为了帮助更多竞赛选手入门进阶比赛,通过数据竞赛提升理论实践能力和团队协作能力.DataFountain 和 Datawhale 联合邀请了 ...

最新文章

  1. VTK:插值相机用法实战
  2. 实操指南 | Resource Queue如何实现对AnalyticDB PostgreSQL的资源管理?
  3. postman websocket_新型开源postwoman接口调试工具VS传统经典postman和crapAPI工具
  4. XML——XSLT的一个简单荔枝
  5. word如何设置长宽高_word怎样设置图片长宽
  6. 15个问题自查你真的了解java编译优化吗?
  7. Android2.2 API 中文文档
  8. 今天用pro安装nginx+php+mysql出现故障的解决方法
  9. 在使用avalon框架的时候,用ms-duplex双工绑定,在template上是有数据渲染的,但是js里面却是undefined...
  10. 干货:完全基于情感词典的文本情感分析
  11. Linux 入侵排查
  12. WordPress 5.2中的致命错误恢复模式
  13. 第三章 灰度变换与空间滤波
  14. [自学笔记]UE4(虚幻四)初学者入门
  15. Oracle 计算百分比
  16. 微服务架构设计总结实践篇,10 步搭建微服务
  17. android 编程词典,基于Android的英文词典的实现方法
  18. Java基础冒泡排序——高低输出十个学生的成绩
  19. 华硕FL5900U如何关闭ahci_实战华硕B360主板RX580显卡安装苹果macOS 10.14 Mojave
  20. C语言实现简单的四则运算计算器

热门文章

  1. 免费影视资源如何引流?如何通过分享影视资源引流
  2. 他人的数据挖掘面试题-经验
  3. 才刚满30岁,就中年危机了...
  4. linux如何做动态壁纸实验报告,Ubuntu制作动态壁纸
  5. 收藏CSDN上一篇文章--勉励自己
  6. 关于使用手机电池替换3节干电池的尝试
  7. 汉白玉产地在哪里_汉白玉产地在哪里?
  8. switchport mode access
  9. python早读读后感_《学习Python》读后感摘抄
  10. CSS3 3D立体旋转