Phân tích mã nguồn MyBatis - Lớp SqlSession

Từ các phân tích trước đó, khi MyBatis khởi động sẽ tải file cấu hình XML và tạo ra đối tượng cấu hình toàn cục Configuration. Lớp SqlSessionFactoryBuilder sẽ dựa vào đối tượng Configuration để tạo ra một đối tượng DefaultSqlSessionFactory, lớp này thực hiện phương thức tạo SqlSession trong SqlSessionFactory. Cuối cùng tạo ra một lớp mặc định triển khai giao diện SqlSession là DefaultSqlSession. Bây giờ hãy tìm hiểu về SqlSession và các lớp triển khai của nó.

Phân tích SqlSession

SqlSession nằm trong thư mục org.apache.ibatis.session của gói mybatis, theo nghĩa đen là phiên làm việc SQL, dùng để giao tiếp SQL giữa chương trình và cơ sở dữ liệu. Mỗi lần thực hiện thao tác cơ sở dữ liệu, chương trình cần tạo một sqlSession và đóng sqlSession sau khi hoàn tất thao tác.

Vì đây là phiên làm việc giữa chương trình và cơ sở dữ liệu nên các phương thức trong giao diện sqlSession nên là những phương thức cả chương trình và cơ sở dữ liệu đều dễ hiểu. Các phương thức được định nghĩa trong sqlSession đều liên quan đến thao tác cơ sở dữ liệu, mã nguồn như sau:

 1 package org.apache.ibatis.session;
 2 
 3 import java.io.Closeable;
 4 import java.sql.Connection;
 5 import java.util.List;
 6 import java.util.Map;
 7 
 8 import org.apache.ibatis.cursor.Cursor;
 9 import org.apache.ibatis.executor.BatchResult;
10 
11 public interface SqlSession extends Closeable {
12 
13     //Truy vấn bản ghi đơn theo câu lệnh SQL
14   <T> T querySingle(String statement);
15   <T> T querySingle(String statement, Object inputParam);//Truy vấn bản ghi đơn theo câu lệnh SQL và tham số
16 
17   //Truy vấn nhiều bản ghi theo câu lệnh SQL
18   <E> List<E> queryMultiple(String statement);
19   <E> List<E> queryMultiple(String statement, Object inputParam);//Truy vấn nhiều bản ghi theo câu lệnh SQL và tham số
20   <E> List<E> queryMultiple(String statement, Object inputParam, PaginationBounds paginationBounds);//Truy vấn nhiều bản ghi theo câu lệnh SQL, tham số và tham số phân trang
21 
22    //selectToMap và selectMultiple nguyên lý giống nhau, chỉ khác là ánh xạ tập kết quả thành đối tượng Map trả về
23   <K, V> Map<K, V> queryToMap(String statement, String mapKey);
24   <K, V> Map<K, V> queryToMap(String statement, Object inputParam, String mapKey);
25   <K, V> Map<K, V> queryToMap(String statement, Object inputParam, String mapKey, PaginationBounds paginationBounds);
26 
27   //Trả về đối tượng con trỏ
28   <T> Cursor<T> fetchCursor(String statement);
29   <T> Cursor<T> fetchCursor(String statement, Object inputParam);
30   <T> Cursor<T> fetchCursor(String statement, Object inputParam, PaginationBounds paginationBounds);
31 
32   //Đối tượng kết quả được xử lý bởi ResultHandler được chỉ định
33   void fetch(String statement, Object inputParam, ResultHandler handler);
34   void fetch(String statement, ResultHandler handler);
35   void fetch(String statement, Object inputParam, PaginationBounds paginationBounds, ResultHandler handler);
36 
37   //Thực thi câu lệnh insert
38   int executeInsert(String statement);
39   int executeInsert(String statement, Object inputParam);
40 
41   //Thực thi câu lệnh update
42   int executeUpdate(String statement);
43   int executeUpdate(String statement, Object inputParam);
44 
45   //Thực thi câu lệnh delete
46   int executeDelete(String statement);
47   int executeDelete(String statement, Object inputParam);
48 
49   //Xác nhận giao dịch
50   void confirmTransaction();
51   void confirmTransaction(boolean force);
52 
53   //Hoàn tác giao dịch
54   void revertTransaction();
55   void revertTransaction(boolean force);
56 
57   //Làm mới yêu cầu xuống cơ sở dữ liệu
58   List<BatchResult> refreshCommands();
59 
60   //Đóng phiên làm việc sql
61   @Override
62   void close();
63 
64   //Xóa bộ nhớ đệm  
65   void clearBuffer();
66   
67   //Lấy đối tượng cấu hình
68   Configuration getConfiguration();
69 
70   //Lấy đối tượng Mapper cho kiểu Type
71   <T> T retrieveMapper(Class<T> type);
72 
73   //Lấy kết nối cơ sở dữ liệu của đối tượng sqlSession
74   Connection getConnection();
75 }

