手写简易Mybatis——体会骨架之美

手写Mybatis,首先要明确手写的目的,不是为了方方面面都去模仿实现,而是为了走通mybatis的核心流程,以及中间涉及到的部分核心组件。

我们从基础的查询走起,为了方便,我们只实现最多带一个参数的查询。

基本流程

一、读取配置初始化

相关数据结构:

1.1 MappedStatement

该类用于保存mapper.xml中sql节点的所有信息,在这里我们只保存基本的信息:

public class MappedStatement {
    private String nameSpace;
    private String id;
    private String resultType;
    private String sql;
    private String parameterType;

    /**
     * @return 命名空间+id
     */
    public String getSourceId(){
        return String.format("%s.%s",getNameSpace(), getId());
    }
   	........其他的getter-setter.......
}

1.1 Configuration

Configuration在Mybatis中是全局单例唯一的,保存了所有的配置信息。

这里我们只存储数据源的驱动、url、用户名密码、存储sql信息的map和mapper接口的动态代理注册中心。

public class Configuration {
    private String jdbcDriver;
    private String jdbcUrl;
    private String jdbcUsername;
    private String jdbcPassword;

    private Map<String,MappedStatement>mappedStatementMap=new HashMap<>();
     /**
     * mapper接口的动态代理注册中心
     */
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

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

1.2 SqlSessionFactory

按照流程,我们首先通过SqlSession工厂的openSession()方法获取一个SqlSession对象。

在工厂的build()方法中会完成mybatis配置文件的解析,这里简单起见,直接在SqlSessionFactory的构造中进行:

public class SqlSessionFactory {
    //全局唯一
    private final Configuration configuration = new Configuration();

    private static final String DB_PROPERTIES = "db.properties";
    private static final String MAPPERS_LOCATION = "mappers";

    private static final String JDBC_DRIVER = "jdbc.driver";
    private static final String JDBC_URL = "jdbc.url";
    private static final String JDBC_USERNAME = "jdbc.username";
    private static final String JDBC_PASSWORD = "jdbc.password";

    public SqlSessionFactory() {
        loadDB();
        loadMapper();

    }
	/**加载mapper.xml**/
    private void loadMapper() {
        URL resource = SqlSessionFactory.class.getClassLoader().getResource(MAPPERS_LOCATION);
        File mappersDir = new File(resource.getFile());
        if (mappersDir.isDirectory()) {
            File[] mapperXmls = mappersDir.listFiles();
            for (File xml : mapperXmls) {
                //加载mapper.xml文件
                loadMapperXML(xml);
            }
        }
    }
	
    //加载单个Mapper.xml
       private void loadMapperXML(File file) {
        SAXReader reader = new SAXReader();
        Document doc = null;
        try {
            doc = reader.read(file);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        MappedStatement mappedStatement = null;

        //获取根节点mapper
        Element mapper = doc.getRootElement();
        String namespace = mapper.attributeValue("namespace");
        List<Element> selectList = mapper.elements("select");
        for (Element select : selectList) {
            mappedStatement = new MappedStatement();
            mappedStatement.setNameSpace(namespace);
            mappedStatement.setId(select.attributeValue("id"));
            mappedStatement.setResultType(select.attributeValue(Constants.RESULT_TYPE.getValue()));
            String sql = select.getData().toString();
            mappedStatement.setSql(sql);
            mappedStatement.setParameterType(select.attributeValue(Constants.PARAMETER_TYPE.getValue()));

            configuration.getMappedStatementMap().put(mappedStatement.getSourceId(), mappedStatement);
        }
        try {
            Class<?> mapperInterface = Class.forName(namespace);
            //将当前mapper.xml命名空间的接口注册到mapperRegistry中
            configuration.addMapper(mapperInterface);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("命名空间"+namespace+"对应的mapper接口不存在");
        }
    }

    //加载数据库配置
    private void loadDB() {
        InputStream dbStream = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_PROPERTIES);
        Properties properties = new Properties();
        try {
            properties.load(dbStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //设置到configuration对象中
        configuration.setJdbcDriver(properties.getProperty(JDBC_DRIVER));
        configuration.setJdbcUrl(properties.getProperty(JDBC_URL));
        configuration.setJdbcUsername(properties.getProperty(JDBC_USERNAME));
        configuration.setJdbcPassword(properties.getProperty(JDBC_PASSWORD));
    }

	//返回一个默认的SqlSession
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

db.properties配置:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://120.27.212.10:3306/mybatis_c?useSSL=false&useUnicode=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=xiaoyunshi

1.3 SqlSession&DefaultSqlSession

SqlSession提供查询修改相关接口,这里只提供查询:

public interface SqlSession {
    /**
     * 根据传入的条件查询单一结果
     * @param statement  sql对应的 namespace + id
     * @return  返回指定的结果对象
     */
    <T> T selectOne(String statement, Object parameter);

    /**
     *
     * 根据条件经过查询,返回泛型集合
     *
     * @param statement  sql对应的 namespace + id
     * @return  返回指定的结果对象的list
     */
    <E> List<E> selectList(String statement, Object parameter);

    /**
     * 根据mapper接口获取接口对应的动态代理实例
     * @param type 指定的mapper接口
     * @return
     */
    <T> T getMapper(Class<T> type);
}

默认实现:

public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private Executor executor;
    public DefaultSqlSession(Configuration configuration) {
        this.configuration=configuration;
        executor=new DefaultExecutor(configuration);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        List<Object> list = this.selectList(statement, parameter);
        if (list == null || list.size() == 0){
            return null;
        }
        if (list.size() == 1){
            return (T) list.get(0);
        }
        throw new RuntimeException("Results size is " + list.size() +", but expected one...");
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        //通过statement拿到对应的MappedStatement对象
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statement);
        return executor.query(mappedStatement,parameter);
    }

    /**
     * 获取 指定的mapper接口type的动态代理实例
     * 每次都创建新的 MapperProxy
     * @param type 指定的mapper接口
     * @return type接口的动态代理实例
     */
    @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type,this);
    }
}

