CREATE TABLE ORDERS( ID VARCHAR NOT NULL PRIMARY KEY, ORDER_DATE TIMESTAMP NOT NULL, PRICE_TOTAL DOUBLE NOT NULL) CREATE TABLE PRODUCTS( ID VARCHAR NOT NULL PRIMARY KEY, NAME VARCHAR NOT NULL, PRICE DOUBLE NOT NULL, AMOUNT INTEGER NOT NULL) CREATE TABLE ORDER_ITEMS( ID VARCHAR NOT NULL PRIMARY KEY, ORDER_ID VARCHAR NOT NULL, PRODUCT_ID VARCHAR NOT NULL, AMOUNT INTEGER NOT NULL, PRICE DOUBLE NOT NULL) |
package test.hibernate; public class Product { private String id; private String name; private double price; private int amount; public String getId() { return id; } public void setId(String string) { id = string; } // 默认的构造函数及其他 // 为了简洁起见,getter/setter方法没有显示 // ... } |
public String toString() { return "[Product] " + name + "(" + id + ") price=" + price + " amount=" + amount; } |
Order
我们需要创建的下一个类是Order,它甚至比Product更简单:它只包含ID、创建日期、总价格和该Order所包括的OrderItems的Set。当然,还需要创建getter和setter方法以及默认的构造函数。
package test.hibernate; import java.util.Date; import java.util.HashSet; import java.util.Set; public class Order { private String id; private Date date; private double priceTotal; private Set orderItems = new HashSet(); // 自动设置该Order的创建时间 public Order() { this.date = new Date(); } public String getId() { return id; } public void setId(String string) { id = string; } // 为了简洁起见,其他getter/setter方法没有显示 // ... } |
package test.hibernate; public class OrderItem { /** * 创建有效的订单项。自动设置订单项的价格,并更正产品的库存可用量 * * @param order 该订单项属于的订单 * @param product 该订单项为哪种产品而创建 * @param amount */ public OrderItem(Order order, Product product, int amount) { this.order = order; this.product = product; this.amount = amount; product.setAmount(product.getAmount() - amount); this.price = product.getPrice() * amount; } // 还需要默认的构造函数来保证Hibernate工作 /** * 空构造函数遵循JavaBeans约定 * */ public OrderItem() { // 空的默认构造函数 } // 字段 private String id; private Product product; private Order order; private String productId; private String orderId; private double price; private int amount; public String getId() { return id; } public String getProductId() { return product.getId(); } public String getOrderId() { return order.getId(); } // 其他getter/setter方法没有显示 // ... //显示该订单项的方便方式 public String toString() { return "[OrderItem] id=" + id + " amount=" + amount + " price=" + price + "(" + product + ")"; } } |
/** * 添加一项产品到订单中。产品自动成为一个订单项。 * priceTotal被自动更新。 * * @param p 添加到该订单的产品 * @param amount 添加的产品量 */ public void addProduct(Product p, int amount) { OrderItem orderItem = new OrderItem(this, p, amount); this.priceTotal = this.priceTotal + p.getPrice() * amount; this.orderItems.add(orderItem); } |
创建和持久化Product
现在我们终于用到Hibernate了。使用的场景非常简单:
正如我们所看到的,这里没有提到JDBC、SQL或任何类似的东西。非常令人振奋!下面的示例遵循了上面提到的步骤:
package test; import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.Transaction; import net.sf.hibernate.cfg.Configuration; import test.hibernate.Product; // 用法: // java test.InsertProduct name amount price public class InsertProduct { public static void main(String[] args) throws Exception { // 1. 创建Product对象 Product p = new Product(); p.setName(args[0]); p.setAmount(Integer.parseInt(args[1])); p.setPrice(Double.parseDouble(args[2])); // 2. 启动Hibernate Configuration cfg = new Configuration() .addClass(Product.class); SessionFactory sf = cfg.buildSessionFactory(); // 3. 打开Session Session sess = sf.openSession(); // 4. 保存Product,关闭Session Transaction t = sess.beginTransaction(); sess.save(p); t.commit(); sess.close(); } } |
Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit> INFO: Hibernate 2.0.3 Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit> INFO: hibernate.properties not found Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit> INFO: using CGLIB reflection optimizer Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit> INFO: JVM proxy support: true Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Configuration addClass INFO: Mapping resource: test/hibernate/Product.hbm.xml Exception in thread "main" net.sf.hibernate.MappingException: Resource: test/hibernate/Product.hbm.xml not found at net.sf.hibernate.cfg.Configuration.addClass(Configuration.java:285) at test.FindProductByName.main(FindProductByName.java:24) |
INFO: hibernate.properties not found and Resource: test/hibernate/Product.hbm.xml not found. |
hibernate.connection.username=sa hibernate.connection.password= hibernate.connection.url=jdbc:hsqldb:/home/davor/hibernate/orders hibernate.connection.driver_class=org.hsqldb.jdbcDriver hibernate.dialect=net.sf.hibernate.dialect.HSQLDialect |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="test.hibernate.Product" table="products"> <id name="id" type="string" unsaved-value="null"> <column name="id" sql-type="char(32)" not-null="true"/> <generator class="uuid.hex"/> </id> <property name="name"> <column name="name" sql-type="char(255)" not-null="true"/> </property> <property name="price"> <column name="price" sql-type="double" not-null="true"/> </property> <property name="amount"> <column name="amount" sql-type="integer" not-null="true"/> </property> </class> </hibernate-mapping> |
<generator class="uuid.hex"/>元素乍一看不太好理解。但是知道了它是<id>的一个子元素后,它的作用就很明显了:由于应用程序不知道它的数据如何被持久化(我们一直这么说),我们需要一个没有任何业务含义的代理键帮助Hibernate操纵对象。新创建的Products没有那个id,Hibernate将为我们创建它们。我们选择使用UUID字符串,但它提供了许多ID生成器(顺序的、限定范围的,甚至是用户指派的,等等),而且还可以编写自己的ID生成器。
现在,创建(复制、粘贴)Product.hbm.xml的内容,并把文件和test.hibernate.Product类放到同一个包内(例如,放置Product.java文件的目录),重新运行java test.InsertProduct Milk 100 1.99命令。现在我们看到更多的日志以及...没有其他东西了!它运行正常吗?在Session sess = sf.openSession(); 前和sess.close()后添加System.out.println(p),看一看Produc出了什么问题。重新运行程序。您将看到类似于如下内容的日志输出(ID数字肯定会不同的):
[Product] Milk(null) price=1.99 amount=100
[Product] Milk(40288081f907f42900f907f448460001) price=1.99 amount=100
Hibernate为我们创建了Product的id!让我们看一下Product是否存储到了数据库中。执行select * from products,数据库返回类似于以下内容的输出:
ID |NAME |PRICE |AMOUNT | 40288081f907f42900f907f448460001|Milk |1.99 |100 | |
查找和加载产品
查找和加载已经持久化的对象在Hibernate中非常简单。使用它的查询语言,我们可以很容易地通过ID、名称或其他属性获取一个对象(或对象集)。我们能够获取完整的对象或它的一部分属性。Hibernate将处理余下的工作,最后,我们将拥有相当有用的对象层次体系。我们来看一下test.FindProductByName类。
package test; import java.util.List; import net.sf.hibernate.Hibernate; import net.sf.hibernate.Session; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.cfg.Configuration; import test.hibernate.Product; // 用法: // java test.FindProductByName name public class FindProductByName { public static void main(String[] args) throws Exception { // 执行的查询 String query = "select product from product " + "in class test.hibernate.Product " + "where product.name=:name"; // 搜索的内容 String name = args[0]; // 初始化 Configuration cfg = new Configuration() .addClass(Product.class); SessionFactory sf = cfg.buildSessionFactory(); // 打开会话 Session sess = sf.openSession(); // 搜索并返回 List list = sess.find(query, name, Hibernate.STRING); if (list.size() == 0) { System.out.println("No products named " + name); System.exit(0); } Product p = (Product) list.get(0); sess.close(); System.out.println("Found product: " + p); } } |
执行java test.FindProductByName Milk,查看显示在控制台中的内容。
注意:查询是区分大小写的,所以搜索小写的milk将不会返回任何结果。使用lower()或upper()SQL函数来启用不区分大小写的搜索。在这种情况下,我们会在查询字符串中使用where lower(product.name)=lower(:name)。关于查询的详细内容,请参见文档。此外,如果不希望显示所有的INFO日志信息,可以修改log4j.properties文件,将日志等级设置为warn。
更新和删除产品
到现在为止,您应该对Hibernate的工作方式有了一个基本的了解,因此我们将缩短冗长的示例,只显示重要的部分。
为了在单个事务中将所有产品的价格提高10%,我们可以编写如下的内容:
double percentage = Double.parseDouble(args[0])/100; sess = sf.openSession(); Transaction t = sess.beginTransaction(); // 列表包含产品 Iterator iter = list.iterator(); while (iter.hasNext()) { Product p = (Product) iter.next(); p.setPrice(p.getPrice() * (1 + percentage)); sess.saveOrUpdate(p); } t.commit(); sess.close(); |
Orders,OrderItems
有时一个一个地操纵对象确实可行,但是我们希望能够级联加载和更新。现在我们来看如何做到这一点。
我们需要同时检查Order和OrderItem。就如前面所提到的,我们添加一项Product到一个Order中,它将变成一个OrderItem。Order在内部保存一个OrderItem集。我们希望保存Order,让Hibernate来做其他工作:保存OrderItem和更新所添加的Product的可用库存(数量)。听起来很复杂,但实际上非常简单。Hibernate知道如何处理一对一、一对多、多对一和多对多方式中的相关对象。我们将从映射文件开始。
Order.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="test.hibernate.Order" table="orders"> <id name="id" type="string" unsaved-value="null" > <column name="id" sql-type="char(32)" not-null="true"/> <generator class="uuid.hex"/> </id> <property name="date"> <column name="order_date" sql-type="datetime" not-null="true"/> </property> <property name="priceTotal"> <column name="price_total" sql-type="double" not-null="true"/> </property> <set name="orderItems" table="order_items" inverse="true" cascade="all"> <key column="order_id" /> <one-to-many class="test.hibernate.OrderItem" /> </set> </class> </hibernate-mapping> |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="test.hibernate.OrderItem" table="order_items"> <id name="id" type="string" unsaved-value="null" > <column name="id" sql-type="char(32)" not-null="true"/> <generator class="uuid.hex"/> </id> <property name="orderId" insert="false" update="false"> <column name="order_id" sql-type="char(32)" not-null="true"/> </property> <property name="productId" insert="false" update="false"> <column name="product_id" sql-type="char(32)" not-null="true"/> </property> <property name="amount"> <column name="amount" sql-type="int" not-null="true"/> </property> <property name="price"> <column name="price" sql-type="double" not-null="true"/> </property> <many-to-one name="order" class="test.hibernate.Order" column="order_id" /> <many-to-one name="product" class="test.hibernate.Product" cascade="save-update" column="product_id"/> </class> </hibernate-mapping> |
用法示例
创建一个订单。在该示例中,我们创建并持久化一个订单。反复运行这个示例,查看产品数量在每次成功创建订单后如何变化。
// ... Configuration cfg = new Configuration() .addClass(Product.class) .addClass(Order.class) .addClass(OrderItem.class); // ... Order order = new Order(); order.addProduct(milk, 3); order.addProduct(coffee, 5); // ... sess = sf.openSession(); Transaction t = sess.beginTransaction(); sess.save(order); t.commit(); sess.close(); System.out.println(order); // ... |
// ... String query = "select o from o " + "in class test.hibernate.Order " + "where o.priceTotal > :priceTotalLower " + "and o.priceTotal < :priceTotalUpper"; // ... Query q = sess.createQuery(query); q.setDouble("priceTotalLower", Double.parseDouble(args[0])); q.setDouble("priceTotalUpper", Double.parseDouble(args[1])); List list = q.list(); // ... sess.close(); // ... |
// ... String query = "select o from o " + "in class test.hibernate.Order " + "where o.priceTotal > :priceTotalLower " + "and o.priceTotal < :priceTotalUpper"; Transaction tx = sess.beginTransaction(); sess.delete(query, new Object[]{new Double(args[0]), new Double(args[1])}, new Type[]{Hibernate.DOUBLE, Hibernate.DOUBLE} ); tx.commit(); sess.close(); |