Các phương thức trong SqlSession đều liên quan đến thao tác thêm, xóa, sửa, tìm kiếm cơ sở dữ liệu và phương thức xác nhận giao dịch.

SqlSession có ba lớp triển khai, ngoài lớp mặc định DefaultSqlSession còn có SqlSessionManager và SqlSessionTemplate.

Giờ hãy xem cách lớp triển khai mặc định DefaultSqlSession thực hiện.

Phân tích lớp DefaultSqlSession

DefaultSqlSession có 5 thuộc tính và 2 phương thức tạo như sau:

 1   private Configuration systemConfig;//cấu hình hệ thống
 2   private Executor operationExecutor;//trình thực thi
 3 
 4   private boolean autoConfirm;//cờ tự động xác nhận
 5   private boolean modifiedFlag;
 6   private List<Cursor<?>> activeCursorList;//danh sách con trỏ hoạt động
 7 
 8   public DefaultSqlSession(Configuration systemConfig, Executor operationExecutor, boolean autoConfirm) {
 9     this.systemConfig = systemConfig;
10     this.operationExecutor = operationExecutor;
11     this.modifiedFlag = false;
12     this.autoConfirm = autoConfirm;
13   }
14 
15   public DefaultSqlSession(Configuration systemConfig, Executor operationExecutor) {
16     this(systemConfig, operationExecutor, false);
17   }

Sau đây lấy phương thức select làm ví dụ:

 1 @Override
 2   public <T> T querySingle(String statement) {
 3     return this.<T>querySingle(statement, null);
 4   }
 5 
 6   @Override
 7   public <T> T querySingle(String statement, Object inputParam) {
 8     // Quyết định phổ biến là trả về null khi không có kết quả và ném ngoại lệ khi quá nhiều kết quả
 9     List<T> resultList = this.<T>queryMultiple(statement, inputParam);
10     if (resultList.size() == 1) {
11       return resultList.get(0);
12     } else if (resultList.size() > 1) {
13       throw new TooManyResultsException("Expected one result (or null) to be returned by querySingle(), but found: " + resultList.size());
14     } else {
15       return null;
16     }
17   }

Rõ ràng phương thức querySingle cuối cùng đều gọi phương thức queryMultiple, sau đó lấy bản ghi duy nhất trả về. Hãy xem mã liên quan đến queryMultiple:

 1 @Override
 2   public <E> List<E> queryMultiple(String statement) {
 3     return this.queryMultiple(statement, null);
 4   }
 5 
 6   @Override
 7   public <E> List<E> queryMultiple(String statement, Object inputParam) {
 8     return this.queryMultiple(statement, inputParam, PaginationBounds.DEFAULT);
 9   }
10 
11   @Override
12   public <E> List<E> queryMultiple(String statement, Object inputParam, PaginationBounds paginationBounds) {
13     try {
14       MappedStatement mappedStmt = systemConfig.getMappedStatement(statement);
15       return operationExecutor.query(mappedStmt, wrapCollection(inputParam), paginationBounds, Executor.NO_RESULT_HANDLER);
16     } catch (Exception e) {
17       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
18     } finally {
19       ErrorContext.instance().reset();
20     }
21   }

