简单版Mybatis框架的实现

一、基本概述

最近心血来潮,想加深学习框架实现的思想,所以抽空研究研究Mybatis,Mybatis的核心工作流程实现其实并不是很难。核心都是对反射,动态代理、设计模式以及JDBC的运用。

当然想要很完善的实现,包括各种特性、各种严谨的代码规范,那可能需要花费更多时间去研究源码。这里更多的是通过手写简单版本的mybatis加深理解mybatis的核心工作流程,核心原理。

二、Mybatis的核心架构流程

上图是mybatis的核心工作流程,通过理解其工作流程后,对于自己去实现就会有了一个很好的思路。

mybatis的核心工作流程就是(分为mybatis读取配置的初始阶段和执行jdbc操作的运行阶段):

初始阶段

1、通过SqlSessionFactoryBuilder加载配置,包括了核心配置文件,里面保存与mybatis运行环境有关的全局配置(JDBC配置,Mapper文件位置等)

和Mapper映射文件,里面都是CRUD标签的内容。加载的时候通过相应的解析器,最终将所有的内容以面向对象的思想封装成对象,

最终保存到Configuration对象中。最后生成SqlSessionFactory对象,session工厂对象。

2、另外对于<select/insert/update/delete>的标签解析都会生成MappedStatement对象,里面都封装每条SQL的参数类型,结果类型,statement类型,sql文本等

执行阶段

1、每次进行JDBC操作的时候,会通过SqlsessionFactory对象生成一个SqlSession接口的对象,作为session访问处理。真正的CRUD操作,

它会交给Executor接口的实现类去真正实现。

2、Executor也会将相应的操作交给具体handler去处理,包括statementhandler、parameterhandler、resultsethandler处理。

当然里面无非就是对于jdbc的statement处理输入参数的映射,最后对结果集的映射封装结果而已。

所以在这里也看得出来mybatis算是半ORM框架,具体体现在输入参数和输出结果集的映射处理。

三、实现细节

上面的是具体实现的工程目录,里面实现也是基于执行流程的思路去实现的。

3.1、pom.xml依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.hjj.mybatis</groupId>
  <artifactId>simplemybatis</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
      <dependency>
          <groupId>dom4j</groupId>
          <artifactId>dom4j</artifactId>
          <version>1.6</version>
      </dependency>
      <dependency>
          <groupId>commons-dbcp</groupId>
          <artifactId>commons-dbcp</artifactId>
          <version>1.4</version>
      </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.20</version>
    </dependency>
  </dependencies>
  
  <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2、SqlSessionFactoryBuilder,主要就是传入主配置文件的流对象,通过相应解析器解析得到Configuration对象,封装所有的配置文件信息

public class SqlSessionFactoryBuilder {
    
    public SqlSessionFactory build(InputStream inputStream) {
        //解析配置,封装Configuration对象,
        XmlConfigBuilder parser = new XmlConfigBuilder(inputStream);
        return build(parser.parse());
    }
    
    
    public SqlSessionFactory build(Reader reader) {
        //TO DO
        return null;
    }
    
    public SqlSessionFactory build(Configuration configuration) {
        return new DefaultSqlSessionFactory(configuration);
    }
    
    
}

3.3、DefaultSqlSessionFactory,主要就是通过它获取Session会话对象

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
    private Configuration configuration;
    
    public DefaultSqlSessionFactory(Configuration configuration) {
        super();
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        // TODO Auto-generated method stub
        return new DefaultSqlSession(configuration, null);
    }

}

3.4、DefaultSqlSession,主要是封装JDBC操作,内部通过Executor接口的具体对象真正执行

public class DefaultSqlSession implements SqlSession {
    
    private Configuration configuration;
    private Executor executor;
    
    public DefaultSqlSession(Configuration configuration, String executorType) {
        this.configuration = configuration;
        
        if(executorType == null) {
            this.executor = new SimpleExecutor();            
        }else {
            ////
        }
    }

    @Override
    public <T> T selectOne(String statementId, Object params) throws SQLException {
        // TODO Auto-generated method stub
        List<T> list = this.selectList(statementId, params);
        
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new RuntimeException(
                    "通过selectOne()方法,期待返回一个结果, 但实际返回结果数为: " + list.size());
        } else {
            return null;
        }
    }

    @Override
    public <T> List<T> selectList(String statementId, Object params) throws SQLException {
        // TODO Auto-generated method stub
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return executor.selectList(configuration, mappedStatement, params);
    }

}

3.5、XmlConfigBuilder,主要是用于解析mybatis的主配置文件,在这里使用dom4j对xml文件进行解析。以及主要解析封装数据源对象

