30分钟教你写一个mybatis框架

目标:将解析mybatis配置文件和mapper文件,封装jdbc实现mybatis4大组件,创建sqlSession。以下代码gitee地址为 https://gitee.com/zumengjie/shouxie-mybatis

第一部分解析mybatis配置文件,解析mapper文件。

XMLConfigBuilder解析mybatis配置文件,创建一个Configuration对象,该对象是mybatis的核心配置类。对配置文件中的<environments>标签解析,<environments>包含多个<environment>每个包含<dataSource>根据<environments>标签的default属性选择一个environment,读取对应的<dataSource>配置信息。根据<dataSource>的type属性,确定要使用的连接池。使用<dataSource>中配置的数据库信息进而创建数据源DataSource,将DataSource设置到Configuration中。

<configuration>
    <!-- mybatis 数据源环境配置 -->
    <environments default="dev">
        <environment id="dev">
            <!-- 配置数据源信息 -->
            <dataSource type="DBCP">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/petstore?serverTimezone=GMT%2B8&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>



    <!-- 映射文件加载 -->
    <mappers>
        <!-- resource指定映射文件的类路径 -->
        <mapper resource="mapper/manageUser.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>
View Code
package builder;

import mapping.Configuration;
import org.apache.commons.dbcp.BasicDataSource;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 10:42
 * @notify 解析mybatisConfig的类,当前解析 environments  mappers
 * 创建Configuration 设置DataSource
 *  mappers 则获取输入流,交给XMLMapperBuilder解析每一个mapper文件
 * @version 1.0
 */
public class XMLConfigBuilder {

    private Configuration configuration = new Configuration();

    public Configuration parse(Element rootElement) throws Exception {
        //解析<environments>
        Element environments = rootElement.element("environments");
        parseEnvironments(environments);
        //解析<mappers>
        Element mappers = rootElement.element("mappers");
        parseMappers(mappers);
        return configuration;
    }

    //解析<environments>
    private void parseEnvironments(Element environments) {
        //查询environments default="dev"
        String aDefault = environments.attributeValue("default");
        //获取全部的 environment
        List<Element> environment = environments.elements("environment");
        //循环所有的 environment
        for (Element env : environment) {
            //如果当前 environment 的id和默认的id相同则继续向下解析
            if (env.attributeValue("id").equals(aDefault)) {
                parseEnvironment(env);
            }
        }
    }

    //解析environment
    private void parseEnvironment(Element environment) {
        //解析<dataSource type="DBCP">
        Element dataSource = environment.element("dataSource");
        parseDataSource(dataSource);
    }

    //解析dataSource
    private void parseDataSource(Element dataSource) {
        //获取连接池类型
        String type = dataSource.attributeValue("type");
        //设置 <dataSource type="DBCP"> 连接池
        if (type.equals("DBCP")) {
            //创建 DBCP连接池
            BasicDataSource dataSource1 = new BasicDataSource();
            //创建配置类
            Properties properties = new Properties();
            //获取全部的property
            List<Element> propertys = dataSource.elements("property");
            //循环拿到<property name="driver" value="com.mysql.jdbc.Driver"/>
            for (Element prop : propertys) {
                //获取标签name属性值
                String name = prop.attributeValue("name");
                //获取标签value属性值
                String value = prop.attributeValue("value");
                //设置到配置类
                properties.put(name, value);
            }
            //设置连接池属性
            dataSource1.setDriverClassName(properties.get("driver").toString());
            dataSource1.setUrl(properties.get("url").toString());
            dataSource1.setUsername(properties.get("username").toString());
            dataSource1.setPassword(properties.get("password").toString());
            //给Configuration设置数据源信息
            configuration.setDataSource(dataSource1);
        }
    }

    //解析<mappers>
    private void parseMappers(Element mappers) throws Exception {
        //拿到所有的<mapper resource="mapper/UserMapper.xml"></mapper>
        List<Element> mapperElements = mappers.elements("mapper");
        //遍历解析每一个 mapper.xml
        for (Element mapperElement : mapperElements) {
            parseMapper(mapperElement);
        }
    }

    //解析每一个mapper标签
    private void parseMapper(Element mapperElement) throws Exception {
        //TODO 此处还有url等方式
        String resource = mapperElement.attributeValue("resource");
        //根据文件名获取输入流
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource);
        //dom4j解析
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        //获取跟标签
        Element rootElement = document.getRootElement();
        XMLMapperBuilder mapperBuilder = new XMLMapperBuilder(configuration);
        mapperBuilder.parse(rootElement);
    }


}
View Code

循环<mappers>标签获取多个<mapper>循环解析mapper配置文件,使用XMLMapperBuilder类获取每个mapper配置文件的namespace,在解析不同的sql标签(<insert><select>)。

<mapper namespace="manageUser">
    <!-- select标签,封装了SQL语句信息、入参类型、结果映射类型 -->
    <select id="getManageUserById"
            parameterType="pojo.ManageUser"
            resultType="pojo.ManageUser" statementType="prepared">
        SELECT * FROM manage_user WHERE id = #{id}
    </select>

    <insert  id="insertManage"
             parameterType="pojo.ManageUser"
             statementType="prepared">
        insert into manage_user values(#{id},#{username},#{password},#{create_date});
    </insert>

    <select id="getManageUserByUserName"
            parameterType="pojo.ManageUser"
            resultType="pojo.ManageUser" statementType="prepared">
        SELECT * FROM manage_user WHERE id = #{id}
        <if test="username!=null and username!=''">
            and username = ${username}
        </if>
    </select>
</mapper>
View Code
package builder;


import mapping.Configuration;
import org.dom4j.Element;

import java.util.List;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 11:13
 * @notify 解析mapper文件获取namespace,读取到<insert>和<select>标签,交给XMLStatementBuilder
 * @version 1.0
 */
public class XMLMapperBuilder {

    private String namespace = "";

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    //解析每一个mapper文件
    public void parse(Element rootElement) throws Exception {
        //查询namespace
        namespace = rootElement.attributeValue("namespace");
        //获取select标签
        List<Element> selectElements = rootElement.elements("select");
        parse(selectElements, "select");

        List<Element> insertElements = rootElement.elements("insert");
        parse(insertElements, "insert");

    }

    public void parse(List<Element> selectElements, String sqlType) throws Exception {
        for (Element selectElement : selectElements) {
            XMLStatementBuilder xmlStatementBuilder = new XMLStatementBuilder(configuration);
            xmlStatementBuilder.parseStatementElement(selectElement, namespace, sqlType);
        }
    }


}
View Code

XMLStatementBuilder类解析具体的sql标签。每一个sql标签都是一个MappedStatement对象,而每一个mapper文件中有N个sql标签,一个项目又有M个mapper文件。所以一个Configuration中有一个map,key是statementid,(由mapper文件的namespace和sql标签的id组成)value是MappedStatement。一个MappedStatement对象由statementid,入参类型,返回值类型,sqlType属于<select>还是<insert>等等 statementType的值分别对应:statement不进行预编译,prepared预编译,callable执行存储过程。最后还有一个专门用来存储sql语句的对象SqlSource,SqlSource是一个接口。

package builder;

import mapping.Configuration;
import mapping.MappedStatement;
import org.dom4j.Element;
import sqlnode.impl.MixedSqlNode;
import sqlsource.SqlSource;
import sqlsource.impl.DynamicSqlSource;
import sqlsource.impl.RawSqlSource;
import utils.ResolveType;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 11:24
 * @notify 解析<insert><select>标签,读取入参,返回值,id等信息,标签内容交给XMLScriptBuilder
 * @version 1.0
 */
public class XMLStatementBuilder {
    private Configuration configuration;

    public XMLStatementBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    //解析标签
    public void parseStatementElement(Element selectElement, String namespace, String sqlType) throws Exception {
        //读取id
        String statementId = selectElement.attributeValue("id");
        //如果id不存在则返回
        if (statementId == null || selectElement.equals("")) {
            return;
        }
        //拼接namespace
        statementId = namespace + "." + statementId;
        //查询 parameterType 属性
        String parameterType = selectElement.attributeValue("parameterType");
        //通过类名获取Class
        Class<?> parameterClass = ResolveType.resolveType(parameterType);
        //查询 resultType 属性
        String resultType = selectElement.attributeValue("resultType");
        Class<?> resultClass = null;
        if (resultType != null && !resultType.equals("")) {
            //通过类名获取Class
            resultClass = ResolveType.resolveType(resultType);
        }

        //获取statementType属性
        String statementType = selectElement.attributeValue("statementType");
        //设置默认的statementType属性
        statementType = statementType == null || statementType == "" ? "prepared" : statementType;
        // 解析SQL信息
        SqlSource sqlSource = createSqlSource(selectElement);

        // TODO 建议使用构建者模式去优化
        MappedStatement mappedStatement = new MappedStatement(statementId, parameterClass, resultClass, statementType,
                sqlSource, sqlType);
        //设置Configuration参数
        configuration.addMappedStatement(statementId, mappedStatement);

    }

    //获取sqlSource
    private SqlSource createSqlSource(Element selectElement) throws Exception {
        XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder();
        SqlSource sqlSource = xmlScriptBuilder.parseScriptNode(selectElement);
        return sqlSource;
    }


}
View Code

XMLScriptBuilder解析sql脚本,这一部分较为复杂。首先我们先明确,我们需要解析的内容是sql标签中的标签体,也就是sql脚本,需要将sql脚本组成一个SqlSource

<select>
  select * from manage_user whereid = #{id}
  <if test="username!=null and username!=''">
    and username = ${username}
  </if>
</select>

这样的sql脚本包含两个节点,一个是只包含普通的文本的sql节点,另一个则是if标签的sql节点。当然,真实的mybatis还包含<where>等节点。封装sql节点引入一个接口,SqlNode。每种节点最终都需要放到SqlSource中,我们可以在SqlSource中使用一个集合来存储,但是我们还有一个更好的选择,使用MixedSqlNode。现在MixedSqlNode中存储多个SqlNode根据不同的节点不同,我们将是文本节点和包含${}的节点封装成TextSqlNode,将只包含文本且只包含#{}的节点封装成StaticTextSqlNode。较为麻烦的是if标签,因为一个if标签里可能会包含带有#{}的文本内容,或者带有${}的文本内容,或许,if标签里还有if标签。这里我们必须要用到递归解析了。我们假设if标签中包含TextSqlNode和另一个if标签,此时我们就需要把两个标签放到if标签中,辛好我们有MixedSqlNode,于是if标签中的test表达式和MixedSqlNode组成了IfSqlNode现在我们解析了全部的sql脚本将所有的SqlNode封装到MixedSqlNode,然后组装一个SqlSource。前边我们SqlSource是一个接口,现在我们将sql脚本中只包含StaticTextSqlNode节点的SqlSource封装成RawSqlSource,而包含TextSqlNodeIfSqlNode节点SqlSource封装成DynamicSqlSource

package builder;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 11:30
 * @notify
 * @version 1.0
 */

import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.Text;
import sqlnode.SqlNode;
import sqlnode.impl.IfSqlNode;
import sqlnode.impl.MixedSqlNode;
import sqlnode.impl.StaticTextSqlNode;
import sqlnode.impl.TextSqlNode;
import sqlsource.SqlSource;
import sqlsource.impl.DynamicSqlSource;
import sqlsource.impl.RawSqlSource;

import java.util.ArrayList;
import java.util.List;

public class XMLScriptBuilder {

    private boolean isDynamic = false;

    //解析标签体
    public SqlSource parseScriptNode(Element selectElement) throws Exception {
        MixedSqlNode mixedSqlNode = parseDynamicTags(selectElement);
        SqlSource sqlSource;
        if (isDynamic) {//如果包含${}或者其他的子标签则为动态的
            sqlSource = new DynamicSqlSource(mixedSqlNode);
        } else {//全部的sqlNode都是文本,并且只包含#{}
            sqlSource = new RawSqlSource(mixedSqlNode);
        }
        return sqlSource;
    }


    private MixedSqlNode parseDynamicTags(Element selectElement) {
        //存储一个 <select> 中的所有sqlNode
        List<SqlNode> sqlNodes = new ArrayList<>();
        //查询总结点数量
        int nodeCount = selectElement.nodeCount();
        //遍历全部的sql节点
        for (int i = 0; i < nodeCount; i++) {
            //获取当前节点
            Node node = selectElement.node(i);
            //如果是纯文本的
            if (node instanceof Text) {
                //拿到文本节点
                String sqlText = node.getText().trim();
                if (!sqlText.equals("")) {
                    //如果包含 ${} 则创建 TextSqlNode 该节点只包含文本和${}
                    if (sqlText.indexOf("${") > -1) {
                        //如果包含${}或者其他的子标签则为动态的
                        isDynamic = true;
                        //将TextSqlNode添加到节点集合中
                        sqlNodes.add(new TextSqlNode(sqlText));
                    } else {
                        //将StaticTextSqlNode添加到节点集合中
                        sqlNodes.add(new StaticTextSqlNode(sqlText));
                    }
                }
            } else if (node instanceof Element) {
                //如果包含${}或者其他的子标签则为动态的
                isDynamic = true;
                //拿到节点名称
                String nodeName = node.getName();
                //如果是 if则表示是if标签
                if (nodeName.equals("if")) {
                    //将node转换成element
                    Element element = (Element) node;
                    //拿到if的条件
                    String test = element.attributeValue("test");
                    /*
                    此处递归调用,因为if标签中还有子节点
                    设sql为
                    select * from user
                    <if test='name!=null and name!='''>
                        and name = #{name}
                    </if>
                    此时 select * from user 已经转换成了sqlNode
                    接下来的if标签,里边也包含子节点,所以递归,
                    第二次进入 (parseDynamicTags)
                    会创建一个StaticTextSqlNode,将该SqlNode添加到
                     List<SqlNode> sqlNodes = new ArrayList<>();
                     而这个集合最终会被封装成一个MixedSqlNode返回到第一次
                     调用(parseDynamicTags),所以此处使用
                     MixedSqlNode接收,并将该MixedSqlNode传递给IfSqlNode,然后
                     添加到sqlNodes中
                    */
                    MixedSqlNode mixedSqlNode = parseDynamicTags(element);
                    sqlNodes.add(new IfSqlNode(test, mixedSqlNode));
                }
            }
        }
        //返回节点集合包装类
        return new MixedSqlNode(sqlNodes);
    }
}
View Code

以上过程只是对sql解析的大概描述。现在我们缕清思路,Configuration包含的是mybatis的全局配置,其中包含DataSource和所有的mapper配置文件信息。形成了一个MappedStatementMappedStatement包含的是sql标签的属性和内容,属性包含id,入参,出参,statement类型等,内容则是多个SqlNode形成的SqlSource。我们没有使用集合来存储SqlNode,而是在SqlSource内部持有一个MixedSqlNode此类是SqlNode接口的实现类,MixedSqlNode里有一个list集合存放各种类型的SqlNode,例如TextSqlNodeIfSqlNode等。

SqlSource接口,该接口中只有一个方法。getBoundSql(Object param);返回一个BoundSql对象。我们先不管BoundSql是干嘛的。

package sqlsource;
/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 14:41
 * @notify 获取sql语句
 * @version 1.0
 */

import mapping.BoundSql;

public interface SqlSource {
    /*
    传入参数,这个param就是sql的入参
    boundSQL 信息返回拼接的sql可能是${} 或者 #{} 如果是${}则可以直接获取sql
    如果是 #{} 还需要有方法获取 ?代表的属性。
     */
    BoundSql getBoundSql(Object param)throws Exception;
}
View Code

接着看SqlSource的实现类。

package sqlsource.impl;

import mapping.BoundSql;
import mapping.DynamicContext;
import sqlnode.SqlNode;
import sqlsource.SqlSource;
import sqlsource.SqlSourceParser;
import tokenparser.GenericTokenParser;
import tokenparser.ParameterMappingTokenHandler;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 14:52
 * @notify #{} 中的内容进行处理 RawSqlSource只包含 StaticTextSqlNode
 * @version 1.0
 */
public class RawSqlSource implements SqlSource {

    private SqlNode mixedSqlNode;

    public RawSqlSource(SqlNode mixedSqlNode) throws Exception {
        this.mixedSqlNode = mixedSqlNode;
    }

    @Override
    public BoundSql getBoundSql(Object param) throws Exception {
        //执行该sqlSource中的所有sqlNode
        DynamicContext dynamicContext = new DynamicContext(null);
        mixedSqlNode.apply(dynamicContext);
        //将 #{ } 替换成 ? 然后将内容存储到 ParameterMapping 中
        SqlSourceParser parser = new SqlSourceParser(dynamicContext);
        SqlSource sqlSource = parser.parse();
        return sqlSource.getBoundSql(param);
    }

}
View Code

调用RawSqlSourcegetBoundSql(Object param);方法,创建了一个DynamicContext对象。这个对象有一个StringBuilder对象,和Object类型参数。

package mapping;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 18:02
 * @notify 每个SqlSource有多个SqlNode,而DynamicContext负责将多个sqlNode解析出来的sql信息拼接到一块
 * @version 1.0
 */
public class DynamicContext {
    //多个sqlNode调用自己的apply()都把解析好的sql放到这里。
    private StringBuilder sb = new StringBuilder();
    //sql入参
    private Object param;

    public String getSql() {
        return sb.toString();
    }

    public DynamicContext(Object param) {
        this.param = param;
    }

    public void appendSql(String sqlText) {
        sb.append(sqlText);
        sb.append(" ");
    }

    public Object getParam() {
        return param;
    }

}
View Code

接着我们调用SqlNode的apply(DynamicContext context)。其实当前的SqlNode其实就是MixedSqlNode

package sqlnode;


import mapping.DynamicContext;

/*
* @auther 顶风少年 
* @mail dfsn19970313@foxmail.com
* @date 2020-01-04 18:00
* @notify 
* @version 1.0
*/
public interface SqlNode {
    void apply(DynamicContext context)throws Exception;
}
View Code
package sqlnode.impl;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 18:10
 * @notify 因为一个sqlSource有多个sqlNode 所以将所有的sqlNode封装成一个对象。
 * 集中的管理所有的sqlNode
 * @version 1.0
 */

import mapping.DynamicContext;
import sqlnode.SqlNode;

import java.util.List;

public class MixedSqlNode implements SqlNode {

    //封装SqlNode集合信息
    private List<SqlNode> sqlNodes;

    public MixedSqlNode(List<SqlNode> sqlNodes) {
        this.sqlNodes = sqlNodes;
    }

    /*
     * 对外提供对数据封装的操作
     * */
    @Override
    public void apply(DynamicContext context) throws Exception {
        //执行sqlSource中所有sqlNode的apply()不同的sqlNode有不同的解析方式。最终都会将自己解析的sql放到DynamicContext中
        for (SqlNode sqlNode : sqlNodes) {
            sqlNode.apply(context);
        }
    }
}
View Code

但我们调用MixedSqlNode的apply(DynamicContext context);其实就是循环调用每一个SqlNode自己的apply(DynamicContext context);前边我们说过,

只包含StaticTextSqlNode节点的才能被封装成RawSqlSource。所以RawSqlSource中存储的只是多个StaticTextSqlNode

package sqlnode.impl;


/*
* @auther 顶风少年 
* @mail dfsn19970313@foxmail.com
* @date 2020-01-04 18:10
* @notify 纯文本的sql节点,包含#{} 但是不包含 ${} 的
* @version 1.0
*/

import mapping.DynamicContext;
import sqlnode.SqlNode;

public class StaticTextSqlNode implements SqlNode {

    private String sqlText;

    public StaticTextSqlNode(String sqlText){
        this.sqlText = sqlText;
    }

    /*
    * 每一个StaticTextSqlNode 仅仅包含纯文本的sql语句和#{}所以不需要额外的处理,他本身的apply,只是
    * 作为拼接。设想select * from 是一个sqlNode where id = #{id} 是第二个sqlNode只要拼接就可以了。不管作为
    * 第几个sqlNode,StaticTextSqlNode不需要做其他的操作${}需要将其中的文本替换,而IfSqlNode则需要判断test等。
    * */
    @Override
    public void apply(DynamicContext context)throws Exception {
        //添加到 DynamicContext
        context.appendSql(sqlText);
    }
}
View Code

StaticTextSqlNodeapply(DynamicContext context);只是将sql文本连接到DynamicContext中的StringBuilder中。当MixedSqlNodeapply(DynamicContext context);执行接收后,DynamicContext中的字符串其实是

select * from table where id = #{id} 此时我们需要将#{id}替换成 ?这里使用到了SqlSourceParser

package sqlsource;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 14:31
 * @notify  解决重用性 RawSqlSource 和 DynamicSqlSource 都需要对  #{} 进行替换 ?
 * @version 1.0
 */

import mapping.DynamicContext;
import sqlsource.impl.StaticSqlSource;
import tokenparser.GenericTokenParser;
import tokenparser.ParameterMappingTokenHandler;

public class SqlSourceParser {
    private DynamicContext dynamicContext = null;

    public SqlSourceParser(DynamicContext context) {
        this.dynamicContext = context;
    }

    public SqlSource parse() throws Exception {
        //将 #{ } 替换成 ? 然后将内容存储到 ParameterMapping 中
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String sql = tokenParser.parse(dynamicContext.getSql());
        return new StaticSqlSource(sql, parameterMappingTokenHandler.getParameterMappings());
    }
}
View Code

SqlSourceParser.parser()方法使用GenericTokenParser工具类的parse(String sql)方法,对字符串中指定开头和结尾中间部分做操作。

package tokenparser;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 14:52
 * @notify 工具类,对sql字符串处理 ${} 和#{}
 * @version 1.0
 */
public class GenericTokenParser {

    private final String openToken;
    private final String closeToken;
    private final TokenHandler handler;

    /*
     * openToken:字符串开始 #{ 或者 ${
     * closeToken 字符串结束 }
     * handler 大括号中间需要处理的方法。
     * */
    public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
        this.openToken = openToken;
        this.closeToken = closeToken;
        this.handler = handler;
    }

    /**
     * 解析${}和#{}
     *
     * @param text
     * @return
     */
    public String parse(String text) throws Exception{
        if (text == null || text.isEmpty()) {
            return "";
        }
        // search open token
        int start = text.indexOf(openToken, 0);
        if (start == -1) {
            return text;
        }
        char[] src = text.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        while (start > -1) {
            if (start > 0 && src[start - 1] == '\') {
                // this open token is escaped. remove the backslash and continue.
                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);
                }
                builder.append(src, offset, start - offset);
                offset = start + openToken.length();
                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.
                        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();
                }
            }
            start = text.indexOf(openToken, offset);
        }
        if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
    }
}
View Code

