开发购物车应用程序(1)-阿里云开发者社区

开发者社区> 数据库> 正文

开发购物车应用程序(1)

简介:
一、简介
在本篇(最后一篇)中,我们有两个目的:首先,我们想实现一个Hibernate数据库更新(实际上是一个“插入”操作),它涉及到两个数据库表格 (Driver和Car)之间的“一对多”的关系。注意,我们是在一个适当配置的Hibernate事务中实现这种更新的;而且其中,我们将展示事务的提 交和回滚这两种情形。其次,我们将通过一个购物车的例子来探讨一下实际开发中的应用程序设计问题。
二、执行数据库插入操作的Servlet示例
在前面的文章中,我们分析了Context.xml文件,web.xml,HibernateUtil.java, hibernate.cfg.xml,以及映射文件Driver.hbm.xml和Car.hbm.xml及其相应的Java类文件— Driver.java和Car.java。在本篇中,所有这些文件将继续使用而不作任何改变。
现在,让我们把注意力立即转向servlet本身—InsertViaHibernate.java。其代码如下:

package tomcatJndi;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.Session;
import org.hibernate.Query;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hibernate.Session;
public class InsertViaHibernate extends HttpServlet {
public InsertViaHibernate() {
super();
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("");
out.println("");
out.println("  ");
out.println("  ");
Session session = null;
try  {
session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Car bigSUV = new Car();
bigSUV.setManufacturer("Ford");
bigSUV.setModel("Expedition");
bigSUV.setYear(2005);
Driver jill = getDriverByName("Jill", session);
addCarToDriver(jill, bigSUV, session);
session.getTransaction().commit();    //自动地关闭会话
}
catch (Exception e)  {
session.getTransaction().rollback();
throw new ServletException();
}
out.println("  ");
out.println("");
out.flush();
out.close();
}
public Driver getDriverByName (String name, Session session)  {
Query q = session.createQuery("from Driver d where d.name = :name");
q.setString("name," name);
Driver newOwner = (Driver) q.list().get(0);
return newOwner;
}
public void addCarToDriver(Driver theOwner, Car theCar, Session session)  {
 theOwner.getCarsOwned().add(theCar);
 session.save(theCar);
session.save(theOwner);
}
}
在这个程序中首先要注意的是,就象在第二篇中一样,我们首先通过对SessionFactory.openSession方法的调用(借助于 HibernateUtil)来引用Hibernate会话;然后,我们通过Session.beginTransaction()启动一个 Hibernate事务。对addCarToDriver()方法的调用产生了两个对象实例—Car和Driver—都被保存在Hibernate会话 中。注意目前为止,还没有发生任何实际的数据库更新。当我们回到doGet()方法时,它调用session.getTransaction(). commit(),从而最终使两个MySQL表格都被更新。
在上一篇中,我们提到了跟随在Hibernate查询后面的面向对象方法。在当前的更新操作中,你再次看到了它—仅引用了Java类,而没有涉及到 MySQL表格。而且,在这些Java代码中根本没有对数据库列car.fk_driver_id(表格car中的外键,在使用原始JDBC时,它允许这 个表格和driver表格关联到一起)的引用。
请注意上面catch语句中的rollback语句;这种情况是需要认真测试的。其中一种测试方法是把下列语句:

Driver jill = getDriverByName("Jill");
改变为:

Driver nosuch = getDriverByName("Melvin");
现在,让我们来分析一下这个getDriverByName()方法。在此,它假定q.list()方法返回一个至少有一个成员的 java.util.List。然而,根本不存在名为“Melvin”的成员。结果是,q.list().get(0)将抛出一个 IndexOutOfBoundsException异常—返回到doGet()方法中的catch语句。然后, doGet()方法调用getTransaction.rollback()。
三、Multi-Http-Request业务事务
到目前为止的本系列的所有示例中,方法调用:

session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();

session.getTransaction().commit();//自动地关闭会话
都发生于同一个Http请求中。根本没有出现一系列的屏幕把beginTransaction和commit分离开来,并且不存在任何用户“介入” —因为session.beginTransaction打开一个实际的数据库事务,而session.getTransaction.commit实际 上执行相应的数据库提交操作。上面这些步骤永远不应该被任何用户“介入”所分离。可以想象,作为用户,他一定希望在这一系列屏幕(对应于单个业务事务)间 插入几个用于“观察”的屏幕;但是,使数据库锁打开这么长的一段时间必须会阻碍与其它活动用户相关的许多并发事务的操作—带来一定的“瓶颈”问题。
在Hibernate参考文档及其它已出版的Hibernate参考资料中都把这种multi-Http-request业务事务称作一种“长时间 运行的事务”或称作一种“对话性事务”(也被称作一个“业务事务”。注意,这三个短语含义是一样的,而且其中任何一个都与数据库事务或Hibernate 事务有关)。Hibernate为处理长时间运行的事务提供了现成的工具。例如,你可以在用户“介入”之前把对象从Hibernate会话分离开来,然后 当用户提交下一屏幕时重新依附它们,之后你就可以再次使用它们。一直以来我们被推荐:在使用Hibernate时,应该使打开Hibernate会话的时 间尽可能缩短—尽可能有限。总之,我们应该想尽一切办法,通过单个方法来实现“打开Hibernate会话→启动事务→保存需要保存的任何对象实例→然后 提交事务”—就象你已经在本系列的示例中所看到的一样。应该尽可能避免再使用任何其它方法。不要试图使用一个Hibernate会话—它会在跨越多个 Http请求时一直保持打开状态。
但是,你完全有理由问:对于multi-Http-request业务事务的情况如何呢?—例如,一种购物车情形?下面让我们来作详细讨论。
四、购物车情形讨论
如果不存在例如Hibernate这样的工具可用,或是在一种非Hibernate环境下,那么,你该如何设计这种类型的应用程序呢?以前人们所使 用的方法是借助于数据库中的“分段表格”(staging table)(或“临时”表格,也称为“工作”表格)。因此,随着用户操作一系列的屏幕,把每一次回寄发送给服务器,打开一个新的Hibernate会 话,执行相应的session.beginTransaction,然后调用一个或多个类似于“保存”这样的方法,最后实现事务提交。仅仅在上面一系列屏 幕操作的最后—在业务事务或对话事务的最后—数据才从分段表格传递到永久性表格中;而且,此时才从分段表格中删除相同的数据。
我们认为,上面这种方法,对于Hibernate环境和非Hibernate环境都一样重要,并且使得事情相当简单而且可预测,并且此方法能够移植到任何数据库平台。
一般情况下,在永久性表格及其相应的分段表格结构之间很可能存在一些不同。例如,分段表格往往会包含一个例如Http会话ID这样的表格列,用于标 识当前用户会话。而通常这个列是不会出现在永久表格中的。在你的servlet中,你可以通过下列语句来取得Http会话ID:

