了解mybatis源码手写mybatis

Posted tanglinsheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了了解mybatis源码手写mybatis相关的知识,希望对你有一定的参考价值。

一:mybatis概述

  MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

二:手写mybatis

  实现思路::

     1创建SqlSessionFactory实例

      2:实例化过程中,加载配置文件创建configuration对象

      3:通过factory创建SqlSeesion

---------------------初始化阶段完成--------------------------

      4:通过SqlSeesion获取mapper接口动态代理

      5:动态代理回调sqlsession中某查询方法

---------------------代理阶段完成-------------------------- 

      6:SqlSession将查询方法转发给Executor

      7:Executor基于jdbc访问数据库获取数据

      8:Executor通过反射将数据转化成POJP并返回给SqlSession

      9:将数据返回给调用者

 手写源码展示:

     2.1、初始化阶段

     将db.properties,以及*mapper.xml读取到内存中,主要通过SqlSessionFactory实现
技术图片
/**
 * 初始化加载配置信息映射
 * 生成sqlsession
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 12:07
 */
public class SqlSessionFactory {
    public final Configuration  conf = new Configuration();
    public SqlSessionFactory(){
        loadDbInfo();
        loadMappersInfo();//加载
    }
    //mappers.xml文件保存路径
    public static final  String MAPPER_CONFIG_LOCATION="mappers";
    //数据库连接配置文件路径
    public static final String DB_CONFIG_FILE="db.properties";
    private void loadDbInfo(){
       InputStream inputStream =  SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
        Properties p = new Properties();
        try {
            p.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        conf.setJdbcDriver(p.get("jdbc.driver").toString());
        conf.setJdbcUrl(p.get("jdbc.url").toString());
        conf.setJdbcUsername(p.get("jdbc.username").toString());
        conf.setJdbcPassword(p.get("jdbc.password").toString());
    }
    //读取xml并解析成map
    private void loadMappersInfo(){
         URL resources =null;
         resources =  SqlSessionFactory.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
         File mappers =  new File(resources.getFile());
         if(mappers.isDirectory()){
             File[] listFiles = mappers.listFiles();
             for(File file:listFiles){
                 loadMapperInfo(file);
             }
         }
    }
    //加载指定*.xml文件
    private  void loadMapperInfo(File file){
        SAXReader reader = new SAXReader();
        Document document=null;
        try {
            document = reader.read(file);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        Element root = document.getRootElement();
        //获取命名空间
        String namespace = root.attribute("namespace").getData().toString();
        //select子元素
        List<Element> selects = root.elements("select");
        //遍历所有查询语句
        for( Element element:selects){
            MappedStatement mappedStatement = new MappedStatement();
            String id =  element.attribute("id").getData().toString();
            String resultType =  element.attribute("resultType").getData().toString();
            String sql =  element.getData().toString();
            String sourceId = namespace+"."+id; //map的key是命名空间+id
            mappedStatement.setNamespace(namespace);
            mappedStatement.setSourceId(sourceId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            conf.getMappedStatements().put(sourceId,mappedStatement);
        }

    }
    public SqlSession openSession(){
         return new DefuaultSqlSession(this.conf);
    }
}
SqlSessionFactory
技术图片
/**
 * 数据库配置文件映射
 * *mapper.xml映射map
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 12:10
 */
public class Configuration {
    private String jdbcDriver;//数据库驱动
    private String jdbcUrl;//数据库url地址
    private String jdbcUsername;//数据库用户名
    private String jdbcPassword;//数据库密码
    private Map<String, MappedStatement> mappedStatements = new HashMap<>();//mapper.xml解析成对象
    public String getJdbcDriver() {
        return jdbcDriver;
    }

    public void setJdbcDriver(String jdbcDriver) {
        this.jdbcDriver = jdbcDriver;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }

    public String getJdbcUsername() {
        return jdbcUsername;
    }

    public void setJdbcUsername(String jdbcUsername) {
        this.jdbcUsername = jdbcUsername;
    }

    public String getJdbcPassword() {
        return jdbcPassword;
    }

    public void setJdbcPassword(String jdbcPassword) {
        this.jdbcPassword = jdbcPassword;
    }

    public Map<String, MappedStatement> getMappedStatements() {
        return mappedStatements;
    }

    public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
        this.mappedStatements = mappedStatements;
    }
}
Configuration
技术图片
/**
 * mapper.xml映射文件
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 12:13
 */
public class MappedStatement {

    private String namespace;//命名空间
    private String sourceId;//方法id名称
    private String resultType;//返回数据类型
    private String sql;//sql语句

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getSourceId() {
        return sourceId;
    }

    public void setSourceId(String sourceId) {
        this.sourceId = sourceId;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}
MappedStatement

 2.2、代理阶段

主要通过DefuaultSqlSession实现
技术图片
/**
 * 1: 对外提供访问的api
 * 2:对内请求转发给Executor实现
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 14:51
 */
public interface SqlSession {
   <T> T selectOne(String statement,Object parameter) throws Exception;

   <E> List<E> selectList(String statement, Object parameter);

   <T> T getMapper(Class<T> type);
}
SqlSession
技术图片
/**
 * 1: 对外提供访问的api
 * 2:对内请求转发给Executor实现
 * 3: 通过动态代理,面向接口编程
 * @param: mybatis03
 * @description:
 * @author: tangl
 * @create: 2019-05-13 14:51
 */
public class DefuaultSqlSession implements SqlSession {

    private final Configuration conf;

    private Executor executor;

    public DefuaultSqlSession(Configuration conf){
        super();
        this.conf=conf;
        executor = new DefaultExcutor(conf);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) throws Exception {
        MappedStatement  ms = conf.getMappedStatements().get(statement);
        List<Object> selectList =  executor.query(ms,parameter);
        if(null==selectList || selectList.size()==0){
            return null;
        }
        if(selectList.size()==1){
            return (T)selectList.get(0);
        }else{
            throw new Exception();
        }
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        MappedStatement  ms = conf.getMappedStatements().get(statement);
        return executor.query(ms,parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
         return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type},new MapperProxy(this));

    }

    public class MapperProxy implements InvocationHandler{
        private SqlSession session;

        public MapperProxy(SqlSession session){
            super();
            this.session = session;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           Class<?>  returnType = method.getReturnType();
            System.out.println("returnType="+returnType.toString());
           if(Collection.class.isAssignableFrom(returnType)){//返回参数是不是集合
               return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(),null==args?null:args[0]);
           }else{
               return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),null==args?null:args[0]);
           }
        }
    }
}
DefuaultSqlSession

  2.3、数据库操作返回数据并做映射处理阶段

     主要通过Executor实现