此类接收三个参数,前两个就是#{   和 } 最后一个参数,对匹配到该规则需要做的操作。是一个接口TokenHandler我们使用在这里我们使用它的实现类ParameterMappingTokenHandler

package tokenparser;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 14:52
 * @notify 对${} 和#{} 中的内容进行处理
 * @version 1.0
 */
public interface TokenHandler {
  String handleToken(String content)throws Exception;
}
View Code
package tokenparser.impl;

import mapping.ParameterMapping;
import tokenparser.TokenHandler;

import java.util.ArrayList;
import java.util.List;

public class ParameterMappingTokenHandler implements TokenHandler {
    private List<ParameterMapping> parameterMappings = new ArrayList<>();

    // content是参数名称
    // content 就是#{}中的内容
    public String handleToken(String content) {
        parameterMappings.add(buildParameterMapping(content));
        return "?";
    }

    //TODO 将参数名封装成 ParameterMapping 此处还要想办法将参数类型设置到 ParameterMapping
    private ParameterMapping buildParameterMapping(String content) {
        ParameterMapping parameterMapping = new ParameterMapping();
        parameterMapping.setName(content);
        return parameterMapping;
    }

    //获取所有 ? 的对象
    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

}
View Code

当匹配到#{id}会将字符串替换成 ?并且id保存到ParameterMapping对象中,因为一个sql可能包含多个#{}所以ParameterMappingTokenHandler内部持有一个list,每一个ParameterMapping则是一个?的真实属性。