public class XmlConfigBuilder {
    
    private InputStream inputStream;
    private Configuration configuration;

    public XmlConfigBuilder(InputStream inputStream) {
        super();
        this.inputStream = inputStream;
        this.configuration = new Configuration();
    }
    
    public Configuration parse(InputStream inputStream) {
        Document document = DocumentReader.createDocument(inputStream);
        parseConfiguration(document.getRootElement());
        
        return configuration;
    }

    public Configuration parse() {
        return parse(inputStream);
    }
    
    private void parseConfiguration(Element element) {
        // TODO Auto-generated method stub
        //解析<environments>
        parseEnvironments(element.element("environments"));
        //解析<mappers>
        parseMappers(element.element("mappers"));
    }

    private void parseEnvironments(Element element) {
        // TODO Auto-generated method stub
        String attr = element.attributeValue("default");
        List<Element> elements = element.elements("environment");
        
        if(attr != null) {
            for (Element ele : elements) {
                String eleId = ele.attributeValue("id");
                if(eleId != null && eleId.equals(attr)) {
                    parseDataSource(ele.element("dataSource"));
                    
                    break;
                }
            }
        }else {
            throw new RuntimeException("environments标签的default属性不能为空");
        }

        
    }

    private void parseDataSource(Element element) {
        // TODO Auto-generated method stub
        String type = element.attributeValue("type");
        if(type == null || type.trim().equals("")) 
            type = "DBCP";
        
        Properties prop = new Properties();
        /*    for (Element ele : element.elements("property")) {
            
        }*/
        List<Element> elements = element.elements("property");
        for (Element ele : elements) {
            String name = ele.attributeValue("name");
            String value = ele.attributeValue("value");
            prop.setProperty(name, value);
        }
        
        //封装数据源对象
        BasicDataSource dataSource = null;
        if (type.equals("DBCP")) {
            dataSource = new BasicDataSource();
            dataSource.setDriverClassName(prop.getProperty("driver"));
            dataSource.setUrl(prop.getProperty("url"));
            dataSource.setUsername(prop.getProperty("username"));
            dataSource.setPassword(prop.getProperty("password"));
        }
        
        configuration.setDataSource(dataSource);
    }
    

    private void parseMappers(Element element) {
        // TODO Auto-generated method stub
        List<Element> elements = element.elements("mapper");
        for(Element ele : elements) {
            parseMapper(ele);
        }
        
    }

    private void parseMapper(Element element) {
        String resource = element.attributeValue("resource");
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource);
        XmlMapperBuilder parser = new XmlMapperBuilder(inputStream, configuration);
        parser.parse();
    }
}

3.6、XmlMapperBuilder,主要是解析所有的Mapper配置文件,封装所以SQL语句标签到MappedStatement对象中

public class XmlMapperBuilder {
    private InputStream inputStream;
    private Configuration configuration;
    private String namespace;

    public XmlMapperBuilder(InputStream inputStream, Configuration configuration) {
        // TODO Auto-generated constructor stub
        this.inputStream = inputStream;
        this.configuration = configuration;
    }

    public void parse() {
        // TODO Auto-generated method stub
        Document document = DocumentReader.createDocument(inputStream);
        Element element = document.getRootElement();
        this.namespace = element.attributeValue("namespace");
        
        if(this.namespace == null || this.namespace.equals("")) {
            throw new RuntimeException("Mapper的namespace值不能为空");
        }
        
        //解析所有select标签
        parseSelectStatements(element.elements("select"));
        //解析所有insert标签
        parseInsertStatements(element.elements("insert"));
    }

    private void parseSelectStatements(List<Element> elements) {
        
        for (Element element : elements) {
            parseSelectStatement(element);
        }
        
    }
    
    private void parseSelectStatement(Element element) {
        // TODO Auto-generated method stub
        String id = this.namespace + "." + element.attributeValue("id");
        Class<?> parameterTypeClass = ClassUtil.getClazz(element.attributeValue("parameterType"));
        Class<?> resultTypeClass = ClassUtil.getClazz(element.attributeValue("resultType"));
        String statementType = element.attributeValue("statementType");
        SqlSource sqlSource = new SqlSource(element.getTextTrim());
        
        MappedStatementBuilder msBuilder = new MappedStatementBuilder();
        MappedStatement mappedStatement = msBuilder.id(id)
                                                   .parameterTypeClass(parameterTypeClass)
                                                   .resultTypeClass(resultTypeClass)
                                                   .statementType(statementType)
                                                   .sqlSource(sqlSource)
                                                   .builder();
        
        configuration.addMappedStatement(id, mappedStatement);
        
    }