request.getSession().getId()
这两个表格中的主键—假定它是一个由数据库或Hibernate赋值的代理键—可能具有相同的数据类型或结构,但是却不会有相同的值。注意,在分段表中的主键值仅是一个临时值。
这两个表的记录结构之间还可能存在其它区别,这要由永久表格记录的处理方式来决定。
让我们假定,MySQL数据库中的car表格有可能会在一种multi-Http-request场所下使用,并且还需要一个分段表格。下面是这个分段表格可能的“create table”SQL语句:

create table car_staging(
car_id bigint(20) PRIMARY KEY not null auto_increment,
http_session_id varchar(64),
manufacturer varchar(40),
model varchar(40),
year int,
fk_driver_id bigint(20),
FOREIGN KEY (fk_driver_id) REFERENCES driver (driver_id)
)
car表格相应的create table SQL语句已在上一篇中描述过。这两个SQL语句之间的唯一区别在于:①表格名不同;②在car_staging表格中增加了一个http_session_id列。
下面是相应的Java源文件—Car_staging.java。它与上一篇中的模块Car.java几乎完全相同。

package tomcatJndi;
public class Car_staging
{
private Long car_id;
private String http_session_id;
private String manufacturer;
private String model;
private int year;
public Car() {}
public Long getId() {
return car_id;
}
private void setId(Long car_id) { //注意这里private属性的可见性
this.car_id = car_id;
}
public String getHttp_session_id() {
return http_session_id;
}
public void setHttp_session_id(String http_session_id) {
this.http_session_id = http_session_id;
}
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
下面是相应的Hibernate映射文件。它与car表格相应的映射文件(上一篇中)也几乎一致。

"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"[url]http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd[/url]">
当然,你不一定非用分段表格技术不可。但是,其它一些可选方案的重点也都应集中在分离(detaching)和重新依附(attaching),如 何利用Hibernate所提供的锁机制,以及处理其它相关的问题方面。但是,为什么上面的方法更合乎需要呢?它对哪些内容进行了简化?
比方说,在与用户的若干交互过程中,你想从Hibernate会话中提取所有已经保存到其中的对象实例—很明显,此时你很可能会需要一个 Iterator或Enumeration这样的工具。但是,你该如何实现呢?我们并不知道。因此,如果你需要对保存在Hibernate会话中的数据实 现某种后处理期更新或查询的话;那么,除了你已经把此信息保存到Hibernate会话内以外,你还需要把对这种数据的独立参考保存在Hibernate 会话外部。
如果你需要把更新信息存储到Hibernate会话的外部,那么,把此信息存储在Hibernate会话内还有什么意义呢?—此时,你甚至还不能遍历这些信息!因此,我们的意见是,最好是当你准备好提交时才把信息存储在Hibernate会话中。
除了上面的分段表格技术之外,还存在其它广泛使用的技术。例如,你还可以把与长时间运行的事务相关的信息存储在Http会话中(这不同与存储在 Hibernate会话中的情形)。然而,如果把大量的数据存储在Http会话中,那么,你的应用程序的灵活性可能会受到影响。另外,服务器一端的簇管理 也可能因此而成为问题。
你还可以选择以另外某种方式串行化信息并且把它下载到一个客户端HTML隐藏域中。然而,这个方法有可能导致在服务器和客户端之间来回传递大量的数 据,并且你可能还需要采取其它一些措施以确保在浏览器端串行化的信息是安全的。在任何web应用程序中,存在三种如上所描述的支持购物车情形的方法: (1)分段表格技术;(2) 通过Http会话中的属性;(3)在HTML隐藏域内实现的串行化(这可能是极少使用的方法)。在非Hibernate环境下,如果使用当前最流行的 web MVC技术—JSF,Spring MVC或Struts进行开发的话,那么,你一定会用到上面所描述的三种方法之一。我们认为,在开发一个基于Hibernate方案的web应用程序时也 应该考虑一下上面这三种方法。当然,这要根据存储在活动购物车中信息的数量以及我们上面所列出的其它一些因素进行综合考虑才行。

















本文转自朱先忠老师51CTO博客,原文链接: http://blog.51cto.com/zhuxianzhong/60128,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章