package mapping;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 14:54
 * @notify  如果SQL是 ? 占位符的形式,就需要这个对象,这个对象封装的则是 ? 代表的参数。
 * @version 1.0
 */
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;
    }
}
View Code

到此时我们已经完全对sql脚本进行了解析,使其成为一个可被执行的sql语句select * from table where id = ? and name = ? 而且我们还获取两个?代表的真实属性名。

将sql脚本字符串和?代表的属性包装类封装成一个StaticSqlSource。这个类也是SqlSource接口的实现类,但是它仅仅用于返回最终的结果,不包含实际业务逻辑。

package sqlsource.impl;


import mapping.BoundSql;
import mapping.ParameterMapping;
import sqlsource.SqlSource;

import java.util.ArrayList;
import java.util.List;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 14:52
 * @notify RawSqlSource 和 DynamicSqlSource处理后的结构。
 * @version 1.0
 */
public class StaticSqlSource implements SqlSource {

    private String sql;
    private List<ParameterMapping> parameterMappings = new ArrayList<>();

    public StaticSqlSource(String sql, List<ParameterMapping> parameterMappings) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
    }

    @Override
    public BoundSql getBoundSql(Object param)throws Exception {
        return new BoundSql(sql,parameterMappings);
    }

}
View Code

在这里我们也可以清晰的看到,BoundSql其实就是可执行的sql和需要入参的?名称。最终RawSqlSourcegetBoundSql(Object param);使用的则是StaticSqlSourcegetBoundSql(Object param);

