1、引言 老谭在面试开发人员的时候,为了考察他们的数据库开发能力,常常祭出我的法宝,就是大学数据库教程中讲到的一个模式:学生选课。这个模式是这样的: 在这个模式中,学生(Student)和课程(Course)都是实体,分别有主键Id。考试成绩(Score)是学生和课程
1、引言
老谭在面试开发人员的时候,为了考察他们的数据库开发能力,常常祭出我的法宝,就是大学数据库教程中讲到的一个模式:学生选课。这个模式是这样的:
在这个模式中,学生(Student)和课程(Course)都是实体,分别有主键Id。考试成绩(Score)是学生和课程之间的多对多关系。
基于这个模式,对于新手,可以出一些简单查询要求,对于熟手,可以出一些复杂的查询要求,用起来得心应手。
但今天要说的重点是,怎么用NHibernate实现这个模式。和一般多对多关系稍有不同的是,这个关系带有一个属性,就是考试成绩(Score)。
如果多对多关系上带有属性,我们一般会把这个关系扩充为实体,也就是把一个多对多关系转换为一个实体加上两个多对一关系。
如果多对多关系上有多个属性,将其转换为一个实体还是有必要的,但只有很少的属性(本例中只有一个),转换为实体实在有些浪费。因为一般情况下,对于实体,我们要为其创建一个人工主键,有了人工主键,又要创建一个序列。在映射时,这个实体自然要有对应的类。这一大堆事情,想想就非常麻烦。
问题的关键是,在概念上这本来就是一个关系,为了实现的方便,而将其转换为一个实体,这个凭空多出来的实体,使概念变得复杂,非常别扭。
因此,这里要探究一下,让关系恢复为为关系,怎么用NHibernate来处理。
2、映射
如果关系表Score中没有Score那个属性字段,Student实体可以映射为这样的类:
public class Student { public virtual long Id { get; set; } public virtual string Name { get; set; } public virtual IList<Course> Courses { get; set; } }
Course也类似。
但有了属性Score,再这样映射就把这个属性丢了。为了带上Score属性,两个实体应该这样映射:
public class Student { public virtual long Id { get; set; } public virtual string Name { get; set; } public virtual IDictionary<Course, int> Courses { get; set; } }
public class Course { public virtual long Id { get; set; } public virtual string Name { get; set; } public virtual IDictionary<Student, int> Students { get; set; } }
原来的列表(List)变成了字典(Dictionary),字典的主键是原来列表中的元素,值则是关系上的属性值,即考试成绩。
对应的映射文件自然也要调整,结果如下:
... <class name="Course" table="Course"> <id name="Id" unsaved-value="0"> <column name="Id" /> ... </id> <property name="Name"> <column name="Name"/> </property> <map name="Students" table="Score" lazy="true"> <key column="CourseId"/> <index-many-to-many column="StudentId" class="Student" /> <element column="Score" /> </map> </class>... <class name="Student" table="Student"> <id name="Id" unsaved-value="0"> <column name="Id" /> ... </id> <property name="Name"> <column name="Name"/> </property> <map name="Courses" table="Score" lazy="true"> <key column="StudentId"/> <index-many-to-many column="CourseId" class="Course"/> <element column="Score" /> </map> </class>
经过这样的映射,多对多关系中的属性带到了类中,而且避免了为关系创建实体——没有Score这样的类。对这样映射结果的操作,和常规多对多关系映射方式多生成的基本类似,但也有几个要注意的问题。
3、查询
对于一些简单的查询,如:
小明所有课程的成绩;化学这门课所有学生的成绩等都比较容易处理,因为这只需要在一个实体上加过滤条件。
如果需要在两个实体上加过滤条件,如查询小明化学课的成绩,就有些复杂,其查询语句Hql是这样的:
select score from Course c, Student s join c.Students score where c.Name='化学' and index(score) = s.Id and s.Name='小明'
这里出现了我们很少用到的hql函数:index()。这是专门应对map的,其目的是获得map的索引字段。
吊诡的是,虽然我们指明,map的key是对象,如Course.Students的key是Student的对象,但index()的结果,仍然是
即便是“简单的查询”,如查询小明所有课程的成绩,也有一个问题要注意。这个问题和懒加载相关。
在这个查询中,我们已经知道需要获取所有课程,因此,希望进行预先加载:
from Student s join fetch s.Courses course where s.Name='小明'
得到结果后,如果脱离查询的环境,如释放Session,在访问课程时,如:
s.Courses.Select(c => c.Key.Name)仍会抛出异常。因为,上述的查询并没有把Course对象加载进来。
目前还不知道怎么预加载map中索引对象。需要的话,只有依赖懒加载机制。
4、维护
除了查询,在对关系进行维护时,也有一点值得特别注意:save-update类型的cascade无效。
cascade属性,为我们进行关系维护带来不少便利。在常规(不带属性)的多对多关系中,我们的维护操作可以是这样的:
小明选化学课:
using (var tx = session.BeginTransaction()) { var student = GetStudent(studentName) ?? new Student { Name = studentName, Courses = new List<Course>() }; var course = GetCourse(courseName) ?? new Course { Name = courseName, Students = new List<Student>() }; if (!course.Students.Contains(student)) { course.Students.Add(student); } session.SaveOrUpdate(course); tx.Commit(); }
其中,GetStudent(studentName) 和 GetCourse(courseName) 分别是指,根据学生名字或课程名字从数据库中加载相应对象。在上面的代码片段中,如果数据库中没有,则新建。维护关系后,进行保存。
需要注意的是,在保存 course 的时候,并没有确保 student 是一个持久化的对象。如果student尚未被持久化,则在保存时,NHibernate会自动保存,并维护和course的关系。能够这么做,依赖于Course中关系上的cascade属性定义(第三行末尾):
<class name="Course" table="Course"> ... <bag name="Students" table="Scoure" lazy="true" cascade="save-update"> <key column="CourseId"/> <many-to-many column="StudentId" class="Student" /> </bag> </class>
但在包含属性的多对多关系上,由于要使用map,就无法进行这样的配置——配置了也不生效。如果数据库中尚未保存该学生,我们不得不首先创建并将其持久化(第7行):
using (var tx = session.BeginTransaction()) { var student = GetStudent(studentName); if (student == null) { student = new Student {Name = studentName, Courses = new Dictionary<Course, int>()}; session.Save(student); } var course = GetCourse(courseName) ?? new Course {Name = courseName, Students = new Dictionary<Student, int>()}; if (course.Students.ContainsKey(student)) { course.Students[student] = score; } else { course.Students.Add(student, score); } session.SaveOrUpdate(course); tx.Commit(); }
否则,就等着NHibernate抛出异常吧。
5、结论
用Map/Dicitionary表达的多对多关系,要比用Bag/List所表达的,操作起来更为复杂。但这样的代价,我们乐意承担。
这是因为,我们更看重模型设计,更重视概念完整性。是模型决定具体实现,而不是反过来,根据具体实现来修改模型的设计。

