栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

mybatis第九话 - 手写实现一个简单的mybatis版本

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

mybatis第九话 - 手写实现一个简单的mybatis版本

本文主要记录了基于整个Mybatis的使用以及源码分析篇总结,为了更加清晰的看懂Mybatis原理,简化源码手写实现一个简单的Mybatis版本

1. 项目依赖以及架构

pom 基于springboot 2.5.6


	org.springframework.boot
	spring-boot-starter-web


	mysql
	mysql-connector-java
	8.0.18

目录
2.自动装配类 MapperAutoConfig

只贴了一个扫描和向容器注册的方法

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    //1.扫描mapper接口
    doScanner("com.example.demo", TMapper.class, this.configuration.getMapperInterfaceList());
    //初始化插件
    doScanner("com.example.demo", Intercepts.class, this.configuration.getPluGinInterfaceList());

    //2.扫描sql 这里方便读取存放到配置文件了
    doScannerStatement("application.yml");

    //3.将mapper接口和sql文件保存到MapperFactoryBean,然后注册到容器中去
    doRegister(registry);
}

//向容器注册
private void doRegister(BeanDefinitionRegistry registry) {
    for (Object object : this.configuration.getMapperInterfaceList()) {
        Class clazz = (Class) object;
        //beanName是mapper接口名
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        //class 替换为MapperFactoryBean类
        beanDefinition.setBeanClass(MapperFactoryBean.class);
        //参数传递
        beanDefinition.getPropertyValues().add("mapperInterface", clazz.getName());
        beanDefinition.getPropertyValues().add("configuration", this.configuration);
        //注册到容器 beanName是接口全名 beanClass替换成MapperFactoryBean
        registry.registerBeanDefinition(clazz.getName(), beanDefinition);

        log.info("add mapper for " + clazz.getName());
    }
}
3.加载MapperFactoryBean类会调用该类的getObject方法
//该类实现了FactoryBean 初始化原始类时会调用该类的getObject方法
@Data
public class MapperFactoryBean implements FactoryBean {

    private Class mapperInterface;
    Configuration configuration;

    public MapperFactoryBean() {

    }

    //此类为工厂bean 初始化该类时可以自己定义实例化对象 这里采用动态代理生成返回的对象 调用类为MapperProxy
    @Override
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                new Class[]{mapperInterface}, new MapperProxy(mapperInterface, configuration));
    }

    @Override
    public Class getObjectType() {
        return this.mapperInterface;
    }
}
4.代理调用类MapperProxy
public class MapperProxy implements InvocationHandler, Serializable {

    Class mapperInterface;
    Configuration configuration;

    public MapperProxy(Class mapperInterface, Configuration configuration) {
        this.mapperInterface = mapperInterface;
        this.configuration = configuration;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //处理sql的相关信息
        MapperMethod mapperMethod = new MapperMethod(configuration, method, this.mapperInterface);
        //真正的调用从这里开始的
        return mapperMethod.execute(args);
    }
}
5.sqlseesion代理生成类MapperMethod
public MapperMethod(Configuration configuration, Method method, Class mapperInterface) {
    this.method = method;
    this.sqlCommend = new SqlCommend();
    String statementId = mapperInterface.getName() + "." + method.getName();
    sqlCommend.setStatementId(statementId);
    sqlCommend.setResultType(method.getReturnType());
    sqlCommend.setParamType(method.getParameterTypes());
    sqlCommend.setSql(configuration.getMappedStatements().get(statementId));
	//创建代理类
    this.sqlSessionProxy = createSqlSessionProxy(configuration);
}

private SqlSession createSqlSessionProxy(Configuration configuration) {
    //代理invoke类指向SqlSessionInvocation类
    return (SqlSession) Proxy.newProxyInstance(this.getClass().getClassLoader(),
            new Class[]{SqlSession.class}, new SqlSessionInvocation(configuration, sqlCommend));
}

