• 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
  • 相关阅读:
    类的再理解
    关于网络配置和zmp以及json
    PCL 库安装
    Ros学习注意点
    Ros集成开发环境配置
    《TCP/IP详解卷1:协议》第3章 IP:网际协议(1)-读书笔记
    《Effective C++》第1章 让自己习惯C++-读书笔记
    《TCP/IP详解卷1:协议》第2章 链路层-读书笔记
    《TCP/IP详解卷1:协议》第1章 概述-读书笔记
    C++内存分配与对象构造的分离
  • 原文地址:https://www.cnblogs.com/zumengjie/p/12180702.html
Copyright © 2020-2023  润新知