用NHibernate处理带属性的多对多关系
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)
目前还不知道怎么预加载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所表达的,操作起来更为复杂。但这样的代价,我们乐意承担。
这是因为,我们更看重模型设计,更重视概念完整性。是模型决定具体实现,而不是反过来,根据具体实现来修改模型的设计。

Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen



1. Zuerst klicken wir mit der rechten Maustaste auf die leere Stelle der Taskleiste und wählen die Option [Task-Manager] oder klicken mit der rechten Maustaste auf das Startlogo und wählen dann die Option [Task-Manager]. 2. In der geöffneten Task-Manager-Oberfläche klicken wir ganz rechts auf die Registerkarte [Dienste]. 3. Klicken Sie in der geöffneten Registerkarte [Dienst] unten auf die Option [Dienst öffnen]. 4. Klicken Sie im sich öffnenden Fenster [Dienste] mit der rechten Maustaste auf den Dienst [InternetConnectionSharing(ICS)] und wählen Sie dann die Option [Eigenschaften]. 5. Ändern Sie im sich öffnenden Eigenschaftenfenster die Option „Öffnen mit“ in „Deaktiviert“, klicken Sie auf „Übernehmen“ und dann auf „OK“. 6. Klicken Sie auf das Startlogo, dann auf die Schaltfläche zum Herunterfahren, wählen Sie [Neustart] und schließen Sie den Neustart des Computers ab.