//根据sql类型区分 先会走SqlSessionInvocation代理类
public Object execute(Object[] args) {
    switch (sqlCommend.getType()) {
        case "SELECt":
            Class returnType = this.method.getReturnType();
            if (List.class.equals(returnType)) {
                return this.sqlSessionProxy.selectList(sqlCommend.getStatementId(), args);
            } else if (!void.class.equals(returnType)) {
                return this.sqlSessionProxy.selectOne(sqlCommend.getStatementId(), args);
            }
        case "UPDATE":
        case "INSERT":
            return this.sqlSessionProxy.update(sqlCommend.getStatementId(), args);
        case "DELETE":
            return this.sqlSessionProxy.delete(sqlCommend.getStatementId(), args);
    }
    throw new RuntimeException("sql类型异常," + sqlCommend.getType());
}
6.SqlSessionInvocation 新sqlSeesion和事务代理类
@Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     try {
         //获取一个新的sqlSession
         DefualtSqlSession sqlSession = getSqlSession();

         Object result = method.invoke(sqlSession, args);

         //做事务的提交 todo
         return result;
     } catch (Exception e) {
         //回滚之类的
         throw new RuntimeException(e);
     }

 }

 private DefualtSqlSession getSqlSession() {
     //创建执行器
     Executor executor = new baseExecutor(configuration, sqlCommend);

     //扫描插件实现
     executor = (Executor) this.pluginAll(executor);

     return new DefualtSqlSession(executor);
 }

 private Object pluginAll(Object executor) {
     //插件list
     List interfaceList = configuration.getPluGinInterfaceList();

     for (Object interceptor : interfaceList) {
         Interceptor instance;
         try {
             //初始化插件 正常这里是从spring拿就好 我这是扫描的class
             instance = (Interceptor) ((Class) interceptor).newInstance();
         } catch (Exception e) {
             continue;
         }
		 //代理插件 这个就是和源码一样了
         executor = instance.plugin(executor);
     }
     return executor;
 }

 
7.sqlSeesion默认实现类DefualtSqlSession 

这里只贴一个selectList方法了

@Override
public  List selectList(String statement, Object parameter) {
    //可能是插件的代理类 多层
    //执行前sql修改或参数修改 执行后的返回字段修改或者转义
    return this.executor.query(statement, parameter);
}
8.插件的执行代理类Plugin

插件实现类

@Slf4j
@Component
@Intercepts(method = "com.example.mybatis.executor.baseExecutor.query")
public class PluginService implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            Object target = invocation.getTarget();
            log.info("Sql{}", ((baseExecutor) target).getSqlCommend().getSql());

            Object[] args = invocation.getArgs();
            log.info("statementId:{}", args[0]);
            log.info("参数:{}", args[1]);
        } catch (Exception e) {
            log.error("插件异常!");
        }
        return invocation.process();
    }
}

插件的invoke代理类

public class Plugin implements InvocationHandler {

    Object target;
    Interceptor interceptor;

    public Plugin(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    public static Object wrap(Object target, Interceptor interceptor) {
        Class clazz = target.getClass();
        Class[] interfaces = clazz.getInterfaces();
        //生成一个新的代理类
        return Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, new Plugin(target, interceptor));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取注解上的内容 这里简便了 直接对应statementId
        Intercepts annotation = interceptor.getClass().getAnnotation(Intercepts.class);
        //arg里面的参数 源码是封装成ms传递的 这里省略了
        //就两个参数通过selectList传递过来的 selectList(sqlCommend.getStatementId(), args)
        //通过被代理的原始类的方法判断是否走代理逻辑
        if (annotation.method().equals(target.getClass().getName()+"."+method.getName())) {
        	//满足条件就调用插件的实现类
            return interceptor.intercept(new Invocation(target, method, args));
        }
        //否则就回到原始类
        return method.invoke(target, args);
    }
}
9.baseExecutor最终的执行类
@Getter
@Slf4j
public class baseExecutor implements Executor {

    Configuration configuration;
    SqlCommend sqlCommend;
    //配置类和连接简化了
    static String driverClassName = "driver-class-name";
    static String url = "url";
    static String username = "username";
    static String password = "password";


    public baseExecutor(Configuration configuration, SqlCommend sqlCommend) {
        this.configuration = configuration;
        this.sqlCommend = sqlCommend;
    }

    @Override
    public int update(String statement, Object parameter) {
        Statement st = null;
        try {
            //预处理sql
            String sql = this.getBoundSql(parameter);
            //连接数据库
            st = prepareStatement();
            //执行查询
            st.executeUpdate(sql);
            //处理结果集
            return st.getUpdateCount();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        } finally {
            closeStatement(st);
        }
    }

    @Override
    public int delete(String statement, Object parameter) {
        Statement st = null;
        try {
            //预处理sql
            String sql = this.getBoundSql(parameter);
            //连接数据库
            st = prepareStatement();
            //执行查询
            st.execute(sql);
            //处理结果集
            return st.getUpdateCount();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return 0;
        } finally {
            closeStatement(st);
        }
    }

