NHibernate实践总结(三)HQL的theta-style join对无关联实体的连接与应用


NHibernate实践系列
1、 NHibernate实践总结(一)
2、 NHibernate实践总结(二) 在mapping文件中设置抓取策略对HQL与Criteria造成不同影响的测试与验证

一、引言
  大部分管理信息系统(MIS)都少不了员工(用户)与操作日志,当为员工创建帐号并分配相应的权限后,该帐号即可登录系统并进行相应的操作。当员工与系统进行交互操作时, 系统会把员工Id、操作时间、操作IP、操作内容等信息记录到操作日志中,以便随时审计。
  这样,从Domain的角度讲, 操作日志对象员工对象之间存在many-to-one的引用关系;从Database的角度讲, 操作日志表的员工Id列是外键,其引用 员工表的主键Id列。如果遵照数据库设计第二范式(2NF),那么操作日志表包含员工Id列(外键),但不包含员工姓名、员工帐号等冗余字段。这样,当查询操作日志获取操作信息的同时,要想获取员工姓名、员工帐号等数据,需要对操作日志表与员工表在员工Id列上进行一个连接(inner join或outer join)操作。这是一个大家比较熟悉的应用连接操作的场景,但在实际项目中可能会变得稍微复杂一点,比如对员工数据的删除。
   大家都知道,如果操作日志与员工之间建立了关联(外键引用)关系,那么在未删除该员工所有的操作日志数据之前,删除该员工数据会发生外键冲突 (foreign key violate),因为在删除员工数据时,数据库会自动检测并确保外键引用的完整性(referential integrity)。解决方法有四个:
(1)在物理删除员工数据之前先删除该员工的所有操作日志数据,然后再删除该员工数据;或者在数据库中设置级联删除,在物理删除员工数据时级联删除该员工的所有操作日志数据。尽管这样可以避免外键冲突,但是这个方法显然是不可行的,因为删除操作日志数据也就意味着破坏了审计功能。
(2)不物理删除员工数据,而是进行“软删除”(soft-delete),也就是说,为员工数据增加一个“是否删除”的标记列,当“删除”员工时设置此标记值。由于没有真正删除员工数据,也就避免了外键冲突问题。这可能是最常见的做法,但是soft-delete也不是没有问题,尤其是使用NHibernate进行面向DDD(Domain Model Development)的开发。大家可以看看这几篇文章: Avoid Soft Deletes (文章后面的评论也精彩)、 Soft Deletes aren’t Append Only model Don’t Delete – Just Don’t Soft-deletes are bad, m'kay?
(3) 操作日志与员工之间不建立关联(外键引用)关系,在数据库中仍可通过对操作日志表和员工表在员工Id列上进行连接,在一条查询语句中获取两张表的数据。由于没有建立关联(外键引用)关系,所以员工数据的物理删除不会 引起外键冲突问题。但是这个方法会引发别的问题:当员工数据被物理删除后,该员工的操作日志数据与该员工进行连接查询时,如果进行的是inner join,那么连接查询结果就为空;如果进行的是left outer join,那么在连接查询结果数据行中,关于该员工的字段信息(员工姓名、员工帐号)都为null(具体可参见outer join的相关资料)。
(4) 操作日志与员工之间不建立关联(外键引用)关系,并且在操作日志表中除包含 员工Id字段外,还包含 员工姓名员工帐号等冗余字段,这样直接查询操作日志表即可获得员工姓名、员工帐号等信息,无需再与员工表进行任何连接操作(因此还可提高查询性能),同时员工数据的删除也不影响操作日志表,但是此方法违反了数据库设计的2NF。在实际开发中为了性能的提高、为了实现的简单性,有时出现类似这样的 反模式是可以接受的,应该不是问题,问题是有冗余就一定会存在数据一致性问题:比如,当修改员工姓名后,操作日志表中该员工的操作日志数据的员工姓名冗余字段是否也需要同步更新呢?在这种情况下,对操作日志等历史数据进行同步更新往往没有必要,因为修改员工姓名这类操作不会经常发生,而且不同步更新正好还可以保留操作员工当时时刻的姓名。
  由上面的分析可见,为实现员工操作的审计功能、确保员工数据的“删除”不引发外键冲突,方法(2)与方法(4)应该是较为可行的方案。当然有的朋友会认为员工数据不该“删除”,而应该通过设立一个表示"禁用"、"启用"的状态字段来解决,当然这与方法(2)在本质上是类似的。
  说了这么多关于数据库设计的内容,不少朋友会怀疑本文是否偏离了该说的主题NHibernate, 接下来就进入主题, 我们选择方法(3)做为本文说明的实例。方法(3)的关键是:在操作日志与员工之间不建立关联(外键引用)关系,然后在员工Id列上进行连接操作。 那么在NHibernate中如何对没有建立关联关系的实体进行连接操作呢?答案是使用theta-style join。 本文首先介绍 theta-style join与常见join的区别,然后 通过此实例 具体 阐述 在NHib ernate中 对无关联实体进行 theta-style join 的实现 。

