(一)hibernate 优化笔记:基础
二.hibernate的映射:一对多关联关系
hibernate的映射可以说是hibernate中最复杂的部分了。我们一步一步来,首先说一对多关联关系,我们知道在数据库只能用外键而且只能出表示一对多和多对一的单向关系,而在hibernate中还有另外一种就是一对多双向关联。
Order到Customer的多对一单项关联
Customer到Order的一对多单项关联
Customer和Order的一对多双向关联
1.多对一的单向关联关系
第一次我把整个映射文件列出来,后面我就只列关键部分了
Order.hbm.xml文件配置如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping >
<class name="mypack.Order" table="ORDERS">
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="orderNumber" type="string" >
<column name="ORDER_NUMBER" length="15" />
</property>
<many-to-one
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
not-null="true"
lazy="false"
/>
</class>
</hibernate-mapping>
BusinessService.java的代码如下:
package mypack;
import org.hibernate.*;
import org.hibernate.cfg.Configuration;
import java.util.*;
public class BusinessService{
public static SessionFactory sessionFactory;
static{
try{
/* 初始化hibernate,建立SessionFactory实例*/
Configuration config = new Configuration();
config.configure();
sessionFactory = config.buildSessionFactory();
}catch(RuntimeException e){e.printStackTrace();throw e;}
}
/* 查询customer对象关联的所有order对象*/
public List findOrdersByCustomer(Customer customer){
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
List orders=session.createQuery("from Order as o where o.customer.id="+customer.getId())
.list();
tx.commit();
return orders;
}catch (RuntimeException e) {
if (tx != null) {
tx.rollback();
}
throw e;
} finally {
session.close();
}
}
/* 按照OID查询customer对象*/
public Customer findCustomer(long customer_id){
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Customer customer=(Customer)session.get(Customer.class,new Long(customer_id));
tx.commit();
return customer;
}catch (RuntimeException e) {
if (tx != null) {
tx.rollback();
}
throw e;
} finally {
session.close();
}
}
/*级联保存customer和order对象,该方法的前提是在Order.hbm.xml中的<many-to-one> 标签中配置casecade="save-update",
* 当保存Order时,会自动持久化处于临时状态的customer对象,保存到数据库时会级联保存那个“one”,也即是customer对象*/
public void saveCustomerAndOrderWithCascade(){
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Customer customer=new Customer("Jack");
Order order1=new Order("Jack_Order001",customer);
Order order2=new Order("Jack_Order002",customer);
session.save(order1);
session.save(order2);
tx.commit();
}catch (RuntimeException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
}
/* 分别保存Customer和Order对象(主动持久化Customer),
* 也可以达到saveCustomerAndOrderWithCascade的效果,而且不用配置casecade="save-update",
*关于这个在实际应用中哪一个比较好还要看实际要求,就客户和订单这方面来说,肯定要配置casecade比较好,因为不可能存在没有客户的订单吧!*/
public void saveCustomerAndOrder(){
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Customer customer=new Customer("Tom");
session.save(customer);
Order order1=new Order("Tom_Order001",customer);
Order order2=new Order("Tom_Order002",customer);
session.save(order1);
session.save(order2);
tx.commit();
}catch (RuntimeException e) {
if (tx != null) {
tx.rollback();
}
throw e;
} finally {
session.close();
}
}
/*打印订单*/
public void printOrders(List orders){
for (Iterator it = orders.iterator(); it.hasNext();) {
Order order=(Order)it.next();
System.out.println("OrderNumber of "+order.getCustomer().getName()+ " :"+order.getOrderNumber());
}
}
public void test(){
// saveCustomerAndOrder();
saveCustomerAndOrderWithCascade();
System.out.println("\n\n****************打印客户号为1的所有Order********************");
Customer customer=findCustomer(1);
List orders=findOrdersByCustomer(customer);
printOrders(orders);
}
public static void main(String args[]){
new BusinessService().test();
sessionFactory.close();
}
}
customer的配置我就不写了,注意上面的配置order.hbm,xml中的not-null=”true”表明该字段是不允许为空的,也就是插入一个order的时候必须指定其customer的ID。
【运行结果】 :
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: insert into CUSTOMERS (NAME, ID) values (?, ?)
Hibernate: insert into ORDERS (ORDER_NUMBER, CUSTOMER_ID, ID) values (?, ?, ?)
Hibernate: insert into ORDERS (ORDER_NUMBER, CUSTOMER_ID, ID) values (?, ?, ?)
****************打印客户号为1的所有Order********************
Hibernate: select customer0_.ID as ID0_, customer0_.NAME as NAME0_0_ from CUSTOMERS custom
er0_ where customer0_.ID=?
Hibernate: select order0_.ID as ID, order0_.ORDER_NUMBER as ORDER2_1_, order0_.CUSTOMER_ID
as CUSTOMER3_1_ from ORDERS order0_ where order0_.CUSTOMER_ID=1
Hibernate: select customer0_.ID as ID0_, customer0_.NAME as NAME0_0_ from CUSTOMERS custom
er0_ where customer0_.ID=?
OrderNumber of Tom :Tom_Order001
OrderNumber of Tom :Tom_Order002
2.一对多的双向关联关系
上面的Order.hbm,xml配置不变,所以我们拥有从order到Customer的单向关联,如果要查询某个订单的Customer,我们直接使用order.getCustomers()就可以了,那我们怎么才能拥有从Customer到order的关联呢?当这里两个关联都有了,那么就一对多的双向关联,我们要实现的需求就是直接customer.getOrders()就取出这个客户的所有订单了。当然这其中还有很多实际应用方面的另外要求,比如作为一个商场不可能客户以来就把他所有的订单都取出来吧,这其中我们要怎么控制,怎么配置我们也会说到。
在Customer.java中要增加如下声明并提供其set和get方法:
private Set orders = new HashSet();
根据上面给出Customer.hbm.xml的配置:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="mypack.Customer" table="CUSTOMERS">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="name" type="string">
<column name="NAME" length="15" />
</property>
<set name="orders" cascade="save-update">
<key column="CUSTOMER_ID" />
<one-to-many class="mypack.Order" />
</set>
</class>
</hibernate-mapping>
2.1配置介绍:上面配置了cascade="save-update" 表明当保存或更新Customer对象时,会级联保存或更新orders集合中的所有order对象,如下面的方法:
public void saveCustomerAndOrderWithCascade(){
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
//创建customer和order对象
Customer customer=new Customer("Tom",new HashSet());
Order order=new Order();
order.setOrderNumber("Tom_Order001");
//建立customer和order的关联关系
order.setCustomer(customer);
customer.getOrders().add(order);//将order加入到customer的set中
// 保存Customer对象,hibernate会级联保存orders
session.save(customer);
tx.commit();
idOfTom=customer.getId();
idOfTomOrder=order.getId();
}catch (RuntimeException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
}
该方法的【运行结果】是:
Hibernate: insert into CUSTOMERS (NAME, ID) values (?, ?)
Hibernate: insert into ORDERS (ORDER_NUMBER, CUSTOMER_ID, ID) values (?, ?, ?)
2.2介绍<set>元素的inverse属性
我们首先假设数据库中有两个没有关联的Custermer和Order,我们要将其取出,并建立关联关系。其方法如下:
public void associateCustomerAndOrder(){
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Customer customer=(Customer)session.load(Customer.class,idOfJack);
Order order=(Order)session.load(Order.class,idOfJackOrder);
order.setCustomer(customer); //建立custermer到order的关联
customer.getOrders().add(order);//将order加入到custermer的set中
tx.commit(); //根据持久化对象的状态执行相应sql语句
}catch (RuntimeException e) {
if (tx != null) {
tx.rollback();
}
e.printStackTrace();
} finally {
session.close();
}
}
上面方法运行后的【结果】是:
update ORDERS set ORDER_NUMBER = ‘JACK_Oredr001’,CUSTOMER_ID=2 where ID=2
update ORDERS set CUSTOMER_ID=2 where ID=2
我们在数据库中明明只修改了一条记录,为什么会有两个sql语句呢,原来是因为hibernate会安装持久化对象的属性变化来同步更新数据库,
当执行到
order.setCustomer(customer);
时,hibernate检测到order的变化则会计划执行相应如下的sql语句:
update ORDERS set ORDER_NUMBER = ‘JACK_Oredr001’,CUSTOMER_ID=2 where ID=2
当执行到
customer.getOrders().add(order);//将order加入到custermer的set中
时,hibernate有检测到customer有上述变化了,于是执行如下sql语句:
update ORDERS set CUSTOMER_ID=2 where ID=2
显然,这一步是完全没有必要的,为提高hibernate性能,防止执行多余的sql语句,我们可以在Customer.hbm.xml中的<set>元素中加入如下配置inverse="true",这段代码表明Customer关联的知识Order端的镜像,当hibernate探测到持久化对象Customer和Order都发生变化时,只安装Order的变化来同步更新数据库。
其实:我们上面的代码中如果注释掉这段代码,
order.setCustomer(customer);//建立custermer到order的关联
我们即使不是设置inverse属性,程序也只执行
update ORDERS set ORDER_NUMBER = ‘JACK_Oredr001’,CUSTOMER_ID=2 where ID=2
这一段sql语句,
Tip:但是我们为了程序的健壮性,在两个对象有双向关联关系的时候,我们应该同时修改两端的对象的响应属性,为避免执行多余的sql语句,我们还应该在set中设置inverse属性,亦然我们在解除双向关联的关系时,也应该修改两端的属性,执行如下代码
customer.getOrders().remove(order);
order.setCustomer(null);
2.3级联删除
考虑我们在删除某个Customer的时候,肯定也要删除对应order的记录,我们就应该设置cascade="delete"
考虑我们在解除某个Customer和某Order的关联关系的时候,如果要删除该order的记录,同时我们还要保证其“save-update“,和”delete”的功能,我们就可以设置cascade="all-delete-orphan"(orphan是“孤儿”的意思)
关于casecade的配置详细表如下:
<!--[endif]-->
2.一对多的双向自身关联关系
<!--[endif]-->
自身关联
该类Category.hbm.xml的配置如下:
<hibernate-mapping >
<class name="mypack.Category" table="CATEGORIES" >
<id name="id" type="long" column="ID">
<generator class="increment"/>
</id>
<property name="name" type="string" >
<column name="NAME" length="15" />
</property>
<set
name="childCategories"
cascade="save-update"
inverse="true"
>
<key column="CATEGORY_ID" />
<one-to-many class="mypack.Category" />
</set>
<many-to-one
name="parentCategory"
column="CATEGORY_ID"
class="mypack.Category"
/>
</class>
</hibernate-mapping>
hibernate的映射到此先告以段落,但是你如果以为hibernate的映射到此就结束了就大错特错了,hibernate的映射远不止这一点。但是上面的是平时比较常用的方式。关于映射我们后面会继续深入来讨论。下一节先打个基础说说hibernate是如何操纵对象的。