持久层是一个应用系统最基本的部份。很显然的,如果没有持久层,所有的工作都将丢失。但是,对不同的人来说持久层意味着不同的东西。持久化时间的长短是选择持久层储存媒介的基本衡量标准之一。例如,对于生命周期为一个用户会话的数据来说,Http
session是非常合适的。与之对应的,跨越多个session,或者多个用户的持久化则需要一个数据库来保持。数据的数量是另一个非常重要的衡量标准。例如,最佳实践表明大量的数据最好不要被存储在一个Http会话中。在这些情况下,你都应该考虑使用数据库。在这篇文章中,我们的目标就是数据库持久层。
create table
elements (id integer(3) IDENTITY,
name char(30),
number char(30),
mass char(30),
symbol char(2));
CREATE
UNIQUE INDEX ui_elements_pk ON elements (symbol)
insert
into elements ( name, number, mass, symbol) values
('Manganese','25','55','Mn');
insert into elements ( name, number, mass, symbol) values
('Zinc','30','65','Zn');
insert into elements ( name, number, mass, symbol) values
('Thulium','69','169','Tm');
insert into elements ( name, number, mass, symbol) values
('Californium','98','251','Cf');
insert into elements ( name, number, mass, symbol) values
('Gold','79','197','Au');
insert into elements ( name, number, mass, symbol) values ('Ytterbium','70','173','Yb');
insert into elements ( name, number, mass, symbol) values
('Molybdenum','42','96','Mo');
insert into elements ( name, number, mass, symbol) values ('Palladium','46','106','Pd');
package
com.strutsrecipes.hibernate.beans;
public class
Element {
private String name;
private String symbol;
private String number;
private String mass;
private int id;
public Element() {
super();
}
public Element(String name, String symbol, String number, String
mass) {
this.name = name;
this.symbol = symbol;
this.number = number;
this.mass = mass;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMass() {
return mass;
}
public String getName() {
return name;
}
public String getNumber() {
return number;
}
public String getSymbol() {
return symbol;
}
public void setMass(String mass) {
this.mass = mass;
}
public void setName(String name) {
this.name = name;
}
public void setNumber(String number) {
this.number = number;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
}
<?xml
version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate
Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property>
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="connection.url">jdbc:hsqldb:hsql://127.0.0.1</property>
<property name="show_sql"> </property>
<property name="">true</property>
<mapping resource="/com/strutscookbook/hibernate/beans/Element.hbm.xml"/>
</session-factory>
</hibernate-configuration>
让我们跳到列表4
我们以指定数据库实现方言开始,允许Hibernate充分利用实现特殊化的属性。我们声明Hypersonic方言。我们可以参考Hibernate文档以选择数据库相应的方言。然后我们声明数据库驱动。必须保证这个驱动在应用程序的classpath上。然后我们声明数据库的用户名,数据库密码,连接数据库的URL。接下来我们通知Hibernate在日志里显示运行时生成的SQL语句。
Hibernate.cfg.xml文件必须被放在你的classpath里。
在你的程序里使用hibernate必须有下面几个步骤:
1、
建立一个Hibernate configuration对象
2、
使用Hibernate configuration对象来建立一个Hibernate
factory对象。
3、
使用Hibernate factory对象来建立一个Hibernate
session对象。
4、
使用Hibernate session对象来开始一个事务(可选)
5、
使用Hibernate session对象来建立、读取、更新、删除数据库里的数据
6、
提交事务(可选)
7、
关闭session
Hibernate最佳实践是建立和缓存Hibernate
factory来提高性能。所以我们最好在第一步和第二步建立一个Struts
plug-in 来在servlet context中缓存Hibernate
factory。如List5所示:
Listing 5. HibernatePlugin.java
package
com.strutsrecipes.hibernate.plugin;
import
java.net.URL;
import javax.servlet.ServletException;
import
net.sf.hibernate.HibernateException;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;
import
org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;
public
class HibernatePlugin implements PlugIn {
private Configuration config;
private SessionFactory factory;
private String path = "/hibernate.cfg.xml";
private static Class clazz = HibernatePlugin.class;
public static final String KEY_NAME = clazz.getName();
private static Log log = LogFactory.getLog(clazz);
public void setPath(String path) {
this.path = path;
}
public void init(ActionServlet servlet, ModuleConfig modConfig)
throws ServletException {
try {
URL url =
HibernatePlugin.class.getResource(path);
config = new
Configuration().configure(url);
factory =
config.buildSessionFactory();
servlet.getServletContext().setAttribute(KEY_NAME, factory);
} catch (MappingException e) {
log.error("mapping error", e);
throw new
ServletException();
} catch (HibernateException e) {
log.error("hibernate error", e);
throw new
ServletException();
}
}
public void destroy() {
try {
factory.close();
} catch (HibernateException e) {
log.error("unable to close factory", e);
}
}
}
建立一个Struts plug-in只需要两个步骤。第一,建立一个类的实现
org.apache.struts.action。PlugIn(列表5)。第二,在struts-config.xml文件中定义<plug-in>标签(列表6)。
让我们跳到第5步:
我们创建一个常量来储存servlet上下文属性键值的名字。这里我们选择HibernatePlugin这个类名。注意到这个常量必须是static
public final的。我们使用HibernatePlugin类来取用Action中这个关键字的值(列表7)。我们定义了path属性。默认的,Hibernate-Plugin会在/hibernate.cfg.ml文件里找hibernate的配置。我们可以使用这个属性来从classpath里的其它任意路径或文件里加载Hibernate的配置。接下来,我们使用classloader来查找Hibernate配置文件,然后建立Hibernate
configuration对象。我们使用Hibernate configuration对象来建立一个Hibernate
factory对象,然后我们把这个Hibernate factory对象存在servlet
context中。这个factory现在便可以在servlet的任意实例中使用了。
作为一个好的实践,应该在destroy方法里面关闭factory。
列表6展示了struts-config应用程序。这里唯一与平常的不一样的地方是<plug-in>标签。我们在这里声明Hibernate
plug-in,它将建立并且缓存Hibernate
factory对象。
Listing 6. struts-config.xml
<?xml
version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE
struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts
Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="searchForm"type="com.strutsrecipes.hibernate.forms.SearchForm"/>
</form-beans>
<global-forwards>
<forward name="search"
path="/search.jsp"/>
<forward name="searchsubmit"
path="/searchsubmit.do"/>
</global-forwards>
<action-mappings>
<action path="/searchsubmit"
type="com.strutsrecipes.hibernate.actions.SearchAction"
name="searchForm"
scope="request"
input="/search.jsp">
<forward name="success" path="/element.jsp"/>
</action>
</action-mappings>
<plug-in
className="com.strutsrecipes.hibernate.plugin.HibernatePlugin">
<set-property
property="path" value="/hibernate.cfg.xml"/>
</plug-in>
</struts-config>
列表7展示了用来查询一个元素的SearchForm。它非常的简单因为用户只能通过元素符号来查询。
Listing 7. SearchForm.java
package
com.strutsrecipes.hibernate.forms;
import
org.apache.struts.action.ActionForm;
public class SearchForm extends ActionForm {
String symbol;
public String getSymbol() {
return symbol;
}
public void
setSymbol(String symbol) {
this.symbol = symbol;
}
}
让我们来看一个列表8的SearchAction。虽然我们也许会决定在系统架构的其它部份上使用Hibernate,在这里我们选择在Action里面使用它。我们将推迟对其它选择的讨论到我们的讨论部份。
Listing 8. SearchAction.java
package
com.strutsrecipes.hibernate.actions;
import
java.util.List;
import
javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import
org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import
net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import
org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import
com.strutsrecipes.hibernate.beans.Element;
import com.strutsrecipes.hibernate.forms.SearchForm;
import com.strutsrecipes.hibernate.plugin.HibernatePlugin;
public
class SearchAction extends Action {
private static Log log =
LogFactory.getLog(SearchAction.class);
final public static String HQL_FIND_ELEMENT =
"from
com.strutsrecipes.hibernate.beans.Element as e where e.symbol =
?";
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
SearchForm searchForm = (SearchForm) form;
Element element = null;
List elements = null;
SessionFactory factory = null;
Session session = null;
try {
factory =
(SessionFactory) servlet.getServletContext()
.getAttribute(HibernatePlugin.KEY_NAME);
session = factory.openSession();
elements = session.find(HQL_FIND_ELEMENT,searchForm.getSymbol(),
Hibernate.STRING);
if (!elements.isEmpty()) {
element = (Element) elements.get(0);
}
} catch (HibernateException e) {
log.error("Hibernate error", e);
} finally {
log.error("Hibernate exception encountered");
session.close();
}
if (element != null) {
request.setAttribute("element", element);
return mapping.findForward("success");
}
ActionErrors errors = new ActionErrors();
errors.add(ActionErrors.GLOBAL_ERROR,
new
ActionError("error.notfound"));
saveErrors(request, errors);
return mapping.getInputForward();
}
}
让我们迅速的浏览一下在SearchAction里面发生了什么。SearchAction使用SearchForm.getSymbol()方法来获取用户在查询页面输入的元素符号。Hibernate被用来查找数据库并且转换存在数据库里的数据到一个Element对象。这个Element对象将被置于请求的下下文中以够JSP页面使用。让我们跳到列表8来一行一行的详细看到底发生了什么。
首先,我们声明一个常量来搜索数据库。接着我们将form转换为SearchForm,然后我们获得Hibernate
factory。重新调用已经被创建而且缓存在servlet上下文的factory。接下来,我们获得一个session。这个session包含了一个到数据库的连接。Hibernate使用在列表4的配置信息来连接到数据库。然后我们搜寻数据库。
有其它的方法来使用Hibernate搜寻数据库,但find方法无论一个查询是否有使用主键都非常的适合。注意,我们已经声明了HQL_FIND_ELEMENT常量。在这个常量里定义的SQL看起来非常的像标准SQL,但不完全。Hibernate使用的SQL是Hibernate私有的,OO版本的SQL语句。
深入研究Hibernate SQL(HQL):
from com.strutsrecipes.hibernate.beans.Element
as e where e.symbol = ?
这个声明告诉Hibernate选择置于com.strutsrecipes.hibernate.beans包下的所有Element对象。where子句来用过滤出元素符号匹配运行时参数的所有Element对象。as
e指明了e将被在HQL的任何位置作为一个别名,正如我们已经在where子句里面做的那样。我们可以看到我们从数据库里选择的是对象而不是行。Hibernate使用列表4里的信息来映射类到其对应的表。在这个例子里,表与对象之间的关系是非常接近的,但这并不是必须的。
find方法的第二个与第三个参数分别是HQL替代参数的值与类型。Hibernate参考资料里描述了替换运行时参数的其它方法。
find方法总是返回一个List对象。在这个例子中,我们获得了包含Element对象的一个List。这里我们可以确定最多一个实例被返回因为elements表在symbol列上有唯一性的索引约束。(参列表1)
返回到列表8,我们拷贝List中的第一个元素的引用给变量element。为了处理任何Hibernate异常,我们记录异常并给用户显示一个“找不到”的消息。但是也许你想显示不同的提示信息或使用不同的异常处理。接着,我们关闭session。在finally区段关闭session保证即使在异常抛出的时候也也要关闭它。我们把Element对象保存在request上下文中并且在最后找不到符号的时候建立了一个ActionError。为了完整起见,我们展示search。jsp和element。jsp(列表10)。
Listing 9. Search.jsp
<%@
page language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld"
prefix="html" %>
<html:html>
<body>
<h1>Search for an Element</h1>
<html:form action="/searchsubmit.do">
symbol <form:text
property="symbol"/>
<html:submit value="Search"/>
</html:form>
<html:errors/>
</body>
</html:html>
Listing 10. Element.jsp
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld"
prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld"
prefix="html" %>
<html:html>
<h1>Periodic Element</h1>
Name: <bean:write name="element"
property="name"/><br>
Symbol: <bean:write name="element"
property="symbol"/><br>
Number: <bean:write name="element"
property="number"/><br>
Mass: <bean:write name="element"
property="mass"/><br>
<html:link forward="search">Search</html:link><p>
</html:html>
在把Hibernate投入使用前,参考Hibernate文档,确保所有Hibernate所需的jar文件都在classpath中。
讨论
数据持久化是一项乏味而且费力的工作。让事情更糟糕的是,将基于对象的数据表现形式转化为关系型的或相反都必须付出相当大的努力。幸运的是,有许多优秀的对象-关系映射工具存在来减轻这种负担。在这个摘录中,我们探究了Hibernate
– 方便Java程序员使用的,非常流行的开源ORM工具之一。
Hibernate是一个功能非常强大的产品,还有一些未知的功能留给你们去发现。我们简单的例子只是关于读这个行为,但是CRUD里的其它功能也是一样的简单。功能性的更新和读取指定对象一样简单,调用JavaBean
setter,调用session的commit方法。Hibernate负责帮你生成SQL语句并且更新数据库。一个删除也是非常的简单—session.delete(element)便是所有要做的!最后建立只是需要初始化对象,调用setters方法,然后调用session.save(element)。
Hibernate最佳实践推荐缓存Hibernate
factory对象。我们选择通过Struts plug-in来建立并且缓存factory。你也可以选择使用其它方法在你的类里缓存它。
虽然这个摘录能很好的满足你的需要,它还有其它的一些缺点。第一,我们在Struts
Action里使用了Hibernate。迁移到其它的持久层框架上便将需要我们改变每个使用Hibernate的Action。第二,我们的持久层紧密的与表示层连接。这种关联使我们在其它表示层机制中没有重新使用持久层逻辑的机会,例如批处理程序。
虽然有许多改进的空间,当你不需要重用表现层的时候,这个摘录还是很适合的。