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 }