本章主要涉及内容
事务模块,配置解析模块
事务模块
事务模块主要内容在transaction
包下面
事务模块主要是两个接口以及两种实现。
事务接口
public interface Transaction {
/**
* 获得连接
* Retrieve inner database connection.
* @return DataBase connection
* @throws SQLException
*/
Connection getConnection() throws SQLException;
/**
* 提交
* Commit inner database connection.
* @throws SQLException
*/
void commit() throws SQLException;
/**
* 回滚
* Rollback inner database connection.
* @throws SQLException
*/
void rollback() throws SQLException;
/**
* 关闭连接
* Close inner database connection.
* @throws SQLException
*/
void close() throws SQLException;
/**
* 获得事务超时时间
* Get transaction timeout if set.
* @throws SQLException
*/
Integer getTimeout() throws SQLException;
}
事务工厂接口
/**
* Creates {@link Transaction} instances.
* 事务的工厂接口
* @author Clinton Begin
*/
public interface TransactionFactory {
/**
* Sets transaction factory custom properties.
* 设置工厂的属性
* @param props
*/
void setProperties(Properties props);
/**
* Creates a {@link Transaction} out of an existing connection.
* 创建新的事务
* 需要连接对象
* @param conn Existing database connection
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(Connection conn);
/**
* Creates a {@link Transaction} out of a datasource.
* 创建事务,需要数据源,事务级别,是否自动提交
* @param dataSource DataSource to take the connection from
* @param level Desired isolation level
* @param autoCommit Desired autocommit
* @return Transaction
* @since 3.1.0
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
其两种实现类
- jdbc:为简单的jdbc实现,源码不算复杂。
- managed:为基于容器管理的事务实现。
需要注意的是managed包下的实现类,很多方法都是空逻辑,其具体实现方法又容器实现,一般是spring来实现。
配置解析模块
解析器主要内容在 包下。其主要内容是:
- 为解析mybatis配置文件,以及映射文件提供支持。
- 另一个是为处理动态SQL中的占位符提供支持
其包下的主要类
- XPathParser:基于java的XPath的解析器,其内部维护了一个XPath
- XNode:mybatis自定义的一种数据对象X
- TokenHandler:一类参数的获取
- PropertyParser:动态属性的解析器
- GenericTokenParser:通用的 Token 解析器
首先看TokenHandler,其主要实现类为
目前配置解析模块里面主要的实现类是VariableTokenHandler(主要是获取参数分割符)。
其代码为:
private static class VariableTokenHandler implements TokenHandler {
// 变量 Properties 对象
private final Properties variables;
// 是否开启默认值功能。默认为 {@link #ENABLE_DEFAULT_VALUE}
// <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
private final boolean enableDefaultValue;
// 默认值的分隔符。默认为 {@link #KEY_DEFAULT_VALUE_SEPARATOR} ,即 ":" 。
// <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value="?:"/>
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
this.variables = variables;
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
private String getPropertyValue(String key, String defaultValue) {
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content) {
if (variables != null) {
String key = content;
// 开启默认值功能
if (enableDefaultValue) {
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
key = content.substring(0, separatorIndex);
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
// 未开启默认值功能,直接替换
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 无 variables ,直接返回
return "${" + content + "}";
}
}
其主要是对变量分割符的获取,
这里面出现三种情况
- 假如使用了默认:则会使用defaultValueSeparator中的值即org.apache.ibatis.parsing.PropertyParser.default-value-separator配置中的值获得key,然后根据字符串截取获得默认值,假如默认值不为空则使用variables.getProperty(key, defaultValue)。从属性中进行一次查询,假如查到值就返回查到的内容。
- 假如没有使用默认值,则直接使用content的内容。
- 假如没有设置Property对象,则直接使用"${" + content + "}"。作为参数分隔符
解析器
XPathParser:基于java的XPath的解析器,其内部维护了一个XPath
其代码为:
public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 创建 DocumentBuilderFactory 对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 是否验证XML
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
// 创建 DocumentBuilder 对象
DocumentBuilder builder = factory.newDocumentBuilder();
// 设置实体解析器
builder.setEntityResolver(entityResolver);
// 错误处理模块
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
// 解析文件
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
主要逻辑,就是为XPathParser初始化了,是否验证XML,Properties参数对象,XML解析器,以及根据XML内容解析成Document 。
其内部方法,主要是将数据解析成XNode对象
public List<XNode> evalNodes(Object root, String expression) {
List<XNode> xnodes = new ArrayList<>();
NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
xnodes.add(new XNode(this, nodes.item(i), variables));
}
return xnodes;
}
PropertyParser:动态属性的解析器
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
对SQL进行解析,将SQL中${}中的内容解析成合适的占位符。
GenericTokenParser:通用的 Token 解析器
主要逻辑就是解析以 openToken
开始,以 closeToken
结束的 Token ,并提交给 handler
进行处理
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
// 寻找开始的 openToken 的位置
int start = text.indexOf(openToken);
// 找不到,直接返回
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
// 起始查找位置
int offset = 0;
// 结果
final StringBuilder builder = new StringBuilder();
// 匹配到 openToken 和 closeToken 之间的表达式
StringBuilder expression = null;
// 循环匹配
while (start > -1) {
// 转义字符
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
// 因为 openToken 前面一个位置是 \ 转义字符,所以忽略 \
// 添加 [offset, start - offset - 1] 和 openToken 的内容,添加到 builder 中
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {// 非转义字符
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
// 添加 offset 和 openToken 之间的内容,添加到 builder 中
builder.append(src, offset, start - offset);
// 修改 offset
offset = start + openToken.length();
// 寻找结束的 closeToken 的位置
int end = text.indexOf(closeToken, offset);
// 转义
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
// 因为 endToken 前面一个位置是 \ 转义字符,所以忽略 \
// 添加 [offset, end - offset - 1] 和 endToken 的内容,添加到 builder 中
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else { // 非转义
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
// 继续,寻找开始的 openToken 的位置
start = text.indexOf(openToken, offset);
}
// 拼接剩余的部分
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
XNode
是mybatis对数据结构的封装
/**
* mybatis自定义的XNode对象,用来保存解析的对象
* @author Clinton Begin
*/
public class XNode {
// 子节点
private final Node node;
// 名称
private final String name;
private final String body;
// 对应数据对象和解析器
private final Properties attributes;
private final Properties variables;
private final XPathParser xpathParser;
//......
}
里面提供很多对XNode查询和操作的方法,方法比较多名但单个内容不算太麻烦,这里有兴趣可以看一看。