    private void parseInsertStatements(List<Element> elements) {
        // TODO Auto-generated method stub
    }

    
    

}

3.7、Configuration,主要是所有配置信息最终保存到这个类

public class Configuration {
    
    private BasicDataSource dataSource;
    
    private Map<String, MappedStatement> mappedStatementMap = new HashMap<>(); 

    public BasicDataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(BasicDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void addMappedStatement(String statementId, MappedStatement mappedStatement) {
        this.mappedStatementMap.put(statementId, mappedStatement);
    }
    
}

3.8、MappedStatement,主要封装所有解析后SQL的CRUD标签内容

public class MappedStatement {
    
    private String id;
    private Class<?> parameterTypeClass;
    private Class<?> resultTypeClass;
    private String statementType;
    private SqlSource sqlSource;
    
    public MappedStatement() {
        
    }
    public MappedStatement(String id, Class<?> parameterTypeClass, Class<?> resultTypeClass, String statementType,
            SqlSource sqlSource) {
        super();
        this.id = id;
        this.parameterTypeClass = parameterTypeClass;
        this.resultTypeClass = resultTypeClass;
        this.statementType = statementType;
        this.sqlSource = sqlSource;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public Class<?> getParameterTypeClass() {
        return parameterTypeClass;
    }
    public void setParameterTypeClass(Class<?> parameterTypeClass) {
        this.parameterTypeClass = parameterTypeClass;
    }
    public Class<?> getResultTypeClass() {
        return resultTypeClass;
    }
    public void setResultTypeClass(Class<?> resultTypeClass) {
        this.resultTypeClass = resultTypeClass;
    }
    public String getStatementType() {
        return statementType;
    }
    public void setStatementType(String statementType) {
        this.statementType = statementType;
    }
    public SqlSource getSqlSource() {
        return sqlSource;
    }
    public void setSqlSource(SqlSource sqlSource) {
        this.sqlSource = sqlSource;
    }
}

3.9、MappedStatementBuilder,主要是MapperStatement的构造者对象,用于定制MappedStatement对象

ublic class MappedStatementBuilder {
    
    private MappedStatement mappedStatement = new MappedStatement();
    
    public MappedStatementBuilder id(String id) {
        mappedStatement.setId(id);
        return this;
    }
    
    public MappedStatementBuilder parameterTypeClass(Class<?> parameterTypeClass) {
        mappedStatement.setParameterTypeClass(parameterTypeClass);
        return this;
    }
    
    public MappedStatementBuilder resultTypeClass(Class<?> resultTypeClass) {
        mappedStatement.setResultTypeClass(resultTypeClass);
        return this;
    }
    
    public MappedStatementBuilder statementType(String statementType) {
        mappedStatement.setStatementType(statementType);
        return this;
    }
    
    public MappedStatementBuilder sqlSource(SqlSource sqlSource) {
        mappedStatement.setSqlSource(sqlSource);
        return this;
    }
    
    public MappedStatement builder() {
        return mappedStatement;
    }
}

3.10、SqlSource,主要是封装原来的sql文本,通过这个类可以得到BoundSql对象,该对象封装最终执行的SQL语句以及参数名称列表(便于通过反射实现参数映射)

public class SqlSource {
    
    private String sqlText;
    
    public SqlSource(String sqlText) {
        super();
        this.sqlText = sqlText;
    }

    public String getSqlText() {
        return sqlText;
    }

    public void setSqlText(String sqlText) {
        this.sqlText = sqlText;
    }
    
    
    public BoundSql getBoundSql() {
        //这是从mybatis源码中直接获得的工具类,用于解析sql获得原始的sql语句
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
        //封装原始的sql以及参数列表名称
        return new BoundSql(parser.parse(sqlText), tokenHandler.getParameterMappings());
    }
    

}

3.11、BoundSql,主要封装用于执行SQL和参数名称列表

public class BoundSql {
    
    private String originalSql;
    
    private List<ParameterMapping> parameterMappings = new ArrayList<>();

    public BoundSql(String originalSql, List<ParameterMapping> parameterMappings) {
        super();
        this.originalSql = originalSql;
        this.parameterMappings = parameterMappings;
    }

    public String getOriginalSql() {
        return originalSql;
    }

    public void setOriginalSql(String originalSql) {
        this.originalSql = originalSql;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public void addParameterMapping(ParameterMapping parameterMapping) {
        this.parameterMappings.add(parameterMapping);
    }
}

3.12、SimpleExecutor,主要是Executor的一个实现类,里面封装基于JDBC的底层操作,以及输入参数和输出结果的映射操作

public class SimpleExecutor implements Executor {