Có tổng cộng ba phương thức queryMultiple, cuối cùng đều gọi phương thức queryMultiple cuối cùng, có ba tham số: statement là câu lệnh sql; inputParam là tham số truyền vào; PaginationBounds là tham số liên quan đến phân trang.

Mã nguồn PaginationBounds như sau:

 1 package org.apache.ibatis.session;
 2 
 3 /**
 4  * @author Clinton Begin
 5  */
 6 public class PaginationBounds {
 7 
 8   public static final int NO_START_INDEX = 0;
 9   public static final int NO_LIMIT_COUNT = Integer.MAX_VALUE; //giá trị lớn nhất của int
10   public static final PaginationBounds DEFAULT = new PaginationBounds();
11 
12   private int startIndex;
13   private int maxCount;
14 
15   public PaginationBounds() {
16     this.startIndex = NO_START_INDEX;
17     this.maxCount = NO_LIMIT_COUNT;
18   }
19 
20   public PaginationBounds(int startIndex, int maxCount) {
21     this.startIndex = startIndex;
22     this.maxCount = maxCount;
23   }
24 
25   public int getStartIndex() {
26     return startIndex;
27   }
28 
29   public int getMaxCount() {
30     return maxCount;
31   }
32 
33 }

Hai thuộc tính của PaginationBounds: offSet là vị trí bắt đầu truy vấn dữ liệu, limit là số lượng dữ liệu trả về, mặc định là bắt đầu từ vị trí 0 đến giá trị lớn nhất của Integer, tương đương với việc mặc định không xử lý phân trang;

Quay lại chủ đề chính, phương thức cuối cùng thực thi queryMultiple thực hiện hai dòng mã:

1 MappedStatement mappedStmt = systemConfig.getMappedStatement(statement);
2 return operationExecutor.query(mappedStmt, wrapCollection(inputParam), paginationBounds, Executor.NO_RESULT_HANDLER);

Gọi phương thức getMappedStatement của configuration để lấy đối tượng MappedStatement, sau đó gọi phương thức query của trình thực thi operationExecutor để lấy kết quả truy vấn.

Đây là phương thức select, hãy xem các phương thức insert, update, delete khác:

 1 @Override
 2   public int executeInsert(String statement) {
 3     return executeInsert(statement, null);
 4   }
 5 
 6   @Override
 7   public int executeInsert(String statement, Object inputParam) {
 8     return executeUpdate(statement, inputParam);
 9   }
10 
11   @Override
12   public int executeUpdate(String statement) {
13     return executeUpdate(statement, null);
14   }
15 
16   @Override
17   public int executeUpdate(String statement, Object inputParam) {
18     try {
19       modifiedFlag = true;
20       MappedStatement mappedStmt = systemConfig.getMappedStatement(statement);
21       return operationExecutor.update(mappedStmt, wrapCollection(inputParam));
22     } catch (Exception e) {
23       throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
24     } finally {
25       ErrorContext.instance().reset();
26     }
27   }
28 
29   @Override
30   public int executeDelete(String statement) {
31     return executeUpdate(statement, null);
32   }
33 
34   @Override
35   public int executeDelete(String statement, Object inputParam) {
36     return executeUpdate(statement, inputParam);
37   }

Có thể thấy các phương thức insert và delete của SqlSession cuối cùng đều gọi phương thức executeUpdate, mà phương thức executeUpdate cũng cuối cùng gọi phương thức update của Executor.

Từ đó có thể rút ra kết luận rằng mặc dù sqlSession được gọi là phiên làm việc SQL giữa chương trình và cơ sở dữ liệu, nhưng nó không trực tiếp thực thi câu lệnh sql, việc thực thi câu lệnh sql cuối cùng do Executor thực hiện, vai trò của SqlSession chỉ là tạo đối tượng MappedStatement và gọi trình thực thi để thực thi SQL.

