安装好Maven后,设置中央工厂的位置:
【步骤:】
(1) 选好目录位置:比如E:\maven\repository;
(2) 拷贝Maven的安装目录conf下的settings.xml到E:\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中有自带的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是子项目名称。
【新建一个写日志的子项目:】
【步骤:】
(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.java的java文件;
(7) 命令行进入该java目录,使用maven命令:mvn compile,此时maven就会自动从网站(在maven的安装目录lib/maven-model-builder.3.0.5.jar中可以找到)上下载该java文件所依赖的jar包,该jar包存放的位置:在maven的anzhuangmulu的conf/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/src、main/src/main、main/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使用的版本、groupId、artifactId、version、package(直接回车使用当前包<好像是由groupId构成>)
1.
1.1.
1.2.
1.3.
低版本的Eclipse中没有自带的Maven插件,高版本中有。但是不要使用自带的Maven而是使用我们自己安装的Maven:
window -> preference -> maven -> installations -> 指定maven的安装路径
window -> preference -> maven -> settings -> 指定maven的settings.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!
1.
2.
pom.xml中可以配置scope属性:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>compile</scope>
</dependency>
</dependencies>
sope的值:
l compile:这是默认值,表示在编译的时候讲依赖包加进来;
l provided:表示在编译和测试的时候加载依赖包,而在打包的时候不加;(比如Servle-api包开发的时候需要,而tomcat服务器本身已经提供这个包,打包的时候就不需要提供了)
l runtime:编译的时候不依赖,运行的时候依赖;(比如数据库连接器只在运行的时候才需要依赖<用得不多>)
l test:只有测试的时候才依赖(编译、打包的时候不会包含进来)
所以,像easyMoke也要放到test范围。
【情况一】、
比如,A依赖B,B依赖C,那么A将依赖C中的compile的包。(test范围的包不会传递)
【情况二】、
Q:比如A依赖testjar1.0,B依赖testjar2.0(是testjar1.0的高版本),C同时依赖A和B,那么C到底是依赖testjar1.0还是2.0呢?
A:答案是使用先声明的依赖。
【问题:】
Q:我们的项目中有好几个模块,每次都需要将每个模块重新编译一遍,如何只用编译一个文件,就将这些模块全部编译了呢?
A:我们可以使用一个pom来管理其他的项目模块,这个pom可以放在一个新的maven项目中:
Eclipse 》 新建Maven项目 》选择简单项目(勾选 skip archetype election)
生成的项目如下所示:
修改pom.xml:
继承的目的是将公共的依赖放到一个pom.xml中——开发中可以新建一个简单的maven项目,在这个简单maven项目的pom.xml中配置公共的依赖包,同时也可以将其他模块都整合在这个配置文件中,统一编译。
为了在测试的时候隔离现有数据库,可以使用dbUnit工具。
【问题的提出】:
问题1:假设一个项目分成了三个模块:core、service、action,其中service是依赖core的,那么这三个模块同时开发的时候,core模块的jar包如何提供给service模块呢?
问题2:因为这三个模块依赖的jar包可能有上百个,当一个新员工来的时候需要搭建开发环境,这时候,就需要将这上百个jar包都下载下来,这将非常的耗时。
直观的解决方法是,每次core开发好后就将jar放到svn上,然后通知service下载,这个虽然可行,当是会比较麻烦,更不要说core有多个版本的情况下了。
【解决方法】:
根据上面的情况分析,我们有必要为这三个模块创建单独的仓库!尤其在企业中的时候,都会搭建自己的服务器用作中央工厂。
具体方法:
我们的模块不直接从中央工厂下载依赖包,而是从我们自己搭建一个服务器(伺服)上下载。每次找依赖包的时候,首先到本地仓库中找,当本地仓库中没有依赖包时,就到中央仓库中找。`1
l 推荐书籍:
jQuery实战(第2版)/图灵... 作者: (美)比伯奥特 (美)卡茨
l 因为Jquery文件需要在页面上载入,载入需要时间,所以,Jquery有两个版本,一个是mini版的,一个是完整版的。mini版的是将空格、空行、将变量名压缩后的版本。
l 学习的部分:
(1) 基础知识;
(2) jqueryUI(图形处理)、文件上传;
(3) UI框架(如收费的miniUI、国产收费EasyUi、DWZ)
(4) DWR框架;
l js文件压缩工具:jsmin.exe
l jquery就是为了方便选中html中的文本并进行样式编辑。
l ^a表示以a为开头$a表示以a为结尾
<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>
<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>
<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>
<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实现:
//目前的li是js的节点
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>
E,F 多元素选择器,同时匹配所有E元素或F元素,E和F之间用逗号分隔
E F 后代元素选择器,匹配所有属于E元素后代的F元素,E和F之间用空格分隔
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");
//取.myList的ul中的包含有a标签的li标签
$(".myList ul li:has('a')").css("background","#ff0");//
//获取页面中所有以pdf结尾的超级链接
$("a[href$='pdf']").css("background","#ff0").css("color","#f00");
//取id为li1的下一个兄弟节点li,仅仅只会去一个节点,
//仅仅只会取相邻的节点,如果相邻的节点不是li就什么都取不出去
$("#li1+li").css("background","#ff0");
//取id为li的下面的所有满足条件的兄弟节点
$("#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>
l Jquery的包装集是指,
通过$(“exp”)会筛选出页面的一组满足表达式的元素,这一组元素就属于包装集中的元素。
l 常用的方法有:
(1) 获取包装集中的元素的个数(size或者length方法);
(2) 通过某个下标获取包装集中的某个元素(get(index)方法);
(3) 获取某个元素在包装集中的位置(index());
<script type="text/javascript">
$(function() {
//获取table的元素个数(size,length都可以)
alert($("table").length);
//获取tr的元素个数
alert($("tr").length);
//当执行了get之后得到的结果是一个js的元素
$($("tr").get(1)).css("color","#f00");
//判断id为abc的tr在包装集的位置
alert($("tr").index($("tr#abc")));
//在表达式中通过,可以分割多个包装集:
//查找tbody中tr值(位置)为2的tr和tr的id为abc的元素(用逗号分隔)
//但是如果包装集太多,而且有时候可以变动的时候,使用这种方式就不好操作
$("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的位置为2、tr中的td位置为2、td元素包含3的td
$("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中的位置1到3形成一个新的包装集,返回的值就是新的包装集
//将原来所有的tr的背景色变成蓝色,tr位置为1到3的颜色变成红色
$("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中的所有元素为值等于3的tr子元素,返回的也是新包装集
$("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");
//返回第3个tr的所有兄弟节点,返回新包装集
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>
<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>
<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>
l has返回新包装集
<script type="text/javascript">
$(function() {
//获取存在有ul的li,返回的是新包装集(含有查找元素的元素)
$("li").has("ul").css("color","#f00");
//获取有span的div
$("div").has("span").css("color","#f00");
});
</script>
代码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>
<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>
(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操作;
有时候不知道对象是离线还是临时状态,这时候可以调用session的saveandupdate()方法
l Transient:是指临时状态,即new出一个对象,但是数据库中还没有这个对象;
l Persistent:new一个对象并save后就是Persistent状态(持久化状态);
l 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加载一个对象的时候,如果取出的对象没有使用,那么就不会发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是只要一执行就会发出sql,get没有延迟加载
User u = (User)session.get(User.class, 101);
//get的时候发现数据库中并没有该数据,所以u是null,打印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();
/*由于使用了load,load是有延迟加载的,返回的时候的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。
<hibernate-mapping package="org.zttc.itat.model">
<class name="Book" table="t_book">
<id name="id">
// assigned:表示要手动指定主键
// uuid:表示自动生成一个uuid字符串,所以主键必须是String
// native:也是自动生成,生成的是1、2、3等有序列,所以检索会快些
// native的缺点是每次插入一条数据库都会查询一下数据库,开发中查询
// 操作会比较多,所以开发中会使用native
<generator class="uuid"/>
</id>
<property name="name"/>
<property name="price"/>
<property name="bookPage" column="book_page" type="int"/>
</class>
</hibernate-mapping>
【提示】:
l 实体字段名,比如bookName,映射到数据库最好映射成book_name
<property name="bookPage" column="book_page" type="int"/>
l 在实际开发中,一般不会使用Hibernate来生成表,一般的开发中,都是首先用PowerDesigner设计好数据库,然后生成SQL语句,然后用这个SQL语句来生成表。
这样的好处是,在开发数据库的时候可以为数据库创建索引和视图。
l 对效率要求不高的项目适合使用Hibernate;
l 对效率比较高的项目,如果使用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语句……?)
l 关联(cascade):不要在“多”的一方使用关联。因为,比如,删除多的一方的一条记录,同时会删除“一”的一方对应的记录,但是如果“一”的一方的该条记录又关联了其他记录的话,就不能删除了。使用cascade的情况一般是:在“一”的一方删除时使用,特殊情况才会在add上做级联。
<property name="title"/>
<property name="content"/>
<!-- 使用了lazy=extra之后会稍微智能一些,会根据去的值的不同来判断是调用count和获取投影 ,比如取出总条数一般情况下会取出所有数据的list然后计算list的size,但是用了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")
// 使用extra后sql语句会较智能,比如计算条数会自动使用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;
}
}
专业-教室: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;
}
}
略,重点提示:
(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>
/*基于?的条件的查询,特别注意:jdbc设置参数的最小下标是1,hibernate是0*/
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();
/*使用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,效率不高,可以直接使用JOIN(Join = 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());
}
连接(Join)有三种:
(1) left join:
以下sql:t1(left)中不符合条件(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
l 查询每个班级的男生和女生的人数:
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(全连接),效率很低!
l 查询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());
}
l 查询每个班的人数
/*使用对象的导航可以完成连接,但是是基于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]);
}
l 查询学生的信息、包括所在班级(班级名)、专业名
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基本上在开发中不用!(我们项目组竟然用了……)
l 统计每个专业的学生的人数:
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");
l 统计人数大于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]);
}
l 统计每个专业的男女生人数:
/*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]);
}
使用映射文件的情况下:
/*
(1)默认情况会发出3条SQL语句,一条取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());
如果不想发出3条SQL,可以在映射文件中这么配置,使用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。
针对“一”的抓取一般不会使用双向关联,只会做“单向关联”。
l 取出班级和班级对应的学生
session = HibernateUtil.openSession();
Classroom cla = (Classroom)session.load(Classroom.class, 1);
/*此时会在发出一条SQL取class对象*/
System.out.println(cla.getName());
/*取学生姓名的时候会再发一条SQL取学生对象,可以在Classroom的配置文件中对学生属性添加fetch=”join”,就会用一条sql完成相关查询了*/
for(Student stu:cla.getStus()) {
System.out.println(stu.getName());
}
classroom的xml配置文件中加入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>
l 取出一组班级
(classroom的xml配置还是使用实例1中的配置内容。)
List<Classroom> clas = session.createQuery("from Classroom").list();
for(Classroom cla:clas) {
System.out.println(cla.getName());
/*对于通过HQL取班级列表且获取相应学生列表时,fecth=join就无效了,这里,每次查询一个班级就会发出一个SQL语句,这样肯定有问题,解决方法:
(1)第一种方案可以设置set的batch-size来完成批量的抓取
(2)可以设置fetch=subselect,使用subselect会完成根据查询出来的班级进行一次对学生对象的子查询(推荐)*/
for(Student stu:cla.getStus()) {
System.out.print(stu.getName());
}
}
l 方法(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条,这样很耗内存!(实际怎么使用还是要根据项目实际情况来定)。
l 方法(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只有两个选项:join和select,而<set>的fetch多一个subselect。
@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;
}
}
/* 基于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());
}
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;
}
}
实例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取出所有的学生信息*/
//发出查class的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());
}
/*使用iterate仅仅只会取Student的id,此时Student的数据已经在缓存中,所以不会在出现N+1*/
//发出查id的sql
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=1的Student对象已经在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=1的Student对象已经在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-corte、Hibernate-ehcache,在Hibernate的optional目录下);
步骤3:要在配置二级缓存提供的类:
<!-- 设置二级缓存所提供的类 -->
<property name="hibernate.cache.provider_class">
net.sf.ehcache.hibernate.EhCacheProvider
</property>
步骤4:Hibernate4.0中还要配置工厂(目的是为了提高效率):
<!-- 在hibernate4.0之后需要设置facotory_class -->
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
步骤5:设置ehcache.xml文件,在该文件中配置二级缓存的参数:
从Hibernate的project目录中拷贝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>
步骤6:在Hibernate.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=1的Student对象已经在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();
/*以上代码仅仅取了id和name,而二级缓存是缓存对象的,所以上一段代码不会
将对象加入二级缓存此时就是发出相应的sql*/
Student stu = (Student)session.load(Student.class, 1);
//会报错,因为二级缓存设置为read-only
//stu.setName("abc");
System.out.println(stu.getName()+",---");
session.getTransaction().commit();
}
l 二级缓存和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) {
}
l 如果使用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>
l 代码中的用法
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());
}
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());
}
/*查询缓存缓存的不是对象而是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()方法中将id为1的学生取出来,并将名字修改为”Jack”,在test02()方法中,也将id为1的学生取出来,并将名字改为”Tom”,并且在对方commit之前完成改名操作,这样的话,名字不会被改掉。
所以,一般情况下,并发会导致更新丢失。
解决方法有两种:
(1) 悲观锁:
悲观锁是Hibernate基于数据库的机制实现的,但是在Hibernate3和Hibernate4它们的实现机制是不一样的——Hibernate3是基于同步的机制实现的(同步的大量的并发就容易导致效率的问题),所以在Hibernate4中就没有使用同步,如果两个同时修改同一个记录的话,就抛出异常。解释错误了,Hibernate3和Hibernate4的机制是一样的,都是同步的。
(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>
(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的缓存在Session和SessionFatory会有耗内存)
(3)
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);
}
}
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);
}
l 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() {
}
}
l 查询
session = HibernateUtil.openSession();
//这里的sql为了和DTO对应,
//可以在select中new 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 ?")
//这里第一感觉可能会想到使用setEntity,setEntity的参数必须是和数据库
//对应的,但是DTO明显不需要和数据库对应,所以这里使用以下这个方法:
.setResultTransformer(Transformers.aliasToBean(StudentDto.class))
.setParameter(0, "%孔%")
.setFirstResult(0).setMaxResults(10)
.list();
for(StudentDto sd:stus) {
System.out.println(sd);
}
【提示】:
这样使用Hibernate的方法(查询使用sql,增、删、改使用Hibernate),效率会大大提高!
l 90%的web项目都会用到Spring;
l Spring除了可以和Hibernate整合也可以和JDBC整合;
l Spring的两个最基本的东西是:IOC(控制反转,也叫“依赖注入”)、AOP(面向切面);
【基本概念】:
<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中所创建的DAO的id -->
<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注入。
【基本使用方法】:
l 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
……
>
<!-- 打开Spring的Annotation支持 -->
<context:annotation-config/>
<!-- 设定Spring 去哪些包中找Annotation -->
<context:component-scan base-package="org.zmj.test.spring"/>
<!-- 打开基于Annotation的AOP -->
<aop:aspectj-autoproxy/>
</beans>
l 将相关类注入
注入UserDao:
//等于完成了<bean id="userDao" class="org.zttc.itat.spring.UserDao"/>
//@Component("userDao")//公共的创建bean的Annotation,所有的类都能用
@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的话类的依赖关系不明显,但是在配置文件中看的话就会一目了然。
背景:假设我们的项目已经上线,已经在运行,突然客户提出要求,要在项目中加上日志的输出。
我们直观的想法是,创建一个类用来输出日志,然后在原来的代码里加入输出日志的代码。但是这个破坏了原来的代码。解决方法是使用代理。
比如,在我们的代码里,我们创建一个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类用来输出日志,如果哪一天不想输出日志了,还要再创建新的代理,这样肯定很麻烦,不可行。
经过分析,我们发现,输出日志的代码都是独立的,和业务逻辑无关,所以,我们可以将这些代码抽出来,然后注入到相关的代码中。
动态代理是指通过一个代理对象来创建需要的业务对象,然后在这个代理对象中统一进行各种需求的处理。
【步骤】:
步骤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中:(因为没有get、set所以不能使用注解注入)
// 以下代码的意思是,使用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(切面)就是将关注的模块抽出来做成一个独立的部分(切面)。
步骤1:设置Schema、打开基于Annotation的AOP
<?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">
<!-- 打开Spring的Annotation支持 -->
<context:annotation-config/>
<!-- 设定Spring 去哪些包中找Annotation -->
<context:component-scan base-package="org.zttc.itat.spring"/>
<!-- 打开基于Annotation的AOP -->
<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");
}
}
首先,第三方包要导入、Schema头要设置。
l 创建切面(代理类):
@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");
}
}
l 配置文件中配置该切面:
<!-- 打开Spring的Annotation支持 -->
<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>
步骤1:导入Spring包和数据库驱动包;
步骤2:选择一个数据源(DBCP和C3P0);
步骤3:导入数据源的包;
步骤4:在beans.xml中创建dataSource数据源;
步骤5:创建数据库的属性文件,存放连接数据库的连接信息;
步骤6:在beans.xml中导入属性文件;
步骤7:创建UserDao并在UserDao中创建JDBCTemplate对象(通过JDBCTemplate可以方
便操作数据库);
步骤8:为Dao注入DataSource并创建JDBCTemplate;
步骤9:完成数据的增删改查
l 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"/>
l 连接数据库的属性文件内容:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring_teach
jdbc.username = root
jdbc.password = 123456
l 创建User实体:
l 根据实体内容创建对应的数据库表:
l 创建UserDao和JDBCTemplate:
@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);
}
}
l 使用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);
}
}
l 添加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;
}
}
}
如果往数据库中存入中文字符的话,则要处理中文乱码的问题,方法是:
在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>
比如,我们在页面中引用了一个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";
}
@RequestMapping(value="/{username}",method=RequestMethod.GET)
public String show(@PathVariable String username,Model model) {
model.addAttribute(users.get(username));
return "user/show";
}
//(1)若要控制返回某个值的话,则要使用@ReponseBody
//(2)params=”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
然后再访问第二个show:http://localhost:8080/项目名/user/jack?par
这样就直接将user转成json格式显示。
l 新建项目
l 拷贝jar包(Spring的dist目录下的jar包,Apache的log4j、commons-loggin)
commons-dbcp、commons-collections、commons-pool
l
推荐js书《JavaScript高级程序设计(第二版)》(美.泽卡斯)
js用来负责页面的交互,但是实际开发中一般使用js框架,比如:Prototype.js(用的不多)、Jquery
【数据类型】:
l 只有一个类型:var,js是动态语言(动态语言比如Python、Ruby,它们都是没有变量类型的,而且,是一行一行解释执行的;静态语言如Java、C有变量类型,且编译执行的)。
l 函数内部没有用var声明的变量会被当做全局变量,且会覆盖外面的同名变量(注意执行到这个变量时候,这个全局变量才会被创建);
【最佳实践】:
函数内部定义的变量一定要加上var。
l 因为js中变量的是不分“类型”的,如果想知道改变量的具体类型的话可以使用
typeof a来查看:alert ( typeof a )
l JS中常用的数据类型有:
Number(如10、10.6都是Number类型);
String
Array
Date
Math
l 类型转换:
var a = “11”;
alert()
l
【例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
//特殊情况:NaN、undefined、0是false
//看0是false还是true
alert(!!0);//提示false
alert(!!NaN);//提示false
//NaN实例运用:比如我们点击一次按钮就让size加1:
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】:点击事件
//调用js的click方法,参数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中节点的id为txt的节点
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中节点的id为txt的节点
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>
【例5】history.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;
//获取父类窗口中的id为bless的div
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>
节点的类型有:
元素节点(节点类型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);
//获取某个文本节点的值,对于IE和firefox而言文本的空格不一致,
//对于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这个节点来找到h3中span的值
//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="获取content的h2" 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为空(undefined为false)就会取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
l 给按钮添加点击事件:
<script type="text/javascript">
//当窗口加载完毕之后才执行init方法,这样可以省略body中的onload
//所以如果希望使用如下的事件定义方式,需要先完成html信息的加载
window.onload = init;
/*此时init方法是在body的onload之后执行,就等于在所有的页面标签加载完毕之后才执行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类型的)。
l 函数是一个非常特殊的对象,是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)); //f是fn1中定义的函数,所以这句话是向fn1中的这个函数传参数
alert(f(11)); //同上
【例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();
函数对象中有一个内置的属性,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)可以做反向的调用。
l 比如我们平时写求阶乘的函数是这么写:
//求阶乘的函数
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中通常都是使用这种方式做递归的!
l 我们使用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关键字在调用时会根据不同的调用对象变得不同。
【例】:
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()方法
//此时的this是c,所以color就是yellow
c.showColor(); //yellow
//此时调用的对象等于是window,showColor的this就是window,所以就会找window中color
showColor(); //red,html默认都有with(window);
函数有两个非常有用的属性:length、prototype
length: 是指该函数期望传进来的参数的个数(就是括号中的参数个数);
prototype: (后面讲)
函数还有两个比较有趣的方法:call、apply,他们都可以通过函数名称来调用函数。
l call:第一个参数是上下文,后面的参数是不同函数参数;
l apply:都有两个参数:第一个是调用的上下文、第二个是参数数组(可以将arguments穿进去)。
【提示】:
call和apply的区别就是第二个参数:
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
/*通过以上发现,使用call和apply之后,对象中可以不需要定义方法了,这就是call和apply的一种运用*/
【例】:
/*在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格式来传递对象。
l JavaScript Simple Object Notation
l Json就是js的对象,但是其省去了xml的标签,而是使用花括号(一个花括号就是一个对象):
/*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"]
}
];
【例】:
/*通过工厂的方式来创建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;
}
/*使用工厂的方式,虽然有效的解决了类的问题,但是依然存在另外一个问题我们无法检测对象p1和p2的数据类型*/
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、闭包。
【例】:
/*以下演示了通过原型的创建方式,使用基于原型的创建可以将属性和方法设置为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())
l Person的prototype属性指向了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"));//false,p1自己的空间中没有值
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))
}
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]
l 为了解决“问题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>
l 上面的“【问题解决方法】”中,是将方法定义在类的外面的,我们可以使用“动态原型”的方法,将方法放到类里面(这样就更接近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,如下图
第三步执行完后:
l c.showParent():先到自己的原型(Child Prototype)找,找不到就往上找(到Parent Prototype中找);c.pv也是这样。
l ——往上一直找,找到最上层还是找不到的话,其实还会到Object类中找,Object类也有一个prototype属性指向Object Prototype原型对象。
l 同时,实际上Parent的Prototype原型对象还有一个_prop_属性指向Object Prototype。
【提示】:
原先的Child Prototype就成了无用内存了。
【注意】:使用原型链需要注意的问题:
l 注意点一、
/*当执行了下面这句话之后,意味着Child的原型又重写了这样就不存在任何的继承关系了使用原型链需要注意的第一个问题*/
Child.prototype = {
showChildValue:function() {
alert(this.v);
}
}
l 注意点二、
一定要在原型链赋值(是指Child.prototype=new Parent())之后才能添加或者覆盖方法(这个比较好理解,因为如果在原型链赋值之前添加方法,原型链赋值相当于重写原型链,自然会把之前添加的方法覆盖掉)。
l 注意点三、
原型链赋值(是指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");
}
Date、Numbe、Function、Object,其中,我们要掌握两个内置对象:Function和Object
所有的对象都有以下属性:
_proto_ Property
constructor
prototype
另外,所有的对象都继承了Object。
Function和Object都是对象,它们都有prototype属性,但是它们有区别。
l Array是一个对象,具体来说他是一个Function类型的对象,它的prototype指向了Function.prototype。
l 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">
/** 此处的sum是Function,不是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;
};
/**让Person的prototype指向的区域存储键值,key=say,value=alert(this.name)*/
Person.prototype.say = function() {
alert(this.name);
};
/**定义一个对象实例p,p是Object类型,p中有_proto_属性,以及name属性,
其中_proto_属性指向Person的原型对象*/
var p = new Person("小李");
/**以下是在p自己的区域里(而不是Person的prototype指向的空间)创建
一个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>
无论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】:function的prototype
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();所以,此时Person的prrototype属性指向一个Object的对象,该Object对象指向了Object的原型对象(想象模型图);
(2) 上面的代码等价于var Person = new Function(“a”,”this.name=name”);所以,Person的_proto_属性指向了Function的原型对象;
(3) var p = new Person(“老王”);这里的p的_proto_属性指向了Person的prototype属性指向的对象。
【小结说明】(更早的笔记):
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也有自己的一些内置的方法,Function的prototype属性指向了自己的原型对象。
在定义
var Person = function( name ){
this.name = name;
}
的时候,实际上是执行了这个代码:
var Person = new Function(“name”,”this.name=name”);
(这里就有点像new一个Object了)new出来的这个Person有一个_proto_属性,它指向Function的原型对象,所以Person就有了Function的所有的方法了(调用方法的时候首先在自己的空间里找,找不到就会到Function中找)。
js中的上下文就是this。
Function中有两个方法:Apply()和call(),这两个方法其实是一样的,只是参数格式不同。apply和call方法的第一个参数就是上下文。
【原理解释】:
<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中的this和Java中的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增加name和age
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实现接口的方法有3种:
【方法1】:基于注释的实现——其实就是用注释来说明而已;
<script type="text/javascript">
var Knight = function(name) {
this.name = name;
// 在注释中说明要实现的接口
//Knight实现了Walkabe和Fightable
};
// 然后根据注释来为其添加方法
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是否实现了Wakable、Fightable接口
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}
此时Interface的prototype属性指向原型对象,原型对象中的
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]+"这个方法!");
}
}
};
在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>
【说明】:
这个例子中是实现了可以通过get、set方法为属性赋值,但是还是可以直接访问其私有属性——解决方法是,将私有属性都加下划线。
【实例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>
【提示】:
这个已经是一个很有效的封装了(使用约定优先,而且这个方法是实际开发中的一个很有效的方式),但是还是不能阻止开发者通过下划线访问私有变量,如果想实现真正的封装,可以使用闭包。
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,它指向作用域链,比如这里的abc的scope就指向了abc的scope list,在abc的scope list中,有指针分别指向不同的作用域(从高到低排序,比如abc有两个作用域,一个是全局的,一个是自己的):1指向全局的(global)(就是最外部的),0指向自己的(函数内部的变量)。
【提示】:
js最重要的核心概念:
(1) Function;
(2) Object;
(3) Function和Object的prototype;
(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) foo的scope属性指向了作用域链(foo scope list),作用域链中的1指向了global作用域,0指向了自己(foo scope)。
(2) 在global中有foo、baz(这两个都是函数);
(3) 在foo scope中有a(值为10)、bar(函数);
(4) 接下来执行baz = foo(),baz也会有自己的作用域(baz scope list),在bazscope list中有3个作用域:2指向global、1指向foo scope、0指向自己(baz scope 内容空)
(5) 此时,执行baz,那么foo scope中的a的值为20,再执行一次40,……
【示意图】:
【实例1】:
<script type="text/javascript" src="../inc/Interface.js"></script>
<script type="text/javascript">
/*使用这种方式虽然可以严格实现封装,但是带来的问题是get和set方法都不
能存储在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);
}
的内存模型是:
即,Person的prototype属性指向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本身就有了name和age属性,所以当我们再创建对象的时候:
var stu2=new Student();
此时stu2本身没有name和age属性,即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">
/*使用这种方式虽然可以严格实现封装,但是带来的问题是get和set方法都
不能存储在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这种引用类型的属性会出现数据不一致
* 2、Student没有办法使用构造函数*/
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中没有name和age,其_proto_指向Person原型*/
var stu1 = new Student();
// 下面的语句就会在stu1本身中加上name、age和books属性
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());//打印java、javascript
</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;
/* Student的superClass属性在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表示在一行显示。
【示例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.js和02.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这样的命名空间,那么我们就要把zmj和org都要创建出来:
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);
}
};
即使用下划线表示私有变量。
<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," ");
},
_tranBrace:function(txt) {
return txt.replace(/\>/g,">").replace(/\</g,"<");
},
_resumeSpace:function(txt) {
return txt.replace(/ /ig," ").replace(/<br*\/?>/ig,"\n");
},
_resumeBrace:function(txt) {
return txt.replace(/>/ig,">").replace(/</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," ");
};
function _tranBrace(txt) {
return txt.replace(/\>/g,">").replace(/\</g,"<");
};
// ig表示忽略大小写
function _resumeSpace(txt) {
return txt.replace(/ /ig," ").replace(/<br*\/?>/ig,"\n");//<br>
};
function _resumeBrace(txt) {
return txt.replace(/>/ig,">").replace(/</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," ");
};
function _tranBrace(txt) {
return txt.replace(/\>/g,">").replace(/\</g,"<");
};
function _resumeSpace(txt) {
return txt.replace(/ /ig," ").replace(/<br*\/?>/ig,"\n");
};
function _resumeBrace(txt) {
return txt.replace(/>/ig,">").replace(/</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;
}
};
})();
【知识点】:
我们经常会使用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)”。
可以设计Organization表,Organization和自己关联(一对多),比如将部门放到该表中(其父ID为School)。
同时,学校下面可能也有Classroom和Sprcial。
Id |
Name |
Pid |
ShortName |
Type |
1 |
南京大学 |
Null |
XX001 |
XX |
2 |
计软院 |
1 |
JRY001 |
JXBM |
3 |
软件工程 |
2 |
ZY001 |
ZY |
4 |
网络工程 |
2 |
ZY002 |
ZY |
我们还需要另外一张表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 |
我们还要给类型定规则,专门用来映射部门和部门之间的关系,也就是用来确定哪些组织下面有哪些组织:
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有三个外键,分别指向Person、Organization、Position。
Id |
Name |
1 |
张三 |
2 |
李四 |
3 |
王五 |
4 |
李六 |
5 |
赵七 |
6 |
吴八 |
Id |
Name |
1 |
科长 |
2 |
科员 |
3 |
处长 |
Id |
Perid |
Orgid |
PosiId |
1 |
1(张三) |
2(计软院) |
1(科长) |
2 |
2(李四) |
3(网络工程) |
2(科员) |
【继续增加内容】:
比如,张三登陆系统后,要决定张三能够管理哪些数据(这是组织机构的核心):
为此,我们要为Organization设置一张OrgType表,该表用来确定组织所能管理的部门
另外:
(1) Person要想登陆系统,还要跟用户表(User)一一对应,即属于用户的Person才能登陆系统
(2) 还要和权限关联,这样就牵引出角色和资源的概念。
如果使用Hibernate,不要使用双向关联,更不要使用一对多的关联,最多使用多对一,要绝对控制住Hibernate发出的sql数。
大项目中外键都很少用,而是使用冗余字段。
【用到的技术】:
1、 Maven分模块管理;
2、 Spring+Hibernate+SpringMVC;
3、 页面采用FreeMarker;
4、 JS库使用Jquery
5、 树展示:Ztree;
6、 上传文件:uploadfy;
7、 Ajax、Dwr
【分成的模块】:
-basic-hibernate:基础模块,用来增删改查,可以用于大多数项目;
-cms-core:model、dao
-cms-user:user、role、group 主要是Service;
-cms-article:channel(栏目)、article 主要是Service;
-cms-system:link、backup、info;
-cms-web:
-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>
新建一个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层
视频中basic-hibernate和cms-core执行了clean install命令了。
cms-core写完后就写cms-user了,cms-user项目建好后,就将三个模块basic-hibernate、cms-core、cms-user聚合到parent中了。
cms-core编写完毕之后,接下来就写cms-user(service模块了)。
视频中,cms-user项目建好后,就将现有的三个模块:basic-hibernate、
cms-core、cms-user聚合到parent中。视频中的意思是好像basic-Hibernate不应该
聚合到parent中,不是很明白。
【改进】:
比如在UserService中,我们注入了IUserDao userDao,当我们用userDao调用方法的时候,会有大量的可选方法,这样对操作不方便:
解决方法:
(1) 我们将IBaseDao中的方法只保留增删改查这几个最基本的方法,BaseDao中将相应的Overrid去掉。
(2) 在我们的UserDao中,提供需要的方法(比如测试中需要什么方法我们就提供,不能直接覆盖BaseDao中的方法,因为泛型)然后,在这些方法中调用BaseDao中的方法;也就是说,我们的Service层需要什么方法,我们就在UserDao中添加什么方法,然后在添加的方法中调用BaseDao中的方法。
@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();
}
环境搭建步骤:
(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一下就好了。
问题7:jetty版本引错
应该是
<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方法上,以及有没有设置枚举映射。
问题10:web项目启动时报错
(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]-----------------------------------
解决方法:Windows》preferences》java》installed 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>
就没有上面的错误了,也不知道是什么原因,和教程上的环境应该是一样的,教程上正常我的环境上就不行,解决这个问题浪费了一个多小时。还有,dwr的listener使用教程上一样的设置方法也不行,在网上找的那种设置方法可以,这个也不知道是为什么,教程上也是官方设置。
模仿别人网站的方法:主要是模仿别人的颜色搭配。
这是一个让内容飘动的标签。
<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>
【前言】:
早前还有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>
<style type="text/css">
/*id为p2的p标签(即ID选择器)*/
p.#p2{
}
/*class=p1的p标签(即类选择器)*/
p.p1{
}
/*p中的所有的span标签,注意,p标签中不能放div*/
p span{
}
/*p标签的直接子span*/
p > span{
}
/*id为d1和id为d2的所有标签*/
#d1,#d2{
}
</style>
l padding
如果一个容器设置了高度是40,在IE浏览器中如果在该容器里设置padding-top值为20,那么该容器的实际高度不变,仍然是40;但是对于IE之外的浏览器来说,高度将变成60——所以,不能使用padding来设置对齐。
【提示】:
有的标签默认是带padding和margin样式的,我们一般习惯将所有的标签去掉自带的padding和margin,方法:
*{
margin:0px;
padding:0px;
}
那么如何设置对齐方式呢?
<!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;
/*设置position为absolute后就可以设置左右边距*/
position: absolute;
/*这个是距离浏览器的上边框距离为0*/
top: 10px;
}
#cc1 {
border: 1px solid blue;
height: 20px;
width: 20px;
/*设置position为absolute后就可以设置左右边距*/
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设置相对定位,然后设置left、top值即可。
有一个css网站zen garden,可以参考,html里面的内容是一模一样,但是使用不同的css让内容展现出完全不同的风格。
【提示】:
l 网页制作一般只用两种字体大小:12px和14px,字体一般使用宋体。
网页上显示图片有两种方式:
方式1:使用img标签:
<img alt="" src="1.jpg">
方式2:使用css的background:
#id{
background: url(01.jpg)
}
</style>
</head>
<body>
<div id="img01"></div>
</body>
</html>
一般图片经常变化的我们使用img标签,而图片一般不变的我们使用css的background。
设置浮动后,一定要在下一个div中设置clear:both,否则下一个div中的内容会飘过来。
<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;
}
全局插件,比如:
<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.
页面:
<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;
}
//使用闭包,第一行的$表示为该闭包定义一个变量$,传递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)
//使用闭包,第一行的$表示为该闭包定义一个变量$,传递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)
<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)
【页面向Controller传值】:
l 方式1:
Controller:
//RequestMapping的value值是一个数组
@RequestMapping({"/add","/"})
public String add(String username){
return "hello";
}
传递请求参数:
如果请求地址可以是:http://localhost:8888/cms/add?username=zhangsan
如果不传username参数也是可以的,不会报错。
l 方式2:
方式1中的参数可传可不传,如果希望一定要传参数,可以给参数加上RequestParam:
@RequestMapping({"/add","/"})
public String add(@RequestParam("username") String username){
return "hello";
}
此时username就作为请求路径的一部分,如果不传此值的话,就报错。
【Controller向页面传值】:
l 方式1:
@RequestMapping({"/add","/"})
public String add(String username,Map<String,String>context){
context.put("username", "zhangsan");
return "hello";
}
这样,在jsp页面就可以通过${username}将值取出来了。
l 方式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);
l 方式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());
【补充】:Struts和Spring的对比:
在Struts中,参数要定义在Controller类中,这样为了防止属性共享,就必须将struts定义成单例prototype模式;而Spring是将参数传递放到函数参数中的,所以不存在属性共享的问题,所以Spring的控制器是单例模式,效率比Struts要高。
在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属性上的错误信息。
比如,控制器传过来角色的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的属性,并且带有groupIds、roleIds的数组属性;
(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>
我们已经定义了一个异常类:CmsException。
在UserController中我们可以定义“局部异常”,该异常方法只能处理UserController中的异常:
@ExceptionHandler(CmsException.class)
public String handlerException(HttpServletRequest req){
return null;
}
这里我们使用“全局异常”。
全局异常在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-INF的jsp目录下创建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>
【提示】:
当写了局部异常就默认使用局部异常而不是用全局异常了。
工具模块用来存放自己的工具类,比如EnumUtills、转换json数据的工具,我还是将工具类放到basic-hibernate中。
转换json格式的工具:jackson
拼音工具maven中央工厂没有:手动下载导入到项目中。
<dependency>
<groupId>net.sourceforge.pinyin4j</groupId>
<artifactId>pinyin4j-core</artifactId>
<version>2.5.0</version>
</dependency>
使用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:
建一个树实体,用来存放树信息:
public class ChannelTree {
private Integer id;
private Integer parentId;
private String name;
dao查询:
@Override
public List<ChannelTree> generateFullTreen() {
//我们只需要id、name、parentId,所以应该使用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_id为null
*要将其转换成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>
l 一次性加载全部数据
实际中使用异步方式取数据,而不是使用上面的方式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>
l 点击一次展开就加载一次
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
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>
同步刷新左侧页面。
左侧树列表页面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里
使用jquery中的一个组件:sortable。
可以指定导入包:jquery.ui.core.js、jquery.ui.widget.js、jquery.ui.mouse.js、jquery.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());
});
//更改(_helper)tr的颜色
_helper.css("background","grey");
return _helper;
}
$(".test tbody").sortable("disable");//阻止排序
//-------------------------------------------------
/**点击开始排序按钮后设置表格可排序*/
$("#beginSort").click(function(){
if( _sort ){//已经是开始排序状态
alert("已经是排序状态");
return;
}
//给thead的tr增加<td>
$(".test thead tr").append("<td>序号</td>");
/*给tbody中的每一个tr增加一个<td></td>,但是希望排序后的序号依旧
是从小到大排序,所以下面的代码写到函数中,每次更新排序都执行
$(".test tbody tr").each(function(index){
$(this).append("<td>"+(index+1)+"</td>");
});*/
setNum();
//给tfoot的tr增加<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)
我们在闭包:
(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);
@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的值映射成String的List(ADMIN、PUBLISHER等),是因为这些在页面上的显示效果是:
<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的映射了。
介绍:dwr(Direct Web Remoting),百度百科:
DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJAX技术的网站。它可以允许在浏览器里的代码使用运行在WEB服务器上的JAVA函数,就像它就在浏览器里一样。
简单说,dwr可以在js中直接调用Java代码。
使用步骤:
l 新建一个maven的web项目用来测试dwr,注意新建的maven的web项目的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">
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
public class HelloDwr {
public String sayHello(String name){
System.out.println("zmj --> name="+name);
return "Hello,"+name;
}
}
在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>
create的JavaScript属性可以为公布的对象设定一个别名,但是在页面中就要用hello.js来引入。
<dwr>
<allow>
<create creator="new" javascript="hello">
<param name="class" value="com.zmj.service.HelloDwr"/>
</create>
</allow>
</dwr>
然后就可以使用hell调用HelloDwr.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"/>
相当于就将Java的HelloDwr.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>
引入jetty,启动:clean jetty:run
需求:从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>
(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;
}
}
有三种方法:
(好像也使用了……)
<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>
public interface IHelloService {
public String say(String name);
}
实现类:
@Service("helloService")
public class HelloService implements IHelloService {
public String say(String name) {
return "hello:"+name;
}
}
<?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>
<?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>
<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>
<script type="text/javascript">
hello.say("zhang",function(data){
alert(data);
})
</script>
</head>
<body>
</body>
</html>
和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");
}
}
将bean、debug、listener都配置在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>
实体:
/*转换成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>
在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中的deleteChannel和addChannel方法,但是这个是在另外一个包中,而且会破坏原来的代码(加入加上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)
思路:
将所有Controlle中的所有的类、所有的方法都进行扫描,将各个角色能访问的方法存放在一个Map<String, Set<String>>中,key是角色名,value是Set,set中是类的完整名+方法名;这个存储的动作在系统启动的时候做初始化,然后存储到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"));
}
}
【提示】:
几乎所有的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。
@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中有没有对应的方法名),有的话就正常访问,没有就抛出不能访问的异常。
思路一:使用过滤器
但是使用过滤器无法很好的获取得到该用户访问的时候哪一个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);
}
}
如果直接使用如下的方法退出:
<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>
菜单也是一种资源,也是可以配置到数据库中的,本项目中就用简单的,在页面进行配置。
【文章查询】:使用构造函数查询需要的字段
Topic实体关联Attachment、Channel、User
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语句太长,可以使用构造函数 仅查询需要的字段。
【文章查询】:使用构造函数查询需要的字段
首先要实现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的倒序排序。
首先,看附件Attachment实体的定义:
@Entity
@Table(name="t_attachment")
public class Attachment {
private int id;
/**附件上传之后的名称*/
private String newName;
/**附件的原始名称*/
private String oldName;
/**附件的类型,这个类型和contentType类型一致 */
private String type;