    @Override
    public <T> List<T> selectList(Configuration configuration, MappedStatement mappedStatement, Object params) throws SQLException {
        // TODO Auto-generated method stub
        Connection connection = getConnection(configuration);
        
        return handleStatementSelect(connection, mappedStatement, params);
    }
    
    private Connection getConnection(Configuration configuration) throws SQLException {
        if(configuration == null || configuration.getDataSource() == null) {
            throw new RuntimeException("Configuration对象或者DataSource对象不能为null");
        }
        
        return configuration.getDataSource().getConnection();
    }
    
    private <E> List<E> handleStatementSelect(Connection connection, MappedStatement mappedStatement, Object params) throws SQLException {
        String statementType = mappedStatement.getStatementType();
        List<Object> result = null;
        //不同statementType会有不同操作
        if("prepared".equals(statementType)) {
            result = handlePreparedStatementSelect(connection, mappedStatement, params);
        }else {
            //other ....
        }
        
        return (List<E>) result;
    }
    
    /**
     * JDBC操作
     * @param connection
     * @param mappedStatement
     * @param params
     * @throws SQLException
     */
    private <E> List<E> handlePreparedStatementSelect(Connection connection, MappedStatement mappedStatement, Object params) throws SQLException {
        // TODO Auto-generated method stub
        List<Object> result = new ArrayList<Object>();
        
        try {
            BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql();
            String sql = boundSql.getOriginalSql();
            
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            //获得参数类型
            Class<?> parameterTypeClass = mappedStatement.getParameterTypeClass();
            //获得参数名称
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            
            if("BASIC".equals(getParamenteTypeString(parameterTypeClass))) {
                preparedStatement.setObject(1, params);
            }else {
                //Object
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappings.get(i);
                    String name = parameterMapping.getName();
                    
                    Object fieldValue = ClassUtil.getPrivateFieldValue(parameterTypeClass, name, params);
                    preparedStatement.setObject(i+1, fieldValue);
                }
                
                
                Class<?> resultTypeClass = mappedStatement.getResultTypeClass();
                ResultSet resultSet = preparedStatement.executeQuery();
                
                while(resultSet.next()) {
                    //遍历封装Result
                    Object newInstance = resultTypeClass.newInstance();
                    int columnCount = resultSet.getMetaData().getColumnCount();
                    for(int i = 1; i <= columnCount; i++) {
                        String columnName = resultSet.getMetaData().getColumnName(i);
                        Field field = resultTypeClass.getDeclaredField(columnName);
                        field.setAccessible(true);
                        field.set(newInstance, resultSet.getObject(columnName));
                    }
                    
                    result.add(newInstance);
                }
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
        
        return (List<E>) result;
    }
    
    private String getParamenteTypeString(Class<?> parameterTypeClass) {
        //八种基本类型以及String类型
        if(parameterTypeClass == Integer.class || parameterTypeClass == Double.class || parameterTypeClass == String.class) {
            
            return "BASIC"; 
        }else {
            //.....Map和List以及Object的处理,这里先处理Object,有待扩展
            return "OBJECT";
        }
    }
    
    
    

}

 

四、测试代码

4.1、sqlConfig.xml, 主配置文件

<configuration>
    <environments default="dev">
        <environment id="dev">
            <dataSource type="DBCP">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://ali_server01:3306/mybatis_study"></property>
                <property name="username" value="root"></property>
                <property name="password" value="root"></property>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>
</configuration>

4.2、UserMapper.xml

<mapper namespace="basic">
    <select id="selectOne" parameterType="com.hjj.mybatis.framework.pojo.User"
        resultType="com.hjj.mybatis.framework.pojo.User" statementType="prepared">
        SELECT * FROM user WHERE id = #{id} 
    </select>
</mapper>

4.3、UserDaoImol,封装CRUD

public class UserDaoImpl implements UserDao {
    
    private SqlSessionFactory sqlSessionFactory;
    
    public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
        super();
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public User selectOne(User user) {
        // TODO Auto-generated method stub
        try {
            return sqlSessionFactory.openSession().selectOne("basic.selectOne", user);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return null;
    }
}

4.4、测试

public class SimpleTest {
    
    private SqlSessionFactory sqlSessionFactory;
    
    @Before
    public void init() {
        String xmlConfig = "sqlConfig.xml";
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(xmlConfig);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    }
    
    @Test
    public void testSelectOne() {
        User param = new User();
        param.setId(1);
        
        User user = new UserDaoImpl(sqlSessionFactory).selectOne(param);
        System.out.println("user: " + user.toString());
    }
}

4.5、测试结果 

猜你喜欢

转载自www.cnblogs.com/jayhou/p/12686986.html