java教程笔记
博客专区 > mysky221 的博客 > 博客详情
java教程笔记
mysky221 发表于3年前
java教程笔记
  • 发表于 3年前
  • 阅读 157
  • 收藏 2
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   


==讲解:Maven

命令行操作:

安装好Maven后,设置中央工厂的位置:

【步骤:】

(1)       选好目录位置:比如E:\maven\repository

(2)       拷贝Maven的安装目录conf下的settings.xmlE:\maven中;

(3)       设置settings.xml中的<localRepository>属性值为E:\maven\repository

(4)       conf下的settings.xml中也如“(3)”设置;

 

 

Maven的中央工厂的位置:

\lib\maven-model-builder-3.0.5.jar中,pom-4.0.0.xml中,里面有<respositories>标签,其中的<url>就定义了中央仓库的位置。

 

 

Maven项目目录组织:

项目名/src/main/java/

项目名/src/test/java/

项目名/pom.xml

pom.xml文件内容:

 

 

nexus solatype: 做私服的软件

 

Snapshot意思是“快照”。

我们可以用命令生成这样的目录结构:mvn  archetype : generate

或者连续写:

 

 

Eclipse中操作:

 

Eclipse中有自带的Maven,但是我们一般不用Eclipse自带的Maven,而是使用我们自己配置的Maven

 

【新建一个User子项目:】

新建Maven项目,下一步到:

红框中的两个平时用得比较多。

然后“下一步”

然后“Finish”,生成的项目名就是和Artifact Id相同的!

新建项目后我的Eclipse目录结构:

视频中的目录结构以及修改,以及我的注释:

 

 

==补充知识点:==

groupId定义了项目属于哪个组,这个组往往和项目所在的组织或公司存在关联,譬如你在googlecode上建立了一个名为myapp的项目,那么groupId就应该是com.googlecode.myapp,如果你的公司是mycom,有一个项目为myapp,那么groupId就应该是com.mycom.myapp。本书中所有的代码都基于groupId com.juvenxu.mvnbook。

artifactId定义了当前Maven项目在组中唯一的ID,我们为这个Hello World项目定义artifactId为hello-world,

Group ID一般写大项目名称。Artifact ID是子项目名称。

 

 

【新建一个写日志的子项目:】

 

 

 

概览

 

HelloWorld项目

【步骤:】

(1)       新建一个文件夹project01-helloworld

(2)       project01-helloworld中新建一个pom.xml,内容为:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

       http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <!-- 前面都是固定写法!!! -->

 

  <!-- group-Id是指公司开发的项目名这里是zmj的improve项目 -->

  <groupId>zmj-improve</groupId>

  <!-- artifactId:是指项目中的一个模块:这里是maven01-helloworld模块 -->

  <artifactId>maven01-helloworld</artifactId>

  <!-- 模块的版本:这里是0.0.1-SNAPSHOT版本,即快照版本 -->

  <version>0.0.1-SNAPSHOT</version>

  </project>

 

(3)       project01-helloworld中新建一个文件夹:src

(4)       src中建两个文件夹:main、test(固定写法)

(5)       test文件夹中可以写测试类文件;

(6)       main中建一个文件夹:java,在java中就可以写代码了,在里面写一个HelloWorld.javajava文件;

(7)       命令行进入该java目录,使用maven命令:mvn compile,此时maven就会自动从网站(在maven的安装目录lib/maven-model-builder.3.0.5.jar中可以找到)上下载该java文件所依赖的jar包,jar包存放的位置:在mavenanzhuangmuluconf/settings.xml中可以做配置,下载的jar包都会放到该目录(也叫仓库)中

编译结束后,src同级目录下就会多出一个叫target的文件夹target中有一个classes文件夹,里面有project01-helloworld.class及其包;target中也存放了运行的日志记录;mvn clean可以删除target文件夹、mvn  jar可以打jar

(8)       我们在test中写一个测试类文件,该文件依赖Junit包,这时候在执行maven test,这时候就会报错,提示找不到Junit包,所以需要在pom.xml中添加依赖

 

  <dependencies>

    <dependency>

       <groupId>junit</groupId>

       <artifactId>junit</artifactId>

       <version>4.10</version>

    </dependency>

  </dependencies>

 

(9)       引用别的模块:在project01-helloworld同级目录下新建第二个项目:

project02-helloworld建同样建立main/srcmain/src/mainmain/src/test目录

main中写一个java类,该类中引用project01-helloworld中的类,使用mvn compile编译的时候就会提示找不到引用的类,这时候就需要在pom.xml中包含依赖:

 

    <dependencies>

       <!-- 添加JUnitjar包 -->

       <dependency>

           <groupId>junit</groupId>

           <artifactId>junit</artifactId>

           <version>4.10</version>

       </dependency>

       <!-- 通过坐标找到对应的模块: -->

       <dependency>

           <groupId>zmj-improve</groupId>

           <artifactId>maven01-helloworld</artifactId>

           <version>0.0.1-SNAPSHOT</version>

       </dependency>

    </dependencies>

这样就引入了之前的项目。但是在编译之前,要到第一个项目中做mvn  install操作,将当前项目打包到仓库中,这样再编译的时候就没问题了!

另外maven的配置文件中有“隐式变量”,如:

    <dependency>

       <!—引用当前配置文件中的groupId -->

       <groupId>${project.groupId}</groupId>

    </dependency>

 

Maven目录结构、pom.xml中的内容结构都是一样的,所以maven提供了一个命令来生成这些目录和配置文件:maven archetype;

 

其他一些基础内容

 