    @Override
    public  List query(String statement, Object parameter) {
        Statement st = null;
        try {
            //预处理sql
            String sql = this.getBoundSql(parameter);
            //连接数据库
            st = prepareStatement();
            //执行查询
            ResultSet rs = st.executeQuery(sql);
            //处理结果集
            return handlerResultSet(rs);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        } finally {
            closeStatement(st);
        }
    }

    private void closeStatement(Statement st) {
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    //这里只简单实现map和单字段传参
    //源码中GenericTokenParser#parse解析 SqlSourceBuilder#handleToken存储字段顺序
    // 实现流程 使用下标匹配#{和}得到每个字段的名称 添加到参数Mapping 然后替换成?
    // 最后传入的参数会处理成数组的方式传输 交由parameterHandler按顺序取值 parameterHandler可以被插件代理
    private String getBoundSql(Object parameter) throws Exception {
        String sql = this.sqlCommend.getSql();
        Object[] args = (Object[]) parameter;
        if (args.length > 0) {
            if (args[0] instanceof Map) {
                Map map = (Map) args[0];
                for (String str : map.keySet()) {
                    sql = sql.replace("#{" + str + "}",
                            "'" + map.get(str).toString() + "'");
                }
            } else {
                sql = sql.replaceAll("#\{.*?}", "%s");
                sql = String.format(sql, args);
            }
        }
        log.info("execute sql:{}", sql);
        return sql;
    }

    //resultHandler 可以被插件多层代理
    private  List handlerResultSet(ResultSet rs) throws Exception {
        List> mapList = new ArrayList<>();
        Map map = new HashMap<>();
        //获取列数据
        ResultSetmetaData metaData = rs.getmetaData();
        while (rs.next()) {
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                map.put(metaData.getColumnName(i), rs.getObject(i));
            }
            mapList.add(map);
        }
        log.info("result:{}", mapList);
        return (List) mapList;
    }

    private Statement prepareStatement() {
        try {
            Properties properties = configuration.getProperties();
            Class.forName(properties.getProperty(driverClassName));
            //conn正常是放数据池管理的
            Connection conn = DriverManager.getConnection(
                    properties.getProperty(url),
                    properties.getProperty(username),
                    properties.getProperty(password));
            //这个相当于session 每次用完就关闭
            Statement stmt = conn.createStatement();
            return stmt;
        } catch (Exception e) {
            throw new RuntimeException("数据库连接失败,e:" + e);
        }
    }
}
10.测试日志 10.1 insert测试

http://127.0.0.1:12233/insert

@GetMapping("/insert")
public Object insert() {
    Map map = new HashMap<>();
    map.put("name", new Date().getTime());
    map.put("age", 22);
    map.put("status", 1);
    int i = testMapper.insert(map);
    return i;
}

日志输出
c.example.mybatis.executor.baseExecutor : execute sql:insert into user_info(`name`,`age`,`status`) values('1647241298926','22','1') 10.2 update测试

http://127.0.0.1:12233/update?id=1

@GetMapping("/update")
public Object update(@RequestParam Long id) {
    Map map = new HashMap<>();
    map.put("id", id);
    map.put("name", "update_" + new Date().getTime());
    int i = testMapper.update(map);
    return i;
}

日志输出
baseExecutor : execute sql:update user_info set name ='update_1647241489267' where id = '1' 10.3 select测试

http://127.0.0.1:12233/select?id=1

@GetMapping("/select")
public Object select(@RequestParam Long id) {
    Map map = testMapper.selectById(id);
    return map;
}

日志测试

com.example.demo.plugin.PluginService    : Sqlselect * from user_info where id = #{id}
com.example.demo.plugin.PluginService    : statementId:com.example.demo.mapper.TestMapper.selectById
com.example.demo.plugin.PluginService    : 参数:[1]
c.example.mybatis.executor.baseExecutor  : execute sql:select * from user_info where id = 1
c.example.mybatis.executor.baseExecutor  : result:[{update_time=2021-09-12 18:17:16.0, create_time=2021-09-12 18:13:50.0, name=update_1647241489267, id=1, age=22, status=1}

大大的简化整个流程,方便读懂mybait整个源码。源码地址直通车
以上就是本章的全部内容了。

上一篇:mybatis第八话 - mybaits之ParameterHandler参数处理源码分析
下一篇:mybatis第十话 - mybaits事务的源码分析

立身以立学为先,立学以读书为本

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号