这里暂时只提供查询的实现。

在构造中会初始化Executor对象,具体的查询都交给Executor执行。

getMapper通过动态代理返回一个Mapper接口的实例。

二、代理封装

2.1 MapperProxy

public class MapperProxy<T> implements InvocationHandler {
    private SqlSession sqlSession;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> returnType = method.getReturnType();
        String statement  = method.getDeclaringClass().toString().substring(10)+"."+method.getName();
        if (Collection.class.isAssignableFrom(returnType)){
            return sqlSession.selectList(statement, getParameter(args));
        }
        return sqlSession.selectOne(statement,getParameter(args));
    }

    private Object getParameter(Object[] args) {
        return args==null?null:args[0];
    }
}

invoke中,判断方法返回值是否为集合,如果是集合就调用selectList,否则调用selectOne方法返回。

2.2 MapperRegistry

使用Map存储mapper接口和对应MapperProxyFactory的映射。


public class MapperRegistry {

    private final Configuration configuration;
    private final Map<Class<?>,MapperProxyFactory<?>>knownMappers=new HashMap<>();

    public MapperRegistry(Configuration configuration) {
        this.configuration = configuration;
    }

    public <T> void addMapper(Class<T> type ){
        if (type.isInterface()){
            if (knownMappers.containsKey(type)){
                throw new RuntimeException("Type"+type+"已经在knownMappers中注册");
            }
            knownMappers.put(type,new MapperProxyFactory<T>(type));
        }
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession){
        MapperProxyFactory<?> mapperProxyFactory = knownMappers.get(type);
        if (mapperProxyFactory==null){
            throw new RuntimeException("Type:"+type+"未在knownMappers中注册");
        }
      return (T) mapperProxyFactory.newInstance(sqlSession);
    }
}

addMaper中,先判断是否为接口类型,再判断当前接口对象是否已经注册过。

getMapper中先根据当前接口对象从注册中心拿到对应的代理工厂,如果为空则说明未注册,抛出异常,否则调用代理工厂创建一个动态代理类。

2.3 MapperProxyFactory

该类用于生产mapper接口的动态代理对象

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;


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

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

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

每次都创建新的MapperProxy,再通过动态代理创建实例返回。

三、数据读写

3.1 Executor

具体操作数据库时,通过Executor实现,这里只提供查询的接口:


public interface Executor {

    /**
     *
     * 查询接口
     *
     * @param ms  封装sql语句标签的MappedStatement对象
     * @param parameter 传入sql的参数
     * @return 将数据转换成指定对象结果集返回
     */
    <E> List<E> query(MappedStatement ms, Object parameter);
}

3.2 DefaultExecutor

这里只提供默认的简单查询,无缓存。

知识点回顾:

Mybatis中如果开启了二级缓存,则会调用通过装饰器模式包装的CachingExecutor执行,如果二级缓存无数据,再调用BaseExecutor,否则默认走BaseExecutor(提供一级缓存)。

BaseExecutor提供了三个实现:BatchXXX(批量)、SimpleXXX(每次创建新的Statement)和ReuseXXX(通过缓存复用Statement)

Myabatis中,实际的执行并不是Executor,而是交给StatementHandler子类处理。

public class DefaultExecutor implements Executor {

    private final Configuration conf;