技术图片
/**
 * @param: mybatis03
 * @description: 封装对数据库操作,以及对返回数据的封装
 * @author: tangl
 * @create: 2019-05-13 15:25
 */
public interface Executor {
   <E> List<E> query(MappedStatement mappedStatement, Object args);
}
Executor
技术图片
/**
 * @param: mybatis03
 * @description: 封装对数据库操作,以及对返回数据的封装
 * @author: tangl
 * @create: 2019-05-13 15:25
 */
public class DefaultExcutor implements Executor {

    private Configuration conf;

    public DefaultExcutor(Configuration conf){
        super();
        this.conf=conf;
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter) {
        List<E> ret = new ArrayList<>();
        try {
            Class.forName(conf.getJdbcDriver());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Connection connection=null;
        PreparedStatement preparedStatement =null;
        ResultSet resultSet=null;
        try {
            connection =  DriverManager.getConnection(conf.getJdbcUrl(),conf.getJdbcUsername(),conf.getJdbcPassword());
            preparedStatement = connection.prepareStatement(ms.getSql());
            //处理sql语句中的占位符
            parameterize(preparedStatement,parameter);
            resultSet = preparedStatement.executeQuery();
            //通过发射技术,填充到list
            handlerResultSet(resultSet,ret,ms.getResultType());
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                resultSet.close();
                preparedStatement.close();
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
       return ret;

    }

    public void parameterize(PreparedStatement preparedStatement,Object parameter) throws SQLException {
          if(parameter instanceof String){
              preparedStatement.setString(1,(String)parameter);
          }else if(parameter instanceof Long){
              preparedStatement.setLong(1,(Long)parameter);
          }else if(parameter instanceof Integer){
              preparedStatement.setInt(1,(int)parameter);
          }
    }

    public <E> void handlerResultSet(ResultSet resultSet,List<E> ret,String className){
        Class<E> clazz = null;
        try {
            clazz = (Class<E>)Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            while(resultSet.next()){
                //通过反射实例化对象
                Object entity  =  clazz.newInstance();
                //使用反射工具将resultSet中的数据填充到entity中
                //id,name,sex,age
//                Integer id  =  resultSet.getInt(1);
//                String  name  =  resultSet.getString(2);
//                String  sex  =  resultSet.getString(3);
//                Integer age  =  resultSet.getInt(4);
//
//                Integer id02 =  resultSet.getInt("id");
//                String name02 =  resultSet.getString("name");
//                String sex02 =  resultSet.getString("id");
//                Integer age02  =  resultSet.getInt("age");
              //  ReflectionUtils.
                // 获取实体类的所有属性,返回Field数组
                Field[] fields = clazz.getDeclaredFields();
                for(Field field:fields){
                     field.setAccessible(true);
                     String fname  =  field.getName();
                     Type  type = field.getGenericType();
                     System.out.println("fname="+fname+" type="+type.toString());
                     if(type.toString().equals("class java.lang.String")){
                         String column = resultSet.getString(fname);
                         field.set(entity,column);
                     }else if(type.toString().equals("class java.lang.Integer")){
                         Integer column = resultSet.getInt(fname);
                         field.set(entity,column);
                     }
                }
                ret.add((E)entity);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
DefaultExcutor

三:测试

技术图片
/**
 * @param: mybatis03
 * @description:手写mybatis测试
 * @author: tangl
 * @create: 2019-05-13 14:57
 */
public class TestMybatis {
    public static void main(String[] args) throws ClassNotFoundException {
       //第一阶段,加载配置信息放入内存
        SqlSessionFactory factory = new SqlSessionFactory();
        SqlSession sqlSession = factory.openSession();
        System.out.println(sqlSession);
        //第二阶段 封装ibatis编程模式,使用mapper接口开发的初始化工作
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
        //第三阶段
        TUser tUser = mapper.getUserById(1);
        System.out.println(tUser);
        List<TUser> allList  = mapper.getUserAllList();
        if(null!=allList && allList.size()>0){
            for (TUser tUser02:allList){
                System.out.println(tUser02);
            }
        }
    }
}
View Code

 

四:项目补充

    1:pom.xml文件

       <dependencies>

  <!-- 解析xml文件-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
</dependencies>

2:db.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&verifyServerCertificate=false&useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

   五:项目说明

     这仅仅是参考源码完成mybatis的核心功能,mybatis还有一些其它辅助功能,如:数据库连接池等。

以上是关于了解mybatis源码手写mybatis的主要内容,如果未能解决你的问题,请参考以下文章

手写一个Mybatis框架

自己手写一个Mybatis框架(简化)

自己实现一个简化版Mybatis框架

大厂程序员,手写Mybatis

手写一个 MyBatis 框架,太强了!

简单实现自定义持久层框架,手写MyBatis实现基础功能