資料對映器模式(Data Mapper Pattern)
該模式屬於 資料來源架構模式 目錄,此目錄屬於 企業應用程式體系結構模式 。
目的
一個對映器層,在物件和資料庫之間移動資料,同時保持它們彼此和對映器本身獨立。對映器(Mapper )是指在兩個獨立物件之間建立通訊的物件。
適用性
在以下任何情況下使用Data Mapper
- 當您想要從DB訪問層分離資料物件時
- 當您想要編寫多個數據檢索/永續性實現時
說明
- Data Mapper是一個軟體層,用於將記憶體中物件與資料庫分開。
- 它的職責是在兩者之間傳輸資料,並將它們彼此隔離。
- 使用Data Mapper,即使存在資料庫,記憶體中的物件也不需要知道; 他們不需要SQL介面程式碼,當然也不需要了解資料庫模式。
如何運作
域和資料來源之間的分離是Data Mapper的主要功能,但要實現這一點,需要處理大量的細節。
一個簡單的案例要有一個 Person 和 Person Mapper 類。若要從資料庫載入人員,客戶端將在mapper上呼叫find方法。對映器使用Identity Map模式來檢視此人是否已載入; 如果沒有,它載入它。
示例程式碼
讓我們為 StudentMapper的 示例程式碼建立一個類圖, 以演示這種模式。
第1步:建立Student域類。
<b>public</b> <b>final</b> <b>class</b> Student implements Serializable { <b>private</b> <b>static</b> <b>final</b> <b>long</b> serialVersionUID = 1L; <b>private</b> <b>int</b> studentId; <b>private</b> String name; <b>private</b> <b>char</b> grade; <font><i>/** * Use this constructor to create a Student with all details * * @param studentId as unique student id * @param name as student name * @param grade as respective grade of student */</i></font><font> <b>public</b> Student(<b>final</b> <b>int</b> studentId, <b>final</b> String name, <b>final</b> <b>char</b> grade) { <b>this</b>.studentId = studentId; <b>this</b>.name = name; <b>this</b>.grade = grade; } </font><font><i>/** * * @return the student id */</i></font><font> <b>public</b> <b>int</b> getStudentId() { <b>return</b> studentId; } </font><font><i>/** * * @param studentId as unique student id */</i></font><font> <b>public</b> <b>void</b> setStudentId(<b>final</b> <b>int</b> studentId) { <b>this</b>.studentId = studentId; } </font><font><i>/** * * @return name of student */</i></font><font> <b>public</b> String getName() { <b>return</b> name; } </font><font><i>/** * * @param name as 'name' of student */</i></font><font> <b>public</b> <b>void</b> setName(<b>final</b> String name) { <b>this</b>.name = name; } </font><font><i>/** * * @return grade of student */</i></font><font> <b>public</b> <b>char</b> getGrade() { <b>return</b> grade; } </font><font><i>/** * * @param grade as 'grade of student' */</i></font><font> <b>public</b> <b>void</b> setGrade(<b>final</b> <b>char</b> grade) { <b>this</b>.grade = grade; } </font><font><i>/** * */</i></font><font> @Override <b>public</b> <b>boolean</b> equals(<b>final</b> Object inputObject) { <b>boolean</b> isEqual = false; </font><font><i>/* Check if both objects are same */</i></font><font> <b>if</b> (<b>this</b> == inputObject) { isEqual = <b>true</b>; } <b>else</b> <b>if</b> (inputObject != <b>null</b> && getClass() == inputObject.getClass()) { <b>final</b> Student inputStudent = (Student) inputObject; </font><font><i>/* If student id matched */</i></font><font> <b>if</b> (<b>this</b>.getStudentId() == inputStudent.getStudentId()) { isEqual = <b>true</b>; } } <b>return</b> isEqual; } </font><font><i>/** * */</i></font><font> @Override <b>public</b> <b>int</b> hashCode() { </font><font><i>/* Student id is assumed to be unique */</i></font><font> <b>return</b> <b>this</b>.getStudentId(); } </font><font><i>/** * */</i></font><font> @Override <b>public</b> String toString() { <b>return</b> </font><font>"Student [studentId="</font><font> + studentId + </font><font>", name="</font><font> + name + </font><font>", grade="</font><font> + grade + </font><font>"]"</font><font>; } } </font>
第2步:讓我們使用Runtime Exception建立,以避免依賴於實現異常。這有助於解耦。
<b>public</b> <b>final</b> <b>class</b> DataMapperException <b>extends</b> RuntimeException { <b>private</b> <b>static</b> <b>final</b> <b>long</b> serialVersionUID = 1L; <font><i>/** * Constructs a new runtime exception with the specified detail message. The cause is not * initialized, and may subsequently be initialized by a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for later retrieval by the *{@link #getMessage()} method. */</i></font><font> <b>public</b> DataMapperException(<b>final</b> String message) { <b>super</b>(message); } } </font>
第3步:建立 StudentDataMapper 介面 - 介面列出了所有可能的學生對映器的可能行為。
<b>public</b> <b>interface</b> StudentDataMapper { Optional<Student> find(<b>int</b> studentId); <b>void</b> insert(Student student) throws DataMapperException; <b>void</b> update(Student student) throws DataMapperException; <b>void</b> delete(Student student) throws DataMapperException; }
第4步:讓我們實現上面的介面 - 對學生資料執行動作。這個實現是在記憶體中,你可以在這裡使用資料庫連線。
<b>public</b> <b>final</b> <b>class</b> StudentDataMapperImpl implements StudentDataMapper { <font><i>/* Note: Normally this would be in the form of an actual database */</i></font><font> <b>private</b> List<Student> students = <b>new</b> ArrayList<>(); @Override <b>public</b> Optional<Student> find(<b>int</b> studentId) { </font><font><i>/* Compare with existing students */</i></font><font> <b>for</b> (<b>final</b> Student student : <b>this</b>.getStudents()) { </font><font><i>/* Check if student is found */</i></font><font> <b>if</b> (student.getStudentId() == studentId) { <b>return</b> Optional.of(student); } } </font><font><i>/* Return empty value */</i></font><font> <b>return</b> Optional.empty(); } @Override <b>public</b> <b>void</b> update(Student studentToBeUpdated) throws DataMapperException { </font><font><i>/* Check with existing students */</i></font><font> <b>if</b> (<b>this</b>.getStudents().contains(studentToBeUpdated)) { </font><font><i>/* Get the index of student in list */</i></font><font> <b>final</b> <b>int</b> index = <b>this</b>.getStudents().indexOf(studentToBeUpdated); </font><font><i>/* Update the student in list */</i></font><font> <b>this</b>.getStudents().set(index, studentToBeUpdated); } <b>else</b> { </font><font><i>/* Throw user error after wrapping in a runtime exception */</i></font><font> <b>throw</b> <b>new</b> DataMapperException(</font><font>"Student ["</font><font> + studentToBeUpdated.getName() + </font><font>"] is not found"</font><font>); } } @Override <b>public</b> <b>void</b> insert(Student studentToBeInserted) throws DataMapperException { </font><font><i>/* Check with existing students */</i></font><font> <b>if</b> (!<b>this</b>.getStudents().contains(studentToBeInserted)) { </font><font><i>/* Add student in list */</i></font><font> <b>this</b>.getStudents().add(studentToBeInserted); } <b>else</b> { </font><font><i>/* Throw user error after wrapping in a runtime exception */</i></font><font> <b>throw</b> <b>new</b> DataMapperException(</font><font>"Student already ["</font><font> + studentToBeInserted.getName() + </font><font>"] exists"</font><font>); } } @Override <b>public</b> <b>void</b> delete(Student studentToBeDeleted) throws DataMapperException { </font><font><i>/* Check with existing students */</i></font><font> <b>if</b> (<b>this</b>.getStudents().contains(studentToBeDeleted)) { </font><font><i>/* Delete the student from list */</i></font><font> <b>this</b>.getStudents().remove(studentToBeDeleted); } <b>else</b> { </font><font><i>/* Throw user error after wrapping in a runtime exception */</i></font><font> <b>throw</b> <b>new</b> DataMapperException(</font><font>"Student ["</font><font> + studentToBeDeleted.getName() + </font><font>"] is not found"</font><font>); } } <b>public</b> List<Student> getStudents() { <b>return</b> <b>this</b>.students; } } </font>
第5步:讓我們測試一下這種模式。下面的Client類演示了基本的CRUD操作:建立,讀取,更新和刪除。
<b>public</b> <b>final</b> <b>class</b> Client { <b>private</b> <b>static</b> Logger log = Logger.getLogger(App.<b>class</b>); <font><i>/** * Program entry point. * * @param args command line args. */</i></font><font> <b>public</b> <b>static</b> <b>void</b> main(<b>final</b> String... args) { </font><font><i>/* Create new data mapper for type 'first' */</i></font><font> <b>final</b> StudentDataMapper mapper = <b>new</b> StudentDataMapperImpl(); </font><font><i>/* Create new student */</i></font><font> Student student = <b>new</b> Student(1, </font><font>"Adam"</font><font>, 'A'); </font><font><i>/* Add student in respectibe store */</i></font><font> mapper.insert(student); log.debug(</font><font>"App.main(), student : "</font><font> + student + </font><font>", is inserted"</font><font>); </font><font><i>/* Find this student */</i></font><font> <b>final</b> Optional<Student> studentToBeFound = mapper.find(student.getStudentId()); log.debug(</font><font>"App.main(), student : "</font><font> + studentToBeFound + </font><font>", is searched"</font><font>); </font><font><i>/* Update existing student object */</i></font><font> student = <b>new</b> Student(student.getStudentId(), </font><font>"AdamUpdated"</font><font>, 'A'); </font><font><i>/* Update student in respectibe db */</i></font><font> mapper.update(student); log.debug(</font><font>"App.main(), student : "</font><font> + student + </font><font>", is updated"</font><font>); log.debug(</font><font>"App.main(), student : "</font><font> + student + </font><font>", is going to be deleted"</font><font>); </font><font><i>/* Delete student in db */</i></font><font> mapper.delete(student); } } </font>