下面我们在看DynamicSqlSource这个SqlSource中包含的SqlNode可能是任意类型的SqlNode,所以getBoundSql(Object param)方法中调用MixedSqlNode可能调用的是TextSqlNodeIfSqlNodeStaticTextSqlNode

RawSqlSource的getBoundSql(Object param)方法中,我们使用的DynamicContext传递的构造参数是null,因为我们在apply()操作并不需要对实际参数做处理。但在DynamicSqlSource中我们要使用到sql的实参。

package sqlsource.impl;

import mapping.BoundSql;
import mapping.DynamicContext;
import sqlnode.SqlNode;
import sqlsource.SqlSource;
import sqlsource.SqlSourceParser;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 14:52
 * @notify DynamicSqlSource可能包含 #{} ${} 和<if>标签等。可能包含所有类型的sqlNode
 * @version 1.0
 */
public class DynamicSqlSource implements SqlSource {

    private SqlNode mixedSqlNode;

    public DynamicSqlSource(SqlNode mixedSqlNode) {
        this.mixedSqlNode = mixedSqlNode;
    }

    @Override
    public BoundSql getBoundSql(Object param) throws Exception {
        /*此处需要注意,因为有多个sqlNode,他们单独执行自己的解析过程,但是,如果sql是
         * TextSqlNode 他包含${} 但是也可能包含 #{} 此时保险起见,我们需要再次解析sql将
         * #{} 替换成 ? 并封装#{name} 中的name
         * */
        DynamicContext dynamicContext = new DynamicContext(param);
        mixedSqlNode.apply(dynamicContext);
        //将 #{ } 替换成 ? 然后将内容存储到 ParameterMapping 中
        SqlSourceParser parser = new SqlSourceParser(dynamicContext);
        SqlSource sqlSource = parser.parse();
        return sqlSource.getBoundSql(param);
    }

}
View Code