Im PHP-Entwicklungsprozess ist der Umgang mit Sonderzeichen ein häufiges Problem, insbesondere bei der Zeichenfolgenverarbeitung werden Sonderzeichen häufig mit Escapezeichen versehen. Unter diesen ist die Umwandlung von Sonderzeichen in einfache Anführungszeichen eine relativ häufige Anforderung, da einfache Anführungszeichen in PHP eine gängige Methode zum Umschließen von Zeichenfolgen sind. In diesem Artikel erklären wir, wie man in PHP mit einfachen Anführungszeichen bei der Konvertierung von Sonderzeichen umgeht, und stellen spezifische Codebeispiele bereit. Zu den Sonderzeichen in PHP gehören unter anderem einfache Anführungszeichen ('), doppelte Anführungszeichen ("), Backslash () usw. In Zeichenfolgen

Der Python-Paketmanager ist ein leistungsstarkes und praktisches Tool zum Verwalten und Installieren von Python-Paketen. Wenn Sie jedoch bei der Verwendung nicht vorsichtig sind, können Sie in verschiedene Fallen tappen. Dieser Artikel beschreibt diese Fallstricke und Strategien, um Entwicklern zu helfen, sie zu vermeiden. Falle 1: Installationskonfliktproblem: Wenn mehrere Pakete Funktionen oder Klassen mit demselben Namen, aber unterschiedlichen Versionen bereitstellen, kann es zu Installationskonflikten kommen. Antwort: Überprüfen Sie vor der Installation die Abhängigkeiten, um sicherzustellen, dass es keine Konflikte zwischen Paketen gibt. Verwenden Sie die Option --no-deps von pip, um die automatische Installation von Abhängigkeiten zu vermeiden. Fallstrick 2: Probleme mit Paketen mit alten Versionen: Wenn keine Version angegeben ist, installiert der Paketmanager möglicherweise die neueste Version, selbst wenn es eine ältere Version gibt, die stabiler oder für Ihre Anforderungen geeigneter ist. Antwort: Geben Sie bei der Installation explizit die erforderliche Version an, z. B. p

JavaServerPages (jsP) ist eine Java-Technologie zur Erstellung dynamischer WEB-Anwendungen. JSP-Skripte werden auf der Serverseite ausgeführt und auf der Clientseite in HTML gerendert. JSP-Anwendungen sind jedoch anfällig für verschiedene Sicherheitslücken, die zu Datenlecks, Codeausführung oder Denial-of-Service führen können. Häufige Sicherheitslücken 1. Cross-Site-Scripting (XSS) XSS-Schwachstellen ermöglichen es Angreifern, bösartige Skripte in Webanwendungen einzuschleusen, die ausgeführt werden, wenn das Opfer auf die Seite zugreift. Angreifer können diese Skripte verwenden, um vertrauliche Informationen (wie Cookies und Sitzungs-IDs) zu stehlen, Benutzer umzuleiten oder Seiten zu kompromittieren. 2. Injektionsschwachstelle Eine Injektionsschwachstelle ermöglicht es einem Angreifer, die Datenbank einer Webanwendung abzufragen

Ein Versionskontrollsystem (VCS) ist ein unverzichtbares Werkzeug in der Softwareentwicklung, mit dem Entwickler Codeänderungen verfolgen und verwalten können. Git ist ein beliebtes und leistungsstarkes VCS, das in der Java-Entwicklung weit verbreitet ist. In diesem Handbuch werden die grundlegenden Konzepte und Vorgänge von Git vorgestellt und Java-Entwicklern die Grundlagen der Versionskontrolle vermittelt. Das Grundkonzept von Git ist ein Repository: Hier werden Code und Versionsverlauf gespeichert. Zweig: Eine unabhängige Entwicklungslinie in einer Codebasis, die es Entwicklern ermöglicht, Änderungen vorzunehmen, ohne die Hauptentwicklungslinie zu beeinträchtigen. Commit: Eine Änderung des Codes in der Codebasis. Rollback: Setzen Sie die Codebasis auf einen vorherigen Commit zurück. Zusammenführen: Änderungen von zwei oder mehr Zweigen in einem einzigen Zweig zusammenführen. Erste Schritte mit Git 1. Installieren Sie Git. Laden Sie es herunter und laden Sie es von der offiziellen Website herunter

Dateien sind die Grundeinheit der Informationsspeicherung und -verwaltung in Computersystemen und stehen auch im Mittelpunkt der Java-Dateioperationen. Das Verständnis der Beschaffenheit von Dateien ist für den effektiven Betrieb und die effektive Verwaltung von Dateien von entscheidender Bedeutung. Abstraktion und Hierarchie Eine Datei ist im Wesentlichen eine Abstraktion, die einen Satz von Daten darstellt, die auf einem dauerhaften Medium wie einer Festplatte oder einem Speicher gespeichert sind. Die logische Struktur einer Datei wird normalerweise vom Betriebssystem definiert und bietet einen Mechanismus zum Organisieren und Zugreifen auf Daten. In Java werden Dateien durch die File-Klasse dargestellt, die abstrakten Zugriff auf das Dateisystem ermöglicht. Datenpersistenz Eines der Hauptmerkmale einer Datei ist ihre Datenpersistenz. Im Gegensatz zu Daten im Speicher bleiben Daten in Dateien auch nach dem Beenden der Anwendung bestehen. Diese Persistenz macht Dateien für die langfristige Speicherung und Weitergabe von Informationen nützlich.

PHP-Programmiertipps: So gehen Sie mit dem letzten Semikolon um Bei der PHP-Programmierung kommt es häufig vor, dass Sie mit dem letzten Semikolon umgehen müssen. Insbesondere in Schleifen und bedingten Anweisungen kann es leicht zu Programmfehlern kommen, indem man ein Semikolon weniger oder mehr schreibt. Um diese Situation zu vermeiden, können wir einige Programmiertechniken anwenden, um mit der letzten Semikolon-Situation umzugehen. Im Folgenden sind einige gängige Techniken und Codebeispiele für den Umgang mit dem letzten Semikolon aufgeführt: 1. Verwenden Sie if-Anweisungen, um das letzte Semikolon zu bestimmen

Mit dem Aufkommen verteilter Systeme und Mehrkernprozessoren sind gleichzeitige Sammlungen in der modernen Softwareentwicklung von entscheidender Bedeutung geworden. Gleichzeitige Java-Sammlungen bieten effiziente und threadsichere Sammlungsimplementierungen und verwalten gleichzeitig die Komplexität des gleichzeitigen Zugriffs. Dieser Artikel untersucht die Zukunft gleichzeitiger Sammlungen in Java und konzentriert sich dabei auf neue Funktionen und Trends. Neue Funktion JSR354: Resiliente gleichzeitige Sammlungen jsR354 definiert eine neue Schnittstelle für gleichzeitige Sammlungen mit elastischem Verhalten, um Leistung und Zuverlässigkeit auch unter extremen Parallelitätsbedingungen sicherzustellen. Diese Schnittstellen bieten zusätzliche Funktionen der Atomizität, z. B. Unterstützung für veränderliche Invarianten und nicht blockierende Iteration. RxJava3.0: Reaktive gleichzeitige Sammlungen RxJava3.0 führt das Konzept der reaktiven Programmierung ein und ermöglicht die einfache Integration gleichzeitiger Sammlungen in reaktive Datenflüsse.
