手写mybatis2.0版本(面向过程)
配置和代码分离,配置是XML文件,代码是使用面向过程去编写
手写mybatis的2.0版本(配置和代码分离,配置是XML文件,代码是使用面向过程去编写)
核心流程
a)加载XML配置文件(XML如何配置,我们直接是参考的mybatis的配置方式)
* 【解析全局配置文件】(配置的是全应用范围内一次性的配置信息)
** 数据源的配置信息(driver、url、username、password)----最终解析出DataSource对象,并封装到Configuration对象中
** 映射文件的信息
* 【解析映射文件】SqlSource和SqlNode,都是为了去存储数据,并且对存储的数据对外提供了一些操作。
** 解析select标签(statement标签)---封装【MappedStatement】对象,将id、parameterType、ResultType、statementType
** 解析select标签中的SQL脚本信息(script)-----封装成【SqlSource对象】---->【SqlNode】
*** DynamicSqlSource和RawSqlSource区别:
**** DynamicSqlSource的SqlNode集合信息【解析】工作是发生在【每一次调用getBoundSql方法的时候】
**** RawSqlSource(#{}也就是替换为?占位符)的SqlNode集合信息【解析】工作是发生在【第一次构造RawSqlSource的时候】,因为被RawSqlSource封装的节点信息,【只需要被解析一次】
b)JDBC执行代码(如果需要配置文件的信息,则需要从【Configuration】对象中获取)
* 【获取连接】
** 使用数据源对象去优化连接的创建(DBCP,需要添加第三方的依赖),并且将该信息配置到XML文件中
** 需要通过Configuration对象,去获取解析出来的【DataSource】对象信息
* 【获取SQL】
** 拼接SQL语句:将所有的【SqlNode】(【SqlSource】(里边存储了)--->SqlNode集合(树状结构)中保存的信息,都【拼接】到StringBuffer对象中。在拼接的过程中,会将【${}】解决掉。
*** SqlNode中包含SqlNode,SqlNode中包含SqlNode集合
** 解析SQL语句:将完成的SQL语句中的【#{}】处理掉,在解析之后,会形成两部分数据【最终的SQL语句、#{}中的属性名称】,将信息封装到StaticSqlSource中
Sqlsource-->就是SQL源(里边存储的就是SQL信息)-->SQL信息就是以SQLNode这种形态呈现出来的
** 通过SqlSource获取BoundSql(jdbc可执行的SQl语句,参数信息集合)
** 通过【BoundSql】,获取里面存储的SQL语句
*** 【BoundSql】将最终的【SQL语句(可执行的)】和【该SQL语句中解析出来的参数信息】,绑定到一起,方便后边一起使用。
select * from user where id =#{id} and name = #{name}
BoundSql(
select * from user where id = ? and name = ?
List<ParameterMapping>
id和name这两个【参数名称】
除了参数名称,还需要封装【参数类型】
)
* 【创建Statement】
** 通过【MappedStatement】对象获取statementType
* 【设置参数】
** 通过【MappedStatement】对象获取入参类型(简单类型、引用类型)
** 如果是引用类型,则需要SQL解析过程中,产生的参数信息(ParameterMapping集合信息),我要根据这个参数信息,去入参对象获取指定属性值
** 调用statment.setString(1,"zhangsan")
* 【执行Statement】
* 【处理结果】
** 通过【MappedStatement】对象输出映射类型
** 通过反射给输出映射类型对应的对象,去设置属性值(通过resultSet结果集中的每一列中来)
目的是使用XML来表达mybatis的全局配置信息,和业务相关的SQL映射信息 (映射文件) 其次,优化数据连接的创建(使用连接池)
全局配置文件
<configuration>
<!-- mybatis 数据源环境配置 -->
<environments default="dev">
<environment id="dev">
<!-- 配置数据源信息 -->
<dataSource type="DBCP">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url"
value="jdbc:mysql://XXX"></property>
<property name="username" value="XXX"></property>
<property name="password" value="XXX"></property>
</dataSource>
</environment>
</environments>
<!-- 映射文件加载 -->
<mappers>
<!-- resource指定映射文件的类路径 -->
<mapper resource="mapper/UserMapper.xml"></mapper>
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
<!-- <mapper resource="mapper/UserMapper.xml"></mapper> -->
</mappers>
</configuration>
映射文件
<mapper namespace="test">
<!-- select标签,封装了SQL语句信息、入参类型、结果映射类型 -->
<select id="findUserById"
parameterType="com.kkb.mybatis.po.User"
resultType="com.kkb.mybatis.po.User" statementType="prepared">
SELECT * FROM user WHERE id = #{id}
<!-- and sex = #{sex} AND username like '%${username}' -->
<!-- <if test="username != null and username !='' "> -->
<!-- AND username like '%${username}' -->
<!-- <if test="username != null and username !=''"> -->
<!-- AND 1=1 -->
<!-- </if> -->
<!-- </if> -->
</select>
</mapper>
类图
核心流程代码
public class TestMybatis2 {
private Configuration configuration = new Configuration();
private String namespace;
//${}和动态标签都是一种字符串拼接的操作,这种操作认为是一种动态的SQL信息
private boolean isDynamic;
/**
* 加载全局配置文件,然后将数据封装到Configuration对象中
*
* @return
*/
public void loadConfiguration(String location) {
// 读取指定路径的配置文件
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
// 根据InputStream,创建Document对象,使用的是sax解析
Document document = DocumentUtils.readDocument(inputStream);
// 按照XML标签中的语义去进行解析
parseConfiguration(document.getRootElement());
}
//解析全局配置文件
private void parseConfiguration(Element rootElement) {
Element environments = rootElement.element("environments");
parseEnv(environments);
Element mappers = rootElement.element("mappers");
parseMappers(mappers);
}
//解析mappers标签 遍历每个mapper标签
private void parseMappers(Element mappers) {
List<Element> mapperElements = mappers.elements("mapper");
for (Element mapperElement : mapperElements) {
parseMapper(mapperElement);
}
}
//解析mapper标签 然后通过流 读到映射文件
private void parseMapper(Element mapperElement) {
String resource = mapperElement.attributeValue("resource");
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource);
Document document = DocumentUtils.readDocument(inputStream);
parseXmlMapper(document.getRootElement());
}
//解析映射文件 遍历解析select标签
private void parseXmlMapper(Element rootElement) {
// 为了方便管理statement,需要使用statement唯一标识
namespace = rootElement.attributeValue("namespace");
List<Element> selectElements = rootElement.elements("select");
for (Element selectElement : selectElements) {
parseStatementElement(selectElement);
}
}
//解析每一个select标签 将SQL信息封装到sqlsource里
private void parseStatementElement(Element selectElement) {
String statementId = selectElement.attributeValue("id");
if (statementId == null || selectElement.equals("")) {
return;
}
// 一个CURD标签对应一个MappedStatement对象
// 一个MappedStatement对象由一个statementId来标识,所以保证唯一性
// statementId = namespace + "." + CRUD标签的id属性
statementId = namespace + "." + statementId;
String parameterType = selectElement.attributeValue("parameterType");
Class<?> parameterClass = resolveType(parameterType);
String resultType = selectElement.attributeValue("resultType");
Class<?> resultClass = resolveType(resultType);
String statementType = selectElement.attributeValue("statementType");
statementType = statementType == null || statementType == "" ? "prepared" : statementType;
// 解析SQL信息
SqlSource sqlSource = createSqlSource(selectElement);
// TODO 建议使用构建者模式去优化
MappedStatement mappedStatement = new MappedStatement(statementId, parameterClass, resultClass, statementType,
sqlSource);
configuration.addMappedStatement(statementId, mappedStatement);
}
//创建sqlsource 对象 用来封装 封装了不同SQL信息的SQLnode
private SqlSource createSqlSource(Element selectElement) {
// 去解析select等CURD标签中的SQL脚本信息
SqlSource sqlSource = parseScriptNode(selectElement);
return sqlSource;
}
//解析crud标签中的SQL脚本信息 对不同类型的SQL信息 分别封装到不同实现sqlsource(接口)对象中
private SqlSource parseScriptNode(Element selectElement) {
// 1.解析出来所有的SqlNode信息
MixedSqlNode rootSqlNode = parseDynamicTags(selectElement);
// isDynamic是parseDynamicTags过程中,得到的值
// 如果包含${}或者包含动态标签,则isDynamic为true
SqlSource sqlSource;
// 2.将SqlNode信息封装到SqlSource中,并且要根据是否动态节点去选择不同的SqlSource
// 如果isDynamic为true,则说明SqlNode集合信息里面包含${}SqlNode节点信息或者动态标签的节点信息
if (isDynamic) {
sqlSource = new DynamicSqlSource(rootSqlNode);
} else {
sqlSource = new RawSqlSource(rootSqlNode);
}
return sqlSource;
}
//解析(select标签) sql信息大集合(包括select子标签 和文本信息),将未分类的sqlnode从select标签中区分出来 以便对应的封装到不同的sqlsource中
private MixedSqlNode parseDynamicTags(Element selectElement) {
List<SqlNode> sqlNodes = new ArrayList<>();
int nodeCount = selectElement.nodeCount();
// 获取select标签的子标签
for (int i = 0; i < nodeCount; i++) {
Node node = selectElement.node(i);
// 区分是文本标签还是元素标签
if (node instanceof Text) {
String sqlText = node.getText().trim();// trim 此字符串移除了前导和尾部空白的副本
if (sqlText == null || "".equals(sqlText)) {
continue;
}
TextSqlNode textSqlNode = new TextSqlNode(sqlText);
if (textSqlNode.isDynamic()) {//判断是否含有${}
// 设置是否动态为true
isDynamic = true;
sqlNodes.add(textSqlNode);
} else {
sqlNodes.add(new StaticTextSqlNode(sqlText));
}
} else if (node instanceof Element) {
// 获取动态标签的标签名称
String nodeName = node.getName();
// TODO 设计模式优化
if ("if".equals(nodeName)) {
// 封装成IfSqlNode(test信息、子标签信息)
Element element = (Element) node;
// if标签的test属性信息
String test = element.attributeValue("test");
// if标签的子标签信息
MixedSqlNode rootSqlNode = parseDynamicTags(element);
// IfSqlNode就是封装if标签信息的
IfSqlNode ifSqlNode = new IfSqlNode(test, rootSqlNode);
sqlNodes.add(ifSqlNode);
}
isDynamic = true;
}
}
return new MixedSqlNode(sqlNodes);
}
private Class<?> resolveType(String parameterType) {
try {
Class<?> clazz = Class.forName(parameterType);
return clazz;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
private void parseEnv(Element environments) {
String def = environments.attributeValue("default");
List<Element> elements = environments.elements("environment");
for (Element envElement : elements) {
String id = envElement.attributeValue("id");
if (id.equals(def)) {
parseEnvironment(envElement);
}
}
}
private void parseEnvironment(Element envElement) {
Element dataSource = envElement.element("dataSource");
parseDataSource(dataSource);
}
private void parseDataSource(Element dataSourceElement) {
String type = dataSourceElement.attributeValue("type");
if ("DBCP".equals(type)) {
BasicDataSource dataSource = new BasicDataSource();
Properties properties = new Properties();
List<Element> propertyElements = dataSourceElement.elements();
for (Element prop : propertyElements) {
String name = prop.attributeValue("name");
String value = prop.attributeValue("value");
properties.put(name, value);
}
dataSource.setDriverClassName(properties.getProperty("driver"));
dataSource.setUrl(properties.getProperty("url"));
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(dataSource);
}
}
public List<Object> queryByJDBC(Configuration configuration, String statementId, Object param) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
// 1、获取Connection
// 此处使用DataSource去优化connection的获取
// DataSource是通过XML配置来产生的
// XML信息是通过Configuration对象保存
connection = getConnection(configuration);
// 2、获取可以JDBC执行的SQL语句
// 【SQL信息】是配置在映射文件中的,是通过【select等statement标签】来配置的
// 不同脚本的【SQL信息】,是封装到不同类型的【SqlNode】对象中
// 而【SqlNode】信息是保存到【SqlSource】中的
// 【SqlSource】的信息是封装到【MappedStatement】对象中
// 【MappedStatement】对象被保存到【Configuration】对象中的
MappedStatement mappedStatement = configuration.getMappedStatementById(statementId);
if (mappedStatement == null) {
return null;
}
// 获取SqlSource
SqlSource sqlSource = mappedStatement.getSqlSource();
// 执行SqlSource去获取Sql信息
BoundSql boundSql = sqlSource.getBoundSql(param);
// 获取JDBC可以直接执行的SQL语句
String sql = boundSql.getSql();
// 需要获取statament的类型,这个类型是保存到MappedStatement对象中的
String statementType = mappedStatement.getStatementType();
if ("prepared".equals(statementType)) {
// 3、获取statement
preparedStatement = connection.prepareStatement(sql);//创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库
// 4、设置参数
handleParameter(preparedStatement, mappedStatement, boundSql, param);
// 5、向数据库发出 sql 执行查询,查询出结果集
rs = preparedStatement.executeQuery();//在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的 ResultSet 对象。
// 6、处理结果集
return handleResultSet(rs, mappedStatement);
} else if ("simple".equals(statementType)) {
// 创建简单的Statement对象....
} // ....
return null;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
}
return null;
}
private List<Object> handleResultSet(ResultSet rs, MappedStatement mappedStatement) throws Exception {
List<Object> results = new ArrayList<Object>();
Class<?> resultTypeClass = mappedStatement.getResultTypeClass();
while (rs.next()) {
// 遍历一次是一行,也对应一个对象,利用反射new一个对象
Object result = resultTypeClass.newInstance();//创建此 Class 对象所表示的类的一个新实例
// 要获取每一列的值,然后封装到结果对象中对应的属性名称上
//ResultSetMetaData 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
ResultSetMetaData metaData = rs.getMetaData();//获取此 ResultSet 对象的列的编号、类型和属性
int columnCount = metaData.getColumnCount();//返回此 ResultSet 对象中的列数。
for (int i = 0; i < columnCount; i++) {
// 获取每一列的值 Object 的形式获取此 ResultSet 对象的当前行中指定列的值。
Object value = rs.getObject(i + 1);
// 列的名称
String columnName = metaData.getColumnName(i + 1);
// 列名和属性名称要严格一致
Field field = resultTypeClass.getDeclaredField(columnName);//返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段
field.setAccessible(true);
// 给映射的对象赋值
field.set(result, value);
}
results.add(result);
}
return results;
}
//参数处理
private void handleParameter(PreparedStatement preparedStatement, MappedStatement mappedStatement,
BoundSql boundSql, Object param) throws Exception {
// 从mappedStatement获取入参的类型
Class<?> parameterTypeClass = mappedStatement.getParameterTypeClass();
// 如果入参是8种基本类型和String类型
if (SimpleTypeRegistry.isSimpleType(parameterTypeClass)) {
preparedStatement.setObject(1, param);
} else if (parameterTypeClass == Map.class) {
// 如果入参是Map类型
// ....
} else {
// 如果入参是POJO类型(比如User类型)
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 封装的#{}里面的属性名称
String name = parameterMapping.getName();
// 利用反射去入参对象根据属性名称获取指定的属性值
Field field = parameterTypeClass.getDeclaredField(name);//返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段
field.setAccessible(true);//值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查
Object value = field.get(param);
//参数:obj - 从中提取所表示字段的值的对象 返回:对象 obj 中的所表示字段的值;在返回之前,基值包装在一个适当的对象中
// TODO 可以使用ParameterMapping里面的type对Object类型的value进行类型处理
// 设置statement占位符中的值
preparedStatement.setObject(i + 1, value);
}
}
}
private Connection getConnection(Configuration configuration) throws Exception {
DataSource dataSource = configuration.getDataSource();
Connection connection = dataSource.getConnection();//尝试建立与此 DataSource 对象所表示的数据源的连接。
return connection;
}
@Test
public void test() throws Exception {
// 第一步:加载XML配置文件,包括全局配置文件和映射文件
loadConfiguration("mybatis-config.xml");
String statementId = "test" + "." + "findUserById";
User user = new User();
user.setId(1);
// 第二步:执行JDBC代码,并返回已经映射的结果
List<Object> list = queryByJDBC(configuration, statementId, user);
for (Object object : list) {
User u = (User) object;
System.out.println(u);
}
}
}
Configuration类和MappedStatement代码
Configuration类
- 封装XML配置文件中的信息 封装mybatis-config.xml和*mapper.xml中存储的数据
public class Configuration {
// 封装MappedStatement对象集合
private Map<String, MappedStatement> mappedStatements = new HashMap<String, MappedStatement>();
// 封装数据源对象
private DataSource dataSource;
public MappedStatement getMappedStatementById(String statementId) {
return this.mappedStatements.get(statementId);
}
public void addMappedStatement(String statementId, MappedStatement mappedStatement) {
this.mappedStatements.put(statementId, mappedStatement);
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
MappedStatement类
- 用来封装映射文件中的CRUD标签脚本内容,比如select标签
public class MappedStatement {
private String statementId;
private SqlSource sqlSource;
private String statementType;
private Class<?> parameterTypeClass;
private Class<?> resultTypeClass;
public MappedStatement(String statementId, Class<?> parameterTypeClass, Class<?> resultTypeClass,
String statementType, SqlSource sqlSource) {
this.statementId = statementId;
this.parameterTypeClass = parameterTypeClass;
this.resultTypeClass = resultTypeClass;
this.statementType = statementType;
this.sqlSource = sqlSource;
}
public SqlSource getSqlSource() {
return sqlSource;
}
public void setSqlSource(SqlSource sqlSource) {
this.sqlSource = sqlSource;
}
public String getStatementType() {
return statementType;
}
public void setStatementType(String statementType) {
this.statementType = statementType;
}
public String getStatementId() {
return statementId;
}
public void setStatementId(String statementId) {
this.statementId = statementId;
}
public Class<?> getParameterTypeClass() {
return parameterTypeClass;
}
public void setParameterTypeClass(Class<?> parameterTypeClass) {
this.parameterTypeClass = parameterTypeClass;
}
public Class<?> getResultTypeClass() {
return resultTypeClass;
}
public void setResultTypeClass(Class<?> resultTypeClass) {
this.resultTypeClass = resultTypeClass;
}
}
SqlNode接口及其实现类代码
SQLNode接口
- 1、封装SQL节点的信息
- 2、提供对封装的SQL节点的解析功能
public interface SqlNode {
/**
* 解析功能
* 最终解析出来的SQL语句,封装到DynamicContext中的StringBuffer对象中
* 解析的时候,可能要用到入参对象
*
* 此时解析出来的SQL语句,只是根据动态标签的逻辑,完成了字符串的拼接,它还没有被解析
* @param context
*/
void apply(DynamicContext context);
}
DynamicContext 类
- 动态上下文,就是封装的入参信息,解析过程中的SQL信息
public class DynamicContext {
private StringBuffer sb = new StringBuffer();
private HashMap<String, Object> bindings = new HashMap<>();
public DynamicContext(Object param) {
bindings.put("_parameter", param);
}
public String getSql() {
return sb.toString();
}
public void appendSql(String sqlText) {
this.sb.append(sqlText);
this.sb.append(" ");
}
public HashMap<String, Object> getBindings() {
return bindings;
}
public void addBinding(String name, Object param) {
this.bindings.put(name, param);
}
}
MixedSqlNode 类(实现SqlNode接口)
- 封装的是带有${}的文本字符串
- 封装SqlNode集合
public class MixedSqlNode implements SqlNode {
// 封装SqlNode集合信息
private List<SqlNode> sqlNodes;
public MixedSqlNode(List<SqlNode> sqlNodes) {
this.sqlNodes = sqlNodes;
}
/**
* 对外提供对封装数据的操作
*/
@Override
public void apply(DynamicContext context) {
for (SqlNode sqlNode : sqlNodes) {
sqlNode.apply(context);
}
}
}
StaticTextSqlNode 类(实现SqlNode接口)
- 封装的是带有#{}的文本字符串
public class StaticTextSqlNode implements SqlNode {
private String sqlText;
public StaticTextSqlNode(String sqlText) {
this.sqlText = sqlText;
}
@Override
public void apply(DynamicContext context) {
context.appendSql(sqlText);
}
}
TextSqlNode 类(实现SqlNode接口)
- 封装的是带有${}的文本字符串
public class TextSqlNode implements SqlNode {
private String sqlText;
public TextSqlNode(String sqlText) {
this.sqlText = sqlText;
}
// ${}的处理,就是在这个时候
@Override
public void apply(DynamicContext context) {
BindingTokenParser handler = new BindingTokenParser(context);
// 将#{}解析为?并保存参数信息
GenericTokenParser tokenParser = new GenericTokenParser("${", "}", handler);
// 获取真正可以执行的SQL语句
String sql = tokenParser.parse(sqlText);
context.appendSql(sql);
}
/**
* 对外提供保存数据的处理功能
*
* @return
*/
public boolean isDynamic() {
if (sqlText.indexOf("${") > -1) {
return true;
}
return false;
}
private static class BindingTokenParser implements TokenHandler {
private DynamicContext context;
public BindingTokenParser(DynamicContext context) {
this.context = context;
}
/**
* expression:比如说${username},那么expression就是username username也就是Ognl表达式
*/
@Override
public String handleToken(String expression) {
Object paramObject = context.getBindings().get("_parameter");
if (paramObject == null) {
// context.getBindings().put("value", null);
return "";
} else if (SimpleTypeRegistry.isSimpleType(paramObject.getClass())) {
// context.getBindings().put("value", paramObject);
return String.valueOf(paramObject);
}
// 使用Ognl api去获取相应的值
Object value = OgnlUtils.getValue(expression, paramObject);
String srtValue = value == null ? "" : String.valueOf(value);
return srtValue;
}
}
}
IfSqlNode 类(实现SqlNode接口)
- 封装if标签的SQL信息
public class IfSqlNode implements SqlNode {
private String test;
private SqlNode rootSqlNode;
public IfSqlNode(String test, SqlNode rootSqlNode) {
super();
this.test = test;
this.rootSqlNode = rootSqlNode;
}
@Override
public void apply(DynamicContext context) {
boolean evaluateBoolean = OgnlUtils.evaluateBoolean(test, context.getBindings().get("_parameter"));
if (evaluateBoolean) {
rootSqlNode.apply(context);
}
}
}
SqlSource接口及其实现类
SqlSource 接口
- 1、实现类要封装未解析的SQL信息
- 2、要提供对未解析的SQL信息,进行解析的功能
public interface SqlSource {
// String username = "王五";
// statement的使用方式,也就是动态拼接SQL语句
// "select * from user where username = "+username
// "select * from user where username = ? AND age = ?"
// preparedStatement.setString(1,"王五") //【王五】得去入参对象的username属性中获取
// preparedStatement.setString(2,"男") //【男】得去入参对象的age属性中获取
BoundSql getBoundSql(Object param);
}
BoundSql 类
- 封装解析之后的SQL信息,以及解析占位符?时产生的参数信息
public class BoundSql {
// 解析之后的SQL语句
private String sql;
// 解析过程中产生的SQL参数信息
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
public BoundSql(String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void addParameterMappings(ParameterMapping parameterMapping) {
this.parameterMappings.add(parameterMapping);
}
}
ParameterMapping 类
- 从#{}中解析出来的参数信息,包括参数名称和类型
public class ParameterMapping {
private String name;
private Class<?> type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Class<?> getType() {
return type;
}
public void setType(Class<?> type) {
this.type = type;
}
public ParameterMapping(String name) {
super();
this.name = name;
}
}
DynamicSqlSource 类(实现SqlSource)
- 封装带有${}或者动态SQL标签的SQL信息
public class DynamicSqlSource implements SqlSource {
private SqlNode rootSqlNode;
public DynamicSqlSource(SqlNode mixedSqlNode) {
this.rootSqlNode = mixedSqlNode;
}
@Override
public BoundSql getBoundSql(Object param) {
// #{}处理的时候,不需要入参对象的支持
DynamicContext context = new DynamicContext(param);
// 处理SqlNode,先去处理动态标签和${},拼接成一条SQL文本,该SQL文本还包含#{}
rootSqlNode.apply(context);
// 处理#{}
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
// 将#{}解析为?并保存参数信息
GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", handler);
// 获取真正可以执行的SQL语句
String sql = tokenParser.parse(context.getSql());
return new BoundSql(sql, handler.getParameterMappings());
}
}
RawSqlSource 类 (实现SqlSource接口)
- 封装最多只带有#{}的SQL信息
- 也就是#{}需要被处理一次就可以,就可以使用占位符来长期使用。 而这一点和 很 不 一 样 。 {}很不一样。 很不一样。{}每一次被调用时,就需要去解析一次SQL语句
public class RawSqlSource implements SqlSource {
private StaticSqlSource staticSqlSource;
public RawSqlSource(SqlNode mixedSqlNode) {
// #{}处理的时候,不需要入参对象的支持
DynamicContext context = new DynamicContext(null);
// 处理SqlNode
mixedSqlNode.apply(context);
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
// 将#{}解析为?并保存参数信息
GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", handler);
// 获取真正可以执行的SQL语句
String sql = tokenParser.parse(context.getSql());
// 该SqlSource就是封装已经解析完成的Sql语句
staticSqlSource = new StaticSqlSource(sql, handler.getParameterMappings());
}
@Override
public BoundSql getBoundSql(Object param) {
return staticSqlSource.getBoundSql(param);
}
}
StaticSqlSource 类(实现SqlSource接口)
- 存储被解析之后的SQL信息和参数信息
public class StaticSqlSource implements SqlSource {
// 解析之后的SQL语句
private String sql;
// 解析过程中产生的SQL参数信息
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
public StaticSqlSource(String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
}
@Override
public BoundSql getBoundSql(Object param) {
return new BoundSql(sql, parameterMappings);
}
}