簡單版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、測試結果 

 

本文作者:JaySpace
原文出處:https://www.cnblogs.com/jayhou/p/12686986.html
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM