自定义简易MyBatis

7 篇文章 0 订阅
1 篇文章 0 订阅

背景

写了一个简易的mybatis,实现了通过mybatis代理增强Dao层的基本逻辑,这里梳理一下这个Demo的执行流程

核心流程

1.用户通过加载核心配置文件获得输入流,放在内存中

2.通过流信息构建工厂

3.通过工厂打开SqlSession对象

4.SqlSession对象通过getMapper方法获得被代理的接口对象

5.那个被代理的接口对象有增强的方法,通过这个对象操作数据库


实现细节

包结构

      1.自定义mybatis包结构

        

config
   BoundSql   :存储解析过的SQL文本;成员变量->sqlText(解析过后的sql),parameterMappingList(参数)
   XMLConfigBuilder   :解析xml的类;成员变量->Configuration
   XMLMapperBuilder   :解析xml中mapper的类,调用时机是在XMLConfigBuilder.build()   执行时;成员变量->Configuration
io
   Resources   :根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
pojo
   Configuration   :核心配置对象会把sqlMapConfig.xml中的信息解析在这个对象里;成员变量->dataSource(数据源信息),Map<String,MappedStatement>(key: statementid  value:封装好的mappedStatement对象)
   MappedStatement   :Mapper中的SQL解析对象,会把用户定义的Mapper里的信息放在这里面;成员变量->id(id表识),resultType(返回值类型),parameterType(参数类型),sql(sql语句)    {以上成员变量类型都是String
   }
sqlSession
  SqlSessionFactory:规定了SqlSessionFactory的基本功能openSession()
  DefaultSqlSessionFactory:实现了SqlSessionFactory的基本功能openSession()
  SqlSessionFactoryBuilder:通过build方法通过流对象创建出DefaultSqlSessionFactory对象

   SqlSession:规定了SqlSession的基本功能getMapper()
   DefaultSqlSession:实现了SqlSession的基本功能getMapper()。其getMapper核心是用执行器Executor完成的。

  Executor:与数据库交互的对象。规定了基本功能(Configuration configuration, MappedStatement mappedStatement, Object... params)
  simpleExecutor:通过前面构建出来的configuration,MappedStatement ,params实现了基本功能query。

   2.用户包结构

和Mybatis的结构一样~


   几处核心流程分析

1.读取配置文件里的信息到configuration对象中

        1.用户调用SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);创建对象

    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        // 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);


        // 第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;
    }

         2.Configuration configuration = xmlConfigBuilder.parseConfig(in);

    /**
     * 该方法就是使用dom4j对配置文件进行解析,封装Configuration
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {

        Document document = new SAXReader().read(inputStream);
        //<configuration>
        Element rootElement = document.getRootElement();
        //读取property节点
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        //读取property节点下的Element信息
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }
        //设置数据库信息
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));

        configuration.setDataSource(comboPooledDataSource);

        //mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        //对于每一个mapper标签下
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            //新建一个XMLMapperBuilder对象来解析,用的是一个configuration对象
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);

        }

         3.xmlMapperBuilder.parse(resourceAsSteam);

    public void parse(InputStream inputStream) throws DocumentException {

        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        //分别添加增删改查节点,让其可以解析这些节点信息
        addNode(rootElement,"select");
        addNode(rootElement,"insert");
        addNode(rootElement,"update");
        addNode(rootElement,"delete");

    }

    public void addNode(Element rootElement,String nodeName){
        String namespace = rootElement.attributeValue("namespace");

        List<Element> list = rootElement.selectNodes("//"+nodeName);
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);
            String key = namespace+"."+id;
            //放到configuration里
            configuration.getMappedStatementMap().put(key,mappedStatement);
            System.out.println("put " + key + " : " + mappedStatement.toString());

        }
    }

         至此,Configuration里的两个对象已经填充完毕。

2.通过getMapper获得代理对象

     1.用户调用SqlSession的getMapper方法为Dao接口生成代理类(MyBatis是提前就生成好代理类了)。

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

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


    public <E> List<E> selectList(String statementid, Object... params) throws Exception {

        //将要去完成对simpleExecutor里的query方法的调用
        simpleExecutor simpleExecutor = new simpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        //有可能执行insert方法
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);

        return (List<E>) list;
    }


    public <T> T selectOne(String statementid, Object... params) throws Exception {
        List<Object> objects = selectList(statementid, params);
        if(objects.size()==1){
            return (T) objects.get(0);
        }else {
            throw new RuntimeException("查询结果为空或者返回结果过多");
        }


    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理来为Dao接口生成代理对象,并返回

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
                // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
                // 方法名:findAll
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();

                String statementId = className+"."+methodName;

                // 准备参数2:params:args
                // 获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                // 判断是否进行了 泛型类型参数化
                if(genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }

                return selectOne(statementId,args);


            }
        });

        return (T) proxyInstance;
    }




}

     2.这其中的simpleExecutor.query(configuration, mappedStatement, params);方法与通过之前生成的configuration和mappedStatement和params与数据库交互。

public class simpleExecutor implements  Executor {


    @Override                                                                                //user
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1. 注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
            //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数
            //获取到了参数的全路径
         String paramterType = mappedStatement.getParamterType();
         Class<?> paramtertypeClass = getClassType(paramterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);

            preparedStatement.setObject(i+1,o);

        }
        //增加,删除,修改
        if("insert".equals(sql.trim().split(" ")[0]) ||
                "delete".equals(sql.trim().split(" ")[0]) ||
                "update".equals(sql.trim().split(" ")[0])){
            int i = preparedStatement.executeUpdate();
            if(i == 0){
                throw new RuntimeException("操作失败,影响条数为0条");
            }
            else {
                System.out.println("操作成功!");
                List<Integer> list = new ArrayList<>();
                list.add(i);
                return (List<E>)list;
            }

        }
        // 5. 执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();

        // 6. 封装返回结果集
        while (resultSet.next()){
            Object o =resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {

                // 字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);

                //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);


            }
            objects.add(o);

        }
            return (List<E>) objects;

    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if(paramterType!=null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
         return null;

    }


    /**
     * 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
         return boundSql;

    }


}

     关于MyBatis其他的一点小说明

          关于SqlSession对象selectList和getMapper两种方法的差别:

      selectList,selectOne等都是通过StatmentMapper直接对象直接进行查询的。

      getMapper则是把Dao对象代理了一下在执行selectList

 

结束

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值