MySQL和SQLite的主要區別在於設計理念和使用場景:1.MySQL適用於大型應用和企業級解決方案,支持高性能和高並發;2.SQLite適合移動應用和桌面軟件,輕量級且易於嵌入。

MySQL中的索引是數據庫表中一列或多列的有序結構,用於加速數據檢索。 1)索引通過減少掃描數據量提升查詢速度。 2)B-Tree索引利用平衡樹結構,適合範圍查詢和排序。 3)創建索引使用CREATEINDEX語句,如CREATEINDEXidx_customer_idONorders(customer_id)。 4)複合索引可優化多列查詢,如CREATEINDEXidx_customer_orderONorders(customer_id,order_date)。 5)使用EXPLAIN分析查詢計劃,避

在MySQL中使用事務可以確保數據一致性。 1)通過STARTTRANSACTION開始事務,執行SQL操作後用COMMIT提交或ROLLBACK回滾。 2)使用SAVEPOINT可以設置保存點,允許部分回滾。 3)性能優化建議包括縮短事務時間、避免大規模查詢和合理使用隔離級別。

選擇PostgreSQL而非MySQL的場景包括:1)需要復雜查詢和高級SQL功能,2)要求嚴格的數據完整性和ACID遵從性,3)需要高級空間功能,4)處理大數據集時需要高性能。 PostgreSQL在這些方面表現出色,適合需要復雜數據處理和高數據完整性的項目。

MySQL數據庫的安全可以通過以下措施實現:1.用戶權限管理:通過CREATEUSER和GRANT命令嚴格控制訪問權限。 2.加密傳輸:配置SSL/TLS確保數據傳輸安全。 3.數據庫備份和恢復:使用mysqldump或mysqlpump定期備份數據。 4.高級安全策略:使用防火牆限制訪問,並啟用審計日誌記錄操作。 5.性能優化與最佳實踐:通過索引和查詢優化以及定期維護兼顧安全和性能。

如何有效監控MySQL性能?使用mysqladmin、SHOWGLOBALSTATUS、PerconaMonitoringandManagement(PMM)和MySQLEnterpriseMonitor等工具。 1.使用mysqladmin查看連接數。 2.用SHOWGLOBALSTATUS查看查詢數。 3.PMM提供詳細性能數據和圖形化界面。 4.MySQLEnterpriseMonitor提供豐富的監控功能和報警機制。

MySQL和SQLServer的区别在于:1)MySQL是开源的,适用于Web和嵌入式系统,2)SQLServer是微软的商业产品,适用于企业级应用。两者在存储引擎、性能优化和应用场景上有显著差异,选择时需考虑项目规模和未来扩展性。

在需要高可用性、高級安全性和良好集成性的企業級應用場景下,應選擇SQLServer而不是MySQL。 1)SQLServer提供企業級功能,如高可用性和高級安全性。 2)它與微軟生態系統如VisualStudio和PowerBI緊密集成。 3)SQLServer在性能優化方面表現出色,支持內存優化表和列存儲索引。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

SublimeText3漢化版
中文版,非常好用

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

SublimeText3 Linux新版
SublimeText3 Linux最新版

WebStorm Mac版
好用的JavaScript開發工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),