    public DefaultExecutor(Configuration conf) {
        this.conf = conf;
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter) {
        final List<E> results = new ArrayList<>();
        try {
            Class.forName(conf.getJdbcDriver());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Connection conn = null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        try {
            conn= DriverManager.getConnection(conf.getJdbcUrl(),conf.getJdbcUsername(),conf.getJdbcPassword());
            preparedStatement=conn.prepareStatement(ms.getSql());
            parameterize(preparedStatement,parameter,ms.getParameterType());
            resultSet = preparedStatement.executeQuery();
            //处理结果集映射,添加到results中
            handleResultSet(resultSet,results,ms.getResultType());
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return results;
    }
}

query()中就是原始的jdbc查询的流程,创建一个results集合保存处理完毕结果集中的实体对象。

但我们需要对参数占位符进行映射处理:

/**
     * 占位符处理
     * @param ps
     * @param parameter
     * @throws SQLException
     */
    private void parameterize(PreparedStatement ps, Object parameter,String parameterType) throws SQLException {
        if (parameter==null)return;
        Class<?> type=null;
        try {
             type = Class.forName(parameterType);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //判断paramType和当前参数类型是否相同
        if (parameter.getClass().isAssignableFrom(type)){
            //根据不同的参数调用对应的方法填充占位符
            if (parameter instanceof String){
                ps.setString(1, (String) parameter);
            }else if (parameter instanceof Long){
                ps.setLong(1, (long) parameter);
            }else if (parameter instanceof Integer){
                ps.setInt(1, (int) parameter);
            }else {
                throw new RuntimeException("parameter not allowed ! ");
            }
        }else {
            throw new RuntimeException("The current parameter type is different from the paramType");
        }

    }

接着调用executeQuery后拿到resultSet,需要处理结果集:

/**
     * 处理结果集映射
     * @param resultSet
     * @param results
     * @param resultType
     * @param <E>
     */
    private <E> void handleResultSet(ResultSet resultSet, List<E> results, String resultType) {
       Class<E>target=null;
        try {
            target = (Class<E>) Class.forName(resultType);
            while (resultSet.next()) {
                //反射创建目标实体对象
                E result = target.newInstance();
                //将resultSet中的结果添加到目标实体的字段中
                ReflectUtils.setPropToBeanFromResultSetOne(result,resultSet);
                results.add(result);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

首先通过resultType反射获取目标的实体对象result,接着遍resultSet,通过反射将结果集中的值设置到该result对象的字段中,最后将result添加到最终返回的集合results中。

3.3 反射填充resultSet到字段

这里简单起见只处理几个简单的类型。

public class ReflectUtils {

    /**
     * 将一条resultSet的结果设置到目标实体的属性中
     *
     * @param target
     * @param resultSet
     */
   public static void setPropToBeanFromResultSetOne(Object target, ResultSet resultSet) throws SQLException {
        Field[] fields = target.getClass().getDeclaredFields();
        for (Field field : fields) {
            String typeName = field.getType().getSimpleName();
            String fieldName=field.getName();
            switch (typeName) {
                case "String":
                    setPropToFiled(target, field, resultSet.getString(fieldName));
                    break;
                case "int":
                case "Integer":
                    setPropToFiled(target, field, resultSet.getInt(fieldName));
                    break;
                case "long":
                case "Long":
                    setPropToFiled(target, field, resultSet.getLong(fieldName));
                    break;
                default:
                    break;
            }
        }
    }
}
 /**
     * 给字段设置值
     *
     * @param target 目标实体
     * @param field  目标字段
     * @param param  要设置的字段值
     */
    private static void setPropToFiled(Object target, Field field, Object param) {
        if (!field.isAccessible()){
            field.setAccessible(true);
        }
        try {
            field.set(target,param);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

OK到这就结束了。来测试一下

四、测试

public class Test {

    @org.junit.Test
    public void Test(){
        SqlSessionFactory factory=new SqlSessionFactory();
        SqlSession sqlSession = factory.openSession();
         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.list();
        System.out.println(userList);
        User user = mapper.selectById(5);
        System.out.println(user);
    }
}

结果:

[User{id=1, username='zhangsan', password='1234', age=17, name='Zhangsan'}, User{id=2, username='test', password='123', age=22, name='wml'}, User{id=4, username='tes2t', password='1234', age=223, name='wml2'}, User{id=5, username='tes2t', password='1234', age=223, name='wml2'}]
User{id=5, username='tes2t', password='1234', age=223, name='wml2'}

哦对了,mapper接口:

public interface UserMapper {
    User selectById(Integer id);
    List<User> list();
}

xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
		PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
		"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wml.mybatis.mapper.UserMapper">


    <select id="list" resultType="com.wml.mybatis.pojo.User">
		SELECT id, username, password, age, name FROM tb_user
	</select>

    <select id="selectById" resultType="com.wml.mybatis.pojo.User" parameterType="java.lang.Integer">
		select id, username, password, age, name from  tb_user where id= ?
	</select>

</mapper>

详细的源码分析可参考:https://blog.csdn.net/weixin_43696529/article/details/107396049

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值