标签搜索

目 录CONTENT

文章目录

手写框架之Mybatis

陈铭
2021-03-27 / 0 评论 / 2 点赞 / 242 阅读 / 3,295 字 / 正在检测是否收录...

执行流程

示意图

如图所示,Mybatis在执行sql前都会创建一个SqlSessionFactory,在执行sql时候会创建一个SqlSession,去执行sql。

首先我们先看看SqlSessionFactoryBuilder的执行流程。在接受一个输入流,也就是mybatis-config.xml的输入流,会让XMLConfigBuilder对象去解析这个xml。其中解析最关键的部分就是解析数据库的相关信息(environment标签)和mapper.xml的位置(mappers标签)。

在解析environment标签时,会将数据库的driver、url、username、password封装进Environment对象,再将Environment封装进Configuration。注意哦,这里封装Environment并不会创建数据库连接,所以driver、url、username、password任何一个参数有误在这里都不会报错;而Configuration对象相当于一个全局的配置类,封装了数据库、sql等一系列信息,具体属性见下文。

到了解析mapper.xml,会将扫描到的mapper接口进行注册,存进Configuration中的MapperRegistry对象,MapperRegistry保存了接口的类和接口方法与sql映射关系;接着会继续扫描mapper.xml,将parameterType存进Configuration中的parameterMap,将resultType存进Configuration中的resultMap,resultMap中就存放了sql执行结果与返回类的属性对应关系。最后sql语句会封装成MappedStatement对象,同样也是封装进Configuration。以上就完成了基本的Configuration配置,把Configuration封装进SqlSessionFactory,返回工厂类。

在执行sql时,一般都会用工厂类openSession(),获得SqlSession。SqlSession会根据传入的接口类,获得mapper接口的代理对象。这一步就是日常开发@Autowire一个mapper接口所执行的逻辑。这个接口在执行某个具体方法,该方法和sql的映射关系在上述流程已经完成,因此会执行到某个sql,再根据Configuration封装的Environment、parameterMap和resultMap,创建数据库连接,预编译sql,执行sql,封装成返回类。值得注意的是,只有执行sql时候才会创建connection,也就是说,driver、url、username、password的错误,在这步才会抛错。

具体代码见下文的Main方法。
image

关键类

SqlSessionFactory

这是个工厂类,用来创建SqlSession,里面封装了Configuration

public class SqlSessionFactory {
    private Configuration configuration;

    public SqlSessionFactory(Configuration config) {
        this.configuration=config;
    }

    public SqlSession openSession(){
        return new SqlSession(new SimpleExecutor(),configuration);
    };
}

XMLConfigBuilder

用来解析mybatis-config.xml,将对应信息封装进Configuration

public class XMLConfigBuilder {
    private InputStream inputStream;

