Đối tượng MappedStatement nằm trong gói org.apache.ibatis.mapping của thư viện MyBatis và được khai báo là final, nghĩa là không thể thay đổi sau khi khởi tạo.
Mỗi đối tượng MappedStatement tương ứng với một nút select/update/insert/delete trong tệp cấu hình Mapper.xml, đại diện cho một câu lệnh SQL cụ thể. Các thuộc tính của MappedStatement bao gồm:
private String configFile; // tên tệp cấu hình mapper, ví dụ: UserMapper.xml
private Configuration globalConfig; // cấu hình toàn cục
private String identifier; // ID của nút kết hợp với không gian tên, ví dụ: com.lucky.mybatis.dao.UserMapper.selectByExample
private Integer fetchSize;
private Integer timeout; // thời gian chờ
private StatementType statementType; // loại đối tượng thực thi SQL
private ResultSetType resultSetType; // kiểu kết quả trả về
private SqlSource sqlStatement; // câu lệnh SQL
private Cache cache; // bộ nhớ đệm
private ParameterMap parameterMapping;
private List<ResultMap> resultMapList;
private boolean requireFlushCache;
private boolean enableCache; // có sử dụng bộ nhớ đệm hay không, mặc định là true
private boolean orderedResult; // kết quả có được sắp xếp hay không
private SqlCommandType commandType; // loại câu lệnh SQL, ví dụ: select, update, delete, insert
private KeyGenerator keyGen;
private String[] keyProps;
private String[] keyCols;
private boolean hasNestedResultMaps;
private String dbId; // ID cơ sở dữ liệu
private Log statementLogger;
private LanguageDriver languageDriver;
private String[] resultSets;
Trường statementType xác định loại đối tượng thực thi SQL, là một kiểu liệt kê với các giá trị:
- STATEMENT: Thực thi trực tiếp câu lệnh SQL mà không cần biên dịch trước.
- PREPARED: Biên dịch trước các tham số, sau đó thực thi để lấy dữ liệu.
- CALLABLE: Thực thi các thủ tục lưu trữ.
Trường resultSetType xác định kiểu kết quả trả về, cũng là một kiểu liệt kê với các giá trị:
- FORWARD_ONLY: Con trỏ chỉ có thể di chuyển xuống dưới.
- SCROLL_INSENSITIVE: Con trỏ có thể di chuyển lên xuống, nhưng dữ liệu trong tập kết quả không thay đổi khi cơ sở dữ liệu thay đổi.
- SCROLL_SENSITIVE: Con trỏ có thể tự do di chuyển, và tập kết quả sẽ phản ánh mọi thay đổi trong cơ sở dữ liệu.
Từ đó, mỗi đối tượng MappedStatement tương ứng với một nút SQL trong tệp Mapper.xml, và tệp này được phân tích và tải khi khởi tạo đối tượng Configuration. Điều này có nghĩa là MappedStatement được tạo ra cùng lúc với việc khởi tạo Configuration và là final, không thể thay đổi.
Quá trình khởi tạo Configuration được thực hiện thông qua phương thức parse của lớp XMLConfigBuilder, như sau:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Mỗi XMLConfigBuilder chỉ có thể được sử dụng một lần.");
}
parsed = true;
parseRootConfig(parser.evalNode("/configuration"));
return globalConfig;
}
private void parseRootConfig(XNode rootNode) {
try {
Properties settings = settingsAsProperties(rootNode.evalNode("settings"));
propertiesElement(rootNode.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(rootNode.evalNode("typeAliases"));
pluginElement(rootNode.evalNode("plugins"));
objectFactoryElement(rootNode.evalNode("objectFactory"));
objectWrapperFactoryElement(rootNode.evalNode("objectWrapperFactory"));
reflectorFactoryElement(rootNode.evalNode("reflectorFactory"));
settingsElement(settings);
environmentsElement(rootNode.evalNode("environments"));
databaseIdProviderElement(rootNode.evalNode("databaseIdProvider"));
typeHandlerElement(rootNode.evalNode("typeHandlers"));
mapperElement(rootNode.evalNode("mappers")); // Khởi tạo MappedStatement
} catch (Exception e) {
throw new BuilderException("Lỗi phân tích cấu hình SQL Mapper. Nguyên nhân: " + e, e);
}
}
Phương thức parseRootConfig thực hiện việc gán các thuộc tính cho đối tượng Configuration dựa trên XNode, trong khi phương thức mapperElement phân tích nội dung thẻ <mappers>, như sau:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
globalConfig.addMappers(mapperPackage); // Tải tất cả mapper trong gói
} else {
String resourcePath = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClassName = child.getStringAttribute("class");
if (resourcePath != null && url == null && mapperClassName == null) {
ErrorContext.instance().resource(resourcePath);
InputStream inputStream = Resources.getResourceAsStream(resourcePath);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, globalConfig, resourcePath, globalConfig.getSqlFragments());
mapperParser.parse(); // Phân tích luồng tệp XML
} else if (resourcePath == null && url != null && mapperClassName == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, globalConfig, url, globalConfig.getSqlFragments());
mapperParser.parse(); // Phân tích luồng tệp XML
} else if (resourcePath == null && url == null && mapperClassName != null) {
Class<?> mapperClass = Resources.classForName(mapperClassName);
globalConfig.addMapper(mapperClass); // Tải mapper từ tên lớp
} else {
throw new BuilderException("Thẻ mapper chỉ có thể chỉ định một trong resource, url hoặc class, nhưng không nhiều hơn một.");
}
}
}
}
}
Có ba cách để tải mapper: tải tất cả mapper trong một gói, tải một mapper cụ thể thông qua tên lớp, hoặc phân tích luồng tệp XML của mapper. Tiếp theo, chúng ta sẽ xem xét từng cách.
Xét trường hợp đơn giản nhất, tải mapper thông qua tên lớp bằng phương thức globalConfig.addMapper(mapperClass):
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
Phương thức này gọi đến phương thức addMapper của đối tượng mapperRegistry, thuộc tính của Configuration.
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
Lớp MapperRegistry đóng vai trò là trung tâm đăng ký mapper, với hai thuộc tính: cấu hình toàn cục (globalConfig) và danh sách mapper đã đăng ký (knownMappers).
public class MapperRegistry {
private final Configuration globalConfig;
private final Map<Class<?>, MapperProxyFactory<?>> registeredMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.globalConfig = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) registeredMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Kiểu " + type + " chưa được biết đến bởi MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Lỗi khi lấy thể hiện mapper. Nguyên nhân: " + e, e);
}
}
public <T> boolean hasMapper(Class<T> type) {
return registeredMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Kiểu " + type + " đã được biết đến bởi MapperRegistry.");
}
boolean loadSuccess = false;
try {
registeredMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(globalConfig, type);
parser.parse();
loadSuccess = true;
} finally {
if (!loadSuccess) {
registeredMappers.remove(type);
}
}
}
}
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(registeredMappers.keySet());
}
public void addMappers(String packageName, Class<?> superClass) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superClass), packageName);
Set<Class<? extends Class<?>>> mapperClasses = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperClasses) {
addMapper(mapperClass);
}
}
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
}
Khi khởi tạo Configuration, tất cả các mapper được cấu hình trong các tệp XML được phân tích và thêm vào danh sách mapper của Configuration. Tuy nhiên, đến đây vẫn chưa thấy dấu vết của MappedStatement hay quá trình phân tích tệp mapper.xml.
Ở dòng 53 trong đoạn mã trên, phương thức parse của đối tượng MapperAnnotationBuilder được gọi, có thể đoán rằng đây là nơi phân tích tệp mapper.xml.
Tóm lại, MappedStatement đại diện cho một câu lệnh SQL trong tệp Mapper.xml.