1.1中,我们看到,maven的文件目录都是固定的,所以可以使用maven提供的工具:maven archetype:generat产生这样一个架构,然后命令行会提示选择一个archetype使用的版本、groupIdartifactIdversionpackage(直接回车使用当前包<好像是由groupId构成>

 

Eclipse中使用Maven

1.

1.1.

1.2.

1.3.

新建项目

低版本的Eclipse中没有自带的Maven插件,高版本中有。但是不要使用自带的Maven而是使用我们自己安装的Maven

window -> preference -> maven -> installations -> 指定maven的安装路径

window -> preference -> maven -> settings -> 指定mavensettings.xml文件的路径

 

【新建项目步骤:】

(1)       new MavenProject  -> next

(2)       选择archetype -> next

(3)       选择配置文件的各个id

子模块名也是最后生成的项目名字。

(4)       建好后目录结构如下:

 

【固定结构下的java文件所在包,根据实际情况修改!】

项目中的知识点

 

我们当前开发user-service模块,和user-dao模块同时开发,user-service要调用user-dao,那么user-service如何做测试呢(此时user-dao还在开发过程中)?

——使用easymock

 

Maven的依赖特性(scope属性)

1. 

2. 

2.1.  依赖起作用的范围

pom.xml中可以配置scope属性:

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>4.10</version>

      <scope>compile</scope>

    </dependency>

  </dependencies>

 

sope的值:

compile:这是默认值,表示在编译的时候讲依赖包加进来;

provided:表示在编译和测试的时候加载依赖包,而在打包的时候不加;(比如Servle-api包开发的时候需要,而tomcat服务器本身已经提供这个包,打包的时候就不需要提供了)

runtime:编译的时候不依赖,运行的时候依赖;(比如数据库连接器只在运行的时候才需要依赖<用得不多>

test:只有测试的时候才依赖(编译、打包的时候不会包含进来)

 

所以,像easyMoke也要放到test范围。

 

2.2.  依赖的传递

【情况一】、

比如,A依赖BB依赖C,那么A将依赖C中的compile的包。(test范围的包不会传递)

 

 

【情况二】、

Q:比如A依赖testjar1.0B依赖testjar2.0(是testjar1.0的高版本),C同时依赖AB,那么C到底是依赖testjar1.0还是2.0呢?

A:答案是使用先声明的依赖。

 

 

 

Maven的聚合和继承

 

【问题:】

Q:我们的项目中有好几个模块,每次都需要将每个模块重新编译一遍,如何只用编译一个文件,就将这些模块全部编译了呢?

 

A:我们可以使用一个pom来管理其他的项目模块,这个pom可以放在一个新的maven项目中:

Eclipse 》 新建Maven项目 》选择简单项目(勾选 skip archetype election

生成的项目如下所示:

修改pom.xml

 

 

继承的目的是将公共的依赖放到一个pom.xml中——开发中可以新建一个简单的maven项目,在这个简单maven项目的pom.xml中配置公共的依赖包,同时也可以将其他模块都整合在这个配置文件中,统一编译。

 

 

为了在测试的时候隔离现有数据库,可以使用dbUnit工具。

 

Nexus介绍与安装

【问题的提出】:

问题1假设一个项目分成了三个模块:coreserviceaction,其中service是依赖core的,那么这三个模块同时开发的时候,core模块的jar包如何提供给service模块呢?

问题2因为这三个模块依赖的jar包可能有上百个,当一个新员工来的时候需要搭建开发环境,这时候,就需要将这上百个jar包都下载下来,这将非常的耗时。

         直观的解决方法是,每次core开发好后就将jar放到svn上,然后通知service下载,这个虽然可行,当是会比较麻烦,更不要说core有多个版本的情况下了。

【解决方法】:

         根据上面的情况分析,我们有必要为这三个模块创建单独的仓库!尤其在企业中的时候,都会搭建自己的服务器用作中央工厂。

         具体方法:

         我们的模块不直接从中央工厂下载依赖包,而是从我们自己搭建一个服务器(伺服)上下载。每次找依赖包的时候,首先到本地仓库中找,当本地仓库中没有依赖包时,就到中央仓库中找。`1     

 

 

 

 

==讲解:JQuery

前言:

 

l  推荐书籍:

jQuery实战(2)/图灵... 作者: ()比伯奥特 ()卡茨

 

l  因为Jquery文件需要在页面上载入,载入需要时间,所以,Jquery有两个版本,一个是mini版的,一个是完整版的。mini版的是将空格、空行、将变量名压缩后的版本。

 

学习的部分:

(1)       基础知识;

(2)       jqueryUI(图形处理)、文件上传;

(3)       UI框架(如收费的miniUI、国产收费EasyUiDWZ

(4)       DWR框架;

js文件压缩工具:jsmin.exe

jquery就是为了方便选中html中的文本并进行样式编辑。

^a表示以a为开头$a表示以a为结尾

 

Jquery基础:

 

【例1】:helloworld

 

<script type="text/javascript" src="jquery-1.8.3.js"></script>

<script type="text/javascript">

    /**JQuery必须在页面加载完后(即文档加载完后执行)工作*/

    //js中:

    window.onload = function(){ alert(“abc”); };

    //jquery中:方式一、

    $(document).ready(function(){

       alert("hello jquery");

    });

    //jquery中:方式二、

    //经常使用下面的方式来替代上面的操作

    $(function(){

       alert("hello query");

    });

</script>

 

【例2】:css选择器、鼠标事件

    <style type="text/css">

       .bg {

           background: #f00;

           color:#fff;

       }

    </style>

    <script type="text/javascript" src="jquery-1.8.3.js"></script>

    <script type="text/javascript">

       $(function(){

          

           //class="abc"的标签变成红色

           $("li.abc").css("color","#f00");

          

           //将偶数行的背景色变成蓝色,文字颜色变成白色

           $("#hello ul li:even")

.css("background","#00f").css("color","#fff");

 

           //鼠标事件

           $("li").mouseover(setColor).mouseout(setColor);

           function setColor() {

              //toggleClass:鼠标第一次移动上去的时候才添加bg样式

              $(this).toggleClass("bg");

           }

       });

    </script>

</head>

<body>

<div id="hello">

    <ul>

       <li>aaaaaaaaaaaaa</li>

       <li>vbbbbbbbbbbbb</li>

       <li class="abc">ccccccccccccc</li>

       <li>ddddddddddddd</li>

       <li>eeeeeeeeeeeee</li>

    </ul>

</div>

 

 

【例3】:关于两次加载

 

<script type="text/javascript">

    //js中文档加载完成后执行某个函数使用window.onload

    window.onload = function() {

       alert("abc");

    }

    /*第二方法会将第一个覆盖*/

    window.onload = function() {

       alert("bcd");

    }

 

    //jquery中:

    /*JQuery对加载事件了做了特殊的处理,使得两个方法都会被加载

     这样做的好处是:我们可以加载两个文件(比如js文件)*/

    $(function() {

       alert("abc");

    });

 

    $(function() {

       alert("bcd");

    })

</script>

 

 

【例4】: jsjquery对象互转

 

<script type="text/javascript">

    $(function() {

       var hello = document.getElementById("hello");

       //使用$(xx)就可以把xx这个节点封装为jquery的节点

       $(hello).css("color", "#f00");

 

//每一个jquery节点都是一个数组,数组中的值就是js的节点,只能用js的方法

//转换为js节点后,就无法使用jquery的方法,若要使用jquery的方法再用$()封装即可

       ($("li.abc")[0]).innerHTML = "abccdd";

 

       //【实例:】在li内容的前面加上序号

       var lis = $("#hello ul li");

       for (var i = 0; i < lis.length; i++) {

           //一、使用js实现:

           //目前的lijs的节点

           var li = lis[i];

           li.innerHTML = "[" + (i + 1) + "]" + li.innerHTML;

           //二、使用jquery实现:

           //$(li)就变成了JQuery节点

           //xx.html()读取内容,xx.html("abc"):把节点的内容完成替换

           $(li).html((i + 1) + "." + $(li).html());

       }

    });

</script>

</head>

<body>

    <div id="hello">

       <ul>

           <li>aaaaaaaaaaaaa</li>

           <li>vbbbbbbbbbbbb</li>

           <li class="abc">ccccccccccccc</li>

           <li>ddddddddddddd</li>

           <li>eeeeeeeeeeeee</li>

       </ul>

    </div>

</body>

 

 

【例5】: CSS选择器:基本选择器

 

E,F  多元素选择器,同时匹配所有E元素或F元素,EF之间用逗号分隔
E F 
后代元素选择器,匹配所有属于E元素后代的F元素,EF之间用空格分隔
E > F 
子元素选择器,匹配所有E元素的子元素F
E + F 
毗邻元素选择器,匹配所有紧随E元素之后的同级元素F

 

<script type="text/javascript">

    $(function() {

       //li中的所有a

       $("li a").css("color","#f00");

       //class.myList这个标签的下一级标签li的下一级标签为a的节点

       $(".myList>li>a").css("color","#f00");

      

       //a的节点其中a中的href属性是以http://为开头

       $("a[href^='http://']").css("background","#00f").css("color","#fff");

      

       //.myListul中的包含有a标签的li标签

       $(".myList ul li:has('a')").css("background","#ff0");//

      

       //获取页面中所有以pdf结尾的超级链接

       $("a[href$='pdf']").css("background","#ff0").css("color","#f00");

      

       //idli1的下一个兄弟节点li,仅仅只会去一个节点,

       //仅仅只会取相邻的节点,如果相邻的节点不是li就什么都取不出去

       $("#li1+li").css("background","#ff0");

      

       //idli的下面的所有满足条件的兄弟节点

       $("#li1~li").css("background","#ff0");

      

       $("a[title]").css("color","#0f0");

      

        //页面中最先匹配的某个元素

       alert($("li:first").html());

       //页面中最后匹配的元素

       alert($("li:last").html());

       //获取满足要求的第一个li

       $(".myList>li li:first-child").css("background","#f00");

       //获取没有兄弟节点的ul

       alert($("ul:only-child").length); */

    });

</script>

 

Jquery包装集:

 

Jquery的包装集是指,

通过$(“exp”)会筛选出页面的一组满足表达式的元素,这一组元素就属于包装集中的元素。

常用的方法有:

(1)       获取包装集中的元素的个数(size或者length方法);

(2)       通过某个下标获取包装集中的某个元素(get(index)方法);

(3)       获取某个元素在包装集中的位置(index());

 

【例1】:

 

    <script type="text/javascript">

       $(function() {

          

           //获取table的元素个数(size,length都可以)

           alert($("table").length);

           //获取tr的元素个数

           alert($("tr").length);

          

           //当执行了get之后得到的结果是一个js的元素

           $($("tr").get(1)).css("color","#f00");

          

           //判断idabctr在包装集的位置

           alert($("tr").index($("tr#abc")));

          

    //在表达式中通过,可以分割多个包装集:

    //查找tbodytr值(位置)为2trtridabc的元素(用逗号分隔)

    //但是如果包装集太多,而且有时候可以变动的时候,使用这种方式就不好操作

           $("tbody tr:eq(2),tr#abc").css("color","#f00");

          

           /*可以为包装集使用add方法,可以将新加入的元素添加到包装集中*/

           $("tbody tr:eq(2)").add("thead tr td:eq(2)").css("color","#f00");

           //或者加上$包装

           $("tbody tr:eq(2)").add($("thead tr td:eq(2)")).css("color","#f00");

           //找到:tr的位置为2tr中的td位置为2td元素包含3td

           $("tbody tr:eq(2)").add($("thead tr td:eq(2)"))

.add("tr td:contains('3')").css("color","#f00");

          

           //not方法可以将包装集中的元素取消掉   

           $("tr").not("tr#abc").css("color","#f00");

          

           //获取tr中位置小于3的元素

           $("tr").filter("tr:lt(3)").css("color","#f00");

          

           //获取tr中的位置13形成一个新的包装集,返回的值就是新的包装集

           //将原来所有的tr的背景色变成蓝色,tr位置为13的颜色变成红色

           $("tr").css("background","#00f").slice(1,3).css("color","#f00");

          

           //从包装集的内部获取相应的元素,find返回的值也是新包装集

           $("table").find("tr#abc").css("color","#f00");

          

           //is表示的是当前的包装集中是否有某个元素,$(table)的包装集中只有一个元素table,所以没有td

           alert($("table").is("td:contains('用户')"));//false

           alert($("td").is("td:contains('用户')"));//true

          

           //找到tbody的所有tr元素,返回的也是新包装集

           $("tbody").children("tr").css("color","#f00");

           //获取tbody中的所有元素为值等于3tr子元素,返回的也是新包装集

           $("tbody").children("tr:eq(3)").css("color","#f00");

          

           //找到下一个子元素,只是一个元素,返回新包装集

           $("tr#abc").next().css("color","#ff0");

          

           //找到下一个组兄弟元素,所有元素,返回新包装集

           $("tr#abc").nextAll().css("color","#0f0");

          

           //parent仅仅只是返回上一级的div,返回新包装集

           $("#s1").parent("div").css("color","#0f0");

          

           //返回所有满足条件的父类节点,返回新包装集

           $("#s1").parents("div").css("color","#f00");

          

           //返回第3tr的所有兄弟节点,返回新包装集

           var a = $("tr:eq(2)").siblings("tr").css("color","#f00").is("tr#abc");

           alert(a);

       });

    </script>

</head>

<body>

<div id="d1">

    cdd

    <div>

       <span id="s1">abc</span>

    </div>

</div>

<table width="700" border="1" align="center">

    <thead>

       <tr>

           <td>用户标识</td>

           <td>用户姓名</td>

           <td>用户年龄</td>

           <td>用户密码</td>

       </tr>

    </thead>

    <tbody>

       <tr id="abc">

           <td>1</td>

           <td>张三</td>

           <td>23</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>2</td>

           <td>李四</td>

           <td>33</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>3</td>

           <td>王五</td>

           <td>13</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>4</td>

           <td>赵六</td>

           <td>45</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>5</td>

           <td>朱琪</td>

           <td>21</td>

           <td>abc123</td>

       </tr>

    </tbody>

</table>

 

【例2】:

 

    <script type="text/javascript">

       $(function() {

           //使用end可以返回上一个包装集(比如这里的$("tr:eq(2)"))

           $("tr:eq(2)").siblings("tr")

              .css("background","#00f").css("color","#fff")

              .end().css("background","#f00").css("color","#00f");

      

           //第一个end()得到tus;第二个end()得到tbody

           $("#users tbody").clone().appendTo("#tus").find("tr:even").css("color","#f00")

              .end().end().find("tr:odd").css("color","#00f");

          

           //andSelf表示把所有的包装集合并在一起

           $("#users tbody").clone().appendTo("#tus").andSelf().find("td:contains('3')").css("color","#f00");

      

           //查询出了两个包装集,一个为tus的table一个为users的table,此时可以过滤得到users这个table

           //无法使用filter(tr)

           $("table").filter("table#users").css("color","#f00");

           //从users这个id的元素中过滤tr为2的元素

           $("#users").find("tr:eq(2)").css("background","#00f");

       });

    </script>

</head>

<body>

<table id="tus" width="700" border="1" align="center"></table>

<table width="700" border="1" align="center" id="users">

    <thead>

       <tr>

           <td>用户标识</td>

           <td>用户姓名</td>

           <td>用户年龄</td>

           <td>用户密码</td>

       </tr>

    </thead>

    <tbody>

       <tr id="abc">

           <td>1</td>

           <td>张三</td>

           <td>23</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>2</td>

           <td>李四</td>

           <td>33</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>3</td>

           <td>王五</td>

           <td>13</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>4</td>

           <td>赵六</td>

           <td>45</td>

           <td>abc123</td>

       </tr>

       <tr>

           <td>5</td>

           <td>朱琪</td>

           <td>21</td>

           <td>abc123</td>

       </tr>

    </tbody>

</table>

 

【例3】:map将元素转换成数组

 

<script type="text/javascript">

    $(function() {

      

       //找到第一列元素

       $("tbody td:nth-child(1)").css("color","#f00");

      

       //map可以将返回的值作为元素,.get()就可以得到数组

       var ids = $("tbody td:nth-child(1)").map(function(){

           return $(this).html();

       }).get();

      

       //map返回json数据,nth表示子元素(初始下标为1

       //通过map可以有效的将某个包装集中的元素转换为数组

       var ps = $("tbody td:nth-child(1)").map(function(){

           var n = $(this).next("td");

           var p = {"id":$(this).html(),"name":n.html()};

           return p;

       }).get();

      

       for(var i=0;i<ps.length;i++) {

           alert(ps[i].name);

       }

    });

</script>

 

 

【例4】:has查找父类元素是否有某个元素

 

has返回新包装集

 

<script type="text/javascript">

    $(function() {

       //获取存在有ulli,返回的是新包装集(含有查找元素的元素)

       $("li").has("ul").css("color","#f00");

       //获取有spandiv

       $("div").has("span").css("color","#f00");

    });

</script>

 

 

文档的处理:

 

【例1】:each

代码1

<script type="text/javascript">

    $(function() {

       var ns = $("tbody td:nth-child(2)");//获取tbody中tr的第二个td

       /*使用以下方法进行遍历基本上是基于js进行操作

        * 对于jquery有自己的一套遍历方法,可以直接通过

        * each函数进行遍历*/

       for(var i=0;i<ns.length;i++) {

           var nn = ns[i];//nn已经是js的节点

           var id = $(ns[i]).prev("td").html();

           var age = $(ns[i]).next("td").html();

           nn.innerHTML = id+">>"+nn.innerHTML+"("+age+")";

       }

      

       /*对于JQuery而言,可以用each遍历所有的数组对象

        * each中的匿名函数n表示的是数组的下标,0开始*/

       ns.each(function(n){

           $(this).html($(this).prev("td").html()+

              "."+$(this).html()+

              "("+$(this).next("td").html()+")");   

       });

    });

</script>

 

代码2

<script type="text/javascript">

    $(function() {

       var ids = $("tbody td:nth-child(1)");

       var persons = ids.map(function(){

           var idn = $(this);

           var nexts = idn.nextAll();

           var namen = $(nexts[0]);

           var agen = $(nexts[1]);

           var pwdn = $(nexts[2]);

           var p = {"id":idn.html(),"name":namen.html(),"age":agen.html(),"password":pwdn.html()};

           return p;

       }).get();

      

       $(persons).each(function(n){

           alert(this.id+","+this.name+","+this.age+","+this.password);

       });

    });

</script>

</head>

<body>

<table id="tus" width="700" border="1" align="center"></table>

<table width="700" border="1" align="center" id="users">

    <thead>

       <tr>

           <td>用户标识</td>

           <td>用户姓名</td>

           <td>用户年龄</td>

           <td>用户密码</td>

       </tr>

    </thead>

    <tbody>

       <tr id="abc">

           <td>1</td>

           <td>张三</td>

           <td>23</td>

           <td>abc123</td>

       </tr>

 

 

 

【例2】:属性操作

 

<script type="text/javascript">

    $(function() {

       $("tbody tr").each(function(n){

           //使用attr只加入一个参数可以获取属性值

           alert($(this).attr("id"));

          

           //通过attr()设置两个参数,可以完成对某个节点的属性的设置

           $(this).attr("title",$(this).children("td:eq(1)").html());

          

           // 可以基于json的格式来设置属性,甚至可以设置一些非html的属性,

// 通过这些属性来做一些特殊处理

           // 但是设置特殊属性的这种方式在jquery1.4之后就基本不使用,

// 因为在1.4之后提供data方法

           $(this).attr({

              "title":$(this).children("td:eq(1)").html(),

              "id":$(this).children("td:eq(0)").html(),

              "personId":n

           });

          

           //可以移除属性

           $("tr#2").removeAttr("personid");

       });

    });

</script>

</head>

<body>

    <a href="jquery_basic01.html">basic01</a>

    <a href="jquery_basic02.html">basic02</a>

    <a href="jquery_basic03.html">basic03</a>

    <a href="http://www.zttc.edu.cn">zttc</a>

    <a href="http://zjc.zttc.edu.cn:8080">zjc</a>

<table width="700" border="1" align="center" id="users">

    <thead>

       <tr>

           <td>用户标识</td>

           <td>用户姓名</td>

           <td>用户年龄</td>

           <td>用户密码</td>

       </tr>

    </thead>

    <tbody>

       <tr id="abc">

           <td>1</td>

           <td>张三</td>

           <td>23</td>

           <td>abc123</td>

       </tr>

 

 

 

 

==讲解:MVC等设计模式讲解

 

 

 

 

==讲解:Hibernate

 

基本配置和操作

1)配置hibernate-cfg.xml

<hibernate-configuration>

    <session-factory>

    <!-- hibernate的方言,用来确定连接的数据库 -->

       <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

    <!-- 数据库的连接类 -->

       <property name="hibernate.connection.driver_class">

com.mysql.jdbc.Driver

</property>

    <!-- 数据库的连接字符串和用户名密码 -->

       <property name="hibernate.connection.url">

jdbc:mysql://localhost:3306/itat_hibernate

</property>

       <property name="hibernate.connection.username">root</property>

       <property name="hibernate.connection.password">

123456

</property>

    <!-- 在使用hibernate时会显示相应的SQL -->

       <property name="show_sql">true</property>

    <!-- 会自动完成类到数据表的转换 -->

       <property name="hibernate.hbm2ddl.auto">update</property>

    <!-- 加入实体类的映射文件 -->

       <mapping resource="org/zttc/itat/model/User.hbm.xml"/>

       <mapping resource="org/zttc/itat/model/Book.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

2)创建实体映射文件:

(略)

3)代码编写:创建SessionFatory

SessionFactory是线程安全的,所以SessionFatory要基于单利模式来创建。

    @Test

    public void test01() {

       Configuration cfg = new Configuration().configure();

       //cfg.buildSessionFactory();

//在hibernate3中都是使用该种方法创建,但是在4中被禁用了,使用以下方法

       ServiceRegistry serviceRegistry =

new ServiceRegistryBuilder()

                         .applySettings(cfg.getProperties())

.buildServiceRegistry();

       SessionFactory factory =

                       cfg.buildSessionFactory(serviceRegistry);

       Session session = null;

       try {

           session = factory.openSession();

           //开启事务

           session.beginTransaction();

           User u = new User();

           u.setNickname("张三");

           session.save(u);

           //提交事务

           session.getTransaction().commit();

       } catch (HibernateException e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           if(session!=null) session.close();

       }

    }

 

【总结】:

临时状态 à 持久状态:做save操作;

离线状态 à 临时状态:做delete操作;

有时候不知道对象是离线还是临时状态,这时候可以调用sessionsaveandupdate()方法

Hibernate的状态

 

Transient:是指临时状态,即new出一个对象,但是数据库中还没有这个对象;

Persistentnew一个对象并save后就是Persistent状态(持久化状态);

Detached:当session关闭并且对象保存在了数据库中,这时候就是离线状态。

 

关于Persistent

    @Test

    public void testTransient() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           u.setBorn(sdf.parse("1976-2-3"));

           u.setUsername("zxl");

           u.setNickname("赵晓六");

           u.setPassword("123");

//以上u就是Transient(瞬时状态),表示未被session管理且数据库中没有

//save之后,被session所管理,且数据库中已经存在,此时就是Persistent状态

session.save(u); //u变成持久化状态,被sessio管理,正常保存

u.setNickname("赵晓其");//更新缓存内容,提交时候更新数据库

//如果没有提交那么上面的(从save开始)就是持久化状态,session中有完整的对象的//信息处于持久化状态的对象设置了新数据,提交的时候就会做更新操作

           session.getTransaction().commit();//导致更新操作执行

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

继续解释:

    @Test

    public void testPersistent02() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           u.setBorn(sdf.parse("1976-2-3"));

           u.setUsername("zxq");

           u.setNickname("赵晓八");

           u.setPassword("123");

//save后缓存(session)保存这个u对象信息

           session.save(u);

           u.setPassword("222");//不会导致缓存中内容变化

           //缓存内容没有变化,所以不会执行(这部分有些不明白??)

           session.save(u);

           u.setNickname("赵晓吧");

           //下面语句不执行(持久化状态的update不执行,提交时候才会执行update

           session.update(u);

           u.setBorn(sdf.parse("1988-12-22"));

           //没有意义

           session.update(u);

            //执行update

           session.getTransaction().commit();

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

 

 

load出来的对象也是持久化对象:

    //此时u是Persistent

    User u = (User)session.load(User.class, 10);

 

    @Test

    public void testDetach05() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           //u.setId(110);

           u.setNickname("abc");

           //如果u是离线状态就执行update操作,如果是瞬时状态就执行Save操作

           //但是注意:该方法并不常用

           session.saveOrUpdate(u);

           session.getTransaction().commit();

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

 

懒加载

load()get()

当使用load加载一个对象的时候,如果取出的对象没有使用,那么就不会发sql——这就是Hibernate的“延迟加载(懒加载)”。且,该对象其实是一个代理对象,该对象中只有一个id的值,若打印该id也不会发sql(不用从数据库中取)。

    @Test

    public void testLazy02() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           User u = (User)session.load(User.class, 1);

           //此时一条sql都没有发,这就是hibernate的延迟加载

/**延迟加载指的就是,当完成load操作之后,并不会马山发出sql语句,只有在使用到该对象时才会发出sql,当完成load之后,u其实是一个代理对象,这个代理对象中仅仅只有一个id的值*/

           //此时不会发sql

           System.out.println(u.getId());

           //nickname没有值,必须去数据库中取。所以会发出sql

           System.out.println(u.getNickname());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

 

【例】:

    @Test

    public void testLazy04() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           //get是只要一执行就会发出sqlget没有延迟加载

           User u = (User)session.get(User.class, 101);

//get的时候发现数据库中并没有该数据,所以unull,打印u.getId(),会抛出空指针异常,如果是使用load的话,会打印101(代理对象存储了id的值)

           System.out.println(u.getId());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

【例】:

    @Test

    public void testLazy05() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           User u = (User)session.load(User.class, 101);

           //由于id已经存在,所以不会抛出异常

           System.out.println(u.getId());

           //此时会去数据库中取数据,发现没有这个对象,但是u并不是空,所以会抛出ObjectNotFoundException

           System.out.println(u.getNickname());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

 

开发中的懒加载的问题

【情景演示】:

Dao中的load方法:

public User load(int id) {

    Session session = null;

    User u = null;

    try {

       session = HibernateUtil.openSession();

       u = (User)session.load(User.class, 1);

    } catch (Exception e) {

       e.printStackTrace();

    } finally {

       HibernateUtil.close(session);

    }

    return u; //这里返回的仅是一个代理对象,且此时session已经关闭

}

 

控制器中调用这个方法用来在页面显示:

@Test

public void testLazyQuestion() {

    UserDao ud = new UserDao();

    /*由于使用了loadload是有延迟加载的,返回的时候的u是一个代理对象,仅仅只有一个id

     * 但是在返回的时候Session已经被关闭了,此时当需要使用u的其他属性时就需要去数据库中

     * 但是Session关闭了,所以就抛出org.hibernate.LazyInitializationException no session

     */

    User u = ud.load(2);

    System.out.println(u.getUsername());

}

解决方法:

方法1:使用get方法;

方法2:在过滤器中获取session放到context中,然后在过滤器中关闭。Spring中使用openSessionInView

 

ID生成策略

 

<hibernate-mapping package="org.zttc.itat.model">

    <class name="Book" table="t_book">

        <id name="id">

            // assigned:表示要手动指定主键

            // uuid:表示自动生成一个uuid字符串,所以主键必须是String

            // native:也是自动生成,生成的是123等有序列,所以检索会快些

           // native的缺点是每次插入一条数据库都会查询一下数据库,开发中查询

           // 操作会比较多,所以开发中会使用native

            <generator class="uuid"/>

        </id>

        <property name="name"/>

        <property name="price"/>

        <property name="bookPage" column="book_page" type="int"/>

    </class>

</hibernate-mapping>

 

实体表映射-使用xml

单张表映射

【提示】:

l  实体字段名,比如bookName,映射到数据库最好映射成book_name

<property name="bookPage" column="book_page" type="int"/>

在实际开发中,一般不会使用Hibernate来生成表,一般的开发中,都是首先用PowerDesigner设计好数据库,然后生成SQL语句,然后用这个SQL语句来生成表。

这样的好处是,在开发数据库的时候可以为数据库创建索引和视图。

l  对效率要求不高的项目适合使用Hibernate

对效率比较高的项目,如果使用Hibernate的话,若想要效率也高,则可以这么做:增删改使用Hibernate,而查询使用原生态的SQL

l  写Hibernate语句时,比如清楚一句Hibernate代码执行了几条SQL语句,否则性能可能大降!

 

 

关系映射

多对一(学生和班级)

l  关系一般是由“多”的一方来维护;

<!-- inverse=true表示不在自己这一端维护关系 -->

<set name="stus" lazy="extra" inverse="true">

    <key column="cid"/>

    <one-to-many class="Student"/>

</set>

l  添加的时候,一般都是先添加“一”的一方;

l  懒加载会多发一条SQL语句(抓取策略可以只发一条SQL语句……?)

关联(cascade:不要在“多”的一方使用关联。因为,比如,删除多的一方的一条记录,同时会删除“一”的一方对应的记录,但是如果“一”的一方的该条记录又关联了其他记录的话,就不能删除了。使用cascade的情况一般是:在“一”的一方删除时使用,特殊情况才会在add上做级联。

 

一对多(留言和评论)

 

        <property name="title"/>

        <property name="content"/>

<!-- 使用了lazy=extra之后会稍微智能一些,会根据去的值的不同来判断是调用count和获取投影 ,比如取出总条数一般情况下会取出所有数据的list然后计算listsize,但是用了extra后就会直接count(*)-->

        <set name="comments" lazy="extra">

        <!-- key用来指定在对方的外键的名称 -->

            <key column="mid"/>

        <!-- class用来设置列表中的对象类型 -->

            <one-to-many class="Comment"/>

        </set>

    </class>

 

双向关联(班级和学生)

(略)

 

实体表映射-使用注解

 

l  一个Hibernate项目是使用注解还是Annotation呢?

——小项目使用注解,大项目(一两百万行代码的)使用xml。因为大项目的类比较多,看xml文件更方便。大项目不用外键的方式????

 

多对一(学生和教室)

 

@Entity

@Table(name="t_classroom")

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

// mappedBy表示关系由对方的classroom属性维护,如果没有mappedBy的话,会生

// 成中间表

    @OneToMany(mappedBy="classroom")

    // 使用extrasql语句会较智能,比如计算条数会自动使用count(*)

    @LazyCollection(LazyCollectionOption.EXTRA)

    public Set<Student> getStus() {

       return stus;

    }

    public void setStus(Set<Student> stus) {

       this.stus = stus;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

}

 

@Entity

@Table(name="t_student")

public class Student {

    private int id;

    private String name;

    private String no;

    private Classroom classroom;

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    // fetch的默认值是EAGER,表示不使用懒加载,一条语句就能把相关的数据查询

    // 出来;如果值设置为LAZY的话,就会使用懒加载,会多发一条sql

    @ManyToOne(fetch=FetchType.LAZY)

    // 在自己这边生成的外键名是cid,哪一方维护关系就在哪一方加JoinColumn(就是

// 外键的意思)一般都由“多”的一方维护关系,即在自己这方配置对方的外键。

    @JoinColumn(name="cid") 

    public Classroom getClassroom() {

       return classroom;

    }

}

 

一对多(学生和教室)

 

(主要内容就是使用Extra的问题,查询属于教室的学生的数目)。

一对一(人和身份证号)

 

多对多

 

以上都不需要中间表,而这个“多对多”关系需要中间表。

 

【提示】:

多对多关系开发中一般不用,而是使用两个一对多关系来代替。

 

@Entity

@Table(name="t_admin")

public class Admin {

    private int id;

    private String name;

    private Set<Role> roles;

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    // 设置由对方维护关系

    @ManyToMany(mappedBy="admins")

    public Set<Role> getRoles() {

       return roles;

    }

}

 

@Entity

@Table(name="t_role")

public class Role {

    private int id;

    private String name;

    private Set<Admin> admins;

   

    public Role() {

       admins = new HashSet<Admin>();

    }

    public void add(Admin admin) {

       admins.add(admin);

    }

    @ManyToMany(mappedBy="admins")

    public Set<Role> getRoles() {

       return roles;

    }

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    // joinColumns:用来设置自己在中间表的外键名;inverseJoinColumns用来设置

    // 对方在中间表的外键名

    @ManyToMany

    @JoinTable(name="t_role_admin",

joinColumns={@JoinColumn(name="rid")},

                      inverseJoinColumns={@JoinColumn(name="aid")})

    public Set<Admin> getAdmins() {

       return admins;

    }

    public void setAdmins(Set<Admin> admins) {

       this.admins = admins;

    }

}

 

 

 

专业-教室 -学生

基于Annotation

专业-教室:1对多

教室-学生:1对多

 

@Entity

@Table(name="t_stu")

//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class Student {

    private int id;

    private String name;

    private String sex;

    private Classroom classroom;

    private int version;

   

    @Version

    public int getVersion() {

       return version;

    }

    public void setVersion(int version) {

       this.version = version;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    //LAZY就是XML中的select,EAGER就表示XML中的join

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="cid")

    public Classroom getClassroom() {

       return classroom;

    }

}

 

@Entity

@Table(name="t_classroom")

@BatchSize(size=20)

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

    private Special special;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    //由多的一方维护关系

    @OneToMany(mappedBy="classroom")

    @LazyCollection(LazyCollectionOption.EXTRA)

    @Fetch(FetchMode.SUBSELECT)

    public Set<Student> getStus() {

       return stus;

    }  

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="spe_id")

    public Special getSpecial() {

       return special;

    }

}

 

 

@Entity

@Table(name="t_special")

public class Special {

    private int id;

    private String name;

    private String type;

    private Set<Classroom> clas;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    @OneToMany(mappedBy="special")

    @LazyCollection(LazyCollectionOption.EXTRA)

    public Set<Classroom> getClas() {

       return clas;

    }

}

 

 

基于XML

略,重点提示:

(1)       设置<set>的时候,使用inverse,表示不在自己这边维护关系,如下配置classroom

<many-to-one name="special" column="spe_id" fetch="join"/>

<set name="stus" inverse="true" lazy="extra" fetch="subselect">

    <key column="cid"/>

    <one-to-many class="Student"/>

</set>

基于HQL的查询(重要)

 

/*基于?的条件的查询,特别注意:jdbc设置参数的最小下标是1hibernate0*/

List<Student> stus = session

.createQuery("from Student where name like ?")

                           .setParameter(0, "%李%")

                                   .list();

 

/*还可以基于别名进行查询,使用:xxx来说明别名的名称,基于列表查询一定要用别名*/

List<Student> stus = session

.createQuery("from Student where name like :name and sex=:sex")

                  .setParameter("name", "%刘%")

                       .setParameter("sex", "男")

                            .list();

 

 

常用HQL的查询(重要)

 

/*使用uniqueResult可以返回唯一的一个值注意返回值类型(uniqueResult的返回类型是Object*/

Long stus = (Long)session

.createQuery("select count(*)

 from Student where name like :name and sex=:sex")

                         .setParameter("name", "%刘%")

                            .setParameter("sex", "男")

                                .uniqueResult();

 

/*基于投影的查询,通过在列表中存储一个对象的数组,注意返回值类型

基于投影的查询还可以基于DTO——DTO使用来传输数据*/

List<Object[]> stus = session

.createQuery("select stu.sex,

count(*) from Student stu group by stu.sex").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]);

}

【我的总结】:

group by的作用,select后的显示、计算是按照group by后进行的(group by后的记录相当于一张张字表,对这些字表进行select操作)。

 

/*如果对象中相应的导航对象,可以直接导航完成查询*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.classroom.name=? and stu.name like ?")

                           .setParameter(0, "计算机教育班")

.setParameter(1, "%张%")

.list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

 

/*可以使用in来设置基于列表的查询,此处的查询需要使用别名进行查询。

特别注意:使用in的查询必须在其他的查询之后*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.name like ? and stu.classroom.id in (:clas)")

                  .setParameter(0, "%张%")

.setParameterList("clas", new Integer[]{1,2})

                            .list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

【提示】:

基于列表查询要用别名:name

 

/*可以通过is null来查询为空的对象,sql一样不能使用=来查询null的对象*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.classroom is null")

                            .setFirstResult(0)

.setMaxResults(15)

                                          .list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

 

/*使用对象的导航(内部使用Cross JOIN<笛卡尔积>)可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOINJoin = Inner Join)来完成连接*/

List<Student> stus = ession

.createQuery("select stu from Student stu

left join stu.classroom cla where cla.id=2")

                             .setFirstResult(0)

.setMaxResults(15).list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

 

HQL的连接查询(重要)

 

连接(Join)有三种:

(1)      left join

以下sqlt1left)中不符合条件(t1.id=t2.cid)的也打印出来,无值的字段为null

           select * from t_classroom t1

left join t_stu t2 on(t1.id=t2.cid)

 

(2)      right join:同理(1

(3)      inner join(即join):两边都有的才显示。

 

左、右连接常用语统计数据。

// 以下sql语句显示不正常,本意是想显示每个班级的学生人数,但是没有学生的班级也会显示人数:

select t1.name,count(*) from t_classroom t1

join t_stu t2

on(t1.id=t2.cid) group by t1.id

// 改成以下sql

select t1.name,count(t2.cid) from t_classroom t1

left join t_stu t2

on(t1.id=t2.cid) group by t1.id

 

查询每个班级的男生和女生的人数:

select t1.name,t2.sex,count(t2.cid)

from t_classroom t1

left join t_stu t2

on(t1.id=t2.cid)

group by t1.id,t2.sex

 

【提示】:

Hibernate的导航连接内部使用CROSS JOIN(全连接),效率很低!

 

查询2班的所有学生:

/*使用对象的导航可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOIN来完成连接*/

List<Student> stus = session

                  .createQuery("select stu from Student stu

left join stu.classroom cla where cla.id=2").setFirstResult(0)

.setMaxResults(15).list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

 

查询每个班的人数

/*使用对象的导航可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOIN来完成连接

思考:如果把classroom放前面呢?如果使用count(*)会如何?*/

List<Object[]> stus = session

                  .createQuery("select

cla.name,count(stu.classroom.id)

from Student stu

right join stu.classroom cla

group by cla.id").list();

for(Object[] stu:stus) {

System.out.println(stu[0]+","+stu[1]);

}

 

查询学生的信息、包括所在班级(班级名)、专业名

 

SQL_1

List<Ojbet[]> stus =

session.createQuery(“select stu.id,stu.name,stu.sex,cla.name,spe.name

from Student stu

left join stu.classroom cla

left join cla.special spe”).list();

for( Object[] obj:stus ){

    ……

}

之前查询的字段,然后获取信息的时候,都是用Object[]接收返回值然后通过Object的索引获取索引值的但是实际开发中如果这样做就很不方便(如SQL_1,因为一一的取出索引值。这时候就可以使用DTO了,专门用来传输数据,没有任何存储意义。

 

>>>使用DTO解决以上问题:

新建一个DTO对象,要从数据库查询的哪些字段名就添加哪些属性名:

 

public class StudentDto {

    private int sid;

    private String sname;

    private String sex;

    private String cname;

    private String spename;

    get()…;

    set()…;

}

HQL查询:

/*直接可以使用new XXObject完成查询,注意,一定要加上Object的完整包名这里使用的new XX,必须在对象中加入相应的构造函数*/

List<StudentDto> stus = session

                  .createQuery("select

new org.zttc.itat.model.StudentDto(

stu.id as sid,stu.name as sname,stu.sex as

sex,cla.name as cname,spe.name as spename)

                    from Student stu

left join stu.classroom cla

left join cla.special spe").list();

for(StudentDto stu:stus) {        

System.out.println(stu.getSid()

+","+stu.getSname()+","+stu.getSex()+","

+stu.getCname()+","+stu.getSpename());

}

 

【提示】:

Criteria基本上在开发中不用!(我们项目组竟然用了……)

 

统计每个专业的学生的人数:

List<Object[]> stus = session.createQuery("

select spe.name,count(stu.classroom.special.id)

from Student stu

right join

stu.classroom.special spe

group by spe");

 

统计人数大于150的专业:

/*having是为group来设置条件的*/

List<Object[]> stus = session.createQuery("

select spe.name,

(count(stu.classroom.special.id))

from Student stu

right join

stu.classroom.special spe

group by spe

having count(stu.classroom.special.id)>150").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]);

}

 

统计每个专业的男女生人数:

/*having是为group来设置条件的*/

List<Object[]> stus = session.createQuery("

select stu.sex,spe.name

(count(stu.classroom.special.id))

from Student stu

right join

stu.classroom.special spe

group by spe,stu.sex").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]+","+obj[2]);

}

 

抓取策略(XML-对多的一方抓取)

基本文件

使用映射文件的情况下:

/*

(1)默认情况会发出3SQL语句,一条取student,一条取Classroom,一条取Special

(3)通过设置XML中的<many-to-one name="classroom" column="cid" fetch="join"/>可以完成对抓取的设置

(4)如果使用Annotation的话,Hibernate会自动使用Join查询,只发出一条SQL

使用Annotation默认就是基于join抓取的,所以只会发出一条sql*/

session = HibernateUtil.openSession();

Student stu = (Student)session.load(Student.class, 1);  System.out.println(stu.getName()+","+stu.getClassroom().getName()+","+stu.getClassroom().getSpecial().getName());

 

如果不想发出3SQL,可以在映射文件中这么配置,使用fetch=”join”(默认值是select

<class name="Student" table="t_stu">

    <!-- <cache usage="read-only"/> -->

    <id name="id">

        <generator class="native"/>

    </id>

     <version name="version"/>

    <property name="name"/>

    <property name="sex"/>

    <many-to-one name="classroom" column="cid" fetch="join"/>

</class>

专业是通过classroom来获取的,所以,同时要在classroom中配置fetch=”join”

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="subselect">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【警告】:

以上使用fetch=”join”会有这样的问题,就是,即使我query中不查询班级和专业,发出的SQL也会去查专业和学生!也就是说,使用了fetch=”join”后,延迟加载就不生效了!

/*使用fetch=join虽然可以将关联对象抓取,但是如果不使用关联对象也会一并查询出来这样会占用相应的内存*/

session = HibernateUtil.openSession();

Student stu = (Student)session.load(Student.class, 1);

//使用fetch=”join”,延迟加载就失效

System.out.println(stu.getName());

 

【对于Annotation的问题】:

因为在使用Annotation的情况下,默认就是基于join的抓取策略,所以,比如,我们只查学生的话,同时会把班级、专业信息都查出来,即把关联的信息都查出来。

 

 

实例以及解决方案

【基于“多”的一方进行抓取:实例及解决方案】

 

session = HibernateUtil.openSession();

/**

 * XML中配置了fetch=join仅仅只是对load的对象有用,对HQL中查询的对象无用,

 * 所以此时会发出查询班级的SQL,解决的这个SQL的问题有两种方案,

 * 1)设置对象的抓取的batch-size

 * 2)在HQL中使用fecth来指定抓取

 * 特别注意,如果使用了join fetch就无法使用count(*)

 */

List<Student> stus = session.

createQuery("select stu from Student stu).list();

for(Student stu:stus) {

    System.out.println(stu.getName()+","+stu.getClassroom());

}

XML配置:

// batch-size:值默认为1,这里设置成20,即一次抓取20个班;

// 缺点是占用内存较大,且session关闭后数据就丢失(??),这里可以看出

// Hibernate的一个缺点就是很容易影响性能

<class name="Classroom" table="t_classroom" batch-size="20 ">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="subselect">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

 

为了解决batch-size的问题,我们可以在xml中不使用batch-size,在HQL使用fetchy来指定抓取

// 没有fetch进行查询会发出多条sql,有了fetch后只发一条

List<Student> stus = session.

createQuery("select stu from

Student stu join fetch stu.classroom").list();

for(Student stu:stus) {

    System.out.println(stu.getName()+","+stu.getClassroom());

}

 

【提示】:

因为HQL使用了join fetch,就无法使用count(*)了,如果还想使用count(*),可以将HQL格式化,将fetch替换成空。

 

【小结】:

抓取策略有两种:

(1)       batch-size:在被抓取方的xml中配置,可以一次抓取大量的数据;

(2)       HQL中使用fetch

 

 

抓取策略(XML-对一的一方抓取)

 

针对“一”的抓取一般不会使用双向关联,只会做“单向关联”。

 

【实例1

取出班级和班级对应的学生

session = HibernateUtil.openSession();

Classroom cla = (Classroom)session.load(Classroom.class, 1);

/*此时会在发出一条SQLclass对象*/

System.out.println(cla.getName());

/*取学生姓名的时候会再发一条SQL取学生对象,可以在Classroom的配置文件中对学生属性添加fetch=”join”,就会用一条sql完成相关查询了*/

for(Student stu:cla.getStus()) {

    System.out.println(stu.getName());

}

 

classroomxml配置文件中加入fetch=join

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="join">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

 

【实例2

取出一组班级

classroomxml配置还是使用实例1中的配置内容。)

List<Classroom> clas = session.createQuery("from Classroom").list();

for(Classroom cla:clas) {

    System.out.println(cla.getName());

/*对于通过HQL取班级列表且获取相应学生列表时,fecth=join就无效了,这里,每次查询一个班级就会发出一个SQL语句,这样肯定有问题,解决方法:

1)第一种方案可以设置setbatch-size来完成批量的抓取

2)可以设置fetch=subselect,使用subselect会完成根据查询出来的班级进行一次对学生对象的子查询(推荐)*/

    for(Student stu:cla.getStus()) {

       System.out.print(stu.getName());

    }

}

 

方法(1)的xml配置:

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="join" batch-size="400">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【说明】:

这里设置batch-size=400后,虽然发出的SQL大大减少,但是,即使我们实际使用10条,Hibernate也会查询出400条,这样很耗内存!(实际怎么使用还是要根据项目实际情况来定)。

 

方法(2)的xml配置:

 

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="subselect" >

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【说明】:

这中方法只发出两条sql:查班级和查学生(subselect)。

另:这里好像<many-to-one>fetch只有两个选项:joinselect,而<set>fetch多一个subselect

 

 

 

 

抓取策略(注解-对多的一方抓取)

Student实体配置:

@Entity

@Table(name="t_stu")

//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class Student {

    private int id;

    private String name;

    private String sex;

    private Classroom classroom;

    private int version;

   

    @Version

    public int getVersion() {

       return version;

    }

    public void setVersion(int version) {

       this.version = version;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

   

    //LAZY就是XML中的select,EAGER就表示XML中的join

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="cid")

    public Classroom getClassroom() {

       return classroom;

    }

}

 

@Entity

@Table(name="t_classroom")

@BatchSize(size=20)

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

    private Special special;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    @OneToMany(mappedBy="classroom")

    @LazyCollection(LazyCollectionOption.EXTRA)

    @Fetch(FetchMode.SUBSELECT)

    public Set<Student> getStus() {

       return stus;

    }

    //使用注解默认都是join查询的,所以这里的配置默认是:

    //fetch=FetchType.EAGER所以这里默认会将专业信息也查询出来

    //所以这里要配置成LAZY

    //但是,如果配置成LAZY后,如果去班级再取专业,就会多发一条SQL查询专业

    //这个的解决方法有有两种:

//1)在HQL中使用fetch join cla.special

//2

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="spe_id")

    public Special getSpecial() {

       return special;

    }

}

 

方法(1)的HQL

/* 基于Annotation由于默认的many-to-one的抓取策略是EAGER的,所以当抓取classroom时会自动发出多条SQL去查询相应的Special;

此时:

1)可以通过join fecth继续完成对关联的抓取(这样SQL很容易变得很长,尤其是大项目中)

2)直接将关联对象的fecth设置为LAZY(就是说不希望查出关联对象),但是(所以)使用LAZY所带来的问题是在查询关联对象时需要发出相应的SQL,很多时候也会影响效率(要查出关联对象的话还是要发出SQL的)

(效率是第一重要的!)*/

 

List<Student> stus = session.createQuery("select stu from " +

       "Student stu join fetch stu.classroom cla join fetch cla.special").list();

for(Student stu:stus) {

    System.out.println(stu.getName()+","+stu.getClassroom());

}

 

 

抓取策略(注解-对一的一方抓取)

【实例1

session = HibernateUtil.openSession();

Classroom cla = (Classroom)session.load(Classroom.class, 1);

System.out.println(cla.getName());

/*此时会在发出两条SQL(取班级、取学生)取学生对象*/

for(Student stu:cla.getStus()) {

    System.out.println(stu.getName());

}

只发出一条SQL的方法,配置classroom实体:

@Entity

@Table(name="t_classroom")

@BatchSize(size=20)

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

    private Special special;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    @OneToMany(mappedBy="classroom")

    @LazyCollection(LazyCollectionOption.EXTRA)

    @Fetch(FetchMode.JOIN) //方法1:使用join

@Fetch(FetchMode.SUBSELECT) //方法2:使用subselect(同时会查专业,另外,即使只查一个班级也会发出两条sql语句进行查班级和查学生)

    public Set<Student> getStus() {

       return stus;

    }

    @ManyToOne(fetch=FetchType.LAZY) //使用lazy不关联不查询

    @JoinColumn(name="spe_id")

    public Special getSpecial() {

       return special;

    }

}

 

 

缓存(基于XML)(问题较多)

 

(一级缓存)N+1问题

实例1:基本查询:

 

/*此时会发出一条sql取出所有的学生信息*/

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

 

实例2:使用Iterator展示N+1问题:

 

/*如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql。在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息,这就是典型的N+1问题。

存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来,而使用iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取*/

session = HibernateUtil.openSession();

Iterator<Student> stus = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).iterate();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

 

实例3:用Iterator从一级缓存(session)中取值

 

/*此时会发出一条sql取出所有的学生信息*/

//发出查classsql

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*使用iterate仅仅只会取Studentid,此时Student的数据已经在缓存中,所以不会在出现N+1*/

//发出查idsql

stus = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).iterate();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

 

实例4:实例3的变形(注意和实例3对比)

 

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*会发出SQL取完整的学生对象,占用内存相对较多*/

ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

 

【小结】:

(1)       多次在session中查询使用Iterato(此情况很少!);

(2)       一级缓存释义;

 

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*id=1Student对象已经在session的缓存(一级缓存)中,此时就不会发sql去取Student*/

Student stu = (Student)session.load(Student.class, 1);

System.out.println(stu.getName()+",---");

 

(3)       一级缓存中session关闭,一级缓存就关闭。

 

 

二级缓存

【提示】:

(1)       二级缓存是sessionFactory级别的缓存;

(2)       二级缓存是优化比较好、使用比较多的缓存。

 

实例1:演示一级缓存

@SuppressWarnings("unchecked")

public class TestCache {

    @Test

    public void test01() {

       Session session = null;

       try {

           /*此时会发出一条sql取出所有的学生信息*/

           session = HibernateUtil.openSession();

           List<Student> ls = session.createQuery("from Student")

                  .setFirstResult(0).setMaxResults(50).list();

           Iterator<Student> stus = ls.iterator();

           for(;stus.hasNext();) {

              Student stu = stus.next();

              System.out.println(stu.getName());

           }

/*id=1Student对象已经在session的缓存(一级缓存)中,此时就不会发sql去取Student*/

           Student stu = (Student)session.load(Student.class, 1);

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

       try {

           session = HibernateUtil.openSession();

/*上一个Session已经关闭,此时又得重新取Student,这里会再发一条sql*/

           Student stu = (Student)session.load(Student.class, 1);

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

 

实例2:使用二级缓存

使用二级缓存步骤:

步骤1要在配置文件(Hibernate.cfg.xml)中配置,打开二级缓存。

<!-- 设置二级缓存为true -->

<property name="hibernate.cache.use_second_level_cache">

true

</property>

 

步骤2加入二级缓存包(常用的是ehache-corteHibernate-ehcache,在Hibernateoptional目录下);

 

步骤3要在配置二级缓存提供的类:

<!-- 设置二级缓存所提供的类 -->

<property name="hibernate.cache.provider_class">

net.sf.ehcache.hibernate.EhCacheProvider

</property>

 

步骤4Hibernate4.0中还要配置工厂(目的是为了提高效率):

 

<!-- 在hibernate4.0之后需要设置facotory_class -->

<property name="hibernate.cache.region.factory_class">

org.hibernate.cache.ehcache.EhCacheRegionFactory

</property>

 

 

 

步骤5设置ehcache.xml文件,在该文件中配置二级缓存的参数:

Hibernateproject目录中拷贝ehcache.xml配置文件到src目录下

 

<ehcache>

<diskStore path="java.io.tmpdir"/>

<!—默认缓存 ->

    <defaultCache

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="120"

        timeToLiveSeconds="120"

        overflowToDisk="true"

        />

   <!-- 每一个独立的cache可以单独为不同的对象进行设置

没有配置的就使用默认的->

    <cache name="org.zttc.itat.model.Student"

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="300"

        timeToLiveSeconds="600"

        overflowToDisk="true"

        />

    <cache name="sampleCache2"

        maxElementsInMemory="1000"

        eternal="true"

        timeToIdleSeconds="0"

        timeToLiveSeconds="0"

        overflowToDisk="false"

        /> -->

</ehcache>

 

 

步骤6Hibernate.cfg.xml中配置ehcache.xml配置文件的路径

 

<!-- 说明ehcache的配置文件路径 -->

<propertyname="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>

 

步骤7开启二级缓存

 

<hibernate-mapping package="org.zttc.itat.model">

    <class name="Student" table="t_stu">

        //这个使用了锁的机制。也可以是read-write,但是效率更低了。

<cache usage="read-only"/>

        <id name="id">

            <generator class="native"/>

        </id>

         <version name="version"/>

        <property name="name"/>

        <property name="sex"/>

        <many-to-one name="classroom" column="cid" fetch="join"/>

    </class>

</hibernate-mapping>

 

二级缓存使用实例:

/*此时会发出一条sql取出所有的学生信息*/

           session = HibernateUtil.openSession();

           List<Student> ls = session.createQuery("from Student")

                  .setFirstResult(0).setMaxResults(50).list();

           Iterator<Student> stus = ls.iterator();

           for(;stus.hasNext();) {

              Student stu = stus.next();

              System.out.println(stu.getName());

           }

/*id=1Student对象已经在session的缓存(一级缓存)中,此时就不会发sql去取Student*/

           Student stu = (Student)session.load(Student.class, 1);

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

       try {

//Student配置了二级缓存了,所以还能从缓存中取出数据

           session = HibernateUtil.openSession();

/*上一个Session已经关闭,此时又得重新取Student*/

           Student stu = (Student)session.load(Student.class, 1);

//因为二级缓存设置了read-only的话,若改变值stu.setName(“”)会报错

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

 

l  二级缓存缓存的是对象

二级缓存是把所有的对象都缓存到内存中,是基于对象的缓存。

【实例】:

try {

/*此时会发出一条sql取出所有的学生信息*/

session = HibernateUtil.openSession();

List<Object[]> ls = session

.createQuery("select stu.id,stu.name from Student stu")

           .setFirstResult(0).setMaxResults(50).list();

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

try {

    session = HibernateUtil.openSession();

    session.beginTransaction();

/*以上代码仅仅取了idname,而二级缓存是缓存对象的,所以上一段代码不会

将对象加入二级缓存此时就是发出相应的sql*/

Student stu = (Student)session.load(Student.class, 1);

//会报错,因为二级缓存设置为read-only

//stu.setName("abc");

System.out.println(stu.getName()+",---");

session.getTransaction().commit();

}

 

二级缓存和Iterator配合使用

 

Iterator的最主要作用就是在此了!

 

Session session = null;

try {

/*此时会发出一条sql取出所有的学生信息*/

    session = HibernateUtil.openSession();

    List<Object[]> ls = session

.createQuery("select stu from Student stu")

                     .setFirstResult(0).setMaxResults(50).list();

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

try {

    session = HibernateUtil.openSession();

/*由于学生的对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条取id的语句,然后在获取对象时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题而且内存占用也不多*/

    Iterator<Student> stus = session.createQuery("from Student")

           .setFirstResult(0).setMaxResults(50).iterate();

    for(;stus.hasNext();) {

       Student stu = stus.next();

       System.out.println(stu.getName());

    }

} catch (Exception e) {

}

如果使用list()如果想少发SQL就需要使用查询缓存:

session = HibernateUtil.openSession();

/*这里发出两条一模一样的sql,此时如果希望不发sql就需要使用查询缓存*/

List<Student> ls = session

.createQuery("select stu from Student stu")//HQL和上面的一样

                     .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

查询缓存是针对HQL语句的缓存,查询缓存仅仅只会缓存id而不会缓存对象。

使用查询缓存:

Hibernate.cfg.xml中配置查询缓存:

<!--设置相应的查询缓存 -->

<property name="hibernate.cache.use_query_cache">true</property>

代码中的用法

【实例1】:

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

    .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级缓存

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*会发出SQL取完整的学生对象,占用内存相对较多*/

ls = session.createQuery("from Student")

       .setCacheable(true)

       .setFirstResult(0).setMaxResults(50).list();

stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

 

【实例2】:

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

    .setCacheable(true)

    // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

    .setParameter(0, "%王%").setFirstResult(0).setMaxResults(50)

           .list();

    Iterator<Student> stus = ls.iterator();

    for (; stus.hasNext();) {

       Student stu = stus.next();

       System.out.println(stu.getName());

    }

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

 

session = null;

try {

/*如果两条sql不一样,就不会开启查询缓存,查询缓存缓存的是HQL语句只有两个HQL完全一致(且参数也要一致)才能使用查询缓存*/

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

    .setCacheable(true)

    // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

    .setParameter(0, "%王%").setFirstResult(0).setMaxResults(50)

       .list();

Iterator<Student> stus = ls.iterator();

for (; stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

 

【实例3】:

/*查询缓存缓存的不是对象而是id,关闭二级缓存后很容易发出大量的sql*/

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

       .setCacheable(true)

       // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

       .setParameter(0, "%王%").setFirstResult(0).setMaxResults(50)

           .list();

    Iterator<Student> stus = ls.iterator();

    for (; stus.hasNext();) {

       Student stu = stus.next();

       System.out.println(stu.getName());

    }

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

 

session = null;

try {

/*查询缓存缓存的是id,此时由于在缓存中已经存在了这样的一组学生数据,但是仅仅只是缓存了id,所以此处会发出大量的sql语句根据id取对象,这也是发现N+1问题的第二个原因 所以如果使用查询缓存必须开启二级缓存*/

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

       .setCacheable(true)

       // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

       .setParameter(0, "%王%").setFirstResult(0).setMaxResults(50)

       .list();

Iterator<Student> stus = ls.iterator();

for (; stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

 

 

缓存(基于注解)

 

开启查询缓存

配置实体中这么配置,其他的查询语句跟在xml中一样:

@Entity

@Table(name="t_stu")

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class Student {

    private int id;

    private String name;

    private String sex;

    private Classroom classroom;

    private int version;

   

    @Version

    public int getVersion() {

       return version;

    }

 

【小结】:

l  经常修改的对象不用二级缓存;

l  用list还是Iterator?如果对象取出来就用Iterator没有的话;

l  查询缓存缓存的是id,但是只有在hql是一样的情况下才使用查询缓存,所以一般不建议使用查询缓存;

 

 

并发

【并发情景演示】:

 

test01()方法中将id1的学生取出来,并将名字修改为”Jack”,在test02()方法中,也将id1的学生取出来,并将名字改为”Tom”,并且在对方commit之前完成改名操作,这样的话,名字不会被改掉。

所以,一般情况下,并发会导致更新丢失

解决方法有两种:

(1)      悲观锁:

悲观锁Hibernate基于数据库的机制实现的,但是在Hibernate3和Hibernate4它们的实现机制是不一样的——Hibernate3是基于同步的机制实现的(同步的大量的并发就容易导致效率的问题),所以在Hibernate4中就没有使用同步,如果两个同时修改同一个记录的话,就抛出异常。解释错误了,Hibernate3Hibernate4的机制是一样的,都是同步的。

(2)      乐观锁:

乐观锁是在数据库中增加一个version的字段来实现的,每次修改都会让这个字段的数字增加1,在读取的时候根据Version这个版本的值来读取,这样如果并发修改就会抛异常。

<class name="Student" table="t_stu">

    <cache usage="read-only"/>

    <id name="id">

        <generator class="native"/>

</id>

//数据库会增加一个version这个字段

    <version name="version"/>

    <property name="name"/>

    <property name="sex"/>

    <many-to-one name="classroom" column="cid" fetch="join"/>

</class>

 

Hibernate提高效率的一种方案(重点!)

 

(1)      在做关系时,尽可能使用单向关联,不要使用双向关联;

(2)      在大项目中(数据量超过百万条)使用Hibernate可以考虑以下几个原则(个人总结):

【原则1】:不要使用对象关联,尽可能使用冗余字段来替代外键(因为百万级别的数据使用跨表查速度会非常慢);

【基于冗余的关联-实例】:

l  上面的实例中,我们的学生表是和班级表关联的,但是,实际上,显示学生的信息时,一般都只是希望显示班级名称即可,并不需要更多的班级信息。

【设置冗余字段的方法:】

1) 给班级增加一个用当前时间毫秒级+随机数作为班级的主键;

2) 我们给Student实体增加班级编号(classBh班级名(className这两个特冗余字段;这样我们在显示班级名的时候就不需要关联班级表了;

3) 同样,学生还需要专业信息,我们就再在Student实体中增加专业编号(speBh)和专业名称(speName),同时,班级实体中也增加专业的编号和专业的名称;

4) 使用冗余的缺点:比如,我们修改了班级名称的时候,学生表里的班级名称也要跟着做修改(但是性能基本没有影响)

Q:但是,如果有一个对象同时需要学生对象、班级对象、专业对象,那又怎么办呢?

——答案是使用DTO(数据传输对象)。

public class StudentDto {

  private Student stu;

  private Classroom cla;

  private Special spe;

}

【原则2】:不使用HQL,而全部使用SQL;如果需要缓存,就使用自己的缓存,而不适用Hibernate的缓存(Hibernate的缓存在SessionSessionFatory会有耗内存)  

 

(3)        

 

使用Hibernate SQL查询

【实例1】:

public void test01() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> stus = session

.createSQLQuery("select * from t_stu where name like ?")

           .addEntity(Student.class)

           .setParameter(0, "%%")

           .setFirstResult(0).setMaxResults(10)

           .list();

       for(Student stu:stus) {

           System.out.println(stu.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

    } finally {

       HibernateUtil.close(session);

    }

}

 

【实例2】:将查询结果映射到DTO

 

session = HibernateUtil.openSession();

//注意SQL语句上加了{},返回值用Object来接收,实例3中使用DTO接收

List<Object[]> stus = session

.createSQLQuery("select {stu.*},{cla.*},{spe.*} from

t_stu stu left join t_classroom cla

on(stu.cid=cla.id)

left join t_special spe on(spe.id=cla.spe_id)

where stu.name like ?")

.addEntity("stu",Student.class)

.addEntity("cla",Classroom.class)

.addEntity("spe",Special.class)

.setParameter(0, "%孔%")

.setFirstResult(0).setMaxResults(10)

.list();

Student stu = null;

Classroom cla = null;

Special spe = null;

List<StuDto> list = new ArrayList<StuDto>();

for(Object[] obj:stus) {

    stu = (Student)obj[0];

    cla = (Classroom)obj[1];

    spe = (Special)obj[2];

    list.add(new StuDto(stu, cla, spe));

    //这里会将学生姓名打印3遍,所以sql语句中加了花括号,用来自动将结果映射

//到前缀对应的对象中

    System.out.println(stu.name+cla.name+spe.name);

}

 

【实例2】:使用DTO接收返回值

StudentDto实体:

public class StudentDto {

    private int sid;

    private String sname;

    private String sex;

    private String cname;

    private String spename;

 

    public StudentDto(int sid, String sname, String sex, String cname,

           String spename) {

       super();

        this.sid = sid;

       this.sname = sname;

       this.sex = sex;

       this.cname = cname;

       this.spename = spename;

    }

   

    public StudentDto() {

    }

}

 

查询

 

session = HibernateUtil.openSession();

//这里的sql为了和DTO对应,

//可以在selectnew com.zmj…StudentDto(stu.id as sid,……)

List<StudentDto> stus = session.createSQLQuery("select

stu.id as sid,

stu.name as sname,

stu.sex as sex,

cla.name as cname,

spe.name as spename

from t_stu stu

left join t_classroom cla

on(stu.cid=cla.id) "

left join t_special spe

on(spe.id=cla.spe_id)

where stu.name like ?")

//这里第一感觉可能会想到使用setEntitysetEntity的参数必须是和数据库

//对应的,但是DTO明显不需要和数据库对应,所以这里使用以下这个方法:

    .setResultTransformer(Transformers.aliasToBean(StudentDto.class))

    .setParameter(0, "%孔%")

    .setFirstResult(0).setMaxResults(10)

    .list();

for(StudentDto sd:stus) {

    System.out.println(sd);

}

【提示】:

这样使用Hibernate的方法(查询使用sql,增、删、改使用Hibernate),效率会大大提高!

 

 

 

 

==讲解:Spring

 

 

基本概念

 

90%web项目都会用到Spring

Spring除了可以和Hibernate整合也可以和JDBC整合;

Spring的两个最基本的东西是:IOC(控制反转,也叫“依赖注入”)、AOP(面向切面);

 

IOC

【基本概念】:

<bean

id="userAction"

class="org.zttc.itat.spring.action.UserAction"

scope="prototype">//prototype:多例;singlton:单例(默认)

【提示】:

Action(Controller)中的状态没有修改就用单例,有修改的话就用多例。比如,添加User的Action,一般都会用多例,因为每次请求添加的内容都不一样。

 

【使用属性注入】:

<bean id="userService" class="org.zttc.itat.spring.service.UserService">

<!-- name中的值会在userService对象中调用setXX方法来注入,诸如:name="userDao"在具体注入时会调用setUserDao(IUserDao userDao)来完成注入ref="userDao"表示是配置文件中的bean中所创建的DAOid -->

    <property name="userDao" ref="userDao"></property>

</bean>

 

【使用构造函数注入(不常用)】:

public class UserAction {

    private User user;

    private IUserService userService;

    private int id;

    private List<String> names;

    // 构造函数

    public UserAction(IUserService userService) {

       super();

       this.userService = userService;

    }

 

在配置文件中使用构造函数注入:

<!-- 以下是使用构造函数来注入,不常用,基本都是使用set方法注入 -->

<bean id="userAction"

class="org.zttc.itat.spring.action.UserAction"

scope="prototype">

    <constructor-arg ref="userService"/>// 构造函数的参数,依顺序往下写

</bean>

 

【自动注入(不常用,也不建议使用)】:

// autowire会自动找到属性和对应的set方法进行注入(若值为default则不自动注入)

<bean id="userAction"

class="org.zttc.itat.spring.action.UserAction"

scope="prototype" autowire="byname ">

    <!--<property ref="userService"/>-->

</bean>

 

【提示】:

Spring3.0后提供了基于Annotation注入。

 

使用Annotation

 

【基本使用方法】:

beans.xml配置文件:

 

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

     xmlns:aop="http://www.springframework.org/schema/aop"

     xmlns:context="http://www.springframework.org/schema/context"

     xsi:schemaLocation="http://www.springframework.org/schema/beans

……

   <!-- 打开SpringAnnotation支持 -->

   <context:annotation-config/>

   <!-- 设定Spring 去哪些包中找Annotation -->

   <context:component-scan base-package="org.zmj.test.spring"/>

   <!-- 打开基于AnnotationAOP -->

   <aop:aspectj-autoproxy/>

</beans>

 

l  将相关类注入

注入UserDao

//等于完成了<bean id="userDao" class="org.zttc.itat.spring.UserDao"/>

//@Component("userDao")//公共的创建beanAnnotation,所有的类都能用

@Repository("userDao")//@Repository一般用于DAO的注入

public class UserDao implements IUserDao {

    @Override

    public void add(User user) {

       System.out.println("添加了"+user);

    }

    ……

}

 

注入UserService

//@Component("userService")

@Service("userService")//业务层一般使用@Service

public class UserService implements IUserService {

    private IUserDao userDao;

    private IMessageDao messageDao;

   

    @Resource(name="messageDao")

    public void setMessageDao(IMessageDao messageDao) {

       this.messageDao = messageDao;

    }

 

    //默认通过名称注入,在JSR330中提供了@Inject来注入

    //@Autowired也可以,就是默认注入使用类型注入,不建议使用

    @Resource(name="userDao")

    public void setUserDao(IUserDao userDao) {

       this.userDao = userDao;

    }

}

 

注入UserAction

//@Component("userAction")

@Controller("userAction")//MVC的控制层一般使用@Controller

@Scope("prototype")

public class UserAction {

    private User user;

    private IUserService userService;

    private int id;

    private List<String> names;

   

}

【提示】:

项目比较大的时候一般不适用Annotation。因为在大项目中的代码结构是按模块来划分的,类非常多,使用Annotation的话类的依赖关系不明显,但是在配置文件中看的话就会一目了然。

 

AOP(重点)

 

【实例1】:AOP-模拟(静态代理)

背景:假设我们的项目已经上线,已经在运行,突然客户提出要求,要在项目中加上日志的输出。

 

我们直观的想法是,创建一个类用来输出日志,然后在原来的代码里加入输出日志的代码。但是这个破坏了原来的代码。解决方法是使用代理。

 

比如,在我们的代码里,我们创建一个UserProxyDao类,这个类实现了IUserDao,然后实现接口中的方法,然后在这些方法前加入输出日志的代码,这样就不会破坏原来的代码了。

 

l  代码内容:

@Component("userProxyDao")

public class UserProxyDao implements IUserDao {

    private IUserDao userDao;

    @Resource

    public void setUserDao(IUserDao userDao) {

       this.userDao = userDao;

    }

    @Override

    public void add(User user) {

       Logger.info("添加了用户");

       userDao.add(user);

    }

    @Override

    public void delete(int id) {

       Logger.info("删除了用户");

       userDao.delete(id);

    }

    @Override

    public User load(int id) {

       return userDao.load(id);

    }

}

 

UserSerivce中用到了这些方法,所以要在UserService中注入userProxyDao

【提示】:

以上是“静态代理”的实现。

这样虽然解决了问题且没有破坏原来的代码,但是这样还有一个问题,就是,如果还想给其他的Dao(如MessageDao)添加日志,我们还要再创建一个MessageProxyDao用来输出日志,如果哪一天不想输出日志了,还要再创建新的代理,这样肯定很麻烦,不可行。

 

经过分析,我们发现,输出日志的代码都是独立的,和业务逻辑无关,所以,我们可以将这些代码抽出来,然后注入到相关的代码中。

 

【实例2】:AOP-手动实现

 

动态代理是指通过一个代理对象来创建需要的业务对象,然后在这个代理对象中统一进行各种需求的处理。

 

【步骤】:

步骤1写一个类实现InvocationHandler接口

步骤2创建一个代理对象;

步骤3创建一个方法来创建对象,方法的参数是要代理的对象

 

l  创建代理对象:

 

//1、写一个类实现InvocationHandler接口*/

public class LogProxy implements InvocationHandler {

    private LogProxy(){}

    //2、创建一个代理对象

    private Object target;

    //3、创建一个方法来生成对象,这个方法的参数是要代理的对象,

//   getInstacne所返回的对象就是代理对象

    public static Object getInstance(Object o) {

       //3.1、创建LogProxy对象

       LogProxy proxy = new LogProxy();

       //3.2、设置这个代理对象

       proxy.target = o;

       //3.3、通过Proxy的方法创建代理对象,

//      第一个参数是要代理对象的classLoader

        //      第二个参数是要代理对象实现的所有接口,

//      第三个参数是实现类InvocationHandler的对象

       //此时的result就是一个代理对象,代理的是o

       Object result = Proxy.newProxyInstance(

o.getClass().getClassLoader(),

o.getClass().getInterfaces(),

proxy);

       return result;

    }

    /**当有了代理对象之后,不管这个代理对象执行什么方法,

* 都会调用以下的invoke方法*/

    @Override

    public Object invoke(Object proxy, Method method, Object[] args)

           throws Throwable {

       /**控制哪些方法做日志输出 - 判断方法名称*/

       //if(method.getName().equals("add")

||method.getName().equals("delete")) {

       //  Logger.info("进行了相应的操作");

       //}

      

       /**控制哪些方法做日志输出 - 使用Annotation*/

       // 以下代码可以放在方法执行前、方法执行后也可放在异常中执行

       if(method.isAnnotationPresent(LogInfo.class)) {

           LogInfo li = method.getAnnotation(LogInfo.class);

           Logger.info(li.value());

       }

       // 执行方法

       Object obj = method.invoke(target, args);

       return obj;

    }

}

 

l  将代理对象注入到Spring中:(因为没有getset所以不能使用注解注入)

   // 以下代码的意思是,使用LoginProxy类使用getInstance创建userDao

// userDao会和注解中的“userDao”进行匹配,这个userDao会传入代理中的

// getInstance参数中

<bean id="userDynamicDao"

class="org.zttc.itat.spring.proxy.LogProxy"

          factory-method="getInstance">

         <constructor-arg ref="userDao"/>

   </bean>

   <bean id="messageDynamicDao"

class="org.zttc.itat.spring.proxy.LogProxy"

factory-method="getInstance">

         <constructor-arg ref="messageDao"/>

   </bean>

 

l  在UserService中使用userDynamicDao

 

//@Component("userService")

@Service("userService")//业务层一般使用@Service

public class UserService implements IUserService {

    private IUserDao userDao;

    private IMessageDao messageDao;

   

    @Resource(name="messageDynamicDao")

    public void setMessageDao(IMessageDao messageDao) {

       this.messageDao = messageDao;

    }

    //默认通过名称注入,在JSR330中提供了@Inject来注入

    @Resource(name="userDynamicDao")

    public void setUserDao(IUserDao userDao) {

       this.userDao = userDao;

    }

}

 

l  使用Annotation来控制要输出的信息:

@Retention(RetentionPolicy.RUNTIME)

public @interface LogInfo {

    public String value() default"";

}

 

l  在接口上(我们这里是基于接口的代理)添加该注解:

public interface IUserDao {

    @LogInfo("添加用户信息")

    public void add(User user);

    @LogInfo("删除用户信息")

    public void delete(int id);

    public User load(int id);

}

 

【小结】:

所谓AOP(切面)就是将关注的模块抽出来做成一个独立的部分(切面)。

 

【实例3】:使用注解配置AOP

 

步骤1设置Schema、打开基于AnnotationAOP

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

     xmlns:aop="http://www.springframework.org/schema/aop"

     xmlns:context="http://www.springframework.org/schema/context"

     xsi:schemaLocation="http://www.springframework.org/schema/beans

         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

         http://www.springframework.org/schema/context        http://www.springframework.org/schema/context/spring-context-3.0.xsd

         http://www.springframework.org/schema/aop         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

   <!-- 打开SpringAnnotation支持 -->

   <context:annotation-config/>

   <!-- 设定Spring 去哪些包中找Annotation -->

   <context:component-scan base-package="org.zttc.itat.spring"/>

   <!-- 打开基于AnnotationAOP -->

   <aop:aspectj-autoproxy/>

</beans>

 

步骤2创建代理类(Spring使用的是第三方的切面技术,所以要另外导入包:

aopalliance.jar,aspectjrt.jar,aspectjweaver.jar

@Component("logAspect")//让这个切面类被Spring所管理

@Aspect//申明这个类是一个切面类(需要导入第三方包)

public class LogAspect {

   

    /**

     * execution(* org.zttc.itat.spring.dao.*.add*(..))

     * 第一个*表示任意返回值

     * 第二个*表示 org.zttc.itat.spring.dao包中的所有类

     * 第三个*表示以add开头的所有方法

     * (..)表示任意参数

     */

    @Before("execution(* org.zttc.itat.spring.dao.*.add*(..))||" +

           "execution(* org.zttc.itat.spring.dao.*.delete*(..))||" +

           "execution(* org.zttc.itat.spring.dao.*.update*(..))")

    public void logStart(JoinPoint jp) {

       //得到执行的对象

       System.out.println(jp.getTarget());

       //得到执行的方法

       System.out.println(jp.getSignature().getName());

       Logger.info("加入日志");

    }

    /**函数调用完成之后执行*/

    @After("execution(* org.zttc.itat.spring.dao.*.add*(..))||" +

           "execution(* org.zttc.itat.spring.dao.*.delete*(..))||" +

           "execution(* org.zttc.itat.spring.dao.*.update*(..))")

    public void logEnd(JoinPoint jp) {

       Logger.info("方法调用结束加入日志");

    }

   

    /** 函数调用中执行   */

    @Around("execution(* org.zttc.itat.spring.dao.*.add*(..))||" +

           "execution(* org.zttc.itat.spring.dao.*.delete*(..))||" +

           "execution(* org.zttc.itat.spring.dao.*.update*(..))")

    public void logAround(ProceedingJoinPoint pjp) throws Throwable {

       Logger.info("开始在Around中加入日志");

        pjp.proceed();//执行程序

       Logger.info("结束Around");

    }

}

 

【实例3】:使用配置文件配置AOP

 

首先,第三方包要导入、Schema头要设置。

创建切面(代理类):

@Component("logAspect")//让这个切面类被Spring所管理

public class LogAspect {

    public void logStart(JoinPoint jp) {

       //得到执行的对象

       System.out.println(jp.getTarget());

       //得到执行的方法

       System.out.println(jp.getSignature().getName());

       Logger.info("加入日志");

    }

    public void logEnd(JoinPoint jp) {

       Logger.info("方法调用结束加入日志");

    }

    public void logAround(ProceedingJoinPoint pjp) throws Throwable {

       Logger.info("开始在Around中加入日志");

       pjp.proceed();//执行程序

       Logger.info("结束Around");

    }

}

 

配置文件中配置该切面:

 

   <!-- 打开SpringAnnotation支持 -->

   <context:annotation-config/>

   <!-- 设定Spring 去哪些包中找Annotation -->

   <context:component-scan base-package="org.zttc.itat.spring"/>

  

   <aop:config>

   <!-- 定义切面 -->

       <aop:aspect id="myLogAspect" ref="logAspect">

       <!-- 在哪些位置加入相应的Aspect -->

           <aop:pointcut

id="logPointCut"

expression="

execution(* org.zttc.itat.spring.dao.*.add*(..))||

execution(* org.zttc.itat.spring.dao.*.delete*(..))||

execution(* org.zttc.itat.spring.dao.*.update*(..))"

/>

           <aop:before method="logStart" pointcut-ref="logPointCut"/>

           <aop:after method="logEnd" pointcut-ref="logPointCut"/>

           <aop:around method="logAround" pointcut-ref="logPointCut"/>

       </aop:aspect>

   </aop:config>

 

SpringJDBC整合

 

步骤1导入Spring包和数据库驱动包;

步骤2选择一个数据源(DBCPC3P0);

步骤3导入数据源的包;

步骤4beans.xml中创建dataSource数据源;

步骤5创建数据库的属性文件,存放连接数据库的连接信息;

步骤6beans.xml中导入属性文件;

步骤7创建UserDao并在UserDao中创建JDBCTemplate对象(通过JDBCTemplate可以方

便操作数据库);

步骤8Dao注入DataSource并创建JDBCTemplate

步骤9完成数据的增删改查

 

beans.xml文件:

  <bean id="dataSource"

         class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

    <property name="driverClassName" value="${jdbc.driverClassName}"/>

    <property name="url" value="${jdbc.url}"/>

    <property name="username" value="${jdbc.username}"/>

    <property name="password" value="${jdbc.password}"/>

</bean>

<!-- 导入Src目录下的jdbc.properties文件 -->

<context:property-placeholder location="jdbc.properties"/>

连接数据库的属性文件内容:

 

jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.url = jdbc:mysql://localhost:3306/spring_teach

jdbc.username = root

jdbc.password = 123456

 

创建User实体:

 

根据实体内容创建对应的数据库表:

 

创建UserDaoJDBCTemplate

 

@Repository("userJdbcDao")

public class UserDao implements IUserDao {

   

    private JdbcTemplate jdbcTemplate;

   

    @Resource

    public void setDataSource(DataSource dataSource) {

       jdbcTemplate = new JdbcTemplate(dataSource);

    }

 

    @Override

    public void add(User user,int gid) {

       jdbcTemplate.update("insert into t_user(username,password,nickname,gid) value (?,?,?,?)",

              user.getUsername(),user.getPassword(),user.getNickname(),gid);

    }

}

 

使用Spring集成测试:

 

/**当使用了以下注解之后,就可以直接在Test中进行依赖注入*/

//Junit运行在Spring的测试环境中

@RunWith(SpringJUnit4ClassRunner.class)

//加载beans.xml文件

@ContextConfiguration("/beans.xml")

public class TestJdbc {

    @Resource(name="userJdbcDao")//注意:名字要和注解中配置的名字一致!

    private IUserDao userJdbcDao;

    @Resource(name="groupJdbcDao")

    private IGroupDao groupJdbcDao;

   

    @Test

    public void testAdd() {

       Group g = new Group();

       g.setName("文章审核人员");

       groupJdbcDao.add(g);

       System.out.println(g.getId());

       User u = new User("tangsheng","123","唐僧");

       userJdbcDao.add(u, 1);

    }

}

 

添加Group(用户属于组)

 

主要是想介绍如何在添加一个实体后返回主键的((非重点):

@Repository("groupJdbcDao")

public class GroupJdbcDao implements IGroupDao {

    private JdbcTemplate jdbcTemplate;

   

    @Resource

    public void setDataSource(DataSource dataSource) {

       jdbcTemplate = new JdbcTemplate(dataSource);

    }

    @Override

    public void add(final Group group) {

       /**通过以下方法可以添加一个对象,并且获取这个对象自动递增的id*/

       KeyHolder keyHolder = new GeneratedKeyHolder();

       jdbcTemplate.update(new PreparedStatementCreator() {

           @Override

           public PreparedStatement

createPreparedStatement(Connection con)

                  throws SQLException {

              String sql = "insert into t_group (name) value(?)";

              PreparedStatement ps =

con.prepareStatement(sql,new String[]{"id"});

              ps.setString(1, group.getName());

              return ps;

           }

       },keyHolder);

       group.setId(keyHolder.getKey().intValue());

    }

}

 

@Repository("userJdbcDao")

public class UserDao implements IUserDao {

   

    private JdbcTemplate jdbcTemplate;

   

    @Resource

    public void setDataSource(DataSource dataSource) {

       jdbcTemplate = new JdbcTemplate(dataSource);

    }

 

    @Override

    public void add(User user,int gid) {

       jdbcTemplate.update("

insert into t_user

(username,password,nickname,gid)

value (?,?,?,?)",

              user.getUsername(),

user.getPassword(),

user.getNickname(),gid);

    }

 

    @Override

    public void update(User user) {

       jdbcTemplate.update("update t_user set username=?,password=?,nickname=? where id=?",

              user.getUsername(),user.getPassword(),user.getNickname(),user.getId());

    }

 

    @Override

    public void delete(int id) {

       jdbcTemplate.update("delete from t_user where id=?",id);

    }

 

    @Override

    public User load(int id) {

       String sql = "select t1.id uid,t1.*,t2.* from t_user t1 left join t_group t2 on(t1.gid=t2.id) where t1.id=?";

/*

* 第一个参数是SQL语句

* 第二个参数是SQL语句中的参数值,需要传入一个对象数组

* 第三个参数是一个RowMapper,这个rowMapper可以完成一个对象和数据库字段的对应,实现这个RowMapper需要

* 实现mapRow方法,在mapRow方法中有rs这个参数,通过rs可以有效的获取数据库的字段

* 如果这个方法在该DAO中会被重复使用,建议通过内部类来解决,而不要使用匿名的内部类*/

       User u = (User)jdbcTemplate.queryForObject(sql, new Object[]{id},new UserMapper());

       return u;

    }

 

    @Override

    public List<User> list(String sql,Object[] args) {

       String sqlCount = "select count(*) from t_user";

       //获取整数值

       int count = jdbcTemplate.queryForInt(sqlCount);

       System.out.println(count);

       String nCount = "select nickname from t_user";

       //获取String类型的列表

       List<String> ns = jdbcTemplate.queryForList(nCount,String.class);

       for(String n:ns) {

           System.out.println("--->"+n);

       }

       String tSql = "select username,nickname from t_user";

       //无法取出user

       /*List<User> us = jdbcTemplate.queryForList(tSql, User.class);

       for(User u:us) {

           System.out.println(u);

       }*/

       //对象数组也无法返回

       /*List<Object[]> os = jdbcTemplate.queryForList(tSql, Object[].class);

       for(Object[] oo:os) {

           System.out.println(oo[0]+","+oo[1]);

       }*/

      

       List<User> us = jdbcTemplate.query(tSql,new RowMapper<User>(){

           @Override

           public User mapRow(ResultSet rs, int rowNum) throws SQLException {

              User u = new User();

              u.setNickname(rs.getString("nickname"));

              u.setUsername(rs.getString("username"));

              return u;

           }

       });

       for(User u:us) {

           System.out.println(u);

       }

       return jdbcTemplate.query(sql, args, new UserMapper());

    }

   

    private class UserMapper implements RowMapper<User> {

       @Override

       public User mapRow(ResultSet rs, int rowNum) throws SQLException {

           Group g = new Group();

           g.setName(rs.getString("name"));

           g.setId(rs.getInt("gid"));

           User u = new User();

           u.setGroup(g);

           u.setId(rs.getInt("uid"));

           u.setNickname(rs.getString("nickname"));

           u.setPassword(rs.getString("password"));

           u.setUsername(rs.getString("username"));

           return u;

       }

    }

 

}

 

 

 

 

==讲解:SpringMVC

 

 

 

简单的HelloWorld示例:

 

如果往数据库中存入中文字符的话,则要处理中文乱码的问题,方法是:

web.xml中做以下配置:

 

 

JSR是记录Java标准的更新。

 

输入验证:

在实体里使用注解限定属性:

public class User {

    private String username;

    private String password;

    private String nickname;

    private String email;

    // 这里加上注解:

    @NotEmpty(message="用户名不能为空")

    public String getUsername() {

       return username;

    }

 

    @Size(min=1,max=10,message="密码的长度应该在1和10之间")

    public String getPassword() {

       return password;

    }

}

 

controller中使用@Validate来验证用户的输入,BindingResult返回验证结果( 实体中定义的)

    @RequestMapping(value="/{username}/update",

method=RequestMethod.POST)

   // BindingResult用来存放错误信息(写在实体里的)

// BindingResult要紧跟@Validate后面

    public String update(@PathVariable String username,

@Validated User user,BindingResult br) {

       if(br.hasErrors()) {// 有错误信息

       //如果有错误直接跳转到add视图

           return "user/update";

       }

       users.put(username, user);

       return "redirect:/user/users";

    }

    //在具体添加用户时,是post请求,就访问以下代码

    @RequestMapping(value="/add",method=RequestMethod.POST)

    public String add(@Validated User user,BindingResult br,

@RequestParam("attachs")MultipartFile[] attachs,

HttpServletRequest req) throws IOException {

//一定要紧跟Validate之后写验证结果类

       if(br.hasErrors()) {

           //如果有错误直接跳转到add视图

           return "user/add";

       }

       String realpath = req.getSession().getServletContext().

getRealPath("/resources/upload");

       System.out.println(realpath);

       for(MultipartFile attach:attachs) {

           if(attach.isEmpty()) continue;

           File f = new File(realpath+"/"+attach

.getOriginalFilename());

           FileUtils.copyInputStreamToFile(attach

.getInputStream(),f);

       }

       users.put(user.getUsername(), user);

       return "redirect:/user/users";

    }

 

然后在jsp页面显示这些错误信息:

<!-- 此时没有写action,直接提交会提交给/add -->

// 这里为什么是直接给/add不明白,可能是modelAttribute=”user”而使用了参数

// 匹配吧。

<sf:form method="post" modelAttribute="user"

                                   enctype="multipart/form-data">

// path=”usernama”就相当于是:name=”username”,会自动使用user.Set方法

    Username:<sf:input path="username"/>

<sf:errors path="username"/><br/>

    Password:<sf:password path="password"/>

<sf:errors path="password"/><br/>

    Nickname:<sf:input path="nickname"/><br/>

    Email:<sf:input path="email"/>

<sf:errors path="email"/><br/>

    Attach:<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

     <input type="submit" value="添加用户"/>

</sf:form>

 

 

显示用户:

 

// value=”/{username}” 表示username是一个路径参数(@PathVariable

// ?这里的这个username好像是跟show方法中的参数保持一致的……

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

public String show(@PathVariable String username,Model model) {

    // jsp页面就能用${user.username}取值了

    model.addAttribute(users.get(username));

    return "user/show";

}

 

【提示】:

l  关于请求方法:只要不是“更新”操作,就用GET请求。

l  关于model.addAttribute(),如果只写参数,那么其key则是使用对象的类型比如:

model.addAttribute(new User());等价于:model.addAttribute(“user”,new User());

 

修改用户:

// JSP页面

<sf:form method="post" modelAttribute="user">

    Username:<sf:input path="username"/>

<sf:errors path="username"/><br/>

    Password:<sf:password path="password"/>

<sf:errors path="password"/><br/>

    Nickname:<sf:input path="nickname"/><br/>

    Email:<sf:input path="email"/>

<sf:errors path="email"/><br/>

    <input type="submit" value="修改用户"/>

</sf:form>

 

    // 页面跳转

@RequestMapping(value="/{username}/update",

method=RequestMethod.GET)

    public String update(@PathVariable String username,Model model) {

       model.addAttribute(users.get(username)); // 回显

       return "user/update";// 到此页面

    }

 

// 真正的修改

@RequestMapping(value="/{username}/update",

method=RequestMethod.POST)

public String update(@PathVariable String username,

@Validated User user,BindingResult br) {

    if(br.hasErrors()) {

       //如果有错误直接跳转到add视图

       return "user/update";

    }

    users.put(username, user);

    return "redirect:/user/users";

}

 

删除用户:

 

<c:forEach items="${users }" var="um">

    ${um.value.username }

    ----<a href="${um.value.username }">${um.value.nickname }</a>

    ----${um.value.password }

    ----${um.value.email }—

<a href="${um.value.username }/update">修改</a>

    <a href="${um.value.username }/delete">删除</a><br/>

</c:forEach>

 

    @RequestMapping(value="/{username}/delete",

method=RequestMethod.GET)

    public String delete(@PathVariable String username) {

       users.remove(username);

       return "redirect:/user/users";

    }

 

用户登陆、异常处理:

 

局部异常处理

局部异常处理是放在一个单独的Controller中的异常处理,只为这一个Controller服务。

步骤

1)自定义异常:

public class UserException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public UserException() {

       super();

    }

    public UserException(String message, Throwable cause) {

       super(message, cause);

    }

    public UserException(String message) {

       super(message);

    }

    public UserException(Throwable cause) {

       super(cause);

    }

}

 

2)在Controller中使用异常:

   

    @RequestMapping(value="/login",method=RequestMethod.POST)

    public String login(String username,String password,

HttpSession session) {

       if(!users.containsKey(username)) {

           throw new UserException("用户名不存在");//在页面可以打印

       }

       User u = users.get(username);

       if(!u.getPassword().equals(password)) {

           throw new UserException("用户密码不正确");

       }

       session.setAttribute("loginUser", u);

       return "redirect:/user/users";

    }

   

    /**

     * 局部异常处理,仅仅只能处理这个控制器中的异常

     */

    @ExceptionHandler(value={UserException.class})

    public String handlerException(UserException e,

HttpServletRequest req) {

       req.setAttribute("e",e);

       return "error";// 返回到error页面

    }

 

3)在error.jsp中显示异常信息

 

发现错误:

<h1>${e.message}</h1>

 

全局异常处理

使用方法是在springMVC配置文件中将自己定义的异常配置成全局的。

    <bean id="exceptionResolver" class="org.springframework.web.servlet.

handler.SimpleMappingExceptionResolver">

       <property name="exceptionMappings">

           <props>

              // 如果发现UserException就跳转到error页面,异常会放在

//exception中,所以在jsp页面要使用exception来取出异常信息

              <prop key="zttc.itat.model.UserException">error</prop>

              // 如果发现空指针异常就到error2页面

<prop key="java.lang.NullPointException">error2</prop>

           </props>

       </property>

    </bean>

 

在页面打印异常信息:

发现错误:

<h1>${exception.message}</h1>

 

关于springMVC中的静态文件

 

比如,我们在页面中引用了一个css文件:

</title>

<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css">

</head>

 

然后我们试图获取这个文件,比如在浏览器中输入文件地址(或者访问使用了该样式的jsp文件,样式不起作用):

http://localhost:8080/项目名/css/main.css

这时候会报404错误。

处理方法是,将所有这些静态文件放到某个文件夹中,然后在spring配置文件中配置,比如,将main.css文件放到/resources/css/目录下,然后在spring配置文件中做如下配置:

<!-- 将静态文件指定到某个特殊的文件夹中统一处理 -->

// location是指要处理的目录;mapping是指要处理的文件,两个星的第一个星代表

// 当前文件夹(resources)的内容第二个星表示文件夹的子文件夹的内容

// 注意要加的斜杠

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

<bean name="/welcome.html" class="zttc.itat.controller.WelcomeController"></bean>

 

文件上传:

 

文件上传现在一般都用Apache的上传包:

commons-fileupload-1.2.2-bin

commons-io-2.1

 

jsp页面:

<sf:form method="post" modelAttribute="user"

                                   enctype="multipart/form-data">

// path=”usernama”就相当于是:name=”username”,会自动使用user.Set方法

    Username:<sf:input path="username"/>

<sf:errors path="username"/><br/>

    Password:<sf:password path="password"/>

<sf:errors path="password"/><br/>

    Nickname:<sf:input path="nickname"/><br/>

    Email:<sf:input path="email"/>

<sf:errors path="email"/><br/>

    Attach:<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

     <input type="submit" value="添加用户"/>

</sf:form>

 

如果想上传文件就必须在spring配置文件中配置MultipartResolver视图:

    <!-- 设置multipartResolver才能完成文件上传 -->

    <bean id="multipartResolver"class="org.springframework.

web.multipart.commons.CommonsMultipartResolver">

       <property name="maxUploadSize" value="5000000"></property>

    </bean>

 

 

Controller

 

    //注意:我们上传一个文件的时候参数只要写MultipartFile attachs

    //这样上传的文件自动和attachs做匹配;

    //但是如果是上传多个文件就要用数组MultipartFile[]attachs,这时候文件

    //就不能自动匹配了,解决方法是下面的写法:

@RequestMapping(value="/add",method=RequestMethod.POST)

    public String add(@Validated User user,BindingResult br,

@RequestParam("attachs")MultipartFile[] attachs,

HttpServletRequest req) throws IOException {

//一定要紧跟Validate之后写验证结果类

       if(br.hasErrors()) {

           //如果有错误直接跳转到add视图

           return "user/add";

       }

       String realpath = req.getSession().getServletContext().

getRealPath("/resources/upload");

       System.out.println(realpath);

       for(MultipartFile attach:attachs) {

           //如果多个输入框有的没输入,即有空文件的情况

           if(attach.isEmpty()) continue;

           File f = new File(realpath+"/"+attach

.getOriginalFilename());

           FileUtils.copyInputStreamToFile(attach

.getInputStream(),f);

       }

       users.put(user.getUsername(), user);

       return "redirect:/user/users";

    }

 

Controller中返回JSON

 

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

public String show(@PathVariable String username,Model model) {

    model.addAttribute(users.get(username));

    return "user/show";

}

 

//1)若要控制返回某个值的话,则要使用@ReponseBody

//2params=”par”的意思是,若要访问show1,那么必须要有一个参数为par

// 如果没有参数就访问上面的这个show

@RequestMapping(value="/{username}",

method=RequestMethod.GET,params="par")

@ResponseBody

public User show1(@PathVariable String username) {

    return users.get(username);

}

【说明】:

访问第一个show的方式:http://localhost:8080/项目名/user/jack/

访问第二个show的方式:http://localhost:8080/项目名/user/jack?par

(这样直接访问的话,会报406错误,缺少http头)

如果想返回一个json,首先导入转json的开发包:jackson-all-1.9.4.jar

然后再访问第二个showhttp://localhost:8080/项目名/user/jack?par

这样就直接将user转成json格式显示。

 

 

 

 

用户管理

 

l  新建项目

l  拷贝jar包(Springdist目录下的jar包,Apachelog4jcommons-loggin

commons-dbcpcommons-collectionscommons-pool

 

 

 

 

==讲解:JavaScript

 

 

简介与JS基础

推荐jsJavaScript高级程序设计(第二版)》(美.泽卡斯)

 

js用来负责页面的交互,但是实际开发中一般使用js框架,比如:Prototype.js(用的不多)、Jquery

 

【数据类型】:

l  只有一个类型:varjs是动态语言(动态语言比如PythonRuby,它们都是没有变量类型的,而且,是一行一行解释执行的;静态语言如JavaC有变量类型,且编译执行的)。

 

l  函数内部没有用var声明的变量会被当做全局变量,且会覆盖外面的同名变量(注意执行到这个变量时候,这个全局变量才会被创建);

 

【最佳实践】:

函数内部定义的变量一定要加上var

 

l  因为js中变量的是不分“类型”的,如果想知道改变量的具体类型的话可以使用

typeof a来查看:alert ( typeof a )

 

JS中常用的数据类型有:

Number(如1010.6都是Number类型);

String

Array

Date

Math

 

l  类型转换:

var a = “11”;

alert()

 

 

强制类型转换和数据类型

【例1】:

var a = 11;

alert(a+1);//输出12

 

【例2】:

var b = "11";

//将字符串转成数字

alert( Number(a) + 1 )//输出111

 

【例3】:

var c = "hello";

//若将字符串转成数字会提示NaN

alert( Number(c) );//提示框上显示NaN

 

【例=4】:

var d = "12px";

//使用parseInt可以将字符串开头的几个数字转换成int

//但是,如果开头不是数字就会提示NaN

alert(parseInt(12)+1);//显示13

 

【例5】:

var e = ["a","b","c",1, 2, 3];

//对于数组而言,显示的类型结果就是object不会显示、Array

alert(typeof a);//提示object

alert(e instanceof Array)//判断e是否是数组

function fn1(){}

alert(typeof fn1)//提示function

 

【例6】:

Ø  6.1

//布尔类型:在js中非0就是true

//特殊情况:NaNundefined0false

//0false还是true

alert(!!0);//提示false

alert(!!NaN);//提示false

//NaN实例运用:比如我们点击一次按钮就让size1

var size = "";

if(!parseInt(size)){//不能转

}else{//是数字

}

Ø  6.2

// 当一个变量没有定义值的时候,它是undefined

var size;//undefined

alert(!!size);//提示false

alert(size + 1)//提示NaN(因为size没有类型)

alert(size + "1")//提示undefined

 

 

定义对象

 

【例1】:

js是使用function来定义对象的。

 

Ø  1.1先说说js中的函数定义:

 

function的定义方式:

方式一:

// 以下函数的内存模型是:定义了function后就在堆区分了一块内存:function(){alert("x")}

// 然后栈区有一个变量x指向堆区的这个函数

var x = function(){

    alert("x");

}

x();//此时x就是一个function,这行代码执行后弹出x

 

方式二:

//以下函数的内存模型是:在堆去分配了一块区域存储:alert("fn");

//栈区有一个变量fn指向了该堆区的这个内存区域

//定义y=fn();表示y也指向堆中的内存区

function fn(){

    alert("fn");

    return "100";

}

var y = fn;

var z = fn();//fn()执行的返回值赋给z

alert(y);//显示函数代码的内容

alert(z);//显示100

//以下两种方式都可以完成对函数fn的调用

//两种方式是一样的

y();

fn();

 

Ø  1.2 定义对象

//使用function定义Person对象(也就是类),注意参数不要写类型

//是用this为类定义属性:this.aaa(为类定义了aaa属性)

function Persion(name, age){

    //Person定义了属性:name,age,address

    this.name = name;

    this.age = age;

    this.address = "NewYork";

   

    //在类的内部定义方法:要用this,否则就不是类的变量,下面这个say()就错了

    function say(){

       alert("hello");

    }

    //同理,如果在类的内部定义 var n = 10,外部的p1是访问不到的

    var n = 10;//外部的p1不能访问到n,应该用this.n = 10,它是作为类的局部变量

   

    //给该类添加方法:

    //js的函数有一个问题,就是多个对象的实例都会有自己的函数内存空间可以

    //通过Prototype解决)

    this.say1 = function(){

       alert("say1");

    }

   

}

var p1 = new Person("Jack",18);//创建了对象p1

alert(p1.name);

alert(typeof p1);//提示object

alert(p1 instanceof Person);//true

alert(p1.say());//错误!提示say不是一个函数

alert(p1.say1());//正确!

//访问属性的方式除了上面的方式外,还可以用以下方式:

alert(p1["name"]+" | "+p1["address"]);

 

//js中可以通过for in来遍历对象的属性:

for(var a in p1){

    alert(a);//依次打印:name,age,address,say1

    alert(p1.a);//这个不会打印属性值的,提示undefined

    alert(p1[a]);//这个可以讲属性值打印出来(函数会将打印代码)

}

 

 

 

常用对象

Date(可以参照帮助文档查看详细)

【例】:

var d = new Date();

//打印标准的日期格式

document.write(d);

//打印格式化的日期(js中月是从0开始的)

document.write(d.getFullYear()+"年"+(d.getMonth()+1)+"月"+d.getDate()+"日 星期"+d.getDay());

 

String

(略:查看文档)

 

Array

js中的Array就是java中的list和stack的结合。

 

【例1】数组的定义方式:

 

//方式1

var a = new Array();

a.push("dog");

 

//方式2

a = new Array(11,22,33,44,"dog","cat");

alert(a);//打印数组中的内容

 

//方式3(用得较多):

a = [1,2,3,"dog","cat"];

//join:

alert(a);//打印a的内容(逗号分隔)

alert(a.join("---"));//打印a的内容(---分隔)

alert(a.sort());//sort只会通过字符串来排序

 

 

简单的事件处理

【例1】:点击事件

//调用jsclick方法,参数this代表当前的这个标签

<input type="button" value="按钮" onclick="click(this)">

 

【例2】:鼠标事件:

<script type="text/javascript">

function mouseD(obj) {

    //设置这个对象的颜色,在js中设置文本的样式均通过xx.style.样式名称

    obj.style.color = "#f00";

//若使用代码来设置样式若在css中通过-表示的都用驼峰标识

//font-size-->fontSize

    obj.style.fontSize = "18px";

}

function outD(obj) {

    obj.style.color = "#000";

    obj.style.fontSize = "16px";

}

</script>

 

<div onmouseover="mouseD(this)" onmouseout="outD(this)">

鼠标移动上来试试

</div>

 

【例2】:with

 

<script type="text/javascript">

with(document) {

    //此时花括号中的所有代码都是基于document作为根对象,当使用write(xxx)就等于document.write(xxx);

    write("aaa<br/>");

    write("bbb<br/>");

    write("ccc<br/>");

    //使用alert也是允许,会先找document中的alert,找不到再从上一级找

    alert("abc");

}

</script>

 

【例3】实例(点击文字让文字一直变大,大于30px时,就变小)(不重要!)

    <script type="text/javascript">

    var big = true;

    function bigger(obj) {

       var cs = parseInt(obj.style.fontSize);

       if(cs) {

           if(cs>=30) {

              big = false;

              obj.innerHTML = "点击变小";

           }

           if(cs<=12) {

              big = true;

              obj.innerHTML = "点击变大";

           }

           if(big) {

              cs+=2;

           } else {

              cs-=2;

           }

           obj.style.fontSize = cs+"px";

       } else {

           obj.style.fontSize = "14px";

       }

    }

    </script>

</head>

<body>

    <div onclick="bigger(this)" style="cursor: pointer">点击变大</div>

</body>

 

【例4】:setTimeout点击后文字自动变大,然后点击一个按钮就停止(不重要!)

l  变大一次

<script type="text/javascript">

function cd() {

    //3秒后执行bigger这个函数,setTimeout的意思即间隔一段时间就执行某个函数

    //setTimeout仅仅只会执行一次,如果希望重复执行,需要使用setInterval

    setTimeout("bigger()",3000);

}

function bigger() {

    //获取html中节点的idtxt的节点

    var node = document.getElementById("txt");

    node.style.fontSize = "200px";

}

</script>

</head>

<body>

    <div id="txt" style="cursor: pointer">开始</div>

    <div onclick="cd()">点击开始操作</div>

</body>

 

l  连续变大

<script type="text/javascript">

var timeId;

function cd() {

    //setInterval表示每隔一段时间就调用一次函数

    timeId = setInterval("bigger()",500);

}

function sd(){

    clearInterval(timeId);

}

function bigger() {

    //获取html中节点的idtxt的节点

    var node = document.getElementById("txt");

    var size = parseInt(node.style.fontSize);

    if(size) {

       size+=10;

    } else {

       size = "14";

    }

    node.style.fontSize = size+"px";

}

</script>

</head>

<body>

    <div id="txt">开始</div>

    <div onclick="cd()" style="cursor: pointer">点击开始操作</div>

    <div onclick="sd()" style="cursor: pointer">停止操作</div>

</body>

 

 

【例5history.back()

返回到上一个页面,在onclick函数中直接调用这个函数即可,这种返回会传递表单中的数据。

 

【例6】:

 

alert()相当于window.alert();

window中还有:window.clolse();

window.print:打印

window.open:打开一个新窗口(比较常用!)

  <a href="#"

onclick="window.open('test02.html','aaa',

'width=300,height=300,resizable=0')">

test02

</a>

    <a href="#"

onclick="window.open('test03.html','aaa',

'width=400,height=400,resizable=0')">

test03

</a>

【注意】:

l  如果两个<a>的第二个参数(如aaa)相同的话会打开同一个窗口,不同的话,会分别打开自己的窗口。

l  这种弹出窗口会被屏蔽掉;

l  如果想要一个弹出窗口的效果的话现在一般使用div来实现(类似这样的思想)。

 

 

【例7】:弹出窗口输入祝福语,点击窗口上的输入按钮耨,将输入的内容显示在网页上,并关闭当前窗口。

 

<a href="#" onclick="window.open('bless.html','aaa','width=600,height=300')">输入你祝福语</a>

 

bless.html

<script type="text/javascript">

function bless() {

    //获取输入的祝福语

    var mb = document.getElementById("mb").value;

    //获取父类窗口

    var p = window.opener;

    //获取父类窗口中的idblessdiv

    var pd = p.document.getElementById("bless");

    //设置pd的值

    pd.innerHTML = mb;

    //关闭当前窗口

    window.close();

}

</script>

</head>

<body>

    输入祝福语:<input type="text" size="40" id="mb"/><input type="button" onclick="bless()" value="输入" />

</body>

 

JSDOM模型

 

节点

节点的类型有:

元素节点(节点类型1):

属性节点(节点类型2):

文本节点(节点类型3):

注释节点(节点类型8):

文档节点(节点类型9):

 

    <script type="text/javascript">

    function getAllH1() {

       var ah = document.getElementsByTagName("h1");

       for(var i=0;i<ah.length;i++) {

           //获取节点中的文本内容

           alert(ah[i].innerHTML);

           //获取节点的名称

           alert(ah[i].nodeName);

           //获取节点的类型

           alert(ah[i].nodeType);

           //获取节点中的值:节点中的值只是在针对文本节点时有用

           alert(ah[i].nodeValue);

           //获取某个节点的文本节点

           alert(ah[i].firstChild.nodeType);

           //获取某个文本节点的值,对于IEfirefox而言文本的空格不一致,

//对于IE而言,仅仅只会把换行加入空白,但是FF而言就是全部空格

           //所以在获取文本节点值的时候,需要把空格去除

           alert("|"+ah[i].firstChild.nodeValue+"|");

       }

    }

   

    function getConH2() {

       var con = document.getElementById("content");

       var h2 = con.getElementsByTagName("h2");

       //得到的h2元素是一个数组

       alert(h2[0].innerHTML);//显示h2中的所有内容包括<span>

       //通过h2这个节点来找到h3span的值

       //1、找到父节点

       var pn = h2[0].parentNode;

       //2、通过父节点找到名称为h3的节点

       var h3 = pn.getElementsByTagName("h3")[0];

       //3、通过h3找到span

       var s = h3.getElementsByTagName("span")[0];

       alert(s.innerHTML);

    }

    </script>

</head>

<body>

    <div id="content">   //元素节点

<h1>

            aaaaa1        //文本节点

            <span>aaaaassss</span>

</h1>

       <h2>

           bbbbbbbbb1

           <span>bbbbbssss</span>

        </h2>

       <h3>

           cccccccc1

           <span>ccccccssss</span>

       </h3>

    </div>

    <h1>

       hhhhhhhaaaaa1

       <span>hhhhhhhhhhhaaaaassss</span>

    </h1>

    <h2>

       hhhhhhhhhhbbbbbbbbb1

       <span>hhhhhhbbbbbssss</span>

    </h2>

    <h3>

       hhhhhhhhhhhcccccccc1

       <span>hhhhhhhhhhhhhccccccssss</span>

    </h3>

    <input type="button" value="获取所有的h1" onclick="getAllH1()" />

    <input type="button" value="获取contenth2" onclick="getConH2()" />

</body>

 

 

innerHTML是对元素节点使用的,如上面的<span>aaaaassss</span>

 

事件

【说明】:

前面的我们的事件都是放在html代码里的,这样的最大的问题是,相当于将js代码和html代码融合在了一起了,这样不好。

 

【例1】:

<body>

    <ul>

       <li>aaaaaaaaaaaaaa</li>

       <li>bbbbbbbbbbbbbb</li>

       <li>cccccccccccccc</li>

       <li>dddddddddddddd</li>

       <li>eeeeeeeeeeeeee</li>

    </ul>

    <input type="button" value="点击一下" id="btn"/>

    <script type="text/javascript">

       var btn = document.getElementById("btn");

       //可以通过如下方式来绑定事件,就可以完成事件和html的解耦合操作

       //在开发中通常都是使用这种方式来绑定事件

       //这个事件的处理函数中默认有一个event的参数,用来获取相应的事件信息

       btn.onclick = function(event) {

       //注意:对于IE不会自动传递event参数,IE需通过window.event获取事件

       //但是FF却不支持window.event,所以通常使用如下方式解决

//如果event为空(undefinedfalse)就会取window.event

           event = event||window.event;

           alert(event.type);//类型为click,显示click

           //this就表示这个按钮对象

           alert(this.value);

       }

    </script>

</body>

 

【例2】:js中获取html中的元素的时候,有时候html还没加载完,这时候就获取不到html中的元素,解决方法是可以在html(即Document加载完成后再执行js

方法是:

body标签上加上<body onload=”init()”>或者将这个函数也写到js中:window.onload=init

给按钮添加点击事件:

<script type="text/javascript">

//当窗口加载完毕之后才执行init方法,这样可以省略body中的onload

//所以如果希望使用如下的事件定义方式,需要先完成html信息的加载

window.onload = init;

/*此时init方法是在bodyonload之后执行,就等于在所有的页面标签加载完毕之后才执行init,此时节点就存在了*/

function init() {

    alert("abc");

    var btn = document.getElementById("btn");

/*如下绑定方式带来最大的一个问题是如果将该段代码放到head中定义,在执行到绑定事件的时候并没有把html的标签渲染出来,所以通过DOM得到的节点都是null的,就报错了,解决这个问题的方法是在页面加载完成之后才调用以下这段代码可以在body中的通过onload事件来处理*/

    btn.onclick = function(event) {

       event = event||window.event;

       alert(event.type);

       alert(this.value);

    }

}

</script>

</head>

<body>

    <input type="button" value="点击一下" id="btn"/>

</body>

 

【例3】:给li添加鼠标事件(移上和移开改变颜色):

    <script type="text/javascript">

    window.onload = init;

    function init() {

       //1、找到所有的li

       var lis = document.getElementsByTagName("li");

       //2、为所有的li绑定事件

       for(var i=0;i<lis.length;i++) {

           lis[i].onmouseover = changeColor;

           lis[i].onmouseout = reColor;

       }

    }

   

    function changeColor() {

       this.style.color = "#f00";

    }

    function reColor() {

       this.style.color = "#000";

    }

    </script>

</head>

<body>

    <ul>

       <li>aaaaaaaaaaaaaa</li>

       <li>bbbbbbbbbbbbbb</li>

       <li>cccccccccccccc</li>

       <li>dddddddddddddd</li>

       <li>eeeeeeeeeeeeee</li>

    </ul>

</body>

 

【例3】:显示下啦菜单:

    <style type="text/css">

       *{

           padding: 0px;

           margin: 0px;

           font-size:12px;

       }

       #menu_bar {

           position: absolute;

           left:50px;

           top:50px;

       }

       dl.menu {

           float:left;

           width:120px;

       }

       dl.menu dt,dl.menu dd {

           height:30px;

           background: #339;

           color:#fff;

           border-right:#ffffff 1px solid;

           text-align: center;

       }

       dl.menu dt span {

           position: relative;

           top:6px;

       }

       dl.menu dd {

           background: #911;

           color:#fff;

           border-bottom:#ffffff 1px solid;

           display: none;

       }

       dl.menu dd a {

           position: relative;

           top:6px;

       }

       a.menu_href:link,a.menu_href:visited {

           text-decoration: none;

           color:#fff;

       }

       dl.menu dd:hover {

           background:#393;

           cursor:pointer;

       }

    </style>

<script type="text/javascript">

window.onload = function(){

    //1、找到所有dl

    var dls = document.getElementById("menu_bar").getElementsByTagName("dl");

    for(var i=0;i<dls.length;i++) {

       //为所有dl绑定事件

       dls[i].onmouseover = show;

       dls[i].onmouseout = hidden;

    }

};

function show() {

/*这种方式存在事件冒泡(移到dd上会将事件冒泡到dl,触发移出移进事件),会多次调用,严重影响效率(只要移到dd上就会执行show方法),JQUery等框架可解决这样的问题*/

    var c = document.getElementById("content");

    c.innerHTML+="2";

    //1、找到dd

    var dds = this.getElementsByTagName("dd");

    for(var i=0;i<dds.length;i++) {

       dds[i].style.display = "block";

    }

}

function hidden() {

    var c = document.getElementById("content");

    c.innerHTML+="1";

    //1、找到dd

    var dds = this.getElementsByTagName("dd");

    for(var i=0;i<dds.length;i++) {

       dds[i].style.display = "none";

    }

}

</script>

</head>

<body>

    <div id="content"></div>

    <div id="menu_bar">

       <dl class="menu">

           <dt><span>文件管理</span></dt>

           <dd><a href="#" class="menu_href">打开文件</a></dd>

           <dd><a href="#" class="menu_href">删除文件</a></dd>

           <dd><a href="#" class="menu_href">存储文件</a></dd>

           <dd><a href="#" class="menu_href">关闭文件</a></dd>

           <dd><a href="#" class="menu_href">退出</a></dd>

       </dl>

    </div>

</body>

 

 

深入讲解函数(非常重要)

 

l  后面如果我们想熟练的用好js框架,就必须将js的对象掌握清楚,否则不仅用不好,而且,出了错也不知道如何解决。

 

函数深入了解

【我的小结】:

函数一般情况下就可以看成是一个对象。函数和对象的区别是:

对象是通过引用的指向完成对象的赋值的,函数是通过对象的拷贝来完成对象的赋值的。

function可以用来定义一个对象(对象的类型也是Function类型的)。

 

函数是一个非常特殊的对象,是Function类的实例,在内存中存储的操作是通过一对键值对来存储的;

l  函数名是“”,其指向了内存中的一个对象,该对象是Function类型的对象,对象的内容是“”,也就是函数的内容。

//函数的定义方式一:

function fn1() {

       alert("fn1");

    }

    alert(typeof fn1); //Function

    //由于函数是一个对象,所以可以通过如下方式定义

    //以下是通过函数的拷贝来完成赋值,两个引用并没有指向同一个对象

    var fn2 = fn1;//fn1指向的函数在内存中拷贝一份,让fn2指向它

    //函数的定义方式二:

fn2();//调用fn2

    fn1 = function() {

       alert("fnn1");

    }

 

函数重载

【例】:

<script type="text/javascript">

function sum(num1,num2) {

     return num1+num2;

}

var sum = function(num1,num2) {

    return num1+num2;

}

 

function sum(num1) {

     return num1+100;

}

var sum = function(num1) {//sum被重新赋值,重新指向了新的内存空间

    return num1+100;

}

//以下两个都显示19,原因是,如果将上面的函数写成标志了删除线的形式后就会明显看

//到原因了(分析内存结构)

alert(sum(19));      //打印119

alert(sum(19,20));   //打印119

</script>

 

【提示】:

l  函数的参数和调用没有关系,如果函数只有一个参数,但是却传入了两个参数,仅仅只会匹配一个,所以在js中函数不存在重载

 

因为函数就是对象,所以函数还有如下定义方式:

/*

  定义方式(三)等于这样定义了一个函数:

  function fn(num1,num2){

      alert(num1+num2);

  }

  所以通过以下的例子,充分的说明函数就是一个对象

 */

//函数有如下一种定义方式(三)

var fn = new Function("num1","num2","alert('fun:'+(num1+num2))");

fn(12,22);

 

 

函数作为参数传值(重要!要理解!)

理解框架时非常有用!

/*由于函数是对象,所以可以直接把函数通过参数传递进来*/

    function callFun(fun,arg) {

       //第一个参数就是函数对象

       return fun(arg);

    }

    function say(str) {

       alert("hello "+str);

    }

    //调用了say函数

    callFun(say,"Leon");//效果和say(“Leon”)一样

 

函数作为返回值

    function fn1(arg) {

       /*此时返回的是一个函数对象*/

       var rel = function(num) {

           return arg+num;

       }

       return rel;

    }

    //此时f是一个函数对象,可以完成调用

    var f = fn1(20); //这个返回的是fn1中定义的函数

    alert(f(20)); //ffn1中定义的函数,所以这句话是向fn1中的这个函数传参数

    alert(f(11)); //同上

 

 

深入理解sort方法

【例1】:按照数字排序:

sort默认是按字符串来排序的。

    /*根据数字来进行排序的函数*/

    function sortByNum(a,b) {

        return parseInt(a)-parseInt(b);

     }

     alert("11"+1);

     //当进行减法的时候,会自动完成转换

     alert("11"-1);

     var as = [1,2,"11px",33,"12px",190];

     as.sort();//默认是按照字符串来排序的

     //对于js而言,默认是按照字符串来进行排序的

     as.sort(sortByNum); //使用函数来做数字排序

     alert(as);

 

【例2】:按对象排序

2.1 使用一般方式排序

    //测试根据对象排序

    function Person(name,age) {

       this.name = name;

       this.age = age;

    }

    var p1 = new Person("Leno",39);

    var p2 = new Person("John",23);

    var p3 = new Person("Ada",41);

    var ps = [p1,p2,p3];

    ps.sort(sortByAge);

    //取得属性值的方法p1.name,p1["name"]

/*使用以下方法来处理排序,带来的问题是需要为每一个属性都设置一个函数,显然不灵活但是如果通过函数的返回值调用就不一样了(见2.2*/

     function sortByName(obj1,obj2) {

        if(obj1.name>obj2.name) return 1;

        else if(obj1.name==obj2.name) return 0;

        else return -1;

     }

     function sortByAge(obj1,obj2) {

        return obj1.age-obj2.age;

     }

2.2 使用返回函数的方式

    //测试根据对象排序

    function Person(name,age) {

       this.name = name;

       this.age = age;

    }

    var p1 = new Person("Leno",39);

    var p2 = new Person("John",23);

    var p3 = new Person("Ada",41);

    var ps = [p1,p2,p3];

    ps.sort(sortByProperty("age"))

    function sortByProperty(propertyName) {

       var sortFun = function(obj1,obj2) {

           if(obj1[propertyName]>obj2[propertyName]) return 1;

           else if(obj1[propertyName]==obj2[propertyName])return 0;

           else return -1;

       }

       return sortFun;

    }

    function show() {

       var p = document.getElementById("person");

       for(var i=0;i<ps.length;i++) {

           p.innerHTML+=ps[i].name+","+ps[i].age+"<br/>";

       }

    }

    show();

 

 

函数的Argumentsthis

函数对象中有一个内置的属性,arguments,它是一个数组,它存储了向函数传递来的所有参数。

比如,函数function fn1(num){ alert(num) }只有一个参数,但是我们可以向fn1传递3个参数,当然了,在alert中显示一个值,但是我们在fn1内部可以通过arguments这个数组获取到传递的这3个值。

【例1】:

    function say(num) {

/*在函数对象中有一个属性叫做arguments,通过这个属性可以获取相应的参数值,这个属性是一个数组,其实就是传递进来的参数*/

       alert(arguments.length);

       for(var i=0;i<arguments.length;i++) {

           alert(arguments[i]);

       }

       alert(num);

    }

 

【例2】:

arguments这个对象中有一个callee方法,arguments.callee(arg)可以做反向的调用。

比如我们平时写求阶乘的函数是这么写:

//求阶乘的函

function fac(num){

    if(num<=1){

        return 1;

    }else{

       return num*fac(num-1);

    }

}

这样写阶乘函数有一个问题,因为fac内部也用到了fac这个函数名,假如我们在其他地方使用了函数fac

var fn = fac;

然后我们可以使用fn做递归操作:

fn(5);//这时候不会有任何问题,如果在此行前让fac=null,就会报错了,可以使用arguments.callee解决,在js中通常都是使用这种方式做递归的!

 

我们使用arguments.callee可以这么写:

function factorial(num) {

    if(num<=1) return 1;

    //此时和函数名耦合在一起

    // else return num*factorial(num-1);

    //以下就实现了函数名的解耦合,在js中通常都是使用这种方式做递归

    else return num*arguments.callee(num-1);

}

 

 

详解this

我们已经知道,设置类的属性和方法需要通过this关键字来引用;但是,this关键字在调用时会根据不同的调用对象变得不同。

【例】:

var color = "red";

function showColor() {

    alert(this.color);

}

/*创建了一个类,有一个color的属性和一个show的方法*/

function Circle(color) {

    this.color = color;

    this.showColor = showColor;

}

 

var c = new Circle("yellow");

//使用c来调用showColor方法,等于调用了showColor()方法

//此时的thisc,所以color就是yellow

c.showColor();    //yellow

//此时调用的对象等于是window,showColorthis就是window,所以就会找windowcolor

showColor();      //redhtml默认都有with(window);

 

函数的属性

函数有两个非常有用的属性lengthprototype

length           是指该函数期望传进来的参数的个数(就是括号中的参数个数);

prototype     (后面讲)

 

函数还有两个比较有趣的方法callapply,他们都可以通过函数名称来调用函数。

call:第一个参数是上下文,后面的参数是不同函数参数;

apply:都有两个参数:第一个是调用的上下文、第二个是参数数组(可以将arguments穿进去)。

【提示】:

callapply的区别就是第二个参数:

call(this,arg1,arg2,…)

apply(this,arguments)或者apply(this,[arg1, arg2, arg3])

【例】:

function sum(num1,num2) {

    return num1+num2;

}

 

function callSum1(num1,num2) {

    //使用sum这个函数来完成一次调用,调用的参数就是callSum1这个函数的参数

    //apply的第二个参数表示一组参数数组

    //这里的this代表window(表示window中的callSum1函数)

    return sum.apply(this,arguments);

}

 

function callSum2(num1,num2) {

    //关键就是第二个参数是数组

    return sum.apply(this,[num1,num2]);

}

alert(callSum1(12,22));

alert(callSum2(22,32));

 

function callSum3(num1,num2) {

    //call是通过参数列表来完成传递,其他和apply没有任何区别

    return sum.call(this,num1,num2);

}

 

【例】:call的作用:

 

var color = "red";

function showColor() {

    alert(this.color);

}

/*创建了一个类,有一个color的属性和一个show的方法*/

function Circle(color) {

    this.color = color;

}

 

var c = new Circle("yellow");

 

showColor.call(this);//使用上下文来调用showColor,结果是red

showColor.call(c);//上下文对象是c,结果就是yellow

/*通过以上发现,使用callapply之后,对象中可以不需要定义方法了,这就是callapply的一种运用*/

 

面向对象

 

简单对象的创建

【例】:

/*js中并不存在类,所以可以直接通过Object来创建对象但是使用如下方式创建,带来最大的问题是,由于没有类的约束无法实现对象的重复利用,并且没有一种约定,在操作时会带来问题*/

var person = new Object();

person.name = "Leon";

person.age = 33;

person.say = function() {

    alert(this.name+","+this.age);

}

【提示】:

l  除了上面注释中提到的问题外,若我们在网络传递对象的话,用上面的方法也是不可行的,在网络中传递数据的话,传递字符串是最好的。

l  如果在网络中传递对象我们可以将对象用xml的格式传递,但是用xml传递的话,会传多余的字符串,所以我们可以另外一种数据格式Json格式来传递对象。

 

Json(重要!)

JavaScript Simple Object Notation

Json就是js的对象,但是其省去了xml的标签,而是使用花括号(一个花括号就是一个对象):

 

【例1】:Json的基本使用

/*json的意思就是javascript simple object notationjson就是js的对象,但是它省去了xml中标签,而是通过{}来完成对象的说明*/

var person = {

    name:"张三",//通过属性名:属性值来表示,不同的属性通过,来间隔

    age:23,

    say:function() {

       alert(this.name+","+this.age);

    }//最后一个属性之后不能有逗号

}

person.say();

/**

 * 通过json依然可以创建对象数组,创建的方式和js的数组一样

 */

var ps = [

    {name:"Leon",age:22},

    {name:"Ada",age:33}

];

for(var i=0;i<ps.length;i++) {

    alert(ps[i].name);

}

/*创建一组用户,用户的属性有

 * name:string,age:int,friends:array

 * List<Person> ps = new ArrayList<Person>();

 * ps.add(new Person("Leon",22,["Ada","Alice"]));

 * ps.add(new Person("John",33,["Ada","Chris"]));

 * ps转换为json

 */

ps = [

    {

       name:"Leon",

       age:22,

       friends:["Ada","Alice"]

    },

    {

       name:"John",

       age:33,

       friends:["Ada","Chris"]

    }

];

 

 

 

Js的重用和封装

 

工厂方法解决重用的问题

【例】:

/*通过工厂的方式来创建Person对象在createPerson中创建一个对象然后为这个对象设置相应的属性和方法之后返回这个对象*/

function createPerson(name,age) {

    var o = new Object();

    o.name = name;

    o.age = age;

    o.say = function() {

       alert(this.name+","+this.age);

    }

    return o;

}

/*使用工厂的方式,虽然有效的解决了类的问题,但是依然存在另外一个问题我们无法检测对象p1p2的数据类型*/

var p1 = createPerson("Leon",22);

var p2 = createPerson("Ada",33);

p1.say();

p2.say();

 

使用构造函数

【例】:

/*通过构造函数的方式创建,和基于工厂的创建类似最大的区别就是函数的名称就是类的名称,按照java的约定,第一个字母大写。使用构造函数创建时,在函数内部是通过this关键字来完成属性的定义*/

function Person(name,age) {

    this.name = name;

    this.age = age;

    //以下方式带来的问题是所有的对象都会为该行为分配空间

    // this.say = function() {

       // alert(this.name+","+this.age);

    // }

    this.say = say;

}

/*将行为设置为全局的行为,如果将所有的方法都设计为全局函数的时候,这个函数就可以被window调用,此时就破坏对象的封装性而且如果某个对象有大量的方法,就会导致整个代码中充斥着大量的全局函数,这样将不利于开发*/

function say() {

    alert(this.name+","+this.age);

}

/*通过new Person来创建对象*/

var p1 = new Person("Leon",22);

var p2 = new Person("Ada",32);

p1.say(); p2.say();

/*使用构造函数的方式可以通过以下方式来检测对象的类型*/

alert(p1 instanceof Person);

/**

 * 使用构造函数创建所带来的第一个问题就是每一个对象中

 * 都会存在一个方法的拷贝,如果对象的行为很多的话

 * 空间的占用率就会大大增加

 * 可以将函数放到全局变量中定义,这样可以让类中的行为指向

 * 同一个函数

 */

alert(p1.say==p2.say);

 

【提示】:

java中,类的函数是在栈中分配的,调用的时候才分配空间。

js中的难点:函数、Prototype、闭包。

 

原型设计(Prototype

 

【例】:

/*以下演示了通过原型的创建方式,使用基于原型的创建可以将属性和方法设置为Person专有的,不能再通过window来调用*/

function Person(){ }

Person.prototype.name = "Leon";

Person.prototype.age = 23;

Person.prototype.say = function() {

    alert(this.name+","+this.age);

}

var p1 = new Person();

p1.say();

//通过window没有办法调用say方法,如此就完成了封装

// say();

 

 

原型设计-基于模型

 

//第一种状态:新建对象

function Person(){

   

}

 

这个执行完后,内存中分配了两块空间:

Person对象和Person的原型对象(Person prototype

l  原型对象中的construct属性指向了Person函数(这就是为什么可以使用new Person()

Personprototype属性指向了Person的原型对象

 

//第二种状态:为原型设置属性值

Person.prototype.name = "Leon";

Person.prototype.age = 23;

Person.prototype.say = function() {

    alert(this.name+","+this.age);

}

以上那些属性都是保存在Person的原型对象中。

 

//第三中状态:创建对象的实例:

//创建了一个对象之后,对象中会有一个_prop_属性指向原型

//在使用时如果在对象内部没有找到属性(比如p1.say())会去原型中找,完成say

//的调用,_prop_属性是隐藏的

var p1 = new Person();

 

//以下方法可以检测出p1是否有_prop_指向Person的原型

alert(Person.prototype.isPrototypeOf(p1));

 

//第四种状态:在自己的空间定义属性值

 

var p2 = new Person();

//在自己的空间中定义一个属性,此时,p2中也会有一个_prop_指向Person原型对象,

//定义的属性不会替换原型中的属性是在自己的空间存储了name=Ada,调用say方法

//查找name属性时,会先在自己空间查找name,找到后就不会到原型对象中查找了

p2.name = "Ada";//

 

//检测某个对象是否是某个函数的原型

alert(Person.prototype.isPrototypeOf(p2));

 

//检测某个对象的constructor是否指向Person

alert(p1.constructor==Person);

 

//检测某个属性是否是自己的属性

alert(p1.hasOwnProperty("name"));//falsep1自己的空间中没有值

alert(p2.hasOwnProperty("name"));//true,p2在自己的空间中设置了name属性

 

delete p2.name;

p2.say();

alert(p2.hasOwnProperty("name"));//由于已经删除了,所以是false

 

//检测某个对象在原型或者自己中是否包含有某个属性,通过in检测

alert("name" in p1);//true

alert("name" in p2);//true

alert("address" in p1);//在原型和自己的空间中都没有,false

 

alert(hasPrototypeProperty(p1,"name"));//true

alert(hasPrototypeProperty(p2,"name"));//false

 

/*可以通过如下方法检测某个属性是否在原型中存在*/

function hasPrototypeProperty(obj,prop) {

    //不在自己的空间中但是存在于原型对象中

    return ((!obj.hasOwnProperty(prop))&&(prop in obj))

}

 

原型设计-基于Json(重写原型)

 

l  在上一节“原型设计-基于模型”中,我们发现那种写法非常的麻烦,我们可以通过json格式来编写。

 

【例】:

/*使用如下方式来编写代码,当属性和方法特别多时,编写起来不是很方便,可以通过json的格式来编写*/

Person.prototype.name = "Leon";

Person.prototype.age = 23;

Person.prototype.say = function() {

    alert(this.name+","+this.age);

}

/*以下方式将会重写原型由于原型重写,而且没有通过Person.prototype来指定此时的constructor不会再指向Person而是指向Object如果constructor真的比较重要,可以在json中说明原型的指向*/

Person.prototype = {

    constructor:Person,//手动指定constructor

    name:"Leon",

    age:23,

    say:function() {

       alert(this.name+","+this.age);

    }

}

var p1 = new Person();

p1.say();

alert(p1.constructor==Person); //手动指定constructor后输出true

 

 

【可能的问题】:

问题1

//第一种状态

function Person(){

}

var p1 = new Person();

Person.prototype.sayHi = function() {

    alert(this.name+":hi");

}

/*如果把重写放置在new Person之后,注意内存模型,见下图*/

Person.prototype = {

    constructor:Person,//手动指定constructor

    name:"Leon",

    age:23,

    say:function() {

       alert(this.name+","+this.age);

    }

}

p1.sayHi();//不会报错,但是没有this.name

 

var p2 = new Person();

p2.sayHi();//此时p2没有sayHi,所以就会报错

p2.say();

p1.say();

 

问题2:给实例添加一个属性时,可能会往原型中添加:

 

/*基于原型的创建虽然可以有效的完成封装,但是依然有一些问题

 *1、无法通过构造函数来设置属性值

 *2、当属性中有引用类型变量是,可能存在变量值重复

*/

function Person(){ }

Person.prototype = {

    constructor:Person,

    name:"Leon",

    age:30,

    friends:["Ada","Chris"],

    say:function() {

       alert(this.name+"["+this.friends+"]");

    }

}

var p1 = new Person();

p1.name = "John";

p1.say();//john[ada,chris]

//会在原型中找friends,所以Mike是在原型中增加的

p1.friends.push("Mike");//p1增加了一个朋友

var p2 = new Person();

//此时原型中就多了一个Mike,这就是原型所带来的第二个问题

p2.say();//leon[ada chris mike]

 

为了解决“问题1”、“问题2”,此处需要通过组合构造函数和原型来实现对象的创建:将属性在构造函数中定义,将方法在原型中定义,这中方式有效的结合了两者的优点,是目前常用的方式:

【问题解决方法】:

/*为了解决原型所带来的问题,此处需要通过组合构造函数和原型来实现对象的创建

 *将属性在构造函数中定义,将方法在原型中定义

 *这种有效集合了两者的优点,是目前最为常用的一种方式

 */

function Person(name,age,friends){

    //属性在构造函数中定义

    this.name = name;

    this.age = age;

    this.friends = friends;

}

Person.prototype = {

    constructor:Person,

    //方法在原型中定义

    say:function() {

       alert(this.name+"["+this.friends+"]");

    }

}

//此时所有的属性都是保存在自己的空间中的(如果方法也定义在Person中)

//那么每个实例也都将有自己的方法拷贝

var p1 = new Person("Leon",23,["Ada","Chris"]);

p1.name = "John";

p1.friends.push("Mike");//p1增加了一个朋友,但是不影响p2

p1.say();

var p2 = new Person("Ada",33,["Leon"]);

p2.say();

</script>

 

上面的“【问题解决方法】”中,是将方法定义在类的外面的,我们可以使用“动态原型”的方法,将方法放到类里面(这样就更接近Java的风格了)

 

基于组合和动态原型创建对象

 

/*为了让定义的方式更加符合java的需求,就把定义方法的原型代码放置到Person这个构造函数中*/

function Person(name,age,friends){

    //属性在构造函数中定义

    this.name = name;

    this.age = age;

    this.friends = friends;

   

    //不能使用重写的方式定义(因为每次定义的时候都会执行重写)

    Person.prototype = {

       constructor:Person,

       //方法在原型中定义

       say:function() {

           alert(this.name+"["+this.friends+"]");

       }

    }

    /*而应该使用以下方法定义:

判断Person.prototype.say是否存在,如果不存在就表示需要创建

      当存在之后就不会在创建了*/

    if(!Person.prototype.say) {

       Person.prototype.say = function() {

           alert(this.name+"["+this.friends+"]");

       }  

    }

}

 

//此时所以的属性都是保存在自己的空间中的

var p1 = new Person("Leon",23,["Ada","Chris"]);

p1.name = "John";

p1.friends.push("Mike");

p1.say();

var p2 = new Person("Ada",33,["Leon"]);

p2.say();

 

 

继承

 

方式一:基于原型链

【例1】:

【第一步:】

/*js实现继承的第一种方式是基于原型链的方式*/

function Parent() {

    this.pv = "parent";

}

Parent.prototype.pp = "ok";

Parent.prototype.showParentValue = function() {

    alert(this.pv);

}

 

function Child() {

    this.cv = "child";

}

 

【第二步:】

/*Child的原型链指向Parent对象,也就等于完成了一次继承注意内存模型*/

Child.prototype = new Parent();//原型的重写

/*如果想进行赋值之后,才进行原型链的设定,这样赋值的原型对象就会被重写掉,赋值的对象就不存在在新的原型对象中*/

 Child.prototype.showChildValue = function() {

     alert(this.cv);

 }

【第三步:】

var c = new Child();

c.showParentValue();

c.showChildValue();

alert(c.pp);

 

第一步执行完后:

 

 

 

第二步执行完后:

原型重写得到新的Child Prototype,同时,new出一个Parent(有一个_prop属性_指向Parent

)Child Prototype= new Parent(),所以Child Prototype也指向Parent,如下图

 

 

第三步执行完后:

 

 

c.showParent():先到自己的原型(Child Prototype)找,找不到就往上找(到Parent Prototype中找);c.pv也是这样。

l  ——往上一直找,找到最上层还是找不到的话,其实还会到Object类中找,Object类也有一个prototype属性指向Object Prototype原型对象。

l  同时,实际上ParentPrototype原型对象还有一个_prop_属性指向Object Prototype

 

【提示】:

原先的Child Prototype就成了无用内存了。

 

【注意】:使用原型链需要注意的问题:

注意点一、

/*当执行了下面这句话之后,意味着Child的原型又重写了这样就不存在任何的继承关系了使用原型链需要注意的第一个问题*/

 Child.prototype = {

     showChildValue:function() {

        alert(this.v);

     }

 }

 

注意点二、

一定要在原型链赋值(是指Child.prototype=new Parent())之后才能添加或者覆盖方法(这个比较好理解,因为如果在原型链赋值之前添加方法,原型链赋值相当于重写原型链,自然会把之前添加的方法覆盖掉)。

 

注意点三、

原型链赋值(是指Child.prototype=new Parent())后,可以覆盖原型链中的方法(包括父类的方法)。

/*此时完成的对父类对象的覆盖*/

Child.prototype.showParentValue = function() {

    alert("override parent");

}

 

【提示】:

原型链的缺点:

(1)       最大的缺点是,无法从子类中调用父类的构造函数,这样就没有办法把子类中的所有属性赋值到父类(也就是无法在子类中修改父类中的属性值);

(2)       var c1= new Child()  var c2=new Child(),如果c1修改了,会影响到c2

(所以,一般不会单单使用原型链来实现继承)。

 

方式二:基于函数伪造方式

 

function Parent() {

    this.color = ["red","blue"];

    this.name = "Leon";

}

 

function Child() {

    //Child中的this明显应该是指向Child的对象

    //当调用Parent方法的时候,而且this又是指向了Child

    //此时就等于在这里完成this.color = ["red","blue"]

    //也就等于在Child中有了this.color属性,这样也就变相的完成了继承

    Parent.call(this);

    //这种调用方式(即直接调用Parent()),仅仅完成函数调用,无法实现继承

    //Parent();

}

 

var c1 = new Child();

c1.color.push("green");

alert(c1.color);

var c2 = new Child();

alert(c2.color);

alert(c2.name);

 

 

闭包

 

函数的调用顺序的问题:

 

fn1();//不会报错

//不会报错,对于通过function fn()这种写法来定义的函数,永远都会被最先初始化

function fn1() {

    alert("fn1");

}

 

fn2();//报错

//使用如下方式定义函数,不会被先执行,如果在之前调用该函数就会报错

/*以下函数的定义方式是现在内存中创建了一块区域,之后通过一个fn2的变量指向这块区域,这块区域的函数开始是没有名称的 ,这种函数就叫做匿名函数*/

var fn2 = function() {

    alert("fn2");

}  

 

 

==讲解:JS设计模式

 

 

基础理论知识:

 

内置对象:

DateNumbeFunctionObject,其中,我们要掌握两个内置对象:Function和Object

 

所有的对象都有以下属性:

_proto_        Property

constructor

prototype

 

另外,所有的对象都继承了Object

 

FunctionObject都是对象,它们都有prototype属性,但是它们有区别。

Array是一个对象,具体来说他是一个Function类型的对象,它的prototype指向了Function.prototype

new Array()也是一个对象,具体来说是一个Object对象,它的prototype指向了Object.prototype

注意:“new”出来的才是一个对象

比如:

var Dog = function(){

         this.name=”jetty”;

}

这里的Dog是一个function

var dog = new Dog();

这里的dog是一个Object

 

对于函数:

var sum1=function(a,b){

         return a+b;

}

sum2=sum1;

alert(sum2(1,3));    //打印4

 

sum2=function(a){

         alert(a);

}

alert(sum1(3));      //注意这里:这里弹出NaN

原因:

var sum1=function(a,b){return a+b};

等价于:

var sum1= new Function(“a”,”b”,”return a+b”);

 

其实,function的创建实际上也是通过内部的构造函数创建的,等于调用了new Fcuntion()这个构造函数。

 

 

 

 

【定义对象】:

<script type="text/javascript">

    /**【创建对象的方法】*/

    /**第一种方法:

    这种创建对象的问题是对象不能重用没有类的概念*/

    var obj1 = {name:"老张"};

    /**第二种方法:

    这里的function其实就是一个对象(Dog是一个function*/

    var Dog = function() {

       this.name = "旺财";

    };

   

    //注意js对象的定义是基于引用的(和Java一样)

    var d = new Dog();

    // alert(d.name);

    var d1 = d;//指向同一个对象

    // alert(d1.name);

    d1.name = "小强";

    alert(d.name);

</script>

 

【定义函数】:

<script type="text/javascript">

    /** 此处的sumFunction,不是Object

     var s = new sum();这个时候的s就表示是一个Object*/

     

     //【函数的定义方式】:

     /**定义方式1

     这里的sum是一个function,如果这么写:var s = new sum

     那么这里的s就是一个Object

     【注意】: 对于Function的创建其实也都是通过内部的构造函数创建的,

     就等于调用了new Function这个方法来创建的*/

    function sum1(a, b) {

       return a + b;

    };

    /**定义方式2

    (和方式1效果是一样的)这里的sum2也是function*/

    var sum2 = function(a, b) {

       return a + b;

    };

    /**定义方式3

    等同于var sum3 = new Function ("a","b","return a+b")

    注意:方式1、方式2的定义方式和方式3都是一样的*/

    var sum3 = new Function("a", "b", "return a+b");//注意Function大写

   

    alert(sum3(12, 22));

    /**下面这个sum1等同于var sum1 = new Function("a","b","return a+b")*/

    var sum1 = function(a, b) {

       return a + b;

    };

 

    var sum1 = new Function("a","b","return a+b");

    var sum2 = sum1;// sum2的引用指向了sum1的引用

    alert(sum2(2, 2));// 打印4

 

    sum2 = function(a) {

       alert(a);

    };

/**sum2的引用改变了,但是不影响sum1(这个内存模型比较好理解,和Java一样)*/

    sum2 = new Function("a","alert(a)")

    sum1(1);// 无显示

    alert(sum1(2));// 提示NaN,因为sum1有两个参数

</script>

 

【小结】:

new出来的是对象,而用function定义的(如var a = function(){})就是function

 

<script type="text/javascript">

 

    /**定义一个Person对象,该对象中有一个prototype属性其指向

    内存中的一块区域(该区域存储键值对)*/

    var Person = function(name){

       this.name = name;

    };

 

/**Personprototype指向的区域存储键值,key=sayvalue=alert(this.name)*/

    Person.prototype.say = function() {

       alert(this.name);

    };

   

    /**定义一个对象实例ppObject类型,p中有_proto_属性,以及name属性,

    其中_proto_属性指向Person的原型对象*/

    var p = new Person("小李");

    /**以下是在p自己的区域里(而不是Personprototype指向的空间)创建

    一个say属性,say指向一内容是alert("ok")的内存空间,相当于

    var say = new Function("alert('ok')")

    【注意】:这时候如果调用say方法,它会现在自己的空间找say方法,

    如果找不到才到原型中找*/

    p.say = function() { //new Function

       alert("ok");

    };

 

    p.say();

   

    var p2 = new Person("小张");//p2自己的空间没有say方法

    p2.say();//打印小张

 

</script>

 

 

prototype

无论Object还是Function,它们都有一个属性prototype,它会指向一个内存区域。

【例1】:

var Person = function(name){

    this.name=name;

}

Person.prototype.say=function(){

    alert(this.name);

}

var p = new Person(“小李”);

以上内存模型如图:

(图中的右边一栏就是原型对象)。

此时如果调用p.say()则弹出小李

 

如果接着给p赋值:

p.say=function(){

    alert(“ok”);

}

此时的内存模型为:

此时如果调用p.say()则弹出ok(现在自己的作用域找,如果找不到会到上一级去找)。

 

【例2】:functionprototype

var Person = function(name){

    this.name=name;

}

var p = new Person("老王");

当定义var p=new Person(“老王”);后,p就有了Object中的方法了,为什么呢?

分析:

当定义了

var Person = function(name){this.name=name;}

后,实际上隐藏了一个操作:

Person.prototype = new Object()

此时内存模型如下图:

(其实Person也有自己的原型对象,只是Person.prototype=new Object()操作后改变了prototype的指向了)

可以看到p顺着往上找,就可以找到Object中的方法。

 

我们知道,如果是对象,那么对象就有_proto_属性,所以我们接着分析,上面的

var Person = function(name){

         this.name=name;

}

就是等价于:

var Person=new Function(“a”,”this.name=name”)

所以,这里的Person不仅有prototype属性,还有_proto_属性,此时的内存模型为(我的规律:new出来的对象的_proto_指向上一级的prototype指向的原型):

 

【小结】:

当我们写了以下代码的时候:

var Person = function(name){

    this.name=name;

}

var p = new Person("老王");

发生了以下事:

(1)       隐藏的动作:Person.prototype=new Object();所以,此时Personprrototype属性指向一个Object的对象,该Object对象指向了Object的原型对象(想象模型图);

(2)       上面的代码等价于var Person = new Function(“a”,”this.name=name”);所以,Person_proto_属性指向了Function的原型对象;

(3)       var p = new Person(“老王”);这里的p_proto_属性指向了Personprototype属性指向的对象。

 

【小结说明】(更早的笔记):

new出来的对象里有_proto_属性,指向其所属对象的原型对象①如:

var Person = function( name ){

         this.name = name;

}

var p = new Person();

此时p中的_proto_属性就指向Person的原型对象。

【提示一:】

在定义Person的时候,隐藏了一个动作:

Person.prototype = new Object();

也就是说,在定义了一个对象的时候,会在内存中new 一个Object(该Object_proto_属性指向Object的原型对象,即①的解释),然后Person对象的prototype属性就指向在内存中new出来的这个Object(就像Object本身的prototype指向Object的原型对象一样)。

【小心】:Person的原型对象中还有一个参数:constructor,它指向Person

【提示二:】

关于Function

Object一样,Function也有自己的一些内置的方法,Functionprototype属性指向了自己的原型对象。

在定义

var Person = function( name ){

         this.name = name;

}

的时候,实际上是执行了这个代码:

var Person = new Function(“name”,”this.name=name”);

(这里就有点像new一个Object了)new出来的这个Person有一个_proto_属性,它指向Function的原型对象,所以Person就有了Function的所有的方法了(调用方法的时候首先在自己的空间里找,找不到就会到Function中找)。

 

 

contextthis):

 

js中的上下文就是this

Function中有两个方法:Apply()call()这两个方法其实是一样的,只是参数格式不同applycall方法的第一个参数就是上下文。

【原理解释】:

<script type="text/javascript">

    // 定义一个函数

    function hello(a, b) {

       alert("hello apply:" + a + "," + b);

    };

    // 调用该函数

    hello("a", "b");

    /**也可以使用apply调用(hello指向Function的原

    所以可以使用Function中的方法),第一参数就是上下文”*/

    hello.apply(null, [ "d", "d" ]);

    //也可以使用call来调用,第一参数也是上下文

    hello.call(null, "c", "c");

 

    /**js中的thisJava中的this不同,js中的this就是表示上下文”*/

    var Person = function() {

       this.name = "悟空";

    };

 

    /**直接调用Person,那么Person中的this表示window(文档)*/

    Person();//没有new,调用方法,有new就是新建对象了

    alert(window.name);//打印悟空(因为this就是window

   

    //当使用call时,如果不指定上下文,默认的上下文就是window

    Person.call();

    alert(window.name);//打印“悟空”

 

    var o = {};

    Person.call(o);//使用o作为上下文,Person的this就是o了

    alert(window.name);//打印内容是空

    alert(o.name);//打印内容是悟空

</script>

 

【实例】:

 

<script type="text/javascript">

var Person = function(name,age) {

    this.name = name;

    this.age = age;

};

 

// 【实例1】:

// 以下三行代码表示在o增加nameage

var o = {};

Person.call(o,"八戒",222);

alert(o.name+","+o.age);

 

// 【实例2】:

// 使用call实现属性继承

var Student = function(name,age,no) {

    /**以下一句代码相当于:this.name = name;this.age = age;

    相当于完成了属性继承。call中的this代表Student*/

    Person.call(this,name,age);

    this.no = no;

};

 

var s = {};//注意这里的s不能使用call方法,因为其不是Function

Student.call(s,"八戒",222,"011");//效果是让s成为Student的一个实例

alert(s.name+","+s.age+","+s.no);

 

// 当使用new的时候,就会调用构造函数,然后就会将s2赋给Student中的上下文

var s2 = new Student();//s一样,s也成为了Student的一个实例

</script>

 

 

JS接口的实现:

 

js实现接口的方法有3种:

【方法1】:基于注释的实现——其实就是用注释来说明而已;

 

<script type="text/javascript">

    var Knight = function(name) {

       this.name = name;

       // 在注释中说明要实现的接口

       //Knight实现了WalkabeFightable

    };

// 然后根据注释来为其添加方法

    Knight.prototype.walk = function() {

       alert(this.name+" is walking!");

    };

    Knight.prototype.fight = function() {

       alert(this.name+" is fighting!");

    };

    var k = new Knight("悟空");

</script>

 

 

【方法2】:基于属性的设置

<script type="text/javascript">

    function checkImplements(obj) {

       if(!obj.implementInterfaces)

           throw new Error("必须声明所实现的接口");

       //每一个方法中都存在一个对象arguments来存储传递进来的实际参数

       for(var i=1;i<arguments.length;i++) {

           if(typeof arguments[i]!="string")

              throw new Error(arguments[i]+"的类型不正确");

           var found = false;

           for(var j=0;j<obj.implementInterfaces.length;j++) {

              var inter = obj.implementInterfaces[j];

              if(inter==arguments[i]) {

                  found = true;

                  break;

              }

           }

           if(!found) return false;

       }

       return true;

    };

   

    var Knight = function(name) {

       this.name = name;

       //Knight实现了Walkabe和Fightable

       this.implementInterfaces=["Walkable","Fightable"];

    };

    // 实现接口中的方法

    Knight.prototype.walk = function() {

       alert(this.name+" is walking!");

    };

    Knight.prototype.fight = function() {

       alert(this.name+" is fighting!");

    };

   

    function addKnight(knight) {

       // 使用对象前对对象进行检查

       if(!checkImplements(knight,"Fightable","Walkable"))

           throw new Error("必须实现Fightable和Walkable两个接口!");

    };

    var k = new Knight("悟空");

    addKnight(k);

</script>

 

【注意】:

l  这个虽然比方法一好多了,但是缺点也比较明显,就是即使我们不把接口中的方法实现也不会报错;

l  每一个方法中都存在一个对象arguments来存储传递进来的实际参数。

 

【方法3】:

 

<script type="text/javascript">

    // 定义接口,第二个参数是接口中的方法

    var Walkable = new Interface("Walkable", [ "walk" ]);

    var Fightable = new Interface("Fightable", [ "fight", "kill" ]);

 

    var Knight = function(name) {

       this.name = name;

    };

    //Knight实现接口

    Knight.prototype.walk = function() {

       alert(this.name + " is walking!");

    };

    Knight.prototype.fight = function() {

       alert(this.name + " is fighting!");

    };

    Knight.prototype.kill = function() {

       alert(this.name + " is killing!");

    };

    var k = new Knight("悟空");

    function addKnight(knight) {

       // 检查函数knight是否实现了WakableFightable接口

       Interface.checkImplements(knight, Walkable, Fightable);

       k.fight();

    };

    addKnight(k);

</script>

 

新文件中定义Interface和验证方法(Interface.js):

var Interface = function(name) {

    if(arguments.length!=2)

       throw new Error("创建的接口不符合标准,

必须有两个参数,第二个参数是接口的方法");

    this.name = name;

    this.methods = [];//保存接口中的方法名

    var methods = arguments[1];//第二个参数是接口中方法名

    for(var i=1;i<methods.length;i++) {

       this.methods.push(methods[i]);

    }

};

Interface.checkImplements = function(obj) {

    // 第一个参数是方法名,第二个参数是要实现的接口

    if(arguments.length<2)

       throw new Error("要检查的接口必须传入接口对象的参数,

参数的个数必须大于2");

    // 循环传过来的参数第一个往后的参数

    for(var i=1;i<arguments.length;i++) {

       var intObj = arguments[i];//获取接口名

       /**关于constructor,比如:

        var Interface = function(name){this.name=name}

        此时Interfaceprototype属性指向原型对象,原型对象中的

constructor属性指向Person自己。var Walkable = new Interface()

此时p_proto_属性指向Interface的原型对象*/

       // 不是接口类型

       if( intObj.constructor!=Interface )

           throw new Error(intObj+"接口的对象不正确");

       //检查方法是否符合要求

       var cmethods = intObj.methods;//获取接口的方法名

       for(var j=0;j<cmethods.length;j++) {

           // obj[ "walkable" ]obj中是否有walkable这个方法

           if(!obj[ cmethods[j] ] ||

typeof obj[ cmethods[j] ]!="function")

              throw new Error("必须实现:"

+intObj.name+"的"

+cmethods[j]+"这个方法!");

       }

    }

};

封装1(限制属性访问):

 

java中我们通过private来实现封装的。

 

【实例1】:比较直观的实现方法:

<script type="text/javascript">

    var Student = function(no, name, age) {

       if (!this.checkNo(no)) {

           throw new Error("学生的学号必须指定,并且必须是4位数");

       } else {

           this.no = no;

       }

       this.name = name;

       this.age = age;

    };

 

    Student.prototype.checkNo = function(no) {

       if (!no || no.length < 4)

           return false;

       return true;

    };

    Student.prototype.show = function() {

       alert(this.no + "," + this.name + "," + this.age);

    };

 

    var stu = new Student("0001", "猪八戒", 222);

    stu.no = "232333";//因为no没有隐藏,所以可以强制修改

    stu.show();

</script>

 

【说明】:

这个例子中no没有有效的隐藏,这个不符合封装的要求。

【实例2】:

<script type="text/javascript" src="Interface.js"></script>

<script type="text/javascript">

    var StudentInterface = new Interface("StudentInterface", [ "setNo",

           "getNo", "setName", "getName", "setAge", "getAge" ]);

    var Student = function(no, name, age) {

       //implements StudentInterface

       this.setNo(no);

       this.setAge(age);

       this.setName(name);

    };

    Student.prototype = {

       constructor : Student,

       checkNo : function(no) {

           if (!no || no.length != 4)

              return false;

           return true;

       },

       getNo : function() {

           return this.no;

       },

       setNo : function(no) {

           if (!this.checkNo(no)) {

              throw new Error("学生的学号必须指定,并且必须是4位数");

           } else {

              this.no = no;

           }

       },

       getName : function() {

           return this.name;

       },

       setName : function(name) {

           this.name = name;

       },

       getAge : function() {

           return this.age;

       },

       setAge : function(age) {

           this.age = age;

       },

       show : function() {

           alert(this.no + "," + this.name + "," + this.age);

       }

    };

 

    function addStu(stu) {

       Interface.checkImplements(stu, StudentInterface);

    }

 

    var stu = new Student("0001", "猪八戒", 222);

    stu.setNo("2222");

    //依然可以使用stu.no来直接赋值

    stu.no = "233322";

    stu.show();

 

    var stu1 = new Student("01", "孙悟空", 233);

</script>

【说明】:

这个例子中是实现了可以通过getset方法为属性赋值,但是还是可以直接访问其私有属性——解决方法是,将私有属性都加下划线。

 

【实例3】:私有变量都是用下划线开头

<script type="text/javascript" src="Interface.js"></script>

<script type="text/javascript">

    /**

     * 使用约定优先的原则,把所有的私有变量都使用_开头

     */

    var StudentInterface = new Interface("StudentInterface", [ "setNo",

           "getNo", "setName", "getName", "setAge", "getAge" ]);

    var Student = function(no, name, age) {

       //implements StudentInterface

       this.setNo(no);

       this.setAge(age);

       this.setName(name);

    };

    Student.prototype = {

       constructor : Student,

       _checkNo : function(no) {

           if (!no || no.length != 4)

              return false;

           return true;

       },

       getNo : function() {

           return this._no;

       },

       setNo : function(no) {

           if (!this._checkNo(no)) {

              throw new Error("学生的学号必须指定,并且必须是4位数");

           } else {

              this._no = no;

           }

       },

       getName : function() {

           return this._name;

       },

       setName : function(name) {

           this._name = name;

       },

       getAge : function() {

           return this._age;

       },

       setAge : function(age) {

           this._age = age;

       },

       show : function() {

           alert(this._no + "," + this._name + "," + this._age);

       }

    };

 

    function addStu(stu) {

       Interface.checkImplements(stu, StudentInterface);

    }

 

    var stu = new Student("0001", "猪八戒", 222);

    stu.setNo("2222");

    //虽然可以直接设置值,但是由于属性是通过_开头,具体的对象属性其实是没有任何变化

    stu.no = "233322222";

    //stu._no = "2232323";  

    stu.show();

 

    //var stu1 = new Student("01","孙悟空",233);

</script>

 

【提示】:

这个已经是一个很有效的封装了(使用约定优先,而且这个方法是实际开发中的一个很有效的方式),但是还是不能阻止开发者通过下划线访问私有变量,如果想实现真正的封装,可以使用闭包

 

封装2(使用闭包)

闭包的相关概念、作用域链

js的作用域是指函数的作用域,然后还存在作用域链的概念。

(function(name,age){

 

})(“Jack”,18)

这样就是一个闭包。

 

【实例1】:

<script type="text/javascript">

    function abc() {

       sum = 0;

       for (var i = 0; i < 5; i++) {

           sum += i;

       }

       alert(sum);//打印15

       /************************************************

【说明】:

       (1)只要在函数内部使用var定义的变量,那么它的

       作用域就是函数范围内;

       (2)如果函数内部的变量没有使用var,那么表示是全局的变量

       如果外部有同名的则外部的会被覆盖。

*************************************************/

       alert(i); //打印10:这说明i的作用域并不是使用后就消失

    }

    abc();

    alert(sum);

</script>

 

【关于作用域链】:

函数(比如abc)内部有一个变量scope,它指向作用域链,比如这里的abcscope就指向了abcscope list,在abcscope list中,有指针分别指向不同的作用域(从高到低排序,比如abc有两个作用域,一个是全局的,一个是自己的):1指向全局的(global(就是最外部的),0指向自己的(函数内部的变量)。

 

【提示】:

js最重要的核心概念:

(1)       Function

(2)       Object

(3)       FunctionObjectprototype

(4)       this上下文

(5)       作用域

 

【实例2】:

<script type="text/javascript">

    function foo() {

       var a = 10;

       var bar = function() {

           a *= 2;

           return a;

       };

       // foo的返回值是foo中的bar函数的返回值

       return bar;

    };

    // foo的返回值是bar,所以baz就是bar

    var baz = foo();

    // 执行bar函数

    baz();               // a的值为20

    baz();               // a的值为40

    var b = baz();       // a的值为80

    alert(b);            // 打印80

    //---分割线-----

    var faz = foo();  // 重新执行,foo作用域中的a值为10

    var t = faz();    // a为20

    alert(t);         // 打印20

</script>

 

【解释说明】:忽略//---分割线-----下的代码

Q:为什么baz调用多次,a的值一直存在呢?

A:分析内存模型:

(1)       fooscope属性指向了作用域链(foo scope list),作用域链中的1指向了global作用域,0指向了自己(foo scope)。

(2)       global中有foobaz(这两个都是函数);

(3)       foo scope中有a(值为10)、bar(函数);

(4)       接下来执行baz = foo()baz也会有自己的作用域(baz scope list),在bazscope list中有3个作用域:2指向global1指向foo scope0指向自己(baz scope 内容空)

(5)       此时,执行baz,那么foo scope中的a的值为20,再执行一次40,……

【示意图】:

 

有问题的范例

【实例1】:

<script type="text/javascript" src="../inc/Interface.js"></script>

<script type="text/javascript">

    /*使用这种方式虽然可以严格实现封装,但是带来的问题是getset方法都不

能存储在prototype中,都是存储在对象中的这样无形中就增加了开销*/

    var StudentInterface = new Interface("StudentInterface", [ "setNo",

           "getNo", "setName", "getName", "setAge", "getAge" ]);

    var Student = function(_no, _name, _age) {

       //implements StudentInterface

       var no, name, age;//没有使用this

       function checkNo(no) {

           if (!no || no.length != 4)

              return false;

           return true;

       }

       this.setNo = function(_no) {

           if (!checkNo(_no)) {

               throw new Error("学生的学号必须指定,并且必须是4位数");

           } else {

              no = _no;

           }

       };

       this.getNo = function() {

           return no;

       };

       this.setName = function(_name) {

           name = _name;

       };

       this.getName = function() {

           return name;

       };

       this.setAge = function(_age) {

           age = _age;

       };

       this.getAge = function() {

           return age;

       };

       this.setNo(_no);

       this.setAge(_age);

       this.setName(_name);

    };

    Student.prototype = {

       constructor : Student, //应该是定义constructor属性指向Student

       show : function() {

           alert(this.getNo() + "," + this.getName() + "," + this.getAge());

       }

    };

 

    function addStu(stu) {

       Interface.checkImplements(stu, StudentInterface);

    }

 

    var stu = new Student("0001", "猪八戒", 222);

    stu.no = "232323";//修改不起效果

    stu.setNo("2321");

    stu.show();

</script>

【提示】:

(1)       这个方法虽然能严格实现封装,但是内存开销比较大。(一般都使用“封装”中的【实例3),注意看上面的作用域链的讲解。

(2)       这个方法中的checkNo方法没有必要放在对象中,可以做成一个静态函数。

 

 

【实例2】:使用静态函数

关于匿名函数:

第一种方式:

就是上面所讲的定义square函数,这也是最常用的方式之一。

var double = function(x) { return 2* x; }

注意“=”右边的函数就是一个匿名函数,创造完毕函数后,又将该函数赋给了变量。

 

 

第二种方式:

(function(x, y){

    alert(x + y); 

})(2, 3);

这里创建了一个匿名函数(在第一个括号内),第二个括号用于调用该匿名函数,并传入参数。

 

关于构造函数

 

【例】:关于构造函数等

var Person=function(name,age){

    this.name=name;

    this.age=age;

};

Person.prototype.say=function(){

    alert(this.name+","+this.age);

}

var Student = function(name,age){

   

}

Student.prototype=new Person();

分析以上代码:

代码片段

var Person=function(name,age){

    this.name=name;

    this.age=age;

};

Person.prototype.say=function(){

    alert(this.name+","+this.age);

}

的内存模型是:

即,Personprototype属性指向Person的原型对象中的contructor属性,而constructor回指向了Person。可以使用Person.constructor获取Person名称(大概是这个意思)。

而接下来的代码:

var Student = function(name,age){

}

内存原型则和Student类似,具体图略。

接下来的代码片段:

Student.prototype=new Person();

的内存是:

 

然后我们使用Student创建对象:

var stu1=new StudentI();

此时stu1_proto_属性指向Student指向的原型对象,但是当我们设置stu1的属性值后:

stu1.name=”Jack”;

stu1.age=18;

此时stu1本身就有了nameage属性,所以当我们再创建对象的时候:

var stu2=new Student();

此时stu2本身没有nameage属性,即stu2不会将stu1的属性值继承下来。

 

 

【补充】:

假如Person中还有一个this.books=[]属性

那么我们加入这么为stu1赋值:stu1.books.push(“java”),那么这个java值就赋值到了Person所对应的原型对象中的books中去。而如果使用stu1.books=[“java”];那么,就会在stu1自己本身中加入java值。

 

使用闭包完成封装

 

【例】;使用闭包实现封装

<script type="text/javascript" src="../inc/Interface.js"></script>

<script type="text/javascript">

    /*使用这种方式虽然可以严格实现封装,但是带来的问题是getset方法都

不能存储在prototype中,都是存储在对象中的这样无形中就增加了开销*/

    var StudentInterface = new Interface("StudentInterface", [ "setNo",

           "getNo", "setName", "getName", "setAge", "getAge" ]);

 

    var Student = (function(_no, _name, _age) {

       /*此时的checkNo是创建对象外的代码,不会赋给Student

也就等于是一个私有的静态函数,

只会有一份拷贝*/

        //****checkNo没有赋给Student******************************

       function checkNo(no) {

           if (!no || no.length != 4)

              return false;

           return true;

       }

//********************************************************

       //等于一个私有的静态变量

       var times = 0;

       //*****将下面的函数返回赋给Student*************************

       return function(_no, _name, _age) {

           var no, name, age;//没有使用this

           this.setNo = function(_no) {

              if (!checkNo(_no)) {

                  throw new Error("学生的学号必须指定,并且必须是4位数");

              } else {

                  no = _no;

              }

           };

           this.getNo = function() {

              return no;

           };

           this.setName = function(_name) {

              name = _name;

           };

           this.getName = function() {

              return name;

           };

           this.setAge = function(_age) {

              age = _age;

           };

           this.getAge = function() {

              return age;

           };

           times++;

           if (times > 3)

              throw new Error("该对象的实例只能有三份!");

           this.setNo(_no);

           this.setAge(_age);

           this.setName(_name);

       };

    })();

//********************************************************

 

    Student.prototype = {

       constructor : Student,

       show : function() {

           alert(this.getNo() + ","

+ this.getName() + "," + this.getAge());

       }

    };

 

    function addStu(stu) {

       Interface.checkImplements(stu, StudentInterface);

    }

 

    var stu = new Student("0001", "猪八戒", 222);

    stu.no = "232323";

    stu.setNo("2321");

    stu.show();

 

    var stu1 = new Student("0001", "猪八戒", 222);

    var stu2 = new Student("0001", "猪八戒", 222);

    var stu3 = new Student("0001", "猪八戒", 222);

</script>

 

 

继承

基于类的继承:

实现js的继承有多种方式。

【方式一】:

 

<script type="text/javascript">

    /*使用这种继承方式所带来的两个问题

     * 1、对于books这种引用类型的属性会出现数据不一致

     * 2Student没有办法使用构造函数*/

    var Person = function(name, age) {

       this.name = name;

       this.age =  age;

       this.books = [];

    };

    Person.prototype.say = function() {

       alert(this.name + "," + this.age);

    };

    Person.prototype.getBooks = function() {

       return this.books;

    };

    var Student = function(name, age) {

    };

    Student.prototype = new Person();

    /*此时stu1中没有nameage,其_proto_指向Person原型*/

    var stu1 = new Student();

    // 下面的语句就会在stu1本身中加上nameagebooks属性

    stu1.name = "八戒";

    stu1.age = 22;

    /*因为没有写stu1.books=[]这样的语句,所以下面这句代码

    会在Person的原型(父类)的books中添加*/

    stu1.books.push("java");

    // stu1.say();

    alert(stu1.getBooks());

 

    var stu2 = new Student();

    stu2.name = "悟空";

    // stu2.say();

    stu2.books.push("javascript");//stu1

    alert(stu2.getBooks());//打印javajavascript

</script>

【提示】:

使用这个方式实现继承的话有两个问题:

(1)       在父类(Person)中添加引用类型属性this.books = [],然后试图在子类(stu1)中添加自己的内容:stu1.books.push(“java”),这样改变的是父类(Person的原型)中的books

(2)       无法使用Student的构造函数。

 

如果想让Student有自己的属性可以这么做:

    var Student = function(name, age) {

         Person.call(this,name,age)

    };

调用Person.call(this.name,age)就相当于执行了以下代码:

this.name=name;

this.age=age;

this.books=[];//这里这句没看懂,为什么参数里没有books这里也自动设置了??

 

【方式二】:解决“方式一”中的问题:

<script type="text/javascript">

    var People = function(name, age) {

       this.name = name;

       this.age = age;

       this.books = [];

    };

    // Person("abc",22);

    // alert(window.age);

    People.prototype.say = function() {

       alert(this.name + "," + this.age);

    };

    People.prototype.getBooks = function() {

       return this.books;

    };

 

    var Student = function(name, age, no) {

       this.no = no;

       /* StudentsuperClass属性在extend方法中设定

       其指向People的原型*/

       Student.superClass.constructor.call(this, name, age);

    };

    //使用extend方法完成继承

    extend(Student, People);

    var stu1 = new Student();

    stu1.name = "八戒";

    stu1.age = 22;

    stu1.books.push("java");

    stu1.say();

    alert(stu1.getBooks());

 

    var stu2 = new Student();

    stu2.name = "悟空";

    stu2.say();

    stu2.books.push("javascript");

    alert(stu2.getBooks());

</script>

 

extend.util.js

function extend(subClass,superClass) {

    //直观的思维是subClass.prototype=new superClass()因为是变量所以不能

    //直接new,应该使用以下的方法:

    var F = function(){};

    F.prototype = superClass.prototype;

    subClass.prototype = new F();

    /**subClass这个子类中的属性,和参数的superClass不是一个概念

    superClass指向了Person的prototype,此时如果使用

Student.superClass.constructor就可以获取到Person名*/

    subClass.superClass = superClass.prototype;

}

 

【小结】:

基于类的继承其实就是让子类的prototype指向父类的一个实例对象,然后在子类中使用call方法来完成调用。

 

 

 

基于原型链:

 

原型链的继承方式没有类的概念,永远都是通过对象来创建,这个和基于类是两种完全不同的方式。

 

<script type="text/javascript">

    /*这里的Person是一个对象(使用json格式创建)*/

    var Person = {

       name : "悟空",

       say : function() {

           alert(this.name);

       }

    };

 

    var Student = clone(Person);//这句的效果是让Student继承Person

    Student.no = "";

    Student.say = function() {

       alert(this.no + "," + this.name);

    };

    //自定义方法clone

    var p = clone(Person);//同样,可以使用clone方法创建Person对象

 

    /**内存分析:

    F的prototype指向Student,Student的_prop_属性指向Person

    stu1的_prop_属性指向Student*/

    var stu1 = clone(Student);

    stu1.no = "001";

    /*在say()方法中打印name:因为在stu1的范围和Student范围内都没有name

    属性,所以就找到Person中的name*/

    stu1.say();//打印悟空

 

    function clone(obj) {

       var F = function() {

       };

       F.prototype = obj;

       return new F();

    }

</script>

内存分析:

 

 

【注意】:

使用原型链的方式实现继承的话有以下问题:

(1)       不能使用构造函数;

(2)       只有,比如使用stu1.name=“jack”才会在stu1本身中创建name属性,否则就会到父类中找;

(3)       跟使用基于类的继承一样,引用类型的属性会找到父类里去(stu1.books.push(“js”))。

【提示】:

这里提到的clone方法很重要!常用!

 

基于类的继承的实例

(略)

实现可编辑的文本编辑器。

 

inline表示在一行显示。

 

 

单例模式(singleton

单例的使用1:用户注册

【示例1】:

 

<script type="text/javascript">

    var Singleton = {

       att1:"hello",

       att2:"world",

       m1:function() {

           alert("m1");

       },

       m2:function() {

           alert("m2");

       }

    };

   

    alert(Singleton.att1);

    Singleton.m1();

</script>

 

【单例的运用1】:

【注意】:(单例的实际使用:创建命名空间)

比如有两个js文件01.js02.js

01.js

var findName = function(){

}

 

02.js

var findName = $(“#name”).val();

当一个文件同时引入这两个js文件的话,就会出现覆盖的问题。解决方法是,我们可以将这两个文件中的js内容放到自己的命名空间(类似包)中:

01.js

zmj. functions= {

         findName : function(){

}

}

使用findName方法的时候,就可以com.zmj.common.functions.findName()

 

02.js

zmj. values = {

         findName : $(“#name”).val()

}

 

以上,如果直接这么用的话,明显有问题的,因为zmj这个对象并不存在。所以要使用

zmj.functions = { }

这个代码的话,就必须先创建zmj对象:

var zmj = { }

 

有时候我们想让命名空间唯一,就会使用zmj.org.functions这样的命名空间,那么我们就要把zmjorg都要创建出来:

var zmj = {

         org: { }

}

 

【示例2】:文件专属js之注册案例

 

reg.html

 

<script type="text/javascript" src="../inc/jquery-1.8.3.js"></script>

<script type="text/javascript" src="KongHao.js"></script>

<script type="text/javascript" src="KongHao.reg.js"></script>

<script type="text/javascript">

    // 使用jquery

    $(function(){

       Org.reg.init();

    });

    // 或者使用js

    window.onload = Org.reg.init;//init没有括号

</script>

</head>

<body>

    <h2>用户注册</h2>

    <hr />

    <form id="regForm" action="reg.jsp">

    username:<input type="text" id="username" name="username" /><br/>

    nickname:<input type="text" id="nickname" name="nickname" /><br/>

    password:<input type="password" id="password" name="password" />

       <input type="submit" value="注册" /> <br/>

    </form>

    <div id="outputResult"></div>

</body>

</html>

 

Org.reg.js

/**reg代表页面名*/

KongHao.reg = {

    FORM_ID:"regForm",

    OUTPUT_CONTAINER:"outputResult",

    handlerSubmit:function(event) {

       event.preventDefault();

       var data = {};

       KongHao.reg.form.find("input").each(function(i) {

           if(

$(this).attr("type")!="submit"&&

$(this).attr("type")!="button"&&

$(this).attr("type")!="reset")

{

              data[$(this).attr("name")] = $(this).val();

           }

       });

       KongHao.reg.ajaxSubmit(data);

    },

    ajaxSubmit:function(data) {

       //通过$.post(href,data,funtion(data){showResult})

       var href = KongHao.reg.form.attr("action");

       alert(href);

       KongHao.reg.showResult(data);

    },

    showResult:function(data) {

       var str = "";

       for(var k in data) {

           str+="name:"+k+",value:"+data[k]+"<br/>";

       }

       KongHao.reg.outContainer.html(str);

    },

    init:function() {

       var KR = KongHao.reg;

       KR.form = $("#"+KR.FORM_ID);

       KR.outContainer = $("#"+KR.OUTPUT_CONTAINER);

       KR.form.submit(KR.handlerSubmit);

    }

};

 

单例的使用2:私有变量:

 

方式一、常规方式

 

即使用下划线表示私有变量。

 

<script type="text/javascript">

Org.Private = {

    //使用下划线来定义私有的成员

    _pm1:function() {

      

    },

    m1:function() {

      

    }

};

</script>

 

【示例:】

Org.HtmlParser = {

    //使用_开头,将其设定为私有方法

    _tranSpace:function(txt) {

       return txt.replace(/\n/g,"<br/>").replace(/\s/g,"&nbsp;");

    },

    _tranBrace:function(txt) {

       return txt.replace(/\>/g,"&gt;").replace(/\</g,"&lt;");

    },

    _resumeSpace:function(txt) {

       return txt.replace(/&nbsp;/ig," ").replace(/<br*\/?>/ig,"\n");

    },

    _resumeBrace:function(txt) {

       return txt.replace(/&gt;/ig,">").replace(/&lt;/ig,"<");

    },

    parseTxt:function(txt) {

       //1、转换<>括号

       txt = this._tranBrace(txt);

       //2、转换空格

       txt = this._tranSpace(txt);

       return txt;

    },

    resumeHtml:function(txt) {

       //1、转换<>括号

       txt = this._resumeBrace(txt);

       //2、转换空格

       txt = this._resumeSpace(txt);

       return txt;

    }

};

 

方式二:使用闭包将方法私有化

 

【原理:】

<script type="text/javascript">

KongHao.Private = (function(){

    //实现合理的私有化了

    function pm1() {

       alert("pm1");

    };

   

    return {

       m1:function() {

           alert("m1");

           pm1();

       }

    };

})();

 

KongHao.Private.m1();

//报错

KongHao.Private.pm1();

</script>

 

【实例:】基于闭包的定义方式(最常用的方式)

<script type="text/javascript">

$(function() {

    $("#btn").click(function() {

       var KH =Org.HtmlParser;

       //var t = KH._tranSpace("aaa   d");

       var txt = KH.parseTxt($("#intro").val());

       $("#introTxt").html(txt);

       $("#intro1").val(KH.resumeHtml(txt));

    });

});

</script>

</head>

<body>

    <textarea id="intro" name="intro" cols="100" rows="5"> </textarea><br/>

    <input type="button" value="确定" id="btn"/>

    <div id="introTxt"> </div>

    <textarea id="intro1" name="intro1" cols="100" rows="5"> </textarea><br/>

</body>

 

js代码:

Org.HtmlParser = (function(){

   

    // 斜杠开头的方法表示私有方法

    function _tranSpace(txt) {

       // 正则表达式:第一个斜杠表示正则表达式开始;/g表示做全局转换;

       // \n表示换行。

       // \s表示文本中的空格

       return txt.replace(/\n/g,"<br/>").replace(/\s/g,"&nbsp;");

    };

   

    function _tranBrace(txt) {

       return txt.replace(/\>/g,"&gt;").replace(/\</g,"&lt;");

    };

    // ig表示忽略大小写

    function _resumeSpace(txt) {

       return txt.replace(/&nbsp;/ig," ").replace(/<br*\/?>/ig,"\n");//<br>

    };

   

    function _resumeBrace(txt) {

       return txt.replace(/&gt;/ig,">").replace(/&lt;/ig,"<");

    };

   

    return {

       parseTxt:function(txt) {

           /**这里注意转换顺序*/

           //1、转换<>括号

           txt = _tranBrace(txt);

           //2、转换空格

           txt = _tranSpace(txt);

           return txt;

       },

       resumeHtml:function(txt) {

           //1、转换<>括号

           txt = _resumeBrace(txt);

           //2、转换空格

           txt = _resumeSpace(txt);

           return txt;

       }

    };

})();

 

上面这个方法虽然是真正意义上的单例,但是,这个方法会将整个方法全部加载到内存(不管用不用到),所以,可以对上面这个单例方法做进一步改进:

 

方式三:基于延迟的单例

 

<script type="text/javascript">

$(function() {

    $("#btn").click(function() {

       var KH = Org.HtmlParser;

       //var t = KH._tranSpace("aaa   d");

       var txt = KH.getInstance().parseTxt($("#intro").val());

       $("#introTxt").html(txt);

       $("#intro1").val(KH.getInstance().resumeHtml(txt));

    });

});

</script>

</head>

<body>

    <textarea id="intro" name="intro" cols="100" rows="5"> </textarea><br/>

    <input type="button" value="确定" id="btn"/>

    <div id="introTxt"> </div>

    <textarea id="intro1" name="intro1" cols="100" rows="5"> </textarea><br/>

</body>

 

 

js代码:

/* 基于闭包的方式定义,最常用的定义方式

 * 实现了延迟加载,只有在这个对象被使用到的时候才会加载

 * 和基于闭包的方式的唯一区别是,在调用的时候需要通过

* KongHao.HtmlParser.getInstance.parseTxt()*/

Org.HtmlParser = (function(){

    var instance;

    function constructor() {

       function _tranSpace(txt) {

           return txt.replace(/\n/g,"<br/>").replace(/\s/g,"&nbsp;");

       };

      

       function _tranBrace(txt) {

       return txt.replace(/\>/g,"&gt;").replace(/\</g,"&lt;");

       };

      

       function _resumeSpace(txt) {

       return txt.replace(/&nbsp;/ig," ").replace(/<br*\/?>/ig,"\n");

       };

      

       function _resumeBrace(txt) {

       return txt.replace(/&gt;/ig,">").replace(/&lt;/ig,"<");

       };

      

       return {

           parseTxt:function(txt) {

              //1、转换<>括号

              txt = _tranBrace(txt);

              //2、转换空格

              txt = _tranSpace(txt);

              return txt;

           },

           resumeHtml:function(txt) {

              //1、转换<>括号

              txt = _resumeBrace(txt);

              //2、转换空格

              txt = _resumeSpace(txt);

              return txt;

           }

       };

    }

   

    return {

       getInstance:function() {

           if(!instance) {

              instance = constructor();

           }

           return instance;

       }

    };

   

})();

 

单例使用3:在分支里使用单例:

 

【知识点】:

我们经常会使用if(){ }else(){ }来检测不同的浏览器,如果使用浏览器自带的检测方法会有效率的问题,我们可以使用单例来解决这个问题。

 

<script type="text/javascript">

KongHao.CreateObj = (function(){

    //假如objA是IE,objB是FireFox

    var objA = {

       m1:function() {

           alert("obja+m1");

       },

       m2:function() {

           alert("obja+m2");

       }

    };

   

    var objB = {

       m1:function() {

           alert("objb+m1");

       },

       m2:function() {

           alert("objb+m2");

       }

    };

   

    //根据不同的情况返回不同的对象

var x = 3;

    return x<10 ? objA:objB;       

})();

 

KongHao.CreateObj.m1();

</script>

 

【检测浏览器实例】:

<script type="text/javascript">

KongHao.SimpleXhrFactory = (function(){

    var standard = {

       createXhr:function() {

           alert("s");

           return new XMLHttpRequest();

       }

    };

    var activeXNew = {

       createXhr:function() {

           alert("an");

           return new ActiveXObject("Msxml12.XMLHTTP");

       }

    };

    var activeXOld = {

       createXhr:function() {

           alert("on");

           return new ActiveXObject("Microsoft.XMLHTTP");

       }

    };

    try {

       standard.createXhr();

       return standard;

    } catch(e) {

       try{

           activeXNew.createXhr();

           return activeXNew;

       } catch(e) {

           try{

              activeXOld.createXhr();

              return activeXOld;

           } catch(e) {

              throw new Error("你的浏览器不支持XHR");

           }

       }

      

    }

})();

 

var xhr = KongHao.SimpleXhrFactory.createXhr();

alert(xhr);

</script>

 

工厂模式

 

java中,工厂模式使用来创建对象的。

 

简单工厂模式

 

<script type="text/javascript" src="../inc/Interface.js"></script>

<script type="text/javascript">

var ICar = new Interface("ICar",["assemble","wash","run"]);

/**

 * 表示小轿车

 */

var Car = function() {};

Car.prototype = {

    assemble:function() {

       alert("小轿车组装中!");

    },

    wash:function() {

       alert("小轿车清洗中");

    },

    run:function() {

       alert("小轿车移动中");

    }

};

 

var Truck = function() {};

Truck.prototype = {

    assemble:function() {

       alert("大卡车组装中!");

    },

    wash:function() {

       alert("大卡车清洗中");

    },

    run:function() {

       alert("轰隆隆隆!");

    }

};

 

var Org = {};

 

Org.CarFactory = {

    createCar:function(model) {

       var car;

       switch(model) {

           case "car":

              car = new Car();

           break;

           case "truck":

              car = new Truck();

           break;

       }

       Interface.checkImplements(car,ICar);

       return car;

    }

};

 

var CarShop = function(){};

CarShop.prototype = {

    sellCar:function(model) {

       var car = Org.CarFactory.createCar(model);

       car.assemble();

       car.wash();

       return car;  

    }

};

 

var c = new CarShop().sellCar("truck");

c.run();

</script>

</head>

<body>

 

 

 

 

 

==讲解:组织机构

 

 

理论分析

 

学校(School à 部门(Department à 专业(Special à 教室(Classroom

【方案一】:

以上4个实体,它们之间有对应的关系,但是,它们都可以分类为“组织(Organization”。

可以设计OrganizationOrganization和自己关联(一对多),比如将部门放到该表中(其父IDSchool)。

 

同时,学校下面可能也有ClassroomSprcial

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

组织表(Organization):

Id

Name

Pid

ShortName

Type

1

南京大学

Null

XX001

XX

2

计软院

1

JRY001

JXBM

3

软件工程

2

ZY001

ZY

4

网络工程

2

ZY002

ZY

 

 

我们还需要另外一张表OrgType(为了让类型之间建立联系):

组织类型表(OrgType):

Id

Name

ShortName

1

学校

XX

2

行政部门

XZBM

3

教学部门

JXBM

4

班级

BJ

5

专业

ZY

 

(提示:这个结构很容易扩展,比如我们将OrgType用在公司上的话,表结构可以如下:)

Id

Name

ShortName

1

公司

GS

2

区域公司

QYGS

3

子公司

ZGS

4

销售部门

XSBM

5

研发部门

YFBM

6

售后部门

SHBM

 

我们还要给类型定规则,专门用来映射部门和部门之间的关系,也就是用来确定哪些组织下面有哪些组织:

组织类型规则表(OrgTypeRule):

Id

Pid

Cid

Num

Pid意义)

1

1(学校)

2(行政部门)

50

 

2

2(行政部门)

2(行政部门)

10

 

3

3(教学部门)

4(班级)

100

 

4

1(学校)

3(教学部门)

20

 

5

 

 

 

 

6

 

 

 

 

 

比如,我们要添加一个行政部门的话,我们就知道行政部门的上级是学校。

 

提出问题:

有的组织,比如教学部门有自己的信息(比如电话等等)

所以,这时候我们再添加一个新的表Department表(和Organization是一对一的关系),专门用来存放组织信息。(这是一个解决方法,另外一个方法是我们也可以给Organization增加冗余字段)。

 

使用以上表,我们就可以将组织这棵树确定下来了!

 

【继续增加内容】:

(1)       组织机构是用来组织人的,所以,组织机构(Organization)和人(Person)是有对应的关系的;

(2)       人在组织中肯定有一个对应的岗位(Position);

(3)       为了管理人(Person)、组织机构(Organization)、岗位(Position)之间的关系,再增加另外一张表(PersonOrgPos),PersonOrgPos有三个外键,分别指向PersonOrganizationPosition

 

 

Person表:

Id

Name

1

张三

2

李四

3

王五

4

李六

5

赵七

6

吴八

 

 

Position表:

Id

Name

1

科长

2

科员

3

处长

 

PersonOrgPos表:

Id

Perid

Orgid

PosiId

1

1(张三)

2(计软院)

1(科长)

2

2(李四)

3(网络工程)

2(科员)

 

 

【继续增加内容】:

比如,张三登陆系统后,要决定张三能够管理哪些数据(这是组织机构的核心):

 

为此,我们要为Organization设置一张OrgType表,该表用来确定组织所能管理的部门

 

另外:

(1)       Person要想登陆系统,还要跟用户表(User)一一对应,即属于用户的Person才能登陆系统

(2)       还要和权限关联,这样就牵引出角色资源的概念。

 

 

 

实体设计

 

如果使用Hibernate不要使用双向关联更不要使用一对多的关联,最多使用多对一,要绝对控制住Hibernate发出的sql数。

 

大项目中外键都很少用,而是使用冗余字段。

 

 

 

 

 

 

 

==讲解:CMS项目

 

项目前述:

 

【用到的技术】:

1、  Maven分模块管理;

2、  Spring+Hibernate+SpringMVC

3、  页面采用FreeMarker

4、  JS库使用Jquery

5、  树展示:Ztree

6、  上传文件:uploadfy

7、  AjaxDwr

 

 

【分成的模块】:

-basic-hibernate基础模块,用来增删改查,可以用于大多数项目;

-cms-coremodeldao

-cms-useruserrolegroup 主要是Service

-cms-articlechannel(栏目)、article 主要是Service

-cms-systemlinkbackupinfo

-cms-web

-cms-parent

 

cms-parent模块

新建一个maven项目:

下一步:

 

Pom.xml文件内容:

<modelVersion>4.0.0</modelVersion>

<groupId>com.zmj.cms</groupId>

<artifactId>cms-parent</artifactId>

<version>0.0.1-SNAPSHOT</version>

<packaging>pom</packaging>

 

 

 

basic-hibernate模块

新建一个maven项目:

 

pom.xml文件内容:

<modelVersion>4.0.0</modelVersion>

<groupId>com.zmj.basic</groupId>

<artifactId>basic-hibernate</artifactId>

<version>0.0.1-SNAPSHOT</version>

 

 

 

 

零散知识点

 

不需要事务的比如用户列表查询,可以放在dao中处理,而需要事务的放在service中,比如添加用户(包括添加用户实体、用户角色、用户组)。

 

关于权限模型

一般是RBAC模式(Role Base Access Control)。

 

【权限中有】

实体:比如用户ZhangSan就是一个实体,还有其他的如角色、资源、组;

资源:比如访问路径;比如本系统中文章、栏目、功能、附件都是资源

操作:实体对资源的操作;

角色:一个角色对应多个操作;

 

 

【在本系统中】:

角色:只有三种角色:管理员(admin)、文章发布员(publis)、文章审核人员(audit);

                   即:角色限制访问哪些功能;

组:用户可以访问哪些栏目呢?比如有两个文章发布员,那么这两个文章发布员能访问的功能是一样的,所以定义的概念,用来指定一个组能访问哪些栏目。

                   即:组限制能访问哪些栏目;

 

 

 

关于涉及到事务的方法

 

因为一般都是在service上做事务处理,所以涉及到事务的操作(比如增、改)都放到service层,和事务无关的操作(比如查询)就放到dao

 

cms-core编译结束后要install一下

 

视频中basic-hibernatecms-core执行了clean install命令了。

 

cms-core写完后就写cms-user了,cms-user项目建好后,就将三个模块basic-hibernatecms-corecms-user聚合到parent中了。

cms-core编写完毕之后,接下来就写cms-userservice模块了)。

视频中,cms-user项目建好后,就将现有的三个模块:basic-hibernate

cms-corecms-user聚合到parent中。视频中的意思是好像basic-Hibernate不应该

聚合到parent中,不是很明白。

 

cms-coreService层)的编写

 

【改进】:

比如在UserService中,我们注入了IUserDao userDao,当我们用userDao调用方法的时候,会有大量的可选方法,这样对操作不方便:

解决方法:

(1)       我们将IBaseDao中的方法只保留增删改查这几个最基本的方法,BaseDao中将相应的Overrid去掉。

(2)       在我们的UserDao中,提供需要的方法(比如测试中需要什么方法我们就提供,不能直接覆盖BaseDao中的方法,因为泛型)然后,在这些方法中调用BaseDao中的方法;也就是说,我们的Service层需要什么方法,我们就在UserDao中添加什么方法,然后在添加的方法中调用BaseDao中的方法。

 

 

关于懒加载、join fetch

 

@Override

public UserGroup getUserGroup(int userId, int groupId) {

/** 如果使用下面的hql的话,如果用到了UserGroup中的User或者Group的话,就会分别发出hql语句去查询对应的表,所以就会多发出hql查询,所以应该使用join 关联查询,用一条hql将所有的数据都查询出来*/

// String hql = "from UserGroup ug where ug.user.id=? and ug.group.id=?";

    String hql = "from UserGroup ug "

           + "left join fetch ug.user "

           + "left join fetch ug.group "

           + "where ug.user.id=? and ug.group.id=?";

    return (UserGroup) this.getHqlQuery(hql)

           .setParameter(0, userId)

           .setParameter(1, groupId).uniqueResult();

}

 

cms-web编写

 

环境搭建步骤:

(1)       导入包:

 

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>servlet-api</artifactId>

    <version>2.5</version>

    <scope>provided</scope>

</dependency>

<dependency>

    <groupId>jstl</groupId>

    <artifactId>jstl</artifactId>

    <version>1.2</version>

</dependency>

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-web</artifactId>

    <version>3.2.2.RELEASE</version>

</dependency>

<dependency>

    <groupId>javax.servlet.jsp</groupId>

    <artifactId>jsp-api</artifactId>

    <version>2.2</version>

    <scope>provided</scope>

</dependency>

<dependency>

    <groupId>com.zmj.cms</groupId>

    <artifactId>cms-user</artifactId>

    <version>${project.version}</version>

</dependency>

 

 

 

(2)       配置jetty

 

parent中配置:

<plugin>

    <groupId>org.mortbay.jetty</groupId>

    <artifactId>jetty-maven-plugin</artifactId>

    <configuration>

       <scanIntervalSeconds>10</scanIntervalSeconds>

       <webApp>

           <contextPath>/cms</contextPath>

       </webApp>

       <connectors>

           <connector implementation="org.eclipse.jetty.server.nio

.SelectChannelConnector">

              <port>8888</port>

              <maxIdleTime>60000</maxIdleTime>

           </connector>

       </connectors>

    </configuration>

</plugin>

然后在cms-web中配置。

 

栏目的功能

 

栏目的类型有:

 

 

 

 

 

 

 

 

学习过程中遇到的问题及解决方法

 

问题1:运行pom上的test命令不执行test目录下的test方法。

原因:之前我的maven使用3.2.5版本,然后我把3.2.5版本直接替换成3.0.5版本,但是settings中配置的仓库下载路径直接使用3.2.5版本中的仓库,这样不行,要将仓库删掉,重新下载全新的仓库。

 

问题2:org/slf4j/helpers/NOPLoggerFactory/ org/slf4j/impl/StaticLoggerBinder类似的slf4j的错误。

解决方法:视频中对slf4j只是导入了:

<dependency>

    <groupId>org.slf4j</groupId>

    <artifactId>slf4j-api</artifactId>

    <version>1.7.5</version>

</dependency>

从根据网上的零散信息,我又导入了:

<dependency>

    <groupId>org.slf4j</groupId>

    <artifactId>slf4j-nop</artifactId>

    <version>1.7.12</version>

</dependency>

<dependency>

    <groupId>log4j</groupId>

    <artifactId>log4j</artifactId>

    <version>1.2.17</version>

</dependency>

这样才解决了问题!浪费了

 

(上面两个问题浪费了差不多2天的时间,唉……

 

问题3:org.dbunit.dataset.NoSuchTableException: t_user,即不能找到dbunit的t_user数据文件、好像也提示找不到ApplicationContext

原因:没有导入以下包:

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-orm</artifactId>

    <version>${spring.version}</version>

</dependency>

 

问题4:Spring要求Hibernate使用getCurrentSession,所以在junit中要将session绑定到事务管理器中(具体见代码),但是在junit中始终不能用sessionFactory.getCurrentSession,始终No Session found for current thread

原因:

同时集成hibernate4之后,最小事务级别必须是Required,如果是以下的级别,或者没有开启事务的话,无法得到当前的Session详见博客:http://www.iteye.com/topic/1126047

解决方法:

beans.xml中配置事务管理扫描包的范围。

 

 

问题5:关于参数的问题

public class TestMain {

    @Test

    public void testMain() {

       test01(Object.class);// 这个可以调用

       test02(User.class);// 这个不可以调用:类型不匹配

       test03(User.class);// 这个可以调用

       test04(User.class);// 这个可以调用

    }

    public void test01(Class<Object> clazz) {

    }

    public void test02(Class<Object> clazz) {

    }

    /**Class<?> clazz 等价于Class<? extends Object> clazz*/

    public void test03(Class<? extends Object> clazz) {

    }

    public void test04(Class<?> clazz) {

    }

}

 

 

问题6:新建项目(cms-core)运行测试文件报错

Failed to execute goal on project cms-core:

Could not resolve dependencies for project com.zmj.cms:cms-core:jar:0.0.1-SNAPSHOT:

Failed to collect dependencies for

[com.zmj.basic:basic-hibernate:jar:0.0.1-SNAPSHOT (compile),

junit:junit:jar:4.10 (test),

org.wicketstuff:jsr303:jar:1.5-RC5.1 (compile),

org.hibernate:hibernate-validator:jar:4.3.1.Final (compile),

org.dbunit:dbunit:jar:2.4.9 (test),

org.slf4j:slf4j-api:jar:1.7.5 (compile),

org.slf4j:slf4j-nop:jar:1.7.12 (compile),

log4j:log4j:jar:1.2.17 (compile),

org.springframework:spring-test:jar:3.2.2.RELEASE (test),

com.github.springtestdbunit:spring-test-dbunit:jar:1.0.0 (test),

commons-lang:commons-lang:jar:2.6 (compile)]:

Failed to read artifact descriptor for

com.zmj.basic:basic-hibernate:jar:0.0.1-SNAPSHOT:

Could not find artifact com.zmj.cms:cms-parent:pom:0.0.1-SNAPSHOT -> [Help 1]

 

解决方法:parent重新install一下就好了。

 

问题7jetty版本引错

应该是

<artifactId> jetty-maven-plugin</artifactId>

错写成了:

<artifactId>maven-jetty-plugin</artifactId>

错误提示信息:

[WARNING]

[WARNING] Some problems were encountered while building the effective model for com.zmj.cms:cms-web:war:0.0.1-SNAPSHOT

[WARNING] 'build.plugins.plugin.version' for org.mortbay.jetty:jetty-maven-plugin is missing. @ line 59, column 12

[WARNING]

[WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.

[WARNING]

[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.

[WARNING]

 

 

关于实体的验证

使用@NotEmpty而不使用@NotNull

 

 

 

问题8:映射实体时报错

No identifier specified for entity: com.zmj.cms.model.Channel

解决方法:检查是否设置主键了@Id@GeneratedValue

 

问题9:映射实体时报错

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void com.zmj.basic.dao.BaseDao.setSessionFactory(org.hibernate.SessionFactory); nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [beans.xml]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Could not determine type for: com.zmj.cms.model.Channel, at table: t_channel, for columns: [org.hibernate.mapping.Column(parent)]

         at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:601)

         at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)

         at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:285)

解决方法:检查一对多、多对一的映射关系是否放在了get方法上,以及有没有设置枚举映射。

 

 

问题10web项目启动时报错

 

(1)       注意跳过测试的安装命令:clean install –DskipTests=true

(2)       启动服务器命令:clean jetty:run,使用mvn jetty:run会报如下错误:

[ERROR] Unknown lifecycle phase "mvn". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]

[ERROR]

[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.

[ERROR] Re-run Maven using the -X switch to enable full debug logging.

 

 

问题11:cannot change version of project facet Dynamic web module to 2.5

解决方法:
在工程目录下有一个.settings文件夹,打开org.eclipse.wst.common.project.facet.core.xml做如下修改: <installed facet="jst.web" version="2.5"/>

 

问题12

[INFO] -------------------------------------------------------------

[ERROR] COMPILATION ERROR :

[INFO] -------------------------------------------------------------

[ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?

[INFO] 1 error

[INFO] ---------------------------------

[INFO] ---------------------------------

[INFO] BUILD FAILURE

[INFO]-----------------------------------

解决方法:Windowspreferencesjavainstalled JREs配置路径是jdk的路径。

 

 

 

问题12:SpringMVC和dwr整合的错误

15:23:42,295 ERROR DispatcherServlet:467 - Context initialization failed

org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Cannot locate BeanDefinitionParser for element [annotation-scan]

Offending resource: ServletContext resource [/WEB-INF/cms-servlet.xml]

    at org.springframework.beans.factory.parsing.FailFastProblemReporter.fatal(FailFastProblemReporter.java:59)

    at org.springframework.beans.factory.parsing.ReaderContext.fatal(ReaderContext.java:68)

    at org.springframework.beans.factory.parsing.ReaderContext.fatal(ReaderContext.java:55)

    at org.springframework.beans.factory.xml.NamespaceHandlerSupport.findParserForElement(NamespaceHandlerSupport.java:84)

    at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)

 

问题原因:是dwr版本的问题

视频中的版本:

<dependency>

    <groupId>org.directwebremoting</groupId>

    <artifactId>dwr</artifactId>

    <version>3.0.M1</version>

</dependency>

后来改用版本:

<dependency>

    <groupId>org.directwebremoting</groupId>

    <artifactId>dwr</artifactId>

    <version>3.0.0-rc3-RELEASE</version>

</dependency>

就没有上面的错误了,也不知道是什么原因,和教程上的环境应该是一样的,教程上正常我的环境上就不行,解决这个问题浪费了一个多小时。还有,dwrlistener使用教程上一样的设置方法也不行,在网上找的那种设置方法可以,这个也不知道是为什么,教程上也是官方设置。

 

 

 

===内容:HtmlCSS

1.   htmlcss

 

模仿别人网站的方法:主要是模仿别人的颜色搭配。

 

1.1.  marquee

 

这是一个让内容飘动的标签。

<html>

<head>

    <title>01.html</title>

</head>

    <body>

        <marquee direction="up" scrollamount="2"

                 onmouseover="this.stop()" onmouseout="this.start()">

            <ul style="border: 1 red solid;">

                <li>学校新闻</li>

                <li>学生新闻</li>

                <li>宿舍新闻</li>

            </ul>

        </marquee>

       

        <marquee onmouseover="this.stop()" onmouseout="this.start()">

            <a href="www.baidu.com">

                <!-- img上加上title有利于被搜索引擎搜索到                             当没有图片对应的话就会在对应位置显示alt中的内容 -->

                <img alt="滚动" title="图片素材" src="../img/1.jpg">

            </a>

        </marquee>

    </body>

</html>

 

 

1.2.  frameset

【前言】:

早前还有iframe,这个已经被淘汰、被ajax取代了。在html5中还有frameset

 

【注意】:

frameset不能在body中设置。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "

http://www.w3.org/TR/html4/loose.dtd">

 

<html>

<head>

    <title>01.html</title>

</head>

<!-- frameborder设定是否显示边框(值为1/yes或者0/no),

     border设置边框宽度 -->

<frameset cols="120,*,120" frameborder="no" border="1">

    <frame src="left.html" name="left" class="left" ></frame>

    <frame src="content.html" name="content" class="content"></frame>

    <frame src="right.html" name="right" class="right"></frame>

</frameset>

</html>

 

1.3.     css知识

1.3.1.   选择器

<style type="text/css">

/*idp2p标签(即ID选择器)*/

p.#p2{

 

}

 

/*class=p1p标签(即类选择器)*/

p.p1{

 

}

 

/*p中的所有的span标签,注意,p标签中不能放div*/

p span{

 

}

 

/*p标签的直接子span*/

p > span{

 

}

 

/*idd1idd2的所有标签*/

#d1,#d2{

 

}

 

</style>

 

padding

如果一个容器设置了高度是40,在IE浏览器中如果在该容器里设置padding-top值为20,那么该容器的实际高度不变,仍然是40;但是对于IE之外的浏览器来说,高度将变成60——所以,不能使用padding来设置对齐。

 

【提示】:

有的标签默认是带paddingmargin样式的,我们一般习惯将所有的标签去掉自带的paddingmargin,方法:

*{

    margin:0px;

    padding:0px;

  }

 

 

那么如何设置对齐方式呢?

 

1.3.2.   关于定位:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "

http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<title>01.html</title>

<style type="text/css">

    #parent {

       border: 1px solid red;

       height: 300px;

       width: 500px; margin-top : 20px;

       margin-left: 50px;

       margin-top: 20px;

    }

   

    /*相对定位是相对于盒子其本来的位置进行定位*/

    #relative{

       position: relative;

       left:40px/*在其原来的位置向左移动40px*/

       top:100px/*在其原来的位置向右移动100px*/

    }

   

    /*绝对定位是相对于浏览器边框位置位置进行移动的,但是如果上级是

       绝对定位的话,那么就相对于其上级做绝对定位。

       绝对定位中,空出的位置后面的元素会补充上来,而相对定位不会补充上来*/

    #child {

       border: 1px solid blue;

       height: 80px;

       width: 80px;

        /*设置positionabsolute后就可以设置左右边距*/

       position: absolute;

       /*这个是距离浏览器的上边框距离为0*/

       top: 10px;

      

    }

    #cc1 {

        border: 1px solid blue;

        height: 20px;

        width: 20px;

        /*设置positionabsolute后就可以设置左右边距*/

        position: relative;

        /*这个是距离浏览器的上边框距离为0*/

        top: 100px;

        left: 100px;

    }

</style>

</head>

<body>

    <div id="parent">

       <div id="child">

         <div id="cc1"></div>

       </div>

    </div>

</body>

</html>

 

 

【经验】:

经常使用relative实现文本位置的移动。比如:

<ul>

       <li><span>首页</span></li>

       <li><span>新闻</span></li>

       <li><span>咨询</span></li>

</ul>

 

我们可以先为li设置高度和宽度,然后为里面的span设置相对定位,然后设置lefttop值即可。

 

有一个css网站zen garden,可以参考,html里面的内容是一模一样,但是使用不同的css让内容展现出完全不同的风格。

 

1.3.3.   文章列表示例:

 

【提示】:

l  网页制作一般只用两种字体大小:12px14px,字体一般使用宋体。

 

网页上显示图片有两种方式:

方式1:使用img标签:

<img alt="" src="1.jpg">

方式2:使用cssbackground

    #id{

        background: url(01.jpg)

    }

</style>

</head>

<body>

    <div id="img01"></div>

</body>

</html>

 

一般图片经常变化的我们使用img标签,而图片一般不变的我们使用cssbackground

 

1.3.4.   导航栏、浮动

 

设置浮动后,一定要在下一个div中设置clear:both,否则下一个div中的内容会飘过来。

 

 

1.3.5.   内容居中显示

 

<html>

<head>

<title>01.html</title>

 

<style type="text/css">

    *{

        padding:0px;

        margin:0px;

    }

    #content{

        width:800px;

        height:500px;

        border: 1 solid red;

        /*以下3行代码才是正确的让内容居中显示的方式*/

        position: absolute;

        left:50%;/*这个意思是左边界在body中的50%的位置*/

        margin-left:-400px;/*再让边界往左移动一般的宽度*/

    }

</style>

 

</head>

<body>

    <div id="content">aaa</div>

</body>

</html>

【提示】:

1)上面的代码加上文档头就不能正确显示,不知道为什么:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "

http://www.w3.org/TR/html4/loose.dtd">

加上后就不能显示红框。

2)以下两个居中显示的方法只对IE有效:

body{

   text-align: center;

}

#content{

   margin: auto;

}

 

 

 

1.3.6.   网站首页设计

 

 

 

 

 

 

1.4.     jQuery插件

 

全局插件,比如:

<head>

    <title>01.html</title>

    <script type="text/javascript">

    $.sayHello(){

       

    }

    </script>

</head>

 

在使用全局插件的过程中,如果使用了:

$j = $.noConflict()//使用$j代替$符号

的话,就不能直接使用$符号了,这样就恶有问题,解决方法是使用闭包。

 

比如,我们要是想为某个包装集编写插件,我们可以放到prototype中:

<script type="text/javascript">

    //$.fn.say可以写成$.prototype.say

    $.fn.say = function(opts){

      

    }

</script>

</head>

插件编写好后,文件名命名,比如可以命名成:jquery.cms.admin.js

 

 

 

 

1.  

1.1.  

1.2.  

1.3.  

1.4.  

1.4.1.   左侧导航插件

 

页面:

<html>

<head>

<script type="text/javascript" src="<%=request.getContextPath() %>/resources/js/jquery-1.7.2.min.js"></script>

<script type="text/javascript" src="<%=request.getContextPath() %>/resources/js/core_zmj/jquery.cms.core.js"></script>

<script type="text/javascript">

$(function(){

    $("#left").myaccordion({selectedClass:"selected",titleTagName:"h3"});

    $("#left2").myaccordion();

});

</script>

<style type="text/css">

    div{

        border: 2px solid red;

        width:300px;

    }

</style>

</head>

<body>

<div id="left">

    <ul class="selected">

        <h3>电影欣赏</h3>

        <li>谍影重重</li>

        <li>变形金刚</li>

        <li>非常人贩</li>

    </ul>

    <ul class="selected">

        <h3>电视剧</h3>

        <li>权利的游戏</li>

        <li>行尸走肉</li>

        <li>越狱</li>

    </ul>

</div>

<br/>

<div id="left2">

    <ul class="navSelected">

        <h3>中国</h3>

        <li>北京</li>

        <li>上海</li>

        <li>南京</li>

    </ul>

    <ul class="navSelected">

        <h3>美国</h3>

        <li>纽约</li>

        <li>华盛顿</li>

        <li>洛杉矶</li>

    </ul>

</div>

</body>

</html>

 

jQuery

//使用闭包,第一行的$表示为该闭包定义一个变量$,传递jQuery参数

(function($){

    //$.fn等价与$.prototyp

    $.fn.myaccordion=function(opts){

       /**使用opts来覆盖settings中的预置值*/

       //var settings = $.extend({},opts||{});

       var settings = $.extend({

           selectedClass:"navSelected",

           titleTagName:"h3"

       },opts||{});

      

       /**将变量定义到外面方便使用*/

       var selectedClass=settings.selectedClass;

       var titleTagName=settings.titleTagName;

      

       /**将节点定义成变量*/

       //获取所有的标题节点(即所有的h3节点)

       var titleNodes=$(this).find("ul>"+titleTagName);

      

       //navSelected的标题节点

       var selectedNode=$(this).find("ul."+selectedClass+">"+titleTagName);

      

       /**鼠标移动到标题上光标变化*/

       $(this).find(titleTagName).css("cursor","pointer");

       //隐藏所有子节点(这里的nextAll()获取了所有标题节点的子节点)

       titleNodes.nextAll().css("display","none");

       //显示选中的节点

       $(this).find(titleNodes).nextAll().css("display","block");

 

       /**为标题节点添加点击事件*/

       titleNodes.click(function(){

           var isDisplay=$(this).parent().hasClass(selectedClass);

           if( isDisplay ){

              $(this).parent().removeClass(selectedClass);

              $(this).nextAll().slideUp();

           }else{

              $(this).parent().addClass(selectedClass);

              $(this).nextAll().slideDown();

           }

       });

      

    }

})(jQuery)

 

对应的css文件内容:

#top {

    width:1035px;

    height:110px;

}

#topIntro {

    height:80px;

    background:url("img/top_bg.jpg") repeat-x;

}

#logo {

    width:433px;

    height:63px;

    display:block;

    background:url("img/top_logo.jpg") no-repeat;

    position:relative;

    left:20px;

    top:7px;

    float:left;

}

#user_operator {

    font-size:12px;

    position:relative;

    top:15px;

    float:right;

    right:15px;

    color:#fff;

}

#user_operator a:link,#user_operator a:visited {

    color:#fff;

    text-decoration:none;

}

#user_operator a:hover {

    color:#ff0;

    text-decoration:underline;

}

#remind{

    height:28px;

    background:url("img/top_remind_bg.jpg") repeat-x;

    border-top:1px solid #fff;

    border-bottom:1px solid #737373;

}

#remind #showDate{

    font-size:12px;

    color:#233d4e;

    position:relative;

    top:6px;

    margin-left:15px;

    width:500px;

}

 

1.4.2.   表格插件

 

//使用闭包,第一行的$表示为该闭包定义一个变量$,传递jQuery参数

(function($){

    /**==================================================

     * 【插件名称】:设置表格效果

     * 【使用说明】:

     * $("#tableId")

     * .trHover({

     *     trEvenClass:偶数行css样式名(默认使用trEvenColor),

     *     trOverClass:鼠标移过tr时的css样式名(默认使用trMouseover)

     * });

     ====================================================*/

    $.fn.trHover=function(opts){

       var settings=$.extend({

           trEvenClass:"trEvenColor",

           trOverClass:"trMouseover"

       },opts||{});

      

       //将变量定义到外面

       var trEvenClass=settings.trEvenClass;

       var trOverClass=settings.trOverClass;

       //定义事件

       $(this).find("tbody tr:even").addClass(trEvenClass);

       $(this).find("tbody tr")

.on("mouseenter mouseleave",function(){

           $(this).toggleClass(trOverClass);

       });

    };

})(jQuery)

 

 

 

 

 

 

 

1.4.3.   确认对话框插件

 

 

//使用闭包,第一行的$表示为该闭包定义一个变量$,传递jQuery参数

(function($){

    /**==================================================

     * 【插件名称】:弹出确认操作对话框

     * 【使用说明】:

     * $("a.delete").showConfirmDialog({

     * msg:对话框上的提示语(默认"确认继续操作吗?"),

     * eventName:事件名如click(默认)、mouseover

     * });

     ====================================================*/

    $.fn.showConfirmDialog=function(opts){

       var settings=$.extend({

           msg:"确认继续操作吗?",

           eventName:"click"

       },opts||{});

      

       var msg=settings.msg;

       var eventName=settings.eventName;

      

       $(this).on(eventName,function(event){

           if( !confirm(msg) ){

              event.preventDefault();

           }

       });

    };

})(jQuery)

 

 

1.4.4.   验证插件

 

1.   SpringMVC知识

1.1.  关于Spring表单

 

<sf:form method="post" modelAttribute="user" id="addForm">

这里的modelAttribute=”user”会将表单中的数据自动填充到user

<sf:form method="post" modelAttribute="user" id="addForm">

    UserName:<sf:input path="username" />//user.setUsername();

这里的path=”username”表示使用modelAttribute中的user调用set方法:user.setUsername();

而回到这个页面的时候,因为表单中有path=”username”,所以,会试图调用user.getUsername()来填充表单,所以要在跳转到该页面的控制器中设置model.addAttribute(User)

 

 

1.2.  传值

 

【页面向Controller传值】:

方式1

Controller

//RequestMappingvalue值是一个数组

@RequestMapping({"/add","/"})

public String add(String username){

    return "hello";

}

传递请求参数:

如果请求地址可以是:http://localhost:8888/cms/add?username=zhangsan

如果不传username参数也是可以的,不会报错。

 

 

方式2

方式1中的参数可传可不传,如果希望一定要传参数,可以给参数加上RequestParam

@RequestMapping({"/add","/"})

public String add(@RequestParam("username") String username){

    return "hello";

}

此时username就作为请求路径的一部分,如果不传此值的话,就报错。

 

Controller向页面传值】:

方式1

@RequestMapping({"/add","/"})

public String add(String username,Map<String,String>context){

    context.put("username", "zhangsan");

    return "hello";

}

这样,在jsp页面就可以通过${username}将值取出来了。

 

方式2

在方式1中可以使用map传值,但是spring不建议这么用,建议使用model

@RequestMapping({"/add","/"})

public String add(String username,Model model){

    model.addAttribute("username", "zhangsan");

    return "hello";

}

model还可以:

model.addAllAttributes(Map<String,String> map);

model.addAttribute(new User()); --> 等价于:model.addAttribute(“user”,user);

方式3

 

@RequestMapping({"/add","/"})

public String add(@ModelAttribute("user") Model model){

    //model.addAttribute("username", "zhangsan");

    return "hello";

}

 

add(@ModelAttribute(“user”) Model model)相当于model.addAttribute(“user”,new User());

 

 

【补充】:StrutsSpring的对比:

Struts中,参数要定义在Controller类中,这样为了防止属性共享,就必须将struts定义成单例prototype模式;而Spring是将参数传递放到函数参数中的,所以不存在属性共享的问题,所以Spring的控制器是单例模式,效率比Struts要高。

 

 

1.3.     服务器端验证

 

Struts中比如add方法前使用addValidate来进行验证,在SpringMVC中,支持JSR303的验证(Bean Validate)。

 

在实体对应的get方法上加上@NotNull等验证,然后在Controller中:

@RequestMapping({"/add","/"})

public String add(@Validated User user,BindingResult result){

    if( result.hasErrors() ){

       return "error";

    }

    return "hello";

}

页面上显示错误信息:

<sf:errors cssClass="errorContainer" path="username"/>

path表示显示实体username属性上的错误信息。

 

1.4.     Springcheckbox

比如,控制器传过来角色的List列表,在页面如何展示这些多选框呢?

方法1:使用Spring的标签:

<sf:form method="post" modelAttribute="user" id="addForm">

<sf:checkboxes  items="${roles}" itemLabel="name" itemValue="id" path="roleIds"/>

</sf:form>

但是这里有一个问题,就是modelAttribute=”user”path=”roleIds”意思是使用user.setRoleIds();但是明显,user中没有roleIds属性。这个问题如何解决呢?

——使用Dto,方法:

(1)       创建一个Dto对象,该对象中有user的属性,并且带有groupIdsroleIds的数组属性;

(2)       在对应的Controller中使用定义的dto

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

public String add(Model model){

    model.addAttribute("userDto",new UserDto());

    model.addAttribute("roles", roleService.list());

    model.addAttribute("groups",groupService.list());

    return "user/add";

}

 

(3)       jsp页面:

 

<tr>

    <td class="rightTd">角色:</td>

    <td>

    <sf:checkboxes  items="${roles}" itemLabel="name"

itemValue="id" path="roleIds"/>

    </td>

</tr>

<tr>

    <td class="rightTd">用户组:</td>

    <td>

       <sf:checkboxes items="${groups }" path="groupIds"

itemLabel="name" itemValue="id"/>

    </td>

</tr>

 

 

 

(4)        

 

方法2:使用jstl标签:

<c:forEach var="role" items="${roles }">

    ${role.descr }

<input type="checkbox" name="roleIds" value="${role.id }"/>

</c:forEach>

 

1.5.     异常处理

 

1.5.1.   局部异常

我们已经定义了一个异常类:CmsException。

UserController中我们可以定义局部异常,该异常方法只能处理UserController中的异常:

@ExceptionHandler(CmsException.class)

public String handlerException(HttpServletRequest req){

    return null;

}

这里我们使用全局异常

 

1.5.2.   全局异常

 

全局异常在servlet.xml中配置,这里在cms-user中的cms-servlet.xml中配置如下内容:

<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">

    <property name="exceptionMappings">

       <props>

           <prop key="com.zmj.cms.model.CmsException">error</prop>

       </props>

    </property>

</bean>

【注】:

error是指映射到error这个视图页面,我们可以在WEB-INFjsp目录下创建error.jsp

error.jsp页面内容:

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>发生错误</title>

<link rel="stylesheet" type="text/css" href="<%=request.getContextPath() %>/resources/css/admin/error.css" />

</head>

<body>

<div id="container">

    <div id = "error">

       <span>出现错误</span>

       <div id="message">

<span class="errorContainer">${exception.message }</span>

</div>

       <div id="upPage">

<a href="javascript:history.go(-1)">返回上一页</a>

</div>

    </div>

</div>

</body>

</html>

 

【提示】:

当写了局部异常就默认使用局部异常而不是用全局异常了。

 

1.6.     栏目Channel

 

1.6.1.   新建一个工具模块

 

工具模块用来存放自己的工具类,比如EnumUtills、转换json数据的工具,我还是将工具类放到basic-hibernate中。

转换json格式的工具:jackson

拼音工具maven中央工厂没有:手动下载导入到项目中。

<dependency>

   <groupId>net.sourceforge.pinyin4j</groupId>

   <artifactId>pinyin4j-core</artifactId>

   <version>2.5.0</version>

</dependency>

 

 

1.6.2.   树展示

 

使用ztree(示例):

<link rel="stylesheet" type="text/css" href="zTreeStyle.css"/>

<script type="text/javascript" src="jquery-1.7.2.min.js" />

<script type="text/javascript" src="jquery.ztree.core-3.5.min.js"/>

<script type="text/javascript">

var datas=[

           {id:"0",name:"根目录",pid:"-1"},

           {id:"1",name:"用户管理",pid:"0"},

           {id:"2",name:"用户管理1",pid:"1"},

           {id:"3",name:"用户管理2",pid:"1"},

           {id:"4",name:"系统管理",pid:"0"},

           {id:"5",name:"系统管理1",pid:"4"},

           {id:"6",name:"系统管理2",pid:"4"},

           {id:"7",name:"系统管理2",pid:"6"},

           {id:"8",name:"系统管理2",pid:"7"}

           ];

$(function(){

    var setting = {

       data: {

           simpleData: {

               enable: true,

               idKey: "id",

               pIdKey: "pid",

               rootPId: -1

           }

       },

        view: {

           dblClickExpand: false,

            selectedMulti: false

        },

        callback: {

            onClick: function(event, treeId, treeNode){

            }

        }

    };

    var n = {id:"7",name:"系统管理222222",pid:"4"};

    var t = $.fn.zTree.init($("#tree"), setting, datas);

    t.addNodes(t.getNodeByParam("id",4,null),n);

})

</script>

</head>

<body>

<div id="content" class="ztree">

    <div id="tree"></div>

</div>

 

项目中使用ztree

 

1.6.2.1.            同步

 

建一个树实体,用来存放树信息:

public class ChannelTree {

 

    private Integer id;

    private Integer parentId;

    private String name;

 

dao查询:

@Override

public List<ChannelTree> generateFullTreen() {

    //我们只需要idnameparentId,所以应该使用sql效率更高

    String sql = "select id,parent_id as pid,name from t_channel";

    List<ChannelTree> tree

= this.listBySql(sql, ChannelTree.class, false);

    //为树手动添加一个顶级目录:网站系统栏目

    tree.add(0,new ChannelTree(0,-1,"网站系统栏目"));//在0的位置添加

    /*因为有的栏目是顶级父栏目,所以上面的sql查出的结果的parent_idnull

     *要将其转换成0,可以使用sql:select ...,ifnull(parent_id) from...

     *但是ifnull函数是mysql特有的,所以不建议使用,而是在java中转换

     */

    for( ChannelTree t:tree ){

       if( t.getPid()==null ){

           t.setPid(0);

       }

    }

    return tree;

}

 

 

然后在Controller中返回Json数据:

这里我们使用json工具类将数据转换成json数据格式的,在SpringMVC中,如果给方法返回值前加上@ResponseBody,就会自动将数据转换成json数据格式,这里我们为了得到数据格式就使用工具类手动转换了。

@RequestMapping("/list")

public String list(Model model){

    List<ChannelTree>list = channelService.generateFullTreen();

    model.addAttribute("treeDatas", JsonUtil

.getInstance().obj2json(list));

    return "channel/list_tree1";

}

 

在页面展示:

<script type="text/javascript">

var datas=${treeDatas};

$(function(){

    var setting = {

       data: {

           simpleData: {

               enable: true,

               idKey: "id",

               pIdKey: "pid",

               rootPId: -1

           }

       },

        view: {

           dblClickExpand: false,

            selectedMulti: false

        },

        callback: {

            onClick: function(event, treeId, treeNode){

            }

        }

    };

    $.fn.zTree.init($("#tree"), setting, datas);

})

</script>

</head>

<body>

<div id="content" class="ztree">

    <div id="tree"></div>

</div>

 

1.6.2.2.            异步

一次性加载全部数据

实际中使用异步方式取数据,而不是使用上面的方式var datas=${treeDatas};SpringMVC中,使用@ResponseBody可以自动转换成Json格式

所以可以使用ztree的异步方式,然后Controller中返回json数据格式:

Controller

@Controller

@RequestMapping("/admin/channel")

public class ChannelController {

    @RequestMapping("/list")

    public String list(Model model){

       return "channel/list_tree1";

    }

    @RequestMapping("/fullTree")

    public @ResponseBody List<ChannelTree> list(){

       return channelService.generateFullTreen();

    }

}

 

页面:

<script type="text/javascript">

//var datas=${treeDatas};

$(function(){

    var setting = {

       data: {

           simpleData: {

               enable: true,

               idKey: "id",

               pIdKey: "pid",

               rootPId: -1

           }

       },

        view: {

           dblClickExpand: false,

            selectedMulti: false

        },

        callback: {

            onClick: function(event, treeId, treeNode){

            }

        },

        async: {

            enable: true,

            url: "fullTree",

        }

    };

    $.fn.zTree.init($("#tree"), setting);

})

</script>

</head>

<body>

<div id="content" class="ztree">

    <div id="tree"></div>

</div>

</body>

</html>

 

点击一次展开就加载一次

 

Controller

@RequestMapping(value="/treeAsync",method=RequestMethod.POST)

//页面会传递一个parentId的请求参数

public @ResponseBody List<ChannelTreeDto> tree(@Param Integer parentId){

   

    System.out.println("zmj --> List<channelTreeDto> tree() parentId="+parentId);

   

    List<ChannelTreeDto> treeDtoList = new ArrayList<ChannelTreeDto>();

    if( parentId == null ){

       treeDtoList.add(new ChannelTreeDto(0,"网站根栏目",1));

       return treeDtoList;

    }

    List<ChannelTree> treeList

    = channelService.generateTreeeUnderParent(parentId);

    for( ChannelTree tree:treeList ){

       ChannelTreeDto treeDto=new ChannelTreeDto(tree.getId(),tree.getName(),1);

       treeDtoList.add(treeDto);

    }

    return treeDtoList;

}

 

Dao

@Override

public List<ChannelTree> generateTreeeUnderParent(Integer parentId) {

   

    String sql=null;

    if( parentId==null || parentId==0 ){

       sql = "select id,parent_id as pid ,name "

              + "from t_channel "

              + "where parent_id is null "

              + "order by channel_serial_num asc";

    }else{

       sql = "select id,parent_id as pid,name "

              + "from t_channel "

              + "where parent_id="+parentId

              +" order by channel_serial_num asc";

    }

    return this.listBySql(sql, ChannelTree.class, false);

}

 

页面:

<script type="text/javascript">

//var datas=${treeDatas};

$(function(){

    var setting = {

       /*data: {

           simpleData: {

               enable: true,

               idKey: "id",

               pIdKey: "pid",

               rootPId: -1

           }

       },*/

        view: {

           dblClickExpand: false,

            selectedMulti: false

        },

        callback: {

            onClick: function(event, treeId, treeNode){

            }

        },

        async: {

            enable: true,

            url: "treeAsync",

            //表示传递参数的格式是pid=×默认是id=×

            autoParam: ["id=parentId"],

            //用来传递其他参数

            //otherParam: ["age", "18", "name", "tom"],

            //指定请求方法,默认是get,这里使用get和post都可以

            type:"post"

        }

    };

    $.fn.zTree.init($("#tree"), setting);

})

</script>

</head>

<body>

<div id="content" class="ztree">

    <div id="tree"></div>

</div>

</body>

</html>

 

 

clean install -DskipTests=true

 

 

1.6.2.3.            树插件

 

js代码:

(function($){

    /**==================================================

     * 【插件名称】:树形插件

     * 【使用说明】:

     * (1)点击后不显示子节点信息:

     * $("#tree").showTree({myconst:{onclickFun:0}});

     * (2)修改url:$("#tree").showTree({url:"listChildren"});

     *

     ====================================================*/

    $.fn.showTree=function(opts){

        var __setting = {

            data: {

                simpleData: {

                    enable: true,

                    idKey: "id",

                    pIdKey: "pid",

                    rootPId: -1

                }

            },

            view: {

                dblClickExpand: false,

                selectedMulti: false

            },

            callback: {

                onClick: onclickFun

            },

            async: {

                enable: true,

                //获取树节点信息

                url: opts?(opts.url||"fullTree"):"fullTree",

                //表示传递参数的格式是pid=×默认是id=×

                autoParam: ["id=parentId"],

                //用来传递其他参数

                otherParam: ["age", "18", "name", "tom"],

                //指定请求方法,默认是get

                type:"post"

            },

            //自定义常量

            myconst:{

                onclickFun:1,

                srcElement:"#cc"

            }

        };

        //__setting继承opts

       var setting=$.extend(__setting,opts||{});

       //$("#tree").showTree(...);下面的this就表示#tree

       var tree=$.fn.zTree.init($(this), setting);

       var __myconst=__setting.myconst;

      

       if( __myconst.onclickFun ){

           tree.setting.callback.onclick=onclickFun;

       }

       //定义点击事件函数

       function onclickFun(even,treeId,treeNode){

           //获取子节点信息

           $(__myconst.srcElement).attr("src","listChildren/"+treeNode.id);

       }

    }

   

//-------  (function($){....})(jQuery)  //end---------- 

})(jQuery)

 

html页面:

    $("#tree").showTree({myconst:{onclickFun:0}});

})

</script>

</head>

<body>

<div id="content">

    <ul id="tree" class="ztree" style="width:150px; overflow:auto;">

    </ul>

</div>

</body>

</html>

 

 

 

1.6.2.4.            树插件

 

同步刷新左侧页面。

 

左侧树列表页面js

<script type="text/javascript">

var myTree;

$(function(){

   myTree = $("#tree").showTree();

});

function refreshTree(){

    alert("refresh");

    myTree.reAsyncChildNodes(null,"refresh");//helper不写也行

}

</script>

右侧的子栏目表格中点击添加按钮后再回到子栏目列表页面,js如下:

<script type="text/javascript">

parent.refreshTree();

</script>

 

左侧右侧在一个Table

 

 

 

1.6.3.   表格行拖拽排序

 

使用jquery中的一个组件:sortable

可以指定导入包:jquery.ui.core.jsjquery.ui.widget.jsjquery.ui.mouse.jsjquery.ui.sortable.js

 

<script src="../../ui/jquery.ui.core.js"></script>

<script src="../../ui/jquery.ui.widget.js"></script>

<script src="../../ui/jquery.ui.mouse.js"></script>

<script src="../../ui/jquery.ui.sortable.js"></script>

<link rel="stylesheet" href="../demos.css">

 

或者直接导入:

jquery-ui-1.10.0.custom.min.js

 

【使用示例】:

可以为<ul>设置排序

<ul>

    <li></li>

    <li></li>

    <li></li>

</ul>

也可以为表格设置排序:

<div id="content">

    <h3 class="admin_link_bar">

       <jsp:include page="inc.jsp"></jsp:include>

    </h3>

    <input type="hidden" id="refresh" value="${refresh}"/>

    <table width="580" cellspacing="0" class="listTable">

       <thead>

       <tr>

 

js引入包后,使用下面的代码就可以实现表格的拖拽排序了:

<script type="text/javascript">

parent.refreshTree();

$(function(){

    $(".listTable tbody").sortable({helper:"clone"});

});

</script>

 

每一个被排序的行都是一个helper,可以为helper定一个函数:

完整代码:

 

<script type="text/javascript">

parent.refreshTree();

$(function(){

    //点击开始排序按钮后会修改此值

    var _sort=false;

   

    $(".test tbody").sortable({

       axis:"y",//只在y坐标上上下移动(若不设置会上下左右移动)

       helper:dragEle,

       update:changeSeria

    });

    //------------------------------------------------

    //排序更新的时候触发此函数

    function changeSeria(e,ui){

       setNum();

    }

    //-------------------------------------------------

    //为被拖动的元素(即helper)设置执行函数

    function dragEle(eve,ele){

       var _originalEle=ele.children();//得到原始tr中的所有td对象

       var _helper=ele.clone();//新的helper和原始元素相同

       _helper.children().each(function(index){

           //index表示当前td元素的序号,设置宽度相同

           $(this).width(_originalEle.eq(index).width());

       });

       //更改(_helpertr的颜色

       _helper.css("background","grey");

       return _helper;

    }

    $(".test tbody").sortable("disable");//阻止排序

   

    //-------------------------------------------------

    /**点击开始排序按钮后设置表格可排序*/

    $("#beginSort").click(function(){

       if( _sort ){//已经是开始排序状态

           alert("已经是排序状态");

           return;

       }

       //theadtr增加<td>

       $(".test thead tr").append("<td>序号</td>");

      

       /*tbody中的每一个tr增加一个<td></td>,但是希望排序后的序号依旧

       是从小到大排序,所以下面的代码写到函数中,每次更新排序都执行

       $(".test tbody tr").each(function(index){

           $(this).append("<td>"+(index+1)+"</td>");

       });*/

       setNum();

        //tfoottr增加<td>

        $(".test tfoot tr").append("<td>拖动排序</td>");

       $(".test tbody").sortable("enable");//允许排序

        _sort=true;

    });

    //-------------------------------------------------

    /**点击存储排序按钮*/

    $("#saveSort").click(function(){

        if( !_sort ){

           alert("请先排序");

           return;

        }

        $(".test tr").find("td:last").remove();//视频中使用循环

        $(".test tbody").sortable("disable");//阻止排序

        _sort=false;

    });

    //-------------------------------------------------

    /**每次更新排序都执行此函数,重新设置序号*/

    function setNum(){

        $(".test tbody tr").each(function(index){

            if(_sort){

               $(this).find("td:last").html(index+1);

            }else{

                $(this).append("<td>"+(index+1)+"</td>");

            }

        });

    }

});

</script>

</head>

<body>

<br></br><br/><br><br>

<table class="test" border="1" width="500px">

    <thead>

        <tr><td>No</td><td>Name</td><td>Age</td></tr>

    </thead>

    <tbody>

        <tr><td>1</td><td>Jack</td><td>18</td></tr>

        <tr><td>2</td><td>Tom</td><td>19</td></tr>

        <tr><td>3</td><td>Helen</td><td>20</td></tr>

    </tbody>

    <tfoot>

        <tr>

            <td colspan="3" align="center">

                <a id="beginSort" href="#">开始排序</a>  

                <a id="saveSort" href="#">存储排序</a>

            </td>

        </tr>

    </tfoot>

</table>

</body>

</html>

 

 

 

 

【补充与提示】:

(1)       找到tr的最后一个td并修改里面的值:

 

function setNum(){

       $(".test tbody tr").each(function(index){

           $(this).find("td:last").append("<td>"+(index+1)+"</td>");

       });

   }

或者

$(this).children().last().append("<td>"+(index+1)+"</td>");

 

 

(2)        

 

 

1.6.4.   特别提示

 

我们在闭包:

(function($){

    //这个方式定义的函数我们可以直接在js中这么调用:$.checkAjax(data);

    $.checkAjax(data){

       if( data.result ){

           return true;

       }else{

           alert(data.msg);

           return false;

       }

    }

    //这个定义函数的方法我们可以在js中这么调用:$(“tbody”).test();

    $.fn.test=function(opts){

       var setting=$.extend({},opts||{});

    }

})(jQuery);

 

 

1.6.5.   关于枚举映射

 

@Entity

@Table(name = "t_role")

public class Role {

    /** 主键 */

    private int id;

 

    /** 角色中文名 */

    private String name;

 

    /** 角色代号 */

    private String roleShortName;

 

    /** 角色类型(枚举) */

    private RoleType roleType;

 

    public Role() {

    }

    @Column(name = "role_type")

    @Enumerated(EnumType.ORDINAL)

    public RoleType getRoleType() {

       return roleType;

    }

}

 

form表单

<form>

<tr>

       <td class="rightTd">角色类型:</td>

       <td class="leftTd">

            <sf:radiobuttons path="roleType" items="${types}"/>

       </td>

</tr>

</form>

 

Controller

//页面跳转

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

public String add(Model model){

    model.addAttribute(new Role());

    List<String> list =  EnumUtils.enum2Name(RoleType.class);

    model.addAttribute("types",list);

    return "role/add";

}

//插入操作

@RequestMapping(value="/add",method=RequestMethod.POST)

public String add(Role role){

    roleService.add(role);

    return "redirect:/admin/role/list";

}

【注意点】:

上面的代码所要提示的是,EnumUtils.enum2Name(RoleType.class); 上这个是将Enum的值映射成StringListADMINPUBLISHER等),是因为这些在页面上的显示效果是:

<input id="roleType1" name="roleType" type="radio" value="ADMIN">

<label for="roleType1">ADMIN</label>

这里的value=”ADMIN”,所以Controller中的Role才能接收到。开始使用EnumUtils.enum2BasicMap(RoleType.class); R将枚举转换成<0,”ADMIN”>这样的Map映射到页面上的,所以页面上就显示:

<input id="roleType1" name="roleType" type="radio" value="0">

<label for="roleType1">ADMIN</label>

就不能完成到Controller的映射了。

 

2.                  设置栏目

 

 

2.1.  dwr的使用

 

介绍:dwr(Direct Web Remoting),百度百科:

DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJAX技术的网站。它可以允许在浏览器里的代码使用运行在WEB服务器上的JAVA函数,就像它就在浏览器里一样。

简单说,dwr可以在js中直接调用Java代码。

 

使用步骤:

步骤1:新建maven-web项目:

l  新建一个mavenweb项目用来测试dwr,注意新建的mavenweb项目的web.xml是有问题的,应该使用如下的dtd

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    id="WebApp_ID" version="2.5">

 

步骤2:配置web.xml

web.xml中的监听器类使用这个才正常:

    <listener>

     <listener-class>

        org.directwebremoting.servlet.EfficientShutdownServletContextAttributeListener

     </listener-class>

    </listener>

   

    <listener>

     <listener-class>

        org.directwebremoting.servlet.EfficientShutdownServletContextListener

     </listener-class>

    </listener>

否则报错:

java.lang.ClassNotFoundException: org.directwebremoting.servlet.DwrListener

 

 

 

步骤3:写一个Service

public class HelloDwr {

    public String sayHello(String name){

       System.out.println("zmj --> name="+name);

       return "Hello,"+name;

    }

}

步骤4:编写配置文件dwr.xml

WEB-INF中编写dwr的配置文件dwr.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE dwr PUBLIC

"-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN"

"http://getahead.org/dwr/dwr30.dtd">

 

<dwr>

  <allow>

    <!-- create就表示将一个java对象公布为一个JavaScript对象 -->

    <create creator="new" java>

      <param name="class" value="com.zmj.service.HelloDwr"/>

    </create>

  </allow>

</dwr>

createJavaScript属性可以为公布的对象设定一个别名,但是在页面中就要用hello.js来引入。

<dwr>

  <allow>

    <create creator="new" javascript="hello">

      <param name="class" value="com.zmj.service.HelloDwr"/>

    </create>

  </allow>

</dwr>

然后就可以使用hell调用HelloDwr.java中的函数。

 

 

步骤5:创建页面文件dwr01.jsp调用java

 

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title>Insert title here</title>

<%--【引入engin.js----------------------------------------

 engin.js不用真的引入,只要在这里声明即可,dwr会自动生成

(因为在web.xml中已经配置了<url-pattern>/dwr/*</url-pattern>)

所以配置了dwr开头的都会到dwr自己的空间查找对应的资源

提醒:引js的时候不要换行

---------------------------------------------------- --%>

<script type="text/javascript" src="<%=request.getContextPath() %>>/dwr/engine.js"></script>

<%--【引入java类】-------------------------------------------

在下面配置了HelloDwr.js,这个是在dwr.xml中配置了:

 <param name="class" value="com.zmj.service.HelloDwr"/>

相当于就将JavaHelloDwr.java类引入了

------------------------------------------------------ --%>

<script type="text/javascript"

     src="<%=request.getContextPath() %>>/dwr/interfaceHelloDwr.js" ></script>

<%--【在js中调用java方法】------------------------------- --%>

<script type="text/javascript">

     HelloDwr.sayHello("Jack");

</script>

</head>

<body>

 

</body>

</html>

 

调用java中的方法的时候,前面的参数都是实参,最后一个参数是实参,可以获得调用函数的返回值:

<script type="text/javascript">

     HelloDwr.sayHello("Jack");

     HelloDwr.sayHello("Jack",function(data){

         alert(data);

     });

     //最后一个参数就是回调函数,返回函数调用的返回值

</script>

 

 

 

 

步骤6:启动项目

引入jetty,启动:clean jetty:run

 

2.2.     dwr:返回实体、异常

 

需求:从java获取User列表并在页面的table中显示:

User.java

public class User {

    private int id;

    private String name;

    private Group group;

 

Group.java

public class Group {

    private int id;

    private String name;

 

UserService.java(用map模拟数据库操作):

public class UserService {

    private static Map<Integer,User> users = new HashMap<Integer,User>();

    static {

       users.put(1, new User(1,"孙悟空",new Group(1,"取经组")));

       users.put(2, new User(2,"猪八戒",new Group(1,"取经组")));

       users.put(3, new User(3,"白骨精",new Group(2,"吃肉组")));

       users.put(4, new User(4,"牛魔王",new Group(2,"吃肉组")));

    }

    public UserService() {

      

    }

    public User load(int id) {

       return users.get(id);

    }

   

    public void add(User user) {

       users.put(user.getId(), user);

    }

   

    public void deleteUser(int id) {

       if(!users.containsKey(id)) throw new UserException("删除的用户不存在!");

       users.remove(id);

    }

   

    public List<User> list() {

       Set<Integer> keys = users.keySet();

       List<User> us = new ArrayList<User>();

       for(Integer key:keys) {

           us.add(users.get(key));

       }

       return us;

    }

}

 

dwr.xml配置:

<dwr>

 <allow>

    <!--此时hello就是这个HelloDwr对象,但在页面中要通过hello.js来引入 -->

    <create creator="new" javascript="hello">

       <param name="class" value="org.konghao.service.HelloDwr" />

    </create>

    <create creator="new" javascript="UserService">

       <param name="class" value="org.konghao.service.UserService" />

       <include method="list" />

    </create>

    <!-- 使用Convert转换器将java的实体bean转化成JavaScript对象 -->

    <convert converter="bean" match="org.konghao.service.User" />

    <convert converter="bean" match="org.konghao.service.Group" />

    <!-- 使用异常-->

    <convert converter="exception" match="java.lang.Exception" />

    <convert converter="bean" match="java.lang.StackTraceElement" />

 </allow>

</dwr>

 

可以精确控制类的哪些方法可以访问,必须有JavaScript属性:

<create creator="new" javascript="UserService">

    <param name="class" value="org.konghao.service.UserService" />

    <!-- 精确控制哪些方法被公布:include表示被包含的可以访问 -->

    <include method="list" />

    <!-- exclude.delete表示除了delete都可以访问 -->

    <exclude method="delete" />

</create>

 

 

 

在页面使用:

 

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Insert title here</title>

<!-- 必须引入dwr的engine.js -->

<script type="text/javascript" src="<%=request.getContextPath()%>

/dwr/engine.js"></script>

<script type="text/javascript" src="<%=request.getContextPath()%>

/dwr/util.js"></script>

<!-- 将java的类引入 -->

<script type="text/javascript" src="<%=request.getContextPath()%>

/dwr/interface/UserService.js"></script>

<script type="text/javascript">

    window.onload = init;

    //配置全局异常的处理

    dwr.engine.setErrorHandler(errorHandler);

    function init() {

       /*在引入了util.js之后可以直接使用$("xxx")来获取id对象,

       但是通常不赞成使用,特别是在使用jquery之后*/

       initTable();

    }

    function search() {

       var id = document.getElementById("searchId").value;

       UserService.load(id,function(data){

           document.getElementById("searchResult")

           .innerHTML = "搜索结果:"

           +data.name+"-->"+data.group.name;

       });

    }

    function initTable() {

       UserService.list(function(data){

           var t = document.getElementById("users");

           if(t.getElementsByTagName("tbody")[0])

              t.removeChild(t.getElementsByTagName("tbody")[0]);

          

           var tb = "<tbody>"

           for(var i=0;i<data.length;i++) {

              tb+="<tr><td>"+data[i].id+"</td><td>"+data[i].name

              +"</td><td>"+data[i].group.name+"</td></tr>"

           }

           tb+="</tbody>"

           t.innerHTML=t.innerHTML+tb;

       });

    }

    function addUser() {

       var uid = document.getElementById("userId").value;

       var uname = document.getElementById("username").value;

       var gid = document.getElementById("groupId").value;

       var gname = document.getElementById("groupName").value;

       var u = {"id":uid,"name":uname,group:{"id":gid,"name":gname}};

       UserService.add(u);

       initTable();

    }

    function deleteUser() {

       var did = document.getElementById("did").value;

       /*UserService.deleteUser(did,{

           callback:function(data){

              initTable();

           },

           errorHandler:function(msg,e) {

             

           }

       });*/

       UserService.deleteUser(did,function(data){

           initTable();

       });

             

    }

   

    function errorHandler(msg,e) {

       alert(msg);

       for(var eo in e) {

           alert(eo+"------>"+e[eo]);

       }

    }

</script>

</head>

<body>

<table id="users" border="1" width="700" align="center">

    <thead>

    <tr><td colspan="3">输入id:<input type="text" id="searchId"/>

       <input type="button" value="search"

             id="searchBtn" onclick="search()"/>

    </td></tr>

    <tr><td>用户标识</td><td>用户名</td><td>用户所在组</td></tr>

    </thead>

</table>

<p>

    <input type="text" id="did"/>

    <input type="button" value="删除" onclick="deleteUser()"/>

</p>

<p>

用户名:<input type="text" id="username"/>

用户id:<input type="text" id="userId"/><br/>

组名称:<input type="text" id="groupName"/>

组id:<input type="text" id="groupId"/><br/>

<input type="button" value="添加用户" onclick="addUser()"/>

</p>

 

<div id="searchResult"></div>

</body>

</html>

 

 

 

 

 

2.3.     文件上传

(1)       先导入fileupload包:

<dependency>

    <groupId>commons-fileupload</groupId>

    <artifactId>commons-fileupload</artifactId>

    <version>1.3</version>

</dependency>

 

(2)       页面:

 

<script type="text/javascript">

    function upload() {

       var f = document.getElementById("uf");

       hello.upload(f,f.value,function(data){

           alert(data);

       });

    }

</script>

</head>

<body>

    <input type="file" id="uf"/>

    <input type="button" value="上传" onclick="upload()"/>

</body>

</html>

 

(3)       Java

 

public class HelloDwr {

    /**

     * 第一个参数是页面获取的文件,第二个参数是文件名

     * @param is

     * @param filename

     * @return

     * @throws IOException

     */

    public String upload(InputStream is,String fame) throws Exception {

       //可以获取相应的ServletApi

       WebContext wc = WebContextFactory.get();

       HttpServletRequest req = wc.getHttpServletRequest();

       String realPath = req.getSession().getServletContext().getRealPath("/img");

       String fn = FilenameUtils.getName(fame);

       FileUtils.copyInputStreamToFile(is, new File(realPath+"/"+fn));

       return fn;

    }

}

 

2.4.     DWRSpring整合

有三种方法:

2.4.1.   没有使用SpringMVC

(好像也使用了……)

步骤1:导入包:

<dependencies>

   <dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-context</artifactId>

    <version>${spring.version}</version>

</dependency>

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-core</artifactId>

    <version>${spring.version}</version>

</dependency>

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-beans</artifactId>

    <version>${spring.version}</version>

</dependency>

<dependency>

  <groupId>org.springframework</groupId>

  <artifactId>spring-webmvc</artifactId>

  <version>3.2.2.RELEASE</version>

</dependency>

<dependency>

    <groupId>org.directwebremoting</groupId>

    <artifactId>dwr</artifactId>

    <version>3.0.M1</version>

</dependency>

 </dependencies>

 

步骤2:创建Service及其接口:

public interface IHelloService {

    public String say(String name);

}

实现类:

@Service("helloService")

public class HelloService implements IHelloService {

    public String say(String name) {

       return "hello:"+name;

    }

}

 

步骤3beans.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

         http://www.springframework.org/schema/context

         http://www.springframework.org/schema/context/spring-context-3.0.xsd

         http://www.springframework.org/schema/aop

         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd

         http://www.springframework.org/schema/tx

         http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!-- 打开Spring的Annotation支持 -->

    <context:annotation-config />

    <!-- 设定Spring 去哪些包中找Annotation -->

    <context:component-scan base-package="org.konghao" />

</beans>

 

步骤4dwr-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE dwr PUBLIC

"-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN"

"http://getahead.org/dwr/dwr30.dtd">

<dwr>

  <allow>

    <create creator="spring" javascript="hello">

       <param name="beanName" value="helloService"/>

    </create>

  </allow>

</dwr>

 

步骤5web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    id="WebApp_ID" version="2.5">

   

<!-- 创建Spring的监听器 -->

    <listener>

       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

    <!-- Spring 的监听器可以通过这个上下文参数来获取beans.xml的位置 -->

    <context-param>

       <param-name>contextConfigLocation</param-name>

       <param-value>classpath*:beans.xml</param-value>

 

    </context-param>

   

  <servlet>

       <servlet-name>dwr</servlet-name>

       <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    </servlet>

    <servlet-mapping>

       <servlet-name>dwr</servlet-name>

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

    </servlet-mapping>

    <filter>

       <filter-name>CharacterFilter</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>CharacterFilter</filter-name>

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

    </filter-mapping>

   

  <listener>

    <listener-class>org.directwebremoting.servlet.DwrListener</listener-class>

  </listener>

 

  <servlet>

    <servlet-name>dwr-invoker</servlet-name>

    <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>

   

    <!-- This should NEVER be present in live -->

    <init-param>

      <param-name>debug</param-name>

      <param-value>true</param-value>

    </init-param>

 

    <load-on-startup>1</load-on-startup>

  </servlet>

  

  <servlet-mapping>

    <servlet-name>dwr-invoker</servlet-name>

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

  </servlet-mapping>

 

</web-app>

 

步骤6:页面:

<script type="text/javascript">

    hello.say("zhang",function(data){

       alert(data);

    })

</script>

</head>

<body>

 

</body>

</html>

 

 

 

 

2.4.2.   使用了SpringMVC(不用注解)

 

SpringMVC整合不需要dwr.xml配置文件

 

User.java

public class User {

    private int id;

    private String name;

 

Service

public class HelloService implements IHelloService {

    public String say(String name) {

       return "hello:"+name;

    }

    @Override

    public User load() {

       return new User(1,"abc");

    }

}

 

beandebuglistener都配置在dwr-servlet.xml中:

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd

       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

       http://www.directwebremoting.org/schema/spring-dwr

    http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd">

   

    <mvc:annotation-driven/>

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

    <context:component-scan base-package="org.konghao.cms.controller"/>

    <bean class="org.springframework.web.servlet.

handler.SimpleUrlHandlerMapping">

      <property value="true" name="alwaysUseFullPath"></property>

      <property name="mappings">

        <props>

          <prop key="/dwr/**/*">dwrController</prop>

        </props>

     </property>

    </bean>

   

    <!-- dwr控制器,可以控制debug -->

    <dwr:controller id="dwrController" debug="true"/>

   

    <!-- dwr转换器 -->

    <dwr:configuration>

       <dwr:convert type="bean" class="org.konghao.service.User"/>

    </dwr:configuration>

   

    <!-- 如果Service没有使用注解,需要在这里配置 Service,是不是使用注解

    就要单独创建一个beans.xml了?这是两者的区别?-->

    <bean id="helloService" class="org.konghao.service.HelloService">

       <dwr:remote javascript="hello">

           <dwr:include method="load" />

       </dwr:remote>

    </bean>

   

    <bean class="org.springframework.web.servlet.

view.InternalResourceViewResolver">

       <property name="prefix" value="/WEB-INF/jsp/"/>

       <property name="suffix" value=".jsp"/>

    </bean>

   

    <bean id="exceptionResolver" class="org.springframework.web

.servlet.handler.SimpleMappingExceptionResolver">

       <property name="exceptionMappings">

           <props>

               <prop key="org.konghao.cms.model.CmsException">error

</prop>

           </props>

       </property>

    </bean>

</beans>

 

页面:

<script type="text/javascript">

    hello.load(function(data){

       alert(data.name);

    })

</script>

</head>

<body>

</body>

</html>

 

 

 

 

 

2.4.3.   使用了SpringMVC(用dwr注解)

 

实体:

/*转换成dwr对象,但一般不会在实体上使用注解而是在配置文件中使用,不然实体容易被破坏*/

@RemoteProxy

public class User {

    private int id;

    private String name;

 

Service

@RemoteProxy(name="helloService")

public class HelloService implements IHelloService {

    @RemoteMethod

    public String say(String name) {

       return "hello:"+name;

    }

    @Override

    @RemoteMethod

    public User load() {

       return new User(1,"abc");

    }

}

 

dwr-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd

       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

       http://www.directwebremoting.org/schema/spring-dwr

    http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd">

   

    <mvc:annotation-driven/>

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

    <context:component-scan base-package="org.konghao.cms.controller"/>

    <bean class="org.springframework.web.servlet

                 .handler.SimpleUrlHandlerMapping">

      <property value="true" name="alwaysUseFullPath"></property>

      <property name="mappings">

        <props>

          <prop key="/dwr/**/*">dwrController</prop>

        </props>

     </property>

    </bean>

   

    <dwr:controller id="dwrController" debug="true"/>

    <dwr:configuration>

       <dwr:convert type="bean" class="org.konghao.service.User"/>

    </dwr:configuration>

   

    <dwr:annotation-config id="dwrAnnotationConfig" />

    <dwr:annotation-scan base-package="org.konghao.service"

                         scanDataTransferObject="true"/>

   

    <bean class="org.springframework.web.servlet

                         .view.InternalResourceViewResolver">

       <property name="prefix" value="/WEB-INF/jsp/"/>

       <property name="suffix" value=".jsp"/>

    </bean>

   

    <bean id="exceptionResolver" class="org.springframework.web

                  .servlet.handler.SimpleMappingExceptionResolver">

       <property name="exceptionMappings">

           <props>

              <prop key="org.konghao.cms.model.CmsException">error

              </prop>

           </props>

       </property>

    </bean>

</beans>

 

2.5.     Dwrcms项目中的使用

 

cms-web中做如下配置:

(1)       web.xml中加入如下内容:

<!-- 过滤Dwr请求 -->

<servlet-mapping>

    <servlet-name>dwr</servlet-name>

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

</servlet-mapping>

 

(2)       cms-servlet.xml添加如下内容:

<bean class="org.springframework.web.servlet

.handler.SimpleUrlHandlerMapping">

    <property value="true" name="alwaysUseFullPath"></property>

    <property name="mappings">

      <props>

        <prop key="/dwr/**/*">dwrController</prop>

      </props>

    </property>

</bean>

<!-- 关闭dwr的debug -->

<dwr:controller id="dwrController" debug="false"/>

<!-- 设置dwr的扫描包 -->

<dwr:annotation-config id="dwrAnnotationConfig" />

   <dwr:annotation-scan base-package="com.zmj.dwr.service"

scanDataTransferObject="true"/>

 

(3)       我们需要调用GrouService中的deleteChanneladdChannel方法,但是这个是在另外一个包中,而且会破坏原来的代码(加入加上dwr注解的话),所以我们的解决办法是在web中新建一个DwrService然后对这个DwrService做控制

 

/***************************************************

 * 注解相当于在dwr.xml中做如下类似配置

  <allow>

   <create creator="spring" javascript="hello">

       <param name="beanName" value="helloService"/>

    </create>

  </allow>

 *****************************************************/

@RemoteProxy(name="dwrService")

public class DwrService implements IDwrService {

    private IGroupService groupService;

    public IGroupService getGroupService() {

       return groupService;

    }

    @Inject

    public void setGroupService(IGroupService groupService) {

       this.groupService = groupService;

    }

    @Override

    public void addGroupChannel(Integer gid, Integer cid) {

       groupService.addGroupChannel(gid, cid);

    }

    @Override

    public void deleteGroupChannel(Integer gid, Integer cid) {

       groupService.deleteGroupChannel(gid, cid);

    }

}

 

 

 

 

(4)       树展开示例:

<script type="text/javascript">

$(function(){

    //已经选中的复选框

    var checked;

    var t = $("#tree").showTree({

            url:$("#treePath").val(),

            myconst:{listChild:0},

            callback:{

                //这个方法好像 就是加载完成后执行的方法

                onAsyncSuccess:initTree,

                onCheck:onCheckFun,//执行勾选函数

                beforeCheck:befCheckFun

            },

            check:{

                enable:true,

                chkboxType: { "Y": "p", "N": "ps" }

            }

    });

   

    function befCheckFun(treeId,treeNode){

       

    }

   

    function onCheckFun(event,treeId,treeNode){

        //打印treeId将显示“tree”,treeNode.id才是各个节点的id

        //alert(treeNode.id+":"+treeNode.checked);

       

    }

   

    function initTree(){

        t.expandAll(true);

        //获取id为7的节点(id是指ChannelTree中的id)

        //var n = t.getNodeByParam("id",1,null);

        //t.checkNode(n,true,true);

        /*-------------------------------------------------

        <c:forEach items="${cids }" var="cid">

            <input type="hidden" name="cids" value="${cid }">

        </c:forEach>

        ---------------------------------------------------*/

        var cids=$("input[name='cids']");

        for( var i=0;i<cids.length;i++ ){

            var cid=cids[i].value;

            var n = t.getNodeByParam("id",1,null);

            t.checkNode(n,true,true);

        }

        //checked=t.getChangeCheckedNodes();该方法可以获取当前被选中者

    }

   

});

</script>

 

 

 

 

 

(5)        

 

 

2.6.     登录验证码

 

 

 

 

 

 

2.7.     使用注解权限控制

 

2.7.1.   思路以及注解

 

思路:

将所有Controlle中的所有的类、所有的方法都进行扫描,将各个角色能访问的方法存放在一个Map<String, Set<String>>中,key是角色名,valueSetset中是类的完整名+方法名;这个存储的动作在系统启动的时候做初始化,然后存储到ApplicationContext中。

 

注解:

类上的注解AuthClass,该注解表示这个类需要做权限控制,只有这个扫描的时候仅扫描有这个注解的类:

/**

 * 只要在Controller上增加了这个方法的类,都需要进行权限的控制

 * @author Administrator

*/

@Retention(RetentionPolicy.RUNTIME)

public @interface AuthClass {

    /**

     * 如果value为admin就表示这个类只能超级管理员访问

     * 如果value为login表示这个类中的方法,某些可能为相应的角色可以访问

     * @return

     */

    public String value() default "admin";

}

 

方法上的注解:

/**

 * 用来确定哪些方法由哪些角色访问

 * 属性有一个role:如果role的值为base表示这个方法可以被所有的登录用户访问

 * 如果为ROLE_PUBLISH表示只能为文章发布人员访问

 * 如果某个方法中没有加入AuthMethod就表示该方法只能被管理员所访问

 * @author Administrator

 *

 */

@Retention(RetentionPolicy.RUNTIME)

public @interface AuthMethod {

    public String role() default "base";

}

 

扫描所有类、所有方法的代码:

public class AuthUtil {

    /**

     * 初始化系统的角色所访问的功能信息

     * @return

     */

    @SuppressWarnings({ "rawtypes", "unchecked" })

    public static Map<String,Set<String>> initAuth(String pname) {

       try {

           //auths记录各个AuthMethod中记录的角色(base、ROLE_ADMIN等)能访问的方法(类完整名+方法名)

           Map<String,Set<String>> auths = new HashMap<String, Set<String>>();

           String[] ps = getClassByPackage(pname);

           for(String p:ps) {

              String pc = pname+"."+p.substring(0,p.lastIndexOf(".class"));

              //得到了类的class对象

              Class clz = Class.forName(pc);

              if(!clz.isAnnotationPresent(AuthClass.class)) continue;

              //System.out.println(pc);

              //获取每个类中的方法,以此确定哪些角色可以访问哪些方法

              Method[] ms = clz.getDeclaredMethods();

              /*

               * 遍历method来判断每个method上面是否存在相应的AuthMethd

               * 如果存在就直接将这个方法存储到auths中,如果不存在就不存储

               * 不存储就意味着该方法只能由超级管理员访问

               */

              for(Method m:ms) {

                  if(!m.isAnnotationPresent(AuthMethod.class)) continue;

                  //如果存在就要获取这个Annotation

                  AuthMethod am = m.getAnnotation(AuthMethod.class);

                  String roles = am.role();  

                  //可能一个action可以被多个角色所访问,使用,进行分割

                  String[] aRoles = roles.split(",");

                  for(String role:aRoles) {

                     Set<String> actions = auths.get(role);

                     if(actions==null) {

                         actions = new HashSet<String>();

                         auths.put(role, actions);

                     }

                     actions.add(pc+"."+m.getName());

                  }

              }

           }

           return auths;

       } catch (ClassNotFoundException e) {

           e.printStackTrace();

       }

       return null;

    }

    /**

     * 根据包获取所有的类

     * @param pname

     * @return

     */

    private static String[] getClassByPackage(String pname) {

       String pr = pname.replace(".", "/");

       //包路径

       String pp = AuthUtil.class.getClassLoader().getResource(pr).getPath();

       File file = new File(pp);

       String[] fs = file.list(new FilenameFilter() {

           @Override

           public boolean accept(File dir, String name) {

              if(name.endsWith(".class")) return true;

              return false;

           }

       });

       return fs;

    }

   

    public static void main(String[] args) {

       System.out.println(initAuth("org.konghao.cms.controller"));

    }

}

 

 

2.7.2.   初始化权限

 

【提示】:

几乎所有的web项目在启动的时候都会有一个初始化方法,比如,最少也要启动Spring工厂(和初始化Spring不一样,Spring工厂可以使用getBean()方法获取对象)。我们的项目中,我们要将Spring工厂和权限信息都在一个Servlet中初始化。

 

初始化Servlet代码:

public class InitServlet extends HttpServlet {

 

    private static final long serialVersionUID = 1L;

    private static WebApplicationContext wc;

   

    @Override

    public void init(ServletConfig config) throws ServletException {

        super.init(config);

       //初始化spring的工厂

       wc = WebApplicationContextUtils

              .getWebApplicationContext(this.getServletContext());

       //初始化权限信息

       Map<String,Set<String>> auths = AuthUtil

              .initAuth("org.konghao.cms.controller");

       this.getServletContext().setAttribute("allAuths", auths);

       System.out.println("-------系统初始化成功:------------");

    }

   

    public static WebApplicationContext getWc() {

       return wc;

    }

}

 

web.xml中配置,在系统启动的时候就初始化:

<servlet>

    <servlet-name>initServlet</servlet-name>

    <servlet-class>org.konghao.cms.web.InitServlet</servlet-class>

    <load-on-startup>1</load-on-startup>

</servlet>

不用写Mapping

 

 

 

2.7.3.   用户登录后获取用户权限

 

@RequestMapping(value="/login",method=RequestMethod.POST)

public String login(String username,String password,String checkcode,Model model,HttpSession session) {

    String cc = (String)session.getAttribute("cc");

    if(!cc.equals(checkcode)) {

       model.addAttribute("error","验证码出错,请重新输入");

       return "admin/login";

    }

    User loginUser = userService.login(username, password);

    session.setAttribute("loginUser", loginUser);

    List<Role> rs = userService.listUserRoles(loginUser.getId());

    boolean isAdmin = isRole(rs,RoleType.ROLE_ADMIN);

    session.setAttribute("isAdmin", isAdmin);

 

    if(!isAdmin) {

    session.setAttribute("allActions", getAllActions(rs, session));

    session.setAttribute("isAudit", isRole(rs,RoleType.ROLE_AUDIT));

session.setAttribute("isPublish", isRole(rs,RoleType.ROLE_PUBLISH));

    }

    session.removeAttribute("cc");

    CmsSessionContext.addSessoin(session);

    return "redirect:/admin";

}

 

@SuppressWarnings("unchecked")

private Set<String> getAllActions(List<Role> rs,HttpSession session) {

    Set<String> actions = new HashSet<String>();

    Map<String,Set<String>> allAuths =

(Map<String,Set<String>>)session

.getServletContext().getAttribute("allAuths");

    actions.addAll(allAuths.get("base"));

    for(Role r:rs) {

       //为什么admin就跳过,不是很明白。教程中说方法上没有做ROLE_ADMIN的

       //限制

       if(r.getRoleType()==RoleType.ROLE_ADMIN) continue;

       actions.addAll(allAuths.get(r.getRoleType().name()));

    }

    return actions;

}

 

以后,用户访问每一个功能前,都要判断该用户有没有对应的权限(判断Session中的allAuth中有没有对应的方法名),有的话就正常访问,没有就抛出不能访问的异常。

 

 

2.7.4.   用户访问功能前判断权限

 

 

思路一:使用过滤器

但是使用过滤器无法很好的获取得到该用户访问的时候哪一个Controller中的哪一个方法。

 

思路二:使用拦截器

 

定义拦截器:

public class AuthInterceptor extends HandlerInterceptorAdapter {

 

    /**

     * 提交给人一个对象之前执行preHandle方法

     */

    @SuppressWarnings("unchecked")

    @Override

    public boolean preHandle(HttpServletRequest request,

           HttpServletResponse response, Object handler)

                  throws Exception {

       HttpSession session = request.getSession();

       HandlerMethod hm = (HandlerMethod)handler;

       //比如访问首页:就会打印AdminController、index信息

       System.out.println("zmj --> "+hm.getBean().getClass()

              .getName()+"."+hm.getMethod().getName());

       User user = (User)session.getAttribute("loginUser");

       if(user==null) {

           //注意:sendRedirect方法执行后,还会继续往后执行后面的代码

           response.sendRedirect(request.getContextPath()+"/login");

       } else {

           boolean isAdmin=(Boolean)session.getAttribute("isAdmin");

           if(!isAdmin) {

              //不是超级管理人员,就需要判断是否有权限访问某些功能

              Set<String> actions = (Set<String>)session

                     .getAttribute("allActions");

              String aname = hm.getBean().getClass().getName()

                     +"."+hm.getMethod().getName();

              if(!actions.contains(aname))

                  throw new CmsException("没有权限访问该功能");

           }

       }

       return super.preHandle(request, response, handler);

    }

 

    @Override

    public void afterCompletion(HttpServletRequest request,

           HttpServletResponse response, Object handler, Exception ex)

           throws Exception {

       //这里我们可以跳转视图

       super.afterCompletion(request, response, handler, ex);

    }

 

    @Override

    public void postHandle(HttpServletRequest request,

           HttpServletResponse response, Object handler,

           ModelAndView modelAndView) throws Exception {

       //这里我们可以做释放资源操作

       super.postHandle(request, response, handler, modelAndView);

    }

}

 

 

 

2.7.5.   关于在frameset中使用退出功能

如果直接使用如下的方法退出:

<a href="<%=request.getContextPath()%>/admin/user/logout" >退出系统</a>

那么引起的问题是,只会在自己的frame中刷新页面,而不会将整个页面刷新。

 

解决方法是在js中刷新页面,完整代码如下:

index.jsp

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>欢迎使用附中网站后台管理程序</title>

</head>

<frameset cols="*,1035,*"  border="0"

          frameborder="0" frameSpacing="0" scrolling="false">

    <frame src="<%=request.getContextPath() %>

              /resources/admin/background.html" frameSpacing="0">

    <frameset rows="110,*" frameborder="0" noresize frameSpacing="0">

       <frame name="top" src="<%=request.getContextPath() %>

             /jsp/admin/top.jsp" frameborder="0" frameSpacing="0"/>

       <frameset cols="164,*" frameborder="0" frameSpacing="0">

           <frame name="nav" src="<%=request.getContextPath() %>

             /jsp/admin/nav.jsp" frameborder="0"/>

           <frame name="content" src="<%=request.getContextPath() %>

             /resources/admin/01.html" frameborder="0"/>

       </frameset>

    </frameset>

    <frame src="<%=request.getContextPath() %>

              /resources/admin/background.html" frameSpacing="0">

</frameset>

</html>

 

top.jsp

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Insert title here</title>

<link rel="stylesheet" type="text/css"

href="<%=request.getContextPath() %>/resources/css/admin/main.css"/>

<script type="text/javascript" src="<%=request.getContextPath() %>

/resources/js/jquery-1.7.2.min.js"></script>

<script type="text/javascript">

function exitSystem() {

    //alert($("#contextPath").val()+"/admin/logout");

    window.parent.location.href

       = $("#contextPath").val()+"/admin/logout";

}

</script>

</head>

<body>

<jsp:include page="top_inc.jsp"></jsp:include>

</body>

</html>

 

top_inc.jsp

<input type="hidden" id="contextPath" value="<%=request.getContextPath()%>"/>

 

<div id="top">

    <div id="topIntro">

       <span id="logo"></span>

       <span id="user_operator">

           <a href="<%=request.getContextPath()%>/index" 

              target="_blank">网站首页</a>

           |<a href="<%=request.getContextPath()%>

            /admin/user/showSelf"  target="content">查询个人信息</a>

           | <a href="<%=request.getContextPath()%>

            /admin/user/updateSelf"  target="content">修改个人信息</a>

           | <a href="<%=request.getContextPath()%>

             /admin/user/updatePwd"  target="content">修改密码</a>

           | <a href="javascript:exitSystem()">退出系统</a>

       </span>

    </div>

    <div id="remind">

       <span id="showDate">

          欢迎[${loginUser.nickname }]光临${baseInfo.name }后台管理程序

       </span>

    </div>

</div>

 

2.7.6.   根据权限显示菜单

 

菜单也是一种资源,也是可以配置到数据库中的,本项目中就用简单的,在页面进行配置。

 

2.8.     文章、附件、关键字

2.8.1.   一些查询上的注意点

2.8.1.1.            使用构造函数查询

【文章查询】:使用构造函数查询需要的字段

Topic实体关联AttachmentChannelUser

 

private String getTopicSelect() {

    return "select new Topic(t.id,t.title,t.keyword,t.status,t.recommend,t.publishDate,t.author,t.cname)";

}

 

@Override

public Page<Topic> searchTopicByKeyword(String keyword) {

    String hql = getTopicSelect()+" from Topic t where t.status=1 and t.keyword like '%"+keyword+"%'";

    return this.listPage(hql);

}

 

上面的查询本来是可以是用left join fetch的,但是这样查的sql语句太长,可以使用构造函数 仅查询需要的字段。

 

【文章查询】:使用构造函数查询需要的字段

 

 

 

 

 

 

2.8.1.2.            实体排序

首先要实现Comparable接口,重写compareTo方法:

@Entity

@Table(name="t_keyword")

public class Keyword implements Comparable<Keyword>{

 

    private int id;

 

    /**关键字的名称*/

    private String name;

 

    /**被引用的次数*/

    private int times;

 

    /**关键字的全拼*/

    private String nameFullPy;

 

    /**关键字的简拼*/

    private String nameShortPy;

    @Override

    public int compareTo(Keyword o) {

       return this.times>o.times?-1:(this.times==o.times?0:1);

    }

}

以上是根据times的倒序排序。

 

 

2.8.1.3.            取属于某个文章的附件

首先,看附件Attachment实体的定义:

@Entity

@Table(name="t_attachment")

public class Attachment {

    private int id;

    /**附件上传之后的名称*/

    private String newName;

   

    /**附件的原始名称*/

    private String oldName;

   

    /**附件的类型,这个类型和contentType类型一致 */

    private String type;

   

    /**附件的后缀名*/

    private String suffix;

   

    /**附件的大小*/

    private long size;

   

    /**该附件是否是主页图片*/

    private int isIndexPic;

   

    /**该附件是否是图片类型,0表示不是,1表示是*/

    private int isImg;

   

    /**附件所属文章:MantyToOne*/

    private Topic topic;

   

    /**是否是附件信息,0表示不是,1表示是,

     * 如果是附件信息就在文章的附件栏进行显示*/

    private int isAttach;

 

 

如果我们来查询某个文章中的所有的附件可以像下面这样:

@Override

public List<Attachment> listByTopic(int tid) {

    /*这里查询有需要注意的地方:

     * 【注意点1:如果使用hql的这种查询方法,那么在查询a.topic的时

     *候会发出一条hql查询topic;

     * 【注意点2:如果使用left join fetch:select a from

     * Attachment a left join fetch a.topic t where a.topic.id

     * =t.id and t.id=tid,因为topic和User和Chennel关联,所以又会

     * 多发出hql去查询User和Channe。

     */

    String hql1 = "select a from Attachment a where a.topic.id=tid";

   

    String hql = getAttachmentSelect()

           +" from Attachment a where a.topic.id=?";

    return this.list(hql,tid);

}

 

我们的直觉是,在Attachment中增加一个冗余字段t_id,用来存储附件所属的文章,但是实际上已经有一个Hibernate生成的外键tidTopic关联了,这样做显然不合适。

 

上面问题的解决方法:

方法1

步骤1Attachment生成一个构造函数:

@Entity

@Table(name="t_attachment")

public class Attachment {

    private int id;

    /**附件上传之后的名称*/

    private String newName;

   

    /**附件的原始名称*/

    private String oldName;

   

    /**附件的类型,这个类型和contentType类型一致 */

    private String type;

   

    /**附件的后缀名*/

    private String suffix;

   

    /**附件的大小*/

    private long size;

   

    /**该附件是否是主页图片*/

    private int isIndexPic;

   

    /**该附件是否是图片类型,0表示不是,1表示是*/

    private int isImg;

   

    /**附件所属文章:MantyToOne*/

    private Topic topic;

   

    /**是否是附件信息,0表示不是,1表示是,

     * 如果是附件信息就在文章的附件栏进行显示*/

    private int isAttach;

   

    public Attachment() {

    }

    public Attachment(int id, String newName, String oldName,

           String type,String suffix, long size, int isIndexPic,

           int isImg, int isAttach,int tid,String topicTitle,

           Date publishDate,String author) {

       super();

       this.id = id;

       this.newName = newName;

       this.oldName = oldName;

       this.type = type;

       this.suffix = suffix;

       this.size = size;

       this.isIndexPic = isIndexPic;

       this.isImg = isImg;

       this.isAttach = isAttach;

       this.topic = new Topic();

       this.topic.setId(tid);

       this.topic.setTitle(topicTitle);

       this.topic.setPublishDate(publishDate);

       this.topic.setAuthor(author);

    }

}

 

步骤2:在Dao中这么查询:

@Repository("attachmentDao")

public class AttachmentDao extends BaseDao<Attachment> implements

       IAttachmentDao {

   

    private String getAttachmentSelect() {

       return "select new Attachment(a.id,a.newName,a.oldName,"

              + "a.type,a.suffix,a.size,a.isIndexPic,a.isImg,"

              + "a.isAttach,a.topic.id,a.topic.title,"

              + "a.topic.publishDate,a.topic.author)";

    }

    @Override

    public List<Attachment> listByTopic(int tid) {

       String hql = getAttachmentSelect()

              +" from Attachment a where a.topic.id=?";

       return this.list(hql,tid);

    }

}

 

【提示】:

Hibernate中用这样的方式查询数据库,那么Hibernate的效率会非常的好!

 

 

2.8.2.   XhEditor

 

 

 

比如给idcontent的文本域添加XhEditor

/**===============================================

 * 使用XhEditor设置编辑内容

 =================================================*/

var editor = $('#content').xheditor({tools:'full'});

 

 

 

 

 

2.8.3.   关于自动补全

 

 

关于自动补全:自动补全是jqueryui包自带的一个功能,使用方法如下:

<script type="text/javascript">

var data=["apple","orange","banana","pear"];

$("#keyword").autocomplete({

    source:data       //实际开发中写服务器请求数据地址

    minLength:2      //输入多少长度后再做匹配

});

 

</script>

 

 

2.8.4.   文件上传:uplodify

 

2.8.4.1.            讲解与示例

 

uploadify有两个版本,一个是flash版本还有一个是html5版本,本项目使用flash版本。另外,uplodify是基于jquery的,所以,必须使用jquery

 

 

文件上传要求:

上传后要返回上传的信息:文件名、文件大小、附件类型等;

 

 

使用步骤:

(1)      导入js包:

导入uploadify.css

导入jquery.uploadify.min.js

 

(2)      页面:

 

<script type="text/javascript">

    $(function() {

       $("#file_upload_1").uploadify({

           height : 30,

           //配置swf的(上传功能是基于这个组件的 )

           swf : $("#ctx").val+'/resources/uploadify/uploadify.swf',

           //uploader:上传给那个程序处理

           uploader : '/upload',

           //控制器里的参数是:upload(MultipartFile attach)名称要一致

           fileObjName:"attach",

           //限制文件上传的大小(是否可行未知)

           fileSizeLimit:"1000KB",

           //限制上传的文件格式

           fileTypeExts:"*.gif; *.jpg; *.png",

           //auto表示是否自动上传,默认是true

           auto:false,

           onUploadSuccess:function(file, data, response) {

              //三个参数分别为:文件、返回的字符串、true/false

                alert('fName:'+file.name+"resp:"+response+':'+data);

              //将字符串转换成json数据(不能直接alert(data.result))

              var jsonObj=$.parseJSON(data);

              alert(jsonObj.result);

            }

       });

      

       $("#upload").click(function(){

           //调用uplodify的upload的方法,*表示上传所有文件

           $("#attach").uploadify("upload","*");

       });

      

    });

</script>

</head>

<body>

    <input type="hidden" id="ctx" value="<%=request.getContextPath()%>"/>

    <input type="file" name="attach" id="attach" value="" />

    <input type="button" value="开始上传" id="upload"/>

</body>

</html>

 

 

(3)      控制器:

 

/**

 * 注意:这里返回的是json数据,但是uplodify只能接收String字符串

 * 所以:使用upload_2形式上传文件

 * @param attach

 * @return

 */

@RequestMapping(value="/upload")

public @ResponseBody AjaxObj upload_1(MultipartFile attach){

    System.out.println(attach.getOriginalFilename());

    return new AjaxObj();

}

 

/**

 * uploadify上传文件使用以下方式

 * @param attach

 * @param r

 */

@RequestMapping(value="/upload",method=RequestMethod.POST)

public void upload_2(MultipartFile attach,HttpServletResponse r){

    r.setContentType("text/plain;charset=utf-8");

    AjaxObj obj=new AjaxObj();

    try {

       //以字符串的形式返回json数据

       r.getWriter().write(JsonUtil.getInstance().obj2json(obj));

    } catch (IOException e) {

       e.printStackTrace();

    }

}

 

 

 

(4)      cms-servlet.xml中配置multipartresolver

 

<bean id="multipartResolver"    class="org.springframework.web

.multipart.commons.CommonsMultipartResolver">

    <property name="maxUploadSize" value="500000000"></property>

</bean>

 

(5)      导入commons-fileupload.jar

 

<dependency>

    <groupId>commons-fileupload</groupId>

    <artifactId>commons-fileupload</artifactId>

    <version>1.3</version>

</dependency>

 

 

2.8.4.2.            无刷新上传

 

方法可以使用AjaxDwr技术。

 

 

 

2.8.4.3.            图片压缩

 

一般都是将图片等比压缩成合适的大小。

使用工具thumbnails,下载jar包:

 

 

3.                  系统配置

 

3.1.  网站基本信息管理

 

 

网站的底部信息,比如备案号、联系电话什么的,可以存储到数据库中,但是不建议这么做,因为这样的信息比较少,不值得单独建一张表。

 

所以将信息配置在一个属性文件中,然后创建工具类可以修改这个属性文件。

 

 

 

3.2.  网站首页图片管理

 

上传图片的时候,可以使用裁剪工具对图片进行裁剪。jquery有这样的UI工具。但是jquery自带的不是很好用,有一个更方便使用的插件jcrop

 

 

 

3.3.     补充:网站首页图片排序

 

使用spinner给图片排序。

 

3.4.     链接管理

 

同样,使用spinner给链接排序。

 

 

 

 

3.5.     网站备份管理

 

3.5.1.   备份数据库

 

(提示:一般小网站就手动备份就可以了)。

 

备份内容:

(1)       数据库数据;

(2)       附件;

(3)       首页图片信息。

 

mysql的备份命令:

--数据库备份命令,> 号表示重定向

mysqldunp -u roo -p zmj_cms > c:/zmj_cms.sql

所以,备份数据库的思路是,使用Java执行数据库备份命令,来做备份。

 

控制台的/c作用是命令执行完后马上关闭窗口,停止往下执行,比如:

cmd /c ping http://wwww.baidu.com

 

Java中执行命令的方法是使用Process

 

备份和恢复数据库的Java示例代码:

public class TestCmd {

    @Test

    public void testSimpleCmd() {

       try {

           String cmd = "cmd /c dir c:\\";

           Process proc = Runtime.getRuntime().exec(cmd);

           BufferedReader br = new BufferedReader(new

                  InputStreamReader(proc.getInputStream(),"utf-8"));

           String str = null;

           while((str=br.readLine())!=null) {

              System.out.println(str);

           }

           br.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

    @Test

    public void testMySql() {

       try {

           String cmd = "cmd /c mysqldump -ufz -pfz123 fz_cms";

           Process proc = Runtime.getRuntime().exec(cmd);

           BufferedReader br = new BufferedReader(new

                  InputStreamReader(proc.getInputStream()));

           BufferedWriter bw = new BufferedWriter(

                  new FileWriter("d:/fz.sql"));

           String str = null;

           while((str=br.readLine())!=null) {

              bw.write(str);

              bw.newLine();

           }

           br.close();

           bw.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

    @Test

    public void testResume() {

       try {

           String cmd = "cmd /c mysql -ufz -pfz123 fz_cms";

           Process proc = Runtime.getRuntime().exec(cmd);

           BufferedWriter bw = new BufferedWriter(new

                  OutputStreamWriter(proc.getOutputStream()));

           BufferedReader br = new BufferedReader(new

                  FileReader("d:/fz.sql"));

           String str = null;

           while((str=br.readLine())!=null) {

              bw.write(str);

              bw.newLine();

           }

           br.close();

           bw.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

 

3.5.2.   备份文件

 

 

备份文件(附件、图片等):

l  思路是将文件夹复制到某个目录下,然后打包(一般建议打成tar包),然后将复制的文件夹删除,下次要恢复备份的时候,将tar包解压,再将里面的数据、文件恢复。

l  在linux下,压缩使用的是tar,但是只能使用单个文件,不能压缩文件夹,所以需要先对文件夹打包(使用tar命令),比如一个文件夹Test,先用tar命令打成Test.tar包(此时没有压缩),然后使用gzip命令,打成Test.tar.gz包(此时是真的压缩过的)。

 

解压使用apache的开源框架,jar包下载地址:

<dependency>

    <groupId>org.apache.commons</groupId>

    <artifactId>commons-compress</artifactId>

    <version>1.5</version>

</dependency>

 

java示例代码:

@Test

public void testSimpleTar() {

    try {

       String path = "D:\\作业\\tools\\jktkdzjst";

       String f = "d:/2013-8-25/test.tar";

       TarArchiveOutputStream taos = new TarArchiveOutputStream(new FileOutputStream(f));

       File of = new File(path);

       File[] ofs = of.listFiles();

       //遍历文件,并将文件加入到压缩包中      

       for(File off:ofs) {

           FileInputStream fis = new FileInputStream(off);

           /* *************************************************

            * 此时创建Entry时是通过off来传的,会自动找到相应的绝对

            * 路径完成打包,此时如果希望通过相对路径来打包,需要手

            * 动设置路径地址

            * *************************************************/

           //TarArchiveEntry tae = new TarArchiveEntry(off);

           String p = off.getParentFile().getParent();

           String pn = off.getParentFile().getAbsolutePath().substring(p.length()+1);

           System.out.println(pn);

           //手动设置打包地址,File.separator:根据os选择合适的/

           TarArchiveEntry tae = new TarArchiveEntry(pn+File.separator+off.getName());

           //设置大小

           tae.setSize(off.length());

           taos.putArchiveEntry(tae);

           IOUtils.copy(fis, taos);

           fis.close();

           taos.closeArchiveEntry();

       }

       taos.close();

    } catch (FileNotFoundException e) {

       e.printStackTrace();

    } catch (IOException e) {

       e.printStackTrace();

    }

}

 

 

3.6.     samba上传下载文件

 

Linux上搭建samba服务,然后在java程序中实现拷贝:

java代码:上传:

@Test

public void testPut() {

    try {

       /**

        * ipusername,password这些值如果在正式项目中使用,全部应该通过配置文件来设置

        */

       String ip = "192.168.101.2";

       UniAddress ua = UniAddress.getByName(ip);

       NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(ip, "s1", "123");

       SmbSession.logon(ua, auth);//验证是否能够成功登录

       //创建Smb文件,地址一定要使用smb://

       SmbFile sf = new SmbFile("smb://"+ip+"/samba/goagent-goagent-7805174.zip", auth);

       IOUtils.copyLarge(new FileInputStream("d:/goagent-goagent-7805174.zip"), sf.getOutputStream());

    } catch (UnknownHostException e) {

       e.printStackTrace();

    } catch (SmbException e) {

       e.printStackTrace();

    } catch (MalformedURLException e) {

       e.printStackTrace();

    } catch (FileNotFoundException e) {

       e.printStackTrace();

    } catch (IOException e) {

       e.printStackTrace();

    }

}

 

java代码:下载

 

@Test

public void testGet() {

    try {

       /**

        * ipusername,password这些值如果在正式项目中使用,全部应该通过配置文件来设置

        */

       String ip = "192.168.101.2";

       UniAddress ua = UniAddress.getByName(ip);

       NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(ip, "s1", "123");

       SmbSession.logon(ua, auth);//验证是否能够成功登录

       //创建Smb文件,地址一定要使用smb://

       SmbFile sf = new SmbFile("smb://"+ip+"/samba/goagent-goagent-7805174.zip", auth);

       IOUtils.copyLarge(sf.getInputStream(), new FileOutputStream("e:/tt.zip"));

    } catch (UnknownHostException e) {

       e.printStackTrace();

    } catch (SmbException e) {

       e.printStackTrace();

    } catch (MalformedURLException e) {

       e.printStackTrace();

    } catch (FileNotFoundException e) {

       e.printStackTrace();

    } catch (IOException e) {

       e.printStackTrace();

    }

}

 

 

 

 

3.7.     滚动图片

 

滚动图片的效果使用jquery插件:cycle

 

 

 

 

4.                  freemarker

 

首页的信息都是从数据库中取出来的,但是那么内容从数据库取的话,会导致首页显示会非常慢,所以,使用freemarker(另外一个框架交veloity)来实现网页静态化。

 

freemarker的编辑器的选用:

(1)       eclipse自带的freemarkeride

(2)       gvim

 

4.1.  Java的使用

 

使用Java

public class TestHelloFreemarker {

    @Test

    public void testHello() {

       try {

           //1、创建Configuration

           Configuration cfg = new Configuration();

           //2、设置config中加载模板的路径

           //设置了基于classpath加载路径,并且所有的模板文件都放在/ftl

           cfg.setClassForTemplateLoading(TestHelloFreemarker.class, "/ftl");

           //3、获取模板文件,由于已经设置了默认的路径是/ftl,此时hello.ftl就是ftl下的文件

           Template temp = cfg.getTemplate("hello.ftl");

           //4、创建数据文件,非常类似于OGNL,使用map来进行设置

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

           root.put("username", "小张");

           //5、通过模板和数据文件生成相应的输出

           temp.process(root, new PrintWriter(System.out));

       } catch (IOException e) {

           e.printStackTrace();

       } catch (TemplateException e) {

           e.printStackTrace();

       }

    }

    @Test

    public void testHello02() {

       try {

           //1、创建Configuration

           Configuration cfg = new Configuration();

           //2、设置config中加载模板的路径

           //设置了基于classpath加载路径,并且所有的模板文件都放在/ftl

           cfg.setClassForTemplateLoading(TestHelloFreemarker.class, "/ftl");

           //3、获取模板文件,由于已经设置了默认的路径是/ftl,此时hello.ftl就是ftl下的文件

           Template temp = cfg.getTemplate("hello02.ftl");

           //4、创建数据文件,非常类似于OGNL,使用map来进行设置

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

           root.put("username", "小张");

           //5、通过模板和数据文件生成相应的输出

           temp.process(root, new FileWriter("e:/test/freemarker/hello.html"));

       } catch (IOException e) {

           e.printStackTrace();

       } catch (TemplateException e) {

           e.printStackTrace();

       }

    }

}

 

写成Java工具类:

public class FreemarkerUtil {

    private static FreemarkerUtil util;

    private static Configuration cfg;

    private FreemarkerUtil() {

    }

    public static FreemarkerUtil getInstance(String pname) {

       if(util==null) {

           cfg = new Configuration();

           cfg.setClassForTemplateLoading(FreemarkerUtil.class, pname);

           util = new FreemarkerUtil();

       }

       return util;

    }

    private Template getTemplate(String fname) {

       try {

           return cfg.getTemplate(fname);

       } catch (IOException e) {

           e.printStackTrace();

       }

       return null;

    }

    /**

     * 通过标准输出流输出模板的结果

     * @param root 数据对象

     * @param fname 模板文件

     */

    public void sprint(Map<String,Object> root,String fname) {

       try {

           getTemplate(fname).process(root, new PrintWriter(System.out));

       } catch (TemplateException e) {

           e.printStackTrace();

       } catch (IOException e) {

           // TODO Auto-generated catch block

           e.printStackTrace();

       }

    }

    /**

     * 基于文件的输出

     * @param root

     * @param fname

     * @param outpath

     */

    public void fprint(Map<String,Object> root,String fname,String outpath) {

       try {

           getTemplate(fname).process(root, new FileWriter(outpath));

       } catch (TemplateException e) {

           e.printStackTrace();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

 

 

4.2.  ftl语法规则

 

示例01

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"

"http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Insert title here</title>

</head>

<body>

<h1>hello:${emp.name}---${emp.id}----${emp.age}</h1>

 

<#--以下显示了如何使用判断结构,注意在判断的标签中不用加入${}-->

<#if emp.age lt 18>

    ${emp.name}是童工

<#elseif emp.age gt 60>

    ${emp.name}应该退休了

<#else>

    ${emp.name}好好工作

</#if>

 

<#list emps as emp>

    ${emp.id}-----${emp