匆匆地学完了Struts,现在正在学习hibernate。 为了巩固一下所学,找了一个简单版的“奥运门票订购系统”来做练习。在此将整个练习过程记录一下:
1、 关于form表单中有日期类型的数据: 以往在单独使用struts时我一般都是将日期类型的数据定义为String类型的, 然后插入数据库的时侯再使用to_date()函数进行转化,取出是再将日期转换成字符串。 但是这次的练习这一招行不通了, 因为假如我定义为字符串的话那么我再调用hibernate的save()方法时由于数据库中对应的字段为date类型则会报类型不匹配的异常 。于是我在pojo类中还是定义为date类型的, 只是在提交表单的时候不封装页面的日期数据(因为struts不能封装日期类型的数据),而是将页面的日期类型数据独立出来使用request.getParameter("XXX")的方式在dao中得到日期数据并将日期字符串转化为date类型, 然后再将其set进pojo类对应的属性里。
2、 发现xxx.hbm.xml中<property >元素中的column可以设置为表达式: 如我要从门票表中查出某一确定门票的剩余票数, 而数据库对应的门票表中只有总票数totalCount与已售票数orderCount两个字段, 于是我使用session.createSQLQuery("select totalCount - orderCount from tb_tickets") , 然后再映射文件中如下配置:
<property name="restTicket" type="int" column="totalcount-ordercount"></property> ,在pojo类中设置了一个字段restTicket, 一测试在页面可以正确取得restTicket的数据。
这首先纠正了我之前的一个认识: 我原本以为column所对应的字段一定得是数据库表中已经存在的字段。事实证明不是,只要是在查询返回的结果集中存在则都可以通过在pojo类中设置相应的字段以及做好配置的方式去获得。
当然上述方法也存在一个很大的弊端: 那就是在插入数据的时候做了映射但是数据库中没有对应的字段则会报错, 因为向数据库插入记录的方法是save(), save()方法存的是整个对象。
3、 在jsp页面中有时会碰到EL表达式失效的问题, 这时可以尝试着在页面上设置一下:
<%@ taglib isElIgnore="false">
4、 JS困境之“删除确认”—— 我想实现如下功能: 当用户在点击时弹出一个对话框, 提示用户是否确认删除,如果确认则删除否则什么都不做。
我先写了一个js函数:
function checkDelete(){
var valication = window.confirm("是否确认删除");
if (valication == true){
window.location.href="xxxx.do?";
else{.... ... }
}
}
于是一测试OK, 但是问题是我在点击确定删除的时候还得将一条记录对应的ID传到Dao中去进行删除。 这样子我就犯难了,这个ID该如何传呢? 按照我以往的做法都是没做验证的, ID就直接跟在一个记录后面的超链接后。 但是这里我js中的跳转window.location.href="xxxx"中的ID应该是随着不同的行数而不同的。
想了一下没有想到方法于是便上网搜索了一下:
<a style="cursor:hand;" href="javascript:void(0)" οnclick="if(window.confirm('您确定要冻结该资料吗!')) this.href='http://www.baidu.com?temp=${ticket.ticketid}'" id="queding">删除 这样子便解决了我之前的那个问题。
^_^ ^_^ 问题解决了之后再回顾自己的思路突然又想到自己之前的那种写js函数的方式也是可以的, 只要在checkDelete()中传一个参数, 这个参数就是对应记录的ID。
扩展之———————— 关于javascript: void(0)的解释:————————————
在Javascript中void是一个操作符,该操作符指定要计算一个表达式但是不返回值。
void 操作符用法格式如下:
1. javascript:void (expression)
2. javascript:void expression
expression 是一个要计算的 Javascript 标准的表达式。表达式外侧的圆括号是选的,但是写上去是一个好习惯。 (实现版本 Navigator 3.0)
你以使用 void 操作符指定超级链接。表达式会被计算但是不会当前文档处装入任何内容。
下面的代码创建了一个超级链接,当用户以后不会发生任何事。当用户链接时,void(0) 计算为 0,但 Javascript 上没有任何效果。
<A HREF="javascript:void(0)">单此处什么也不会发生</A>
下面的代码创建了一个超级链接,用户单时会提交表单。
<A HREF="javascript:void(document.form.submit())">
单此处提交表单</A>
a href=#与 a href=javascript:void(0) 的区别 链接的几种办法
#包含了一个位置信息
默认的锚是#top 也就是网页的上端
而javascript:void(0) 仅仅表示一个死链接
这就是为什么有的时候页面很长浏览链接明明是#是
跳动到了页首
而javascript:void(0) 则不是如此
所以调用脚本的时候最好用void(0)
或者<input onclick>
<div onclick>等
链接的几种办法
1.window.open(”url“)
2.用自定义函数
<script>
function openWin(tag,obj)
{
obj.target="_blank";
obj.href = "Web/Substation/Substation.aspx?stationno="+tag;
obj.click();
}
</script>
<a href="javascript:void(0)" οnclick="openWin(3,this)">php自学网
5、 今天学到了在tomcat控制台下进入指定web工程的方法, 我们首先输入http://localhost:8088(这里是指你配置的端口号),然后回车在出现的tomcat的首页中的左栏选择Tomcat Manager。 此时它要你输入你的账号与密码, 这个可以在tomcat_user.xml中进行设置。
6、实验中出现的异常: hibernate中 xxx is not mapped 后经排查知道错因是我在调用session.createrSqlQuery("from tb_tickets")中的tb_tickets应该是对应的pojo类而不是数据库中的表名。
7、 在jsp页面中可以直接将从数据库查询出来的日期值显示在页面, 使用struts提供的bean:write标签, 其可以显示日期类型的数据, 而且可以通过fomate设定显示的格式。
8、 EL表达式支持“表达式”, 如: ${tickets.totalCount - tickets.orderCount }。 而<bean:write>不具备此功能
9、 关于在hibernate中修改一条记录的方法有: HQL、 QBC、 load()结合update()。 注意:
Hibernate 是直接操作对象 的, 而不是操作数据库记录的 ,所以 你要改谁 你要把 这个记录 告诉他 , 所以 要 Load或者 Get , 这样他才知道 你要 对哪条记 录做处理。。
10、 在hibernate下往数据库插入记录除了使用save(),还可以使用saveorUpdate()
11 、 想尝试一下在hibernate下删除主键遭到了失败。.(但是我想虽然表的主键是不允许被更改的是数据库设计的基本原则但并没强制说不允许撒... ... )
在hibernate中是不允许修改主键的,这个在使用时一定要记住。你可以自己设置一个业务主键,但hibernate不推荐你这么做
拓展问题: 如果程序涉及到一定要修改主键呢? ————————————————————
添加一个无业务的主键,然后把现在的主键变成一个普通字段, 并且在对更改后的主键做一个unique约束
12、 为什么在hibernate中建议不要使用复合主键
在Hibernate来说,一个PO状态的判定完全依赖于主键属性的值,甚至很多PO的隐形的级联操作,例如关联对象的是否级联增加/更新这些判定也完全依赖于主键属性的值,所以主键属性值的维护对于Hibernate能否正确的运行,正确的持久化数据至关重要。
如果当你使用无意义的逻辑主键的时候,主键的维护完全是由Hibernate自动进行的,你无须关注主键的维护,自然就避免了很多问题的产生;而如果你选择自己手工维护主键(联合主键就必须手工维护),所有的这些维护主键的重任都必须由你来负责,你必须小心翼翼的编程,避免造成无法正确持久化,对于一个不是非常精通Hibernate的人来说,这通常比较难达到,更何况在分层架构中,Web层程序员仅仅操作DAO接口层,他更加不了解PO状态维护的个中微妙之处,极易犯错误那也是在所难免。
所以采用无意义的逻辑主键一定Hibernate的首选。
13、 hibernate中提交数据一定得显式地commit()吗? ————答案是否定的
session.close();之前加上:session.flush();让session执行持久化操作。
取决于flush-mode。如果flushmode是commit则只有调用commit的时候会自动调用flush。这也为什么你加上事务的时候会在数据库看到结果。flushMode是MANUAL的时候必须显式调用session.flush才能更新到数据库中。
可以用System.out.println(session.getFlushMode());进行调试
14、 关于hibernate的“脏检查”认识————————————————————————(转载)
Hibernate 脏数据检查
脏数据检查:
什么是脏数据?脏数据并不是废弃和无用的数据,而是状态前后发生变化的数据。我们看下面的代码:
Transaction tx=session.beginTransaction();
User user=(User)session.load(User.class,”1”);//从数据库中加载符合条件的数据
user.setName(“zx”);//改变了user对象的姓名属性,此时user对象成为了所谓的“脏数据”
tx.commit();
当事务提交时,Hibernate会对session中的PO(持久化对象)进行检测,判断持久化对象的状态是否发生了改变,如果发生了改变就会将改变更新到数据库中。这里就存在一个问题,Hibernate如何来判断一个实体对象的状态前后是否发生了变化。也就是说Hibernate是如何检查出一个数据已经变脏了。
通常脏数据的检查有如下两种办法:
A、数据对象监控:
数据对象监控是通过拦截器对数据对象的setter方法进行监控来实现的,这类似于数据库中的触发器的概念,当某一个对象的属性调用了setter方法而发生了改变,这时拦截器会捕获这个动作,并且将改属性标志为已经改变,在之后的数据库操作时将其更新到数据库中。这个方法的优点是提高了数据更新的同步性,但是这也是它的缺点,如果一个实体对象有很多属性发生了改变,势必造成大量拦截器回调方法的调用,这些拦截器都是通过Dynamic Proxy或者CGLIB实现的,在执行时都会付出一定的执行代价,所以有可能造成更新操作的较大延时。
B、数据版本比对:
这种方法是在持久化框架中保存数据对象的最近读取版本,当提交数据时将提交的数据与这个保存的版本进行比对,如果发现发生了变化则将其同步跟新到数据库中。这种方法降低了同步更新的实时性,但是当一个数据对象的很多属性发生改变时,由于持久层框架缓存的存在,比对版本时可以充分利用缓存,这反而减少了更新数据的延迟。
在Hibernate中是采用数据版本比对的方法来进行脏数据检查的,我们结合下面的代码来讲解Hibernate的具体实现策略。
Transaction tx=session.beginTransaction();
User user=(User)session.load(User.class,”1”);
user.setName(“zx”);
tx.commit();
当调用tx.commit();时好戏就此开场,commit()方法会调用session.flush()方法,在调用flush()方法时,会首先调用flushEverything()来进行一些预处理(如调用intercepter,完成级联操作等),然后调用flushEntities()方法,这个方法是进行脏数据检查的关键。
在继续讲解之前,我要先来介绍一个内部数据结构EntityEntry,EntityEntry是从属于SessionImpl(Session接口的实现类)的内部类,每一个EntityEntry保存了最近一次与数据库同步的实体原始状态信息(如:实体的版本信息,实体的加锁模式,实体的属性信息等)。除了EntityEntry结构之外,还存在一个结构,这个结构称为EntityEntries,它也是SessionImpl的内部类,而且是一个Map类型,它以”key-value”的形式保存了所有与当前session实例相关联的实体对象和原始状态信息,其中key是实体对象,value是EntityEntry。而flushEntities()的工作就是遍历entityEntities,并将其中的实体对象与原始版本进行对比,判断实体对象是否发生来了改变。flushEntities()首先会判断实体的ID是否发生了改变,如果发生了改变则认为发生了异常,因为当前实体与EntityEntry的对应关系非法。如果没有发生异常,而且经过版本比对判断确实实体属性发生了改变,则向当前的更新任务队列中加入一个新的更新任务,此任务将在将在session.flush()方法中的execute()方法的调用中,转化为相应的SQL语句交由数据库去执行。最后Transaction将会调用当前session对应的JDBC Connection的commit()方法将当前事务提交。
脏数据检查是发生在显示保存实体对象时,所谓显示保存是指在代码中明确使用session调用save,update,saveOrupdate方法对实体对象进行保存,如:session.save(user);但是有时候由于级联操作的存在,会产生一个问题,比如当保存一个user对象时,会根据user对象的状态来对他所关联的address对象进行保存,但是此时并没有根据级联对象的显示保存语句。此时需要Hibernate能根据当前对象的状态来判断是否要将级联对象保存到数据库中。此时,Hibernate会根据unsaved-value进行判断。Hibernate将首先取出目标对象的ID,然后将ID与unsaved-value值进行比较,如果相等,则认为实体对象尚未保存,进而马上将进行保存,否则,则认为实体对象已经保存,而无须再次进行保存。比如,当向一个user对象新加入一个它所关联的address对象后,当进行session.save(user)时,Hibernate会根据unsaved-value的值判断出哪个address对象需要保存,对于新加入的address对象它的id尚未赋值,以此为null,与unsaved-value值相等,因此Hibernate会将其视为未保存对象,生成insert语句加以保存。如果想使用unsaved-value必须如下配置address对象的id属性:
……
<id name=”id” type=”java.lang.Integer” unsaved-value=”null”>
<generator class=”increment”/>
</id>
15、 关于hibernate中load()方法与get()方法的比较:
1. 对于Hibernate get方法,Hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有就返回null。这个相对比较简单,也没有太大的争议。主要要说明的一点就是在这个版本中get方法也会查找二级缓存!
2. Hibernate load方法加载实体对象的时候,根据映射文件上类级别的lazy属性的配置(默认为true),分情况讨论:
(1)若为true,则首先在Session缓存中查找,看看该id对应的对象是否存在,不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。等到具体使用该对象(除获取OID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
(2)若为false,就跟Hibernate get方法查找顺序一样,只是最终若没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
这里get和load有两个重要区别:
如果未能发现符合条件的记录,Hibernate get方法返回null,而load方法会抛出一个ObjectNotFoundException。
load方法可返回没有加载实体数据的代理类实例,而get方法永远返回有实体数据的对象。(对于load和get方法返回类型:好多书中都说:“get方法永远只返回实体类”,实际上并不正确,get方法如果在session缓存中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是原先的代理对象,而不是实体类对象,如果该代理对象还没有加载实体数据(就是id以外的其他属性数据),那么它会查询二级缓存或者数据库来加载数据,但是返回的还是代理对象,只不过已经加载了实体数据。)
总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。
16、
关于在actionForm中进行验证并返回错误信息(信息是中文)的解决方案:
提倡的是使用 消息资源文件, 也即在ApplicationResources.properties中定义错误消息, 然后在程序中使用
ActionErrors errors = new ActionErrors();
ActionMessage message = new ActionMessage("消息资源文件中定义的名字","提示内容");
errors.add("自定义名字",message);然后在需要显示错误消息的jsp页面中使用<html:errors name="自定义名字">即可。
当然消息资源文件目前还不支持中文, 这个我们需要使用native2ascii进行转换;
另外一种比较简单的做法是:
ActionErrors errors = new ActionErrors();
if("lsy".equals(account.getAccountid())){
errors.add("loginError", new ActionMessage("帐号或密码不正确,请重新输入sb",false));
} 然后同样的在相应的jsp页面进行接收即可。