NHibernate中Get和Load的区别
NHibernate中的Get和Load方法是我们最常用的加载单个对象实例的方法。如果不了解这两者的区别则会存在随意使用的情况。
主要区别有两个:
1.对于无此POID的情况,Get方法会返回null,而Load方法会抛出异常(异常并不是在调用Load方法时立即抛出的)
2.Get方法是立即从数据库中加载该对象,而Load方法返回的是一个代理对象,没有立即命中数据库,也就是所谓的延迟加载。
其实第二点区别才是最主要的,我们的实际业务中应该根据需要使用Get或者Load。
接下来对上面的两个区别用代码来解释一下:
以下代码使用Get方法获取User对象:
var session1 = sessionFactory.OpenSession();
session1.BeginTransaction();
//使用get方法取得对象实例
Console.WriteLine("----session1.Get方法开始---------");
var user1 = session1.Get<Model.User>(new Guid("1bd7e168-3ba6-43ae-aba3-fb121c6088cc"));
Console.WriteLine("----session1.Get方法结束---------");
Console.WriteLine("----开始输出Id---------");
Console.WriteLine("Id:" + user1.Id);
Console.WriteLine("----开始输出Name---------");
Console.WriteLine("Name:"+user1.Name);
session1.Transaction.Commit();
session1.Close();
上述guid实际存在于数据库中,让我们来看一下NH的SQL语句的执行情况:
从上面的结果可以看到,当调用Get方法时,NH即发出Select语句,立即从数据库中加载数据。
再来看看Load的情况:
session2.BeginTransaction();
//使用load方法取得对象实例
Console.WriteLine("----session2.Load方法开始---------");
var user2 = session2.Load<Model.User>(new Guid("1bd7e168-3ba6-43ae-aba3-fb121c6088cc"));
Console.WriteLine("----session2.Load方法结束---------");
Console.WriteLine("----开始输出Id---------");
Console.WriteLine("Id:" + user2.Id);
Console.WriteLine("----开始输出Name---------");
Console.WriteLine("Name:" + user2.Name);
session2.Transaction.Commit();
session2.Close();
以下是控制台输出结果:
这次和前一次的结果明显不同,在调用Load方法时并没有执行Select语句,当我们输出user2.Id属性时依旧没有执行Select语句,因为Id属性的值不需要从数据库中获得,可以通过传入Load方法的参数值取得,这也可以看出NH是会尽最大努力进行延迟加载。当我们输出user2.Name属性时ND发出了相应的SQL语句,并且真正实例化了user2对象。
通过上面的分析,我们很容易想到,如果当guid不存在于数据库中时,调用Load方法并不会出现异常,而且输出user2.Id属性时也不会出现异常,只有当真正加载数据时才会出现异常。
对比Get方法,如果对于Get方法返回的对象不作是否为null判断的话,则会在输出user.Id是报出异常。
什么情况下使用Get? 什么情况下使用Load?
如果我们获取一个对象实例,只是使用到它的POID时就应该使用Load。下面举例说明:
还是接着使用上面的User类,我们再增加一个Department类,User类中有一个对Department对象的引用,也就是存在着many-to-one的关系,这里只做从User到Department的单向关联关系。两个类的代码如下:
{
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string City { get; set; }
public virtual Int16 Age { get; set; }
public virtual bool Sex { get; set; }
public virtual Model.Department Dept { get; set; }
//需要重写Equals 和GetHashCode方法,这里略
}
public class Department
{
public virtual Guid Id { get;protected set; }
public virtual string Name { get; set; }
}
下面是两个类对应的映射文件:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model" namespace="Model">
<class name="User" table="MyUser">
<id name="Id">
<generator class="guid" />
</id>
<property name="Name" not-null="true" />
<property name="City" />
<property name="Age" />
<property name="Sex" />
<many-to-one name="Dept" class="Department" column="DeptId"></many-to-one>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model" namespace="Model">
<class name="Department" table="MyDept">
<id name="Id">
<generator class="guid" />
</id>
<property name="Name" not-null="true" />
</class>
</hibernate-mapping>
两个类足够简单,只存在着一个many-to-one的单向关联,但是这也足以说明问题了。
代码也很简单,我们新建了一个新的用户,而该用户属于一个部门,所以需要设置User的Dept属性值,而为了设置Dept属性我们则需要一个Department对象。
使用Get的情况:
session3.BeginTransaction();
var newUser = new Model.User { Name = "阿六", Age = 36, City = "北京", Sex = false };
Console.WriteLine("----session3.Get方法开始---------");
var dept1 = session3.Get<Model.Department>(new Guid("7e441cb5-3c96-43b4-b5e5-7a6567edcadf"));
Console.WriteLine("----session3.Get方法结束---------");
newUser.Dept = dept1;
session3.Save(newUser);
session3.Transaction.Commit();
session3.Close();
我们得到的控制台输出结果是:
从上面输出结果可以看出为了得到Department对象,NH从数据库了加载了对应的记录,并初始化了dept1变量,所以上述代码一共执行了两条SQL语句,思考一下,是否真的有必要需要两条SQL语句呢?
接下来是Load的情况:
session4.BeginTransaction();
var newUser4 = new Model.User { Name = "小明", Age = 26, City = "广州", Sex = true };
Console.WriteLine("----session4.Load方法开始---------");
var dept4 = session4.Load<Model.Department>(new Guid("7e441cb5-3c96-43b4-b5e5-7a6567edcadf"));
Console.WriteLine("----session4.Load方法结束---------");
newUser4.Dept = dept4;
session4.Save(newUser4);
session4.Transaction.Commit();
session4.Close();
以下是控制台输出结果:
仔细看一下,我们就会发现和刚才的Get方法不同的是这里少了一条Select语句,虽然一条Select语句对大部分中小型项目来说也许并不重要,但是对于高并发的大型项目来说对性能上的影响还是挺大的,既然可以做的更好,我们为什么不做呢
当然,使用刚才的Load方法也是有缺点的,如果刚才的Department的guid不存在与数据库中,由于NH并不实际发出Select查询语句,所以它并不去验证是否实际存在,当保存User的时候就保存了一个不存在DeptId值,如果数据库中本身有外键关系,则会抛出SQL异常。
讲到这里本该结束了,但是上面还少讲到了一点,如果你在文件中显式设置了class的lazy="false"的话,那么Load也就不会延迟加载了,和Get基本一致了。
希望本文能对NH的初学者有所帮助
最后是本文的示例代码:
/Files/szp1118/NHibernateTest.rar
补充:
刚才有园友回复关于缓存的情况
其实上面的文中的都是指不存在缓存的情况,如果已经存在缓存则会去缓存中查找。
把第一个示例修改一下:代码如下:
var session1 = sessionFactory.OpenSession();
session1.BeginTransaction();
//使用get方法取得对象实例
Console.WriteLine("----session1.Get方法开始---------");
var user1 = session1.Get<Model.User>(new Guid("1bd7e168-3ba6-43ae-aba3-fb121c6088cc"));
Console.WriteLine("----session1.Get方法结束---------");
Console.WriteLine("----再次通过Get加载开始---------");
var user11 = session1.Get<Model.User>(new Guid("1bd7e168-3ba6-43ae-aba3-fb121c6088cc"));
Console.WriteLine("----再次通过Get加载结束---------");
Console.WriteLine("----通过Load加载开始---------");
var user12 = session1.Load<Model.User>(new Guid("1bd7e168-3ba6-43ae-aba3-fb121c6088cc"));
Console.WriteLine("----通过Load加载结束---------");
Console.WriteLine("----开始输出User1 Id---------");
Console.WriteLine("User1 Id:" + user1.Id);
Console.WriteLine("----开始输出User1 Name---------");
Console.WriteLine("User1 Name:"+user1.Name);
Console.WriteLine("----开始输出User11 Id---------");
Console.WriteLine("User11 Id:" + user11.Id);
Console.WriteLine("----开始输出User11 Name---------");
Console.WriteLine("User11 Name:" + user11.Name);
Console.WriteLine("----开始输出User12 Id---------");
Console.WriteLine("User11 Id:" + user12.Id);
Console.WriteLine("----开始输出User12 Name---------");
Console.WriteLine("User12 Name:" + user12.Name);
session1.Transaction.Commit();
session1.Close();
得到的结果如下:
通过上面的输出结果可以看到,三次加载同一个User对象时,只执行一条SQL语句,后两次是从缓存中取得数据。
延迟加载和缓存应该说是两个不同的概念, 本文重点在于说明延迟加载