Các phương thức khác như confirmTransaction, revertTransaction cuối cùng cũng đều gọi các phương thức tương ứng của Executor, vậy tiếp theo hãy tìm hiểu Executor làm gì và MappedStatement được tạo bởi SqlSession là gì?

Mã nguồn hoàn chỉnh của DefaultSqlSession như sau:

  1 /**
  2  *    Copyright 2009-2015 the original author or authors.
  3  *
  4  *    Licensed under the Apache License, Version 2.0 (the "License");
  5  *    you may not use this file except in compliance with the License.
  6  *    You may obtain a copy of the License at
  7  *
  8  *       http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  *    Unless required by applicable law or agreed to in writing, software
 11  *    distributed under the License is distributed on an "AS IS" BASIS,
 12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  *    See the License for the specific language governing permissions and
 14  *    limitations under the License.
 15  */
 16 package org.apache.ibatis.session.defaults;
 17 
 18 import java.io.IOException;
 19 import java.sql.Connection;
 20 import java.sql.SQLException;
 21 import java.util.ArrayList;
 22 import java.util.Collection;
 23 import java.util.HashMap;
 24 import java.util.List;
 25 import java.util.Map;
 26 
 27 import org.apache.ibatis.binding.BindingException;
 28 import org.apache.ibatis.cursor.Cursor;
 29 import org.apache.ibatis.exceptions.ExceptionFactory;
 30 import org.apache.ibatis.exceptions.TooManyResultsException;
 31 import org.apache.ibatis.executor.BatchResult;
 32 import org.apache.ibatis.executor.ErrorContext;
 33 import org.apache.ibatis.executor.Executor;
 34 import org.apache.ibatis.executor.result.DefaultMapResultHandler;
 35 import org.apache.ibatis.executor.result.DefaultResultContext;
 36 import org.apache.ibatis.mapping.MappedStatement;
 37 import org.apache.ibatis.session.Configuration;
 38 import org.apache.ibatis.session.ResultHandler;
 39 import org.apache.ibatis.session.PaginationBounds;
 40 import org.apache.ibatis.session.SqlSession;
 41 
 42 /**
 43  *
 44  * Triển khai mặc định cho {@link SqlSession}.
 45  * Lưu ý lớp này không an toàn khi sử dụng đa luồng.
 46  *
 47  * @author Clinton Begin
 48  */
 49 public class DefaultSqlSession implements SqlSession {
 50 
 51   private Configuration systemConfig;
 52   private Executor operationExecutor;
 53 
 54   private boolean autoConfirm;
 55   private boolean modifiedFlag;
 56   private List<Cursor<?>> activeCursorList;
 57 
 58   public DefaultSqlSession(Configuration systemConfig, Executor operationExecutor, boolean autoConfirm) {
 59     this.systemConfig = systemConfig;
 60     this.operationExecutor = operationExecutor;
 61     this.modifiedFlag = false;
 62     this.autoConfirm = autoConfirm;
 63   }
 64 
 65   public DefaultSqlSession(Configuration systemConfig, Executor operationExecutor) {
 66     this(systemConfig, operationExecutor, false);
 67   }
 68 
 69   @Override
 70   public <T> T querySingle(String statement) {
 71     return this.<T>querySingle(statement, null);
 72   }
 73 
 74   @Override
 75   public <T> T querySingle(String statement, Object inputParam) {
 76     // Quyết định phổ biến là trả về null khi không có kết quả và ném ngoại lệ khi quá nhiều kết quả
 77     List<T> resultList = this.<T>queryMultiple(statement, inputParam);
 78     if (resultList.size() == 1) {
 79       return resultList.get(0);
 80     } else if (resultList.size() > 1) {
 81       throw new TooManyResultsException("Expected one result (or null) to be returned by querySingle(), but found: " + resultList.size());
 82     } else {
 83       return null;
 84     }
 85   }
 86 
 87   @Override
 88   public <K, V> Map<K, V> queryToMap(String statement, String mapKey) {
 89     return this.queryToMap(statement, null, mapKey, PaginationBounds.DEFAULT);
 90   }
 91 
 92   @Override
 93   public <K, V> Map<K, V> queryToMap(String statement, Object inputParam, String mapKey) {
 94     return this.queryToMap(statement, inputParam, mapKey, PaginationBounds.DEFAULT);
 95   }
 96 
 97   @Override
 98   public <K, V> Map<K, V> queryToMap(String statement, Object inputParam, String mapKey, PaginationBounds paginationBounds) {
 99     final List<? extends V> list = queryMultiple(statement, inputParam, paginationBounds);
100     final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
101         systemConfig.getObjectFactory(), systemConfig.getObjectWrapperFactory(), systemConfig.getReflectorFactory());
102     final DefaultResultContext<V> context = new DefaultResultContext<V>();
103     for (V o : list) {
104       context.nextResultObject(o);
105       mapResultHandler.handleResult(context);
106     }
107     return mapResultHandler.getMappedResults();
108   }
109 
110   @Override
111   public <T> Cursor<T> fetchCursor(String statement) {
112     return fetchCursor(statement, null);
113   }
114 
115   @Override
116   public <T> Cursor<T> fetchCursor(String statement, Object inputParam) {
117     return fetchCursor(statement, inputParam, PaginationBounds.DEFAULT);
118   }
119 
120   @Override
121   public <T> Cursor<T> fetchCursor(String statement, Object inputParam, PaginationBounds paginationBounds) {
122     try {
123       MappedStatement mappedStmt = systemConfig.getMappedStatement(statement);
124       Cursor<T> cursor = operationExecutor.queryCursor(mappedStmt, wrapCollection(inputParam), paginationBounds);
125       registerCursor(cursor);
126       return cursor;
127     } catch (Exception e) {
128       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
129     } finally {
130       ErrorContext.instance().reset();
131     }
132   }
133 
134   @Override
135   public <E> List<E> queryMultiple(String statement) {
136     return this.queryMultiple(statement, null);
137   }
138 
139   @Override
140   public <E> List<E> queryMultiple(String statement, Object inputParam) {
141     return this.queryMultiple(statement, inputParam, PaginationBounds.DEFAULT);
142   }
143 
144   @Override
145   public <E> List<E> queryMultiple(String statement, Object inputParam, PaginationBounds paginationBounds) {
146     try {
147       MappedStatement mappedStmt = systemConfig.getMappedStatement(statement);
148       return operationExecutor.query(mappedStmt, wrapCollection(inputParam), paginationBounds, Executor.NO_RESULT_HANDLER);
149     } catch (Exception e) {
150       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
151     } finally {
152       ErrorContext.instance().reset();
153     }
154   }
155 
156   @Override
157   public void fetch(String statement, Object inputParam, ResultHandler handler) {
158     fetch(statement, inputParam, PaginationBounds.DEFAULT, handler);
159   }
160 
161   @Override
162   public void fetch(String statement, ResultHandler handler) {
163     fetch(statement, null, PaginationBounds.DEFAULT, handler);
164   }
165 
166   @Override
167   public void fetch(String statement, Object inputParam, PaginationBounds paginationBounds, ResultHandler handler) {
168     try {
169       MappedStatement mappedStmt = systemConfig.getMappedStatement(statement);
170       operationExecutor.query(mappedStmt, wrapCollection(inputParam), paginationBounds, handler);
171     } catch (Exception e) {
172       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
173     } finally {
174       ErrorContext.instance().reset();
175     }
176   }
177 
178   @Override
179   public int executeInsert(String statement) {
180     return executeInsert(statement, null);
181   }
182 
183   @Override
184   public int executeInsert(String statement, Object inputParam) {
185     return executeUpdate(statement, inputParam);
186   }
187 
188   @Override
189   public int executeUpdate(String statement) {
190     return executeUpdate(statement, null);
191   }
192 
193   @Override
194   public int executeUpdate(String statement, Object inputParam) {
195     try {
196       modifiedFlag = true;
197       MappedStatement mappedStmt = systemConfig.getMappedStatement(statement);
198       return operationExecutor.update(mappedStmt, wrapCollection(inputParam));
199     } catch (Exception e) {
200       throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
201     } finally {
202       ErrorContext.instance().reset();
203     }
204   }
205 
206   @Override
207   public int executeDelete(String statement) {
208     return executeUpdate(statement, null);
209   }
210 
211   @Override
212   public int executeDelete(String statement, Object inputParam) {
213     return executeUpdate(statement, inputParam);
214   }
215 
216   @Override
217   public void confirmTransaction() {
218     confirmTransaction(false);
219   }
220 
221   @Override
222   public void confirmTransaction(boolean force) {
223     try {
224       operationExecutor.commit(isCommitOrRollbackRequired(force));
225       modifiedFlag = false;
226     } catch (Exception e) {
227       throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
228     } finally {
229       ErrorContext.instance().reset();
230     }
231   }
232 
233   @Override
234   public void revertTransaction() {
235     revertTransaction(false);
236   }
237 
238   @Override
239   public void revertTransaction(boolean force) {
240     try {
241       operationExecutor.rollback(isCommitOrRollbackRequired(force));
242       modifiedFlag = false;
243     } catch (Exception e) {
244       throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
245     } finally {
246       ErrorContext.instance().reset();
247     }
248   }
249 
250   @Override
251   public List<BatchResult> refreshCommands() {
252     try {
253       return operationExecutor.flushStatements();
254     } catch (Exception e) {
255       throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
256     } finally {
257       ErrorContext.instance().reset();
258     }
259   }
260 
261   @Override
262   public void close() {
263     try {
264       operationExecutor.close(isCommitOrRollbackRequired(false));
265       closeCursors();
266       modifiedFlag = false;
267     } finally {
268       ErrorContext.instance().reset();
269     }
270   }
271 
272   private void closeCursors() {
273     if (activeCursorList != null && activeCursorList.size() != 0) {
274       for (Cursor<?> cursor : activeCursorList) {
275         try {
276           cursor.close();
277         } catch (IOException e) {
278           throw ExceptionFactory.wrapException("Error closing cursor.  Cause: " + e, e);
279         }
280       }
281       activeCursorList.clear();
282     }
283   }
284 
285   @Override
286   public Configuration getConfiguration() {
287     return systemConfig;
288   }
289 
290   @Override
291   public <T> T retrieveMapper(Class<T> type) {
292     return systemConfig.<T>getMapper(type, this);
293   }
294 
295   @Override
296   public Connection getConnection() {
297     try {
298       return operationExecutor.getTransaction().getConnection();
299     } catch (SQLException e) {
300       throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
301     }
302   }
303 
304   @Override
305   public void clearBuffer() {
306     operationExecutor.clearLocalCache();
307   }
308 
309   private <T> void registerCursor(Cursor<T> cursor) {
310     if (activeCursorList == null) {
311       activeCursorList = new ArrayList<Cursor<?>>();
312     }
313     activeCursorList.add(cursor);
314   }
315 
316   private boolean isCommitOrRollbackRequired(boolean force) {
317     return (!autoConfirm && modifiedFlag) || force;
318   }
319 
320   private Object wrapCollection(final Object object) {
321     if (object instanceof Collection) {
322       StrictMap<Object> map = new StrictMap<Object>();
323       map.put("collection", object);
324       if (object instanceof List) {
325         map.put("list", object);
326       }
327       return map;
328     } else if (object != null && object.getClass().isArray()) {
329       StrictMap<Object> map = new StrictMap<Object>();
330       map.put("array", object);
331       return map;
332     }
333     return object;
334   }
335 
336   public static class StrictMap<V> extends HashMap<String, V> {
337 
338     private static final long serialVersionUID = -5741767162221585340L;
339 
340     @Override
341     public V get(Object key) {
342       if (!super.containsKey(key)) {
343         throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
344       }
345       return super.get(key);
346     }
347 
348   }
349 
350 }
Hiển thị mã

Thẻ: mybatis sqlsession defaultsqlsession pagination executor

Đăng vào ngày 13 tháng 6 lúc 04:20