先来看TextSqlNode这个SqlNode中的apply(DynamicContext context)则会把${}替换成实际值,例如 where id = ${id} 替换成 where id = 1 这个1就是用户传递的入参。

package sqlnode.impl;

/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-04 18:10
 * @notify 纯文本的sql节点,但是包含 ${} 的
 * @version 1.0
 */

import mapping.DynamicContext;
import sqlnode.SqlNode;
import tokenparser.impl.BindingTokenParser;
import tokenparser.GenericTokenParser;

public class TextSqlNode implements SqlNode {

    private String sqlText;

    public TextSqlNode(String sqlText) {
        this.sqlText = sqlText;
    }

    @Override
    public void apply(DynamicContext context)throws Exception {
        //将 ${}去掉,然后将{}中的参数替换成实际的值
        BindingTokenParser bindingTokenParser = new BindingTokenParser(context);
        GenericTokenParser tokenParser = new GenericTokenParser("${", "}", bindingTokenParser);
        String sql = tokenParser.parse(sqlText);
        //添加到 DynamicContext
        context.appendSql(sql);
    }
}
View Code

在这里使用的是BindingTokenParser,来看下BindingTokenParser怎样对${}的内容进行处理。

package tokenparser.impl;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-05 17:23
 * @notify
 * @version 1.0
 */
import mapping.DynamicContext;
import tokenparser.TokenHandler;
import utils.SimpleTypeRegistry;

import java.lang.reflect.Field;

public class BindingTokenParser implements TokenHandler {
    private DynamicContext context;

    public BindingTokenParser(DynamicContext context) {
        this.context = context;
    }

    /**
     * expression:比如说${username},那么expression就是username username也就是Ognl表达式
     */
    public String handleToken(String expression) throws Exception {

        Object paramObject = context.getParam();
        if (paramObject == null) {
            throw new Exception("参数为null不能映射到${}");
        }
        String value = "";
        if (SimpleTypeRegistry.isSimpleType(paramObject.getClass())) {
            value = String.valueOf(paramObject);
        }

        Class<?> aClass = paramObject.getClass();
        Field declaredField = aClass.getDeclaredField(expression);
        declaredField.setAccessible(true);
        Object o = declaredField.get(paramObject);
        value = String.valueOf(o);

        return "'" + value + "'";


    }
}
View Code

首先我们判断DynamicContext中的实参是否为null,如果为null则抛出异常,如果不为null,需要判断入参是否是简单类型,如果是简单类型直接返回值,这个值则会替换掉${id},如果是复杂类型,这里指的是pojo类型,

例如传递一个user对象,需要用到反射获取到user对象中的id属性值,然后将其返回。然后拼接好的sql拼接到DynamicContext中,最终DynamicSqlSource中的全部SqlNode解析完毕会得到一个完整的sql,但是需要注意,

此时得到的sql可能还包含#{name}因为StaticTextSqlNode只是将SqlNode中的文本进行了append()拼接。所以我们需要再次调用SqlSourceParser对象的parse()方法,对#{}进行替换并且生成带有?的sql和ParameterMapping类型

的集合对象。并且组成一个StaticSqlSource

最后IfSqlNode,这个SqlNode包含一个test表达式,和一个SqlNode,因为if标签内可能会存在多个SqlNode。这里就有点绕了。例如

<select> 
SELECT * FROM manage_user WHERE id = #{id}
   <if test="username!=null and username!=''">
   and username = ${username}
    <if test="username!=null and username!=''">
    </if>
</if>
</select>
这个sql脚本,当表达式成立后,这里判断表达式是否成立使用OGNL工具包。他的内部包含的是一个TextSqlNode,和一个IfSqlNode所以此处会出现多种类型SqlNode调用自己的apply
(DynamicContext context),但最终都会将sql拼接到DynamicContext中。
package sqlnode.impl;

/*
* @auther 顶风少年 
* @mail dfsn19970313@foxmail.com
* @date 2020-01-04 18:10
* @notify
   <if test="username!=null and username!=''">
            AND username like '%${username}'
            <if test="password!=null and password!=''">
                AND password = #{password}
            </if>
    </if>
    包含了 test条件还有其子节点,所以内部有SqlNode
* @version 1.0
*/

import mapping.DynamicContext;
import sqlnode.SqlNode;
import utils.OgnlUtils;

public class IfSqlNode implements SqlNode {
    //if语句的表达式
    private String test;

    //if语句成立后的体,可能是一个TextSqlNode,也可能是一个MixedSqlNode
    private SqlNode rootSqlNode;


    public IfSqlNode(String test, SqlNode rootSqlNode) {
        this.test = test;
        this.rootSqlNode = rootSqlNode;
    }

    @Override
    public void apply(DynamicContext context) throws Exception {
        //使用ognl表达式,判断test是否正确
        boolean b = OgnlUtils.evaluateBoolean(test, context.getParam());
        //此时如果是MixedSqlNode那则会执行MixedSqlNode的apply
        if (b) {
            //执行sqlNode自己的apply
            rootSqlNode.apply(context);
        }
    }
}
View Code

第二部分封装jdbc,实现mybatis四大组件

SqlSessionFactoryBuilder接收一个输入流,返回一个SqlSessionFactory工厂。在此处,则用到我们上边解析xml最终生成的Configuration对象

package sqlsession;/*
* @auther 顶风少年 
* @mail dfsn19970313@foxmail.com
* @date 2020-01-08 16:16
* @notify 
* @version 1.0
*/