二、实例场景
  有员工( Employee )和操作日志(OperationLog)两张数据表,为说明问题, 这两张表之间 不建立外键引用关系。创建数据表的SQL如下: 
ContractedBlock.gif ExpandedBlockStart.gif Code
create table Employee
(
    Id 
int identity primary key,
    Name 
varchar(50)
)
create table OperationLog
(
    Id 
int identity primary key,
    EmployeeId 
int--foreign key references Employee(Id),
    OperationDateTime datetime
)
  接着往数据表中插入测试数据, 如下图所示:
      

三、常见join与 theta-style join的差别
  我们对没有建立外键引用关系的 Employee表 和 OperationLog表,分别进行 常见jointheta-style join的连接操作,两者SQL写法的区别如下:
(1)常见join的SQL写法(在inner join中指定要连接的表,在on中指定连接条件。SQL92标准):
select  OperationLog.Id, OperationLog.OperationDateTime, Employee.Name
from  OperationLog  inner   join  Employee
on  OperationLog.EmployeeId = Employee.Id
(2)theta-style join的SQL写法(在from中指定要连接的表,在where中指定连接条件。SQL89标准):
select  OperationLog.Id, OperationLog.OperationDateTime, Employee.Name
from  OperationLog, Employee
where  OperationLog.EmployeeId = Employee.Id
  在关系数据库中, 尽管没有为Employee表和OperationLog表建立外键引用关系,但是仍然可以用SQL在任意列(只要类型兼容)上进行 常见jointheta-style join 的操作 (当然,是否在外键列上进行连接操作会影响到数据库查询优化器对连接操作算法的选择,从而影响连接查询的性能) ,而且对于上面这个例子,这两个连接操作逻辑上是等价的,查询结果是相同的,连接查询结果 如下图所示:
 
   但在NHibernate中,对无关联实体进行连接操作,只能使用 t heta-style join而无法使用 常见join,而且仅HQL支持 theta-style join,Criteria并不支持 theta-style join 。接 下去通过实例进行 验证:

四、使用HQL的theta-style join实现无关联实体的连接
   先分别建立 Employee与OperationLog实体类(注意:两个实体类没有建立关联关系   
Employee.cs:

namespace  NHibernatePractice3.Domain
{
    
public   class  Employee
    {
        
public   virtual   int  Id {  get set ; }
        
public   virtual  String Name {  get set ; }
    }
}
OperationLog.cs:
ContractedBlock.gif ExpandedBlockStart.gif Code
namespace NHibernatePractice3.Domain
{
    
public class OperationLog
    {
        
public virtual int Id { getset; }
        
public virtual int EmployeeId { getset; }        
        
public virtual DateTime OperationDateTime { getset; }
    }
}

   接着创建mapping文件( 注意:在mapping文件中不建立关联关系
Employee.hbm.xml:
ContractedBlock.gif ExpandedBlockStart.gif Code
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
            assembly
="NHibernatePractice3"
            namespace
="NHibernatePractice3.Domain">
  
<class name="Employee">
    
<id name="Id" >
      
<generator class="native" />
    
</id>
    
<property name="Name"/>
  
</class>
</hibernate-mapping>
OperationLog.hbm.xml:
ContractedBlock.gif ExpandedBlockStart.gif Code
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
            assembly
="NHibernatePractice3"
            namespace
="NHibernatePractice3.Domain">
  
<class name="OperationLog">
    
<id name="Id" >
      
<generator class="native" />
    
</id>
    
<!--没有建立与Employee的many-to-one关联关系-->
    
<property name="EmployeeId"/>
    
<property name="OperationDateTime"/>
  
</class>
</hibernate-mapping>
  
  接下去写测试方法,测试theta-style join的实现:
ContractedBlock.gif ExpandedBlockStart.gif Code
[Test]
//测试HQL的Theta style join
public void TestHQLThetaStyleJoin()
{
    
//theta style join
    string sql = "select log.Id, log.OperationDateTime, emp.Name from OperationLog as log, Employee as emp where log.EmployeeId=emp.Id";
    
    IList list 
= null;
    
using (ISession session = NHibernateHelper.OpenSession())
    
using (ITransaction transaction = session.BeginTransaction())
    {
        list 
= session.CreateQuery(sql).List();
        transaction.Commit();
    }
    Assert.AreEqual(
3, list.Count);
}  

[Test]
//测试HQL的Theta style join
public void TestHQLThetaStyleJoin()
{
    
//heta style join
    string sql = "select log.Id, log.OperationDateTime, emp.Name from OperationLog as log, Employee as emp where log.EmployeeId=emp.Id";
    
    IList list 
= null;
    
using (ISession session = NHibernateHelper.OpenSession())
    
using (ITransaction transaction = session.BeginTransaction())
    {
        list 
= session.CreateQuery(sql).List();
        transaction.Commit();
    }
    Assert.AreEqual(
3, list.Count);
}

  单元测试成功,NHibernate生成theta-style join形式的SQL:
ContractedBlock.gif ExpandedBlockStart.gif Code
NHibernate: select operationl0_.Id as x0_0_, operationl0_.OperationDateTime as x1_0_, employee1_.Name as x2_0_ 
from OperationLog operationl0_, Employee employee1_ 
where (operationl0_.EmployeeId=employee1_.Id )
  
  由此可见,HQL支持无关联实体的theta-style join。那么Criteria是否也支持无关联实体的theta-style join、无关联实体的常见join操作呢?答案显然是否定的,因为Criteria在连接时,无论是使用 嵌套Criteria还是 CreateAlias,都必须指定关联实体或关联集合,所以通过Criteria对实体进行连接必须得为实体建立关联关系。 那么Criteria是否支持关联实体的theta-style join操作呢?答案也是否定的 , 有兴趣的朋友自己可以验证一下。现做个小结,如下表所示:

 

常见join

theta-style join

关联实体

HQL支持、Criteria支持

 HQL支持

无关联实体


HQL支持

  这里还要说明的是,HQL的theta-style join与inner join在逻辑上是等价的, 但是目前HQL的theta-style join不能实现outer join功能。 
五、总结
  对无关联实体进行连接操作,首先得考虑一下Domain设计是否合理恰当。对实体进行连接,实体之间往往会存在关联关系。但在实际项目中,也不排除对无关联实体进行连接操作的可能。
  本文首先提出了对无关联实体进行连接的应用场景,然后通过实例说明了在NHibernate中,使用HQL的theta-style join实现无关联实体的连接。当然了直接使用native sql也可以实现无关联实体的连接,不过这不在本文所述范围之内。
 
六、实例源代码下载
   实例源代码下载
  注意:本实例的运行环境与配置请参见 NHibernate实践总结(二) 在mapping文件中设置抓取策略对HQL与Criteria造成不同影响的测试与验证









create table Employee
(
    Id int identity primary key,
    Name varchar(50)
)
create table OperationLog
(
    Id int identity primary key,
    EmployeeId int--foreign key references Employee(Id),
    OperationDateTime datetime
)

转载于:https://www.cnblogs.com/dddd218/archive/2009/09/13/1565921.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值