    public XMLConfigBuilder(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    public Configuration parse() {


        SAXReader reader = new SAXReader();
        Configuration configuration = null;
        try {
            Document document = reader.read(inputStream);
            Element rootElement = document.getRootElement();
            //原来的mybatis是执行parseConfiguration方法后,在XMLConfigBuilder的父类属性Configuration进行赋值
            //这里简化书写,直接返回Configuration
            configuration = this.parseConfiguration(rootElement);
        } catch (DocumentException e) {
            e.printStackTrace();
        }

        return configuration;
    }

    private Configuration parseConfiguration(Element rootElement) {
        //简化书写,就解析出mapper、environment标签,源码其实还解析了xml中的很多其他东西
        Configuration configuration = new Configuration();
        Element dataSource = rootElement.element("environments").element("environment").element("dataSource");
        Element mapper = rootElement.element("mappers").element("mapper");
        this.environmentsElement(configuration, dataSource);
        this.mapperElement(configuration, mapper);
        return configuration;
    }

    private void environmentsElement(Configuration configuration, Element dataSource) {
        Iterator iterator = dataSource.elementIterator();
        DataSource dataSource1 = new DataSource();

        while (iterator.hasNext()) {
            Element element = (Element) iterator.next();
            String name = element.attribute("name").getValue();
            String value = element.attribute("value").getValue();
            if (name.equals("driver")) {
                dataSource1.setDriver(value);
            } else if (name.equals("url")) {
                dataSource1.setUrl(value);
            } else if (name.equals("username")) {
                dataSource1.setUsername(value);
            } else if (name.equals("password")) {
                dataSource1.setPassword(value);
            }
        }
        configuration.setEnvironment(new Environment(dataSource1));
    }

    private void mapperElement(Configuration configuration, Element mapper) {
        //解析mapper.xml,源码中主要是调用了XMLMapperBuilder的parse方法进一步执行业务,这里做了简化
        try {
            Attribute resource = mapper.attribute("resource");
            String resourceValue = resource.getValue();
            InputStream inputStream = ClassLoader.getSystemResourceAsStream(resourceValue);
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();
            //注册扫描到的mapper接口
            String namespace = rootElement.attribute("namespace").getValue();
            configuration.setMapperRegistry(new MapperRegistry());
            Class<?> mapperClass = null;
            try {
                mapperClass = Class.forName(namespace);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            MapperProxyFactory<?> mapperProxyFactory = new MapperProxyFactory<>(mapperClass);
            //接口方法与sql对应还没注册,在后面解析会进行。
            configuration.getMapperRegistry().getKnownMappers().put(mapperClass, mapperProxyFactory);


            configuration.setParameterMaps(new HashMap<String, ParameterMap>());
            configuration.setResultMaps(new HashMap<String, ResultMap>());
            configuration.setMappedStatements(new HashMap<String, MappedStatement>());
            //解析mapper.xml的解析器
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder();
            Iterator iterator = rootElement.elementIterator();
            while (iterator.hasNext()) {
                Element element = (Element) iterator.next();
                String sqlType = element.getQualifiedName();
                String id = element.attribute("id").getValue();
                String resultType = element.attribute("resultType").getValue();
                String parameterType = element.attribute("parameterType").getValue();
                String sql = element.getTextTrim();

                //注册接口方法和sql的对应关系
                for (Method method : mapperClass.getDeclaredMethods()) {
                    if (method != null && method.getName().equals(id)) {
                        MapperProxyFactory<?> mapperProxyFactory1 = configuration.getMapperRegistry().getKnownMappers().get(mapperClass);
                        mapperProxyFactory1.getMethodCache().put(method,new MapperMethod(namespace+"."+id));
                    }
                }

                //注册该sql的参数
                xmlMapperBuilder.parameterMapElement(id, parameterType, configuration);
                //注册该sql的返回封装类
                xmlMapperBuilder.resultMapElement(id, resultType, configuration);
                //注册该sql
                xmlMapperBuilder.sqlElement(namespace, id, sql, configuration);

            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
}

XMLMapperBuilder

XMLConfigBuilder扫描到mapper.xml的位置,读取mapper.xml,并由XMLMapperBuilder去解析

//mapper.xml的解析器
public class XMLMapperBuilder {


    public void sqlElement(String namespace,String id, String sql, Configuration configuration){
        ResultMap resultMap = configuration.getResultMaps().get(id);
        ParameterMap parameterMap = configuration.getParameterMaps().get(id);
        MappedStatement mappedStatement = new MappedStatement(parameterMap, resultMap,sql);
        configuration.getStatementMap().put(namespace+"."+id,mappedStatement);
    };

    public void parameterMapElement(String id, String parameterType, Configuration configuration) {
        ParameterMap parameterMap = null;
        try {
            parameterMap = new ParameterMap(id, Class.forName(parameterType));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        configuration.getParameterMaps().put(id,parameterMap);
    }

    public void resultMapElement(String id, String resultType, Configuration configuration) {
        Class<?> resultClass = null;
        try {
            resultClass = Class.forName(resultType);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        List<ResultMapping> propertyResultMappings = new ArrayList<ResultMapping>();
        for (Field field : resultClass.getDeclaredFields()) {
            String name = field.getName();
            propertyResultMappings.add(new ResultMapping(name,name));
        }

        ResultMap resultMap = new ResultMap(id,resultClass,propertyResultMappings);

        configuration.getResultMaps().put(id,resultMap);
    }
}

Configuration

全局配置类,封装了sql,返回类,属性映射关系等信息

//为了后续传参,属性全都静态处理
public class Configuration {
    //封装数据源的环境
    private Environment environment;
    //返回结果映射
    private Map<String, ResultMap> resultMaps;
    //参数映射
    private Map<String, ParameterMap> parameterMaps;
    //Mapper.xml存的sql  key=namespace+id
    private Map<String, MappedStatement> mappedStatements;
    //mapper接口
    private MapperRegistry mapperRegistry;

    public Map getStatementMap() {
        return mappedStatements;
    }

    public MappedStatement getMappedStatement(String statement) {
        if (mappedStatements.get(statement) == null) {
            throw new RuntimeException("mapper.xml文件找不到对应sql语句");
        }
        return mappedStatements.get(statement);
    }

    public Environment getEnvironment() {
        return environment;
    }

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public Map<String, ResultMap> getResultMaps() {
        return resultMaps;
    }

    public void setResultMaps(Map<String, ResultMap> resultMaps) {
        this.resultMaps = resultMaps;
    }

    public Map<String, ParameterMap> getParameterMaps() {
        return parameterMaps;
    }

    public void setParameterMaps(Map<String, ParameterMap> parameterMaps) {
        this.parameterMaps = parameterMaps;
    }

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

    public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
        this.mappedStatements = mappedStatements;
    }

    public MapperRegistry getMapperRegistry() {
        return mapperRegistry;
    }

    public void setMapperRegistry(MapperRegistry mapperRegistry) {
        this.mapperRegistry = mapperRegistry;
    }

    public <T> T getMapper(Class<MyInfoMapper> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
}

Environment

封装数据库信息

public class Environment {
    private DataSource dataSource;

    public Environment(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

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

public class DataSource {
    private String driver;
    private String url;
    private String username;
    private String password;

    public DataSource() {
    }

    public DataSource(String driver, String url, String username, String password) {
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

ResultMap

封装返回类的信息,其中有数据库字段与类属性的映射关系

/sql结果封装类的属性映射
public class ResultMap {
    private String id;
    private Class<?> type;
    private List<ResultMapping> propertyResultMappings;


    public ResultMap() {
    }

    public ResultMap(String id, Class<?> type, List<ResultMapping> propertyResultMappings) {
        this.id = id;
        this.type = type;
        this.propertyResultMappings = propertyResultMappings;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }

    public List<ResultMapping> getPropertyResultMappings() {
        return propertyResultMappings;
    }

    public void setPropertyResultMappings(List<ResultMapping> propertyResultMappings) {
        this.propertyResultMappings = propertyResultMappings;
    }
}


public class ResultMapping {
    private String property;
    private String column;

    public ResultMapping(String property, String column) {
        this.property = property;
        this.column = column;
    }

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    public String getColumn() {
        return column;
    }

    public void setColumn(String column) {
        this.column = column;
    }
}

ParameterMap

sql参数的对应java类

public class ParameterMap {
    private String id;
    private Class<?> type;

    public ParameterMap(String id, Class<?> type) {
        this.id = id;
        this.type = type;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }
}

MappedStatement

sql的封装类,会包含上述result和parameter信息

public class MappedStatement {
    private ParameterMap parameterMap;
    private ResultMap resultMaps;
    private String sql;

    public MappedStatement(ParameterMap parameterMap, ResultMap resultMaps, String sql) {
        this.parameterMap = parameterMap;
        this.resultMaps = resultMaps;
        this.sql = sql;
    }

    public ParameterMap getParameterMap() {
        return parameterMap;
    }

    public void setParameterMap(ParameterMap parameterMap) {
        this.parameterMap = parameterMap;
    }

    public ResultMap getResultMaps() {
        return resultMaps;
    }

    public void setResultMaps(ResultMap resultMaps) {
        this.resultMaps = resultMaps;
    }

    public String getSql() {
        return sql;
    }

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

MapperRegistry

对mapper接口进行注册

public class MapperRegistry {
    //源码中是个map,存放扫描到的mapper接口类
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public Map<Class<?>, MapperProxyFactory<?>> getKnownMappers() {
        return knownMappers;
    }

    public <T> T getMapper(Class<MyInfoMapper> type, SqlSession sqlSession) {

        if (!knownMappers.containsKey(type)){
            throw new RuntimeException("该mapper接口未被注册");
        }
        MapperProxyFactory<?> mapperProxyFactory = knownMappers.get(type);

        return (T) mapperProxyFactory.newInstance(sqlSession);


    }
}

MapperProxyFactory

MapperRegistry还封装了MapperProxyFactory,该类是对接口进行代理时创建代理对象的具体逻辑。其中methodCache就封装了sql和接口方法的映射关系。

public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    public T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

MapperMethod

sql和接口方法的映射关系

//这个类是映射接口的方法对应那个sql,这里简化书写,传了mappedStatements的key来获取sql
public class MapperMethod {
    private String id;

    public MapperMethod(String id) {
        this.id = id;
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        return sqlSession.selectOne(id, args[0]);
    }
}

SqlSession

执行sql的具体对象,主要是根据接口,创建代理对象,依照Configuration执行对应sql

public class SqlSession {
    private SimpleExecutor executor;
    //源码里面是没有Configuration成员变量的,这里是为了方便传参
    private Configuration configuration;



    public SqlSession(SimpleExecutor executor, Configuration configuration) {
        this.executor = executor;
        this.configuration = configuration;
    }



    public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
            return list.get(0);
        } else {
            return null;
        }
    }

    private <T> List<T> selectList(String statement, Object parameter) {
        List var5;
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            //mybatis这里还执行了很多代码,判断语句,缓存优化等。
            var5 = this.executor.query(ms, parameter,configuration);
        } catch (Exception var9) {
            throw new RuntimeException("查询失败...");
        }

        return var5;
    }

    public <T> T getMapper(Class<MyInfoMapper> type) {
        return configuration.getMapper(type, this);
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public SimpleExecutor getExecutor() {
        return executor;
    }
}

SimpleExecutor

SqlSession执行sql是让Executor来执行的,Mybatis中有三种Executor(Simple、Cache、Batch),这里仅模拟了SimpleExecutor

public class SimpleExecutor {


    public <E> List<E> query(MappedStatement ms, Object parameter, Configuration configuration) {
        List list = this.queryFromDatabase(ms, parameter,configuration);
        return list;
    }

    private List queryFromDatabase(MappedStatement ms, Object parameter, Configuration configuration) {
        List list = this.doQuery(ms, parameter,configuration);
        return list;
    }

    private List doQuery(MappedStatement ms, Object parameter, Configuration configuration) {


        List var9;

        //mybatis返回的是sql语句对象Statement,但是prepareStatement方法里面真正连接了数据库
        //所以为了简化书写,这里直接返回了Connection
        Connection connection = this.prepareStatement(configuration.getEnvironment());

        //mybatis这里是用StatementHandler对象的query方法,执行查询
        //为了书写方便,我直接传了jdbc的connection,源码里面并不是这么写的
        SimpleStatementHandler simpleStatementHandler = new SimpleStatementHandler();
        var9 = simpleStatementHandler.query(connection,ms,parameter);
        return var9;

    }


    private Connection prepareStatement(Environment environment) {
        //因为mybatis是对jdbc的封装,所以直接用jdbc的原生代码生成
        Connection connection=null;
        DataSource dataSource = environment.getDataSource();
        try {
            Class.forName(dataSource.getDriver()); //加载对应驱动
            connection = (Connection) DriverManager.getConnection(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }
}

SimpleStatementHandler

StatementHandler是执行sql的核心位置,Mybatis里面还会进入handler内的其他方法,最后到sql的预编译以及执行。这里也作了简化,直接执行。并且创建返回类,我也是用了暴力反射,根据Configuration的resultMap的属性映射,注入属性值,最后返回对象。

public class SimpleStatementHandler {
    public  List  query(Connection connection, MappedStatement statement, Object parameter) {
        String sql = statement.getSql();
        List list = new ArrayList();
        try {
            PreparedStatement preparedStatement=null;
            if (sql.contains("#{")){
                sql = sql.replace("#{id}", "?");
                preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setInt(1, ((Integer) parameter));
            }else {
                preparedStatement = connection.prepareStatement(sql);
            }
            ResultSet resultSet = preparedStatement.executeQuery();
            Object result=null;
            while (resultSet.next()) {
                Class resultClass = statement.getResultMaps().getType();
                result = resultClass.newInstance();
                for (ResultMapping propertyResultMapping : statement.getResultMaps().getPropertyResultMappings()) {
                    String columnValue = resultSet.getString(propertyResultMapping.getColumn());
                    if (columnValue!=null && !columnValue.equals("")){
                        Field field = resultClass.getDeclaredField(propertyResultMapping.getColumn());
                        field.setAccessible(true);
                        field.set(result,columnValue);
                    }

                }
            }
            list.add(result);
        } catch (SQLException e1) {
            e1.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } finally {
            try {
                if (connection != null)
                    connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return (List) list;
    }
}

Main方法及其测试结果

Main方法

该方法的流程,也就对应上文的执行流程图。

    public static void main(String[] args) throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession session = sqlSessionFactory.openSession();

        //mybatis只有在执行sql时候才会连接数据库,所以手写框架也是在这里面先连接数据库
        //后执行sql

        //(1)直接使用session的方法进行执行,传参需要传mapper.xml中sql的id(namespace+id)
        MyInfo myInfo = session.selectOne("com.CmJava.mapper.MyInfoMapper.getMyInfo",1);

        //(2)session获取mapper的代理对象,让代理对象执行,这也是日常开发中是用mybatis后台的实际操作流程
        MyInfoMapper mapper = session.getMapper(MyInfoMapper.class);
        MyInfo myInfo1 = mapper.getMyInfo(1);

        System.out.println(myInfo);
        System.out.println(myInfo1);

    }

测试结果

如图所示
image

Gitee源码

Gitee地址

请点击此处直接跳转

2

评论区