import builder.XMLConfigBuilder;
import mapping.Configuration;
import org.dom4j.Document;
import org.dom4j.Element;
import sqlsession.impl.DefaultSqlSessionFactory;
import utils.DocumentUtil;

import java.io.InputStream;

public class SqlSessionFactoryBuilder {

    public static SqlSessionFactory build(InputStream inputStream)throws Exception{

        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Document document = DocumentUtil.readDocument(inputStream);
        Element rootElement = document.getRootElement();
        Configuration configuration = xmlConfigBuilder.parse(rootElement);
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;
    }


}
View Code

DefaultSqlSessionFactory接收Configuration创建SqlSession

package sqlsession.impl;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 16:11
 * @notify
 * @version 1.0
 */

import mapping.Configuration;
import sqlsession.SqlSession;
import sqlsession.SqlSessionFactory;

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSqlSession() {
        return new DefaultSqlSession(configuration);
    }
}
View Code

SqlSession中则用到Executor组件,Executor是一个接口。Configuration类会根据我们是否需要缓存帮我们适合的实现类。

package sqlsession.impl;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 16:08
 * @notify
 * @version 1.0
 */

import executor.Executor;
import executor.impl.CachingExecutor;
import mapping.BoundSql;
import mapping.Configuration;
import mapping.MappedStatement;
import sqlsession.SqlSession;
import sqlsource.SqlSource;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <T> T selectOne(String statementId, Object param) throws Exception {
        List<Object> objects = selectList(statementId, param);
        if (objects.size() > 1) {
            throw new Exception("需要获取1个结果,但是获取到了" + objects.size() + "个结果");
        }
        return (T) objects.get(0);
    }

    @Override
    public <T> List<T> selectList(String statementId, Object param) throws Exception {
        Executor executor = configuration.newExecutor(statementId, param);
        Object query = executor.query(statementId, param);
        return (List<T>) query;
    }

    @Override
    public boolean insert(String statementId, Object param) throws Exception {
        Executor executor = configuration.newExecutor(statementId, param);
        return executor.insert();
    }
}
View Code

我们来看下SimpleExecutor实现类。

package executor.impl;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-08 17:22
 * @notify
 * @version 1.0
 */


import handler.StatementHandler;
import mapping.BoundSql;
import mapping.Configuration;
import mapping.MappedStatement;
import sqlsource.SqlSource;

import javax.sql.DataSource;
import java.sql.*;

public class SimpleExecutor extends BaseExecutor {

    private Configuration configuration;
    private String statementId;
    private Object param;
    private MappedStatement mappedStatement;
    private SqlSource sqlSource;
    private BoundSql boundSql;
    private String sql;
    private Connection connection;
    private StatementHandler statementHandler;
    private Statement statement;

    public SimpleExecutor(Configuration configuration, String statementId, Object param) throws Exception {
        this.configuration = configuration;
        this.statementId = statementId;
        this.param = param;
        execute();
    }

    public Object queryFormDataBase(String statementId, Object param) throws Exception {
        return executeJDBC(param);
    }


    private void execute() throws Exception {
        connection = getConnection();
        mappedStatement = configuration.getMappedStatementById(statementId);
        if (mappedStatement == null) {
            return;
        }
        sqlSource = mappedStatement.getSqlSource();
        boundSql = sqlSource.getBoundSql(param);
        sql = boundSql.getSql();

        statementHandler = configuration.newStatementHandler(mappedStatement.getStatementType());

        statement = statementHandler.prepare(connection, sql);

        statementHandler.setParam(configuration, statement, mappedStatement, param);
    }

    //建立连接
    private Connection getConnection() throws Exception {
        DataSource dataSource = configuration.getDataSource();
        Connection connection = dataSource.getConnection();
        return connection;
    }

    //执行sql
    private Object executeJDBC(Object param) throws Exception {
        Object query = statementHandler.query(configuration, mappedStatement, statement);
        connection.close();
        System.out.println("查询");
        return query;
    }


    @Override
    public boolean insert() throws Exception {
        return statementHandler.insert(statement);
    }
}
View Code

SimpleExecutor中出现了第二个组件StatementHandler,这个组件也是一个接口。主要负责创建不同类型的StatementConfiguration根据sql标签中的statementType帮我们创建PreparedStatementHandler或者SimpleStatementHandler。这两个StatementHandler分别对应jdbc中的PreparedStatement和Statement,也就是一个需要预编译,一个不需要预编译的Statement。

StatementHandler中使用了第三个组件ParameterHandlerResultSetHandler分别用来处理参数和返回值。同样这两个组件也有Configuration创建。

package handler.impl;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-10 11:45
 * @notify
 * @version 1.0
 */

import handler.ParameterHandler;
import handler.ResultSetHandler;
import handler.StatementHandler;
import mapping.BoundSql;
import mapping.Configuration;
import mapping.MappedStatement;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;

public class PreparedStatementHandler<E> implements StatementHandler {

    @Override
    public Statement prepare(Connection connection, String sql) throws Exception {
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        return preparedStatement;
    }

    @Override
    public void setParam(Configuration configuration, Statement statement, MappedStatement mappedStatement, Object param) throws Exception {
        ParameterHandler parameterHandler = configuration.newParameterHandler();
        parameterHandler.handleParameter(statement, mappedStatement, param);
    }

    @Override
    public E query(Configuration configuration, MappedStatement mappedStatement, Statement statement) throws Exception {
        PreparedStatement preparedStatement = (PreparedStatement) statement;
        ResultSet resultSet = preparedStatement.executeQuery();
        ResultSetHandler resultSetHandler = configuration.newResultSetHandler();
        return (E) resultSetHandler.handleResultSet(mappedStatement, resultSet);
    }

    @Override
    public boolean insert(Statement statement) throws Exception {
        PreparedStatement preparedStatement = (PreparedStatement) statement;
        return preparedStatement.execute();
    }
}
View Code

ParameterHandler中则需要使用到第一个步骤我们获取的MappedStatement,从中获取SqlSource,在获取BoundSql对象,拿到可执行的sql语句和sql入参的包装对象。使用反射给PreparedStatement设置值。

package handler.impl;/*
 * @auther 顶风少年
 * @mail dfsn19970313@foxmail.com
 * @date 2020-01-10 15:21
 * @notify
 * @version 1.0
 */

import handler.ParameterHandler;
import mapping.Configuration;
import mapping.MappedStatement;
import mapping.ParameterMapping;
import utils.SimpleTypeRegistry;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.List;
import java.util.Map;

public class DefaultParameterHandler implements ParameterHandler {

    @Override
    public void handleParameter(Statement statement,MappedStatement mappedStatement,Object param) throws Exception {
        PreparedStatement preparedStatement = (PreparedStatement) statement;
        //查询入参的类型
        Class<?> parameterTypeClass = mappedStatement.getParameterTypeClass();
        //如果入参类型是简单类型,那肯定就一个 ? 可以直接设置值
        if (SimpleTypeRegistry.isSimpleType(parameterTypeClass)) {
            preparedStatement.setObject(1, param);
        } else if (parameterTypeClass == Map.class) {//map类型
            //TODO
        } else { //pojo类型
            //拿到statement的 ? 的集合
            String sql = mappedStatement.getSqlSource().getBoundSql(param).getSql();
            List<ParameterMapping> parameterMappings = mappedStatement.getSqlSource().getBoundSql(param).getParameterMappings();
            //循环 ?
            for (int i = 0; i < parameterMappings.size(); i++) {
                //拿到每一个 ? 的包装对象
                ParameterMapping parameterMapping = parameterMappings.get(i);
                //获取参数名
                String name = parameterMapping.getName();
                //通过入参的对象的Class,反射拿到属性
                Field field = parameterTypeClass.getDeclaredField(name);
                field.setAccessible(true);
                //拿到实参的属性值
                Object o = field.get(param);
                //TODO 此处可根据 parameterMapping 中的 ? 类型使用具体的 setString 。。。
                //设置到 ? 中
                preparedStatement.setObject(i + 1, o);
            }
        }
    }

}
View Code

ResultSetHandler中需要解析xml拿到的返回值类型。通过反射将ResultSet结果集封装成实际的返回值pojo。

package handler.impl;/*
* @auther 顶风少年 
* @mail dfsn19970313@foxmail.com
* @date 2020-01-10 15:53
* @notify 
* @version 1.0
*/

import handler.ResultSetHandler;
import mapping.MappedStatement;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class DefaultResultSetHandler<E> implements ResultSetHandler {
    @Override
    public List<E> handleResultSet(MappedStatement mappedStatement,ResultSet resultSet)throws Exception{
        //查询返回值类型
        Class<?> resultTypeClass = mappedStatement.getResultTypeClass();
        //创建返回值集合
        List<Object> results = new ArrayList<>();
        //获取一行数据
        while (resultSet.next()) {
            //创建返回值对象
            Object clo = resultTypeClass.newInstance();
            //获取行属性
            ResultSetMetaData metaData = resultSet.getMetaData();
            //获取列字段个数
            int columnCount = metaData.getColumnCount();
            //遍历字段
            for (int i = 0; i < columnCount; i++) {
                //获取列名
                String columnClassName = metaData.getColumnName(i + 1);
                //获取列值
                Object object = resultSet.getObject(columnClassName);
                //通过反射拿到列,此处必须保证类名有对应的属性名
                Field declaredField = resultTypeClass.getDeclaredField(columnClassName);
                declaredField.setAccessible(true);
                //设置属性值
                declaredField.set(clo, object);
            }
            //将每一行添加到集合
            results.add(clo);
        }
        return (List<E>)results;
    }
}
View Code

最后测试代码

import io.Resources;
import org.junit.Test;
import pojo.ManageUser;
import sqlsession.SqlSession;
import sqlsession.SqlSessionFactory;
import sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.Date;
import java.util.List;

public class TestMybatis {

    @Test
    public void t1()throws Exception{
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSqlSession();
        ManageUser manageUser = new ManageUser();
        manageUser.setId(1);
        manageUser.setUsername("rootid");
        List<ManageUser> objects1 = sqlSession.selectList("manageUser.getManageUserById", manageUser);
        List<ManageUser> objects2 = sqlSession.selectList("manageUser.getManageUserById", manageUser);
    }

    @Test
    public void t2()throws Exception{
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSqlSession();
        ManageUser manageUser = new ManageUser();
        manageUser.setId(3);
        manageUser.setPassword("3333");
        manageUser.setUsername("rootid");
        manageUser.setCreate_date(new Date());
        boolean insert = sqlSession.insert("manageUser.insertManage", manageUser);
    }

    @Test
    public void t3()throws Exception{
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSqlSession();
        ManageUser manageUser = new ManageUser();
        manageUser.setId(3);
        manageUser.setUsername("rootid");
        ManageUser o = sqlSession.selectOne("manageUser.getManageUserByUserName", manageUser);
    }
}
View Code
原文地址:https://www.cnblogs.com/zumengjie/p/12180702.html