zoukankan      html  css  js  c++  java
  • Mybatis(2)-自定义mybatis分析(理解其原理)

    一、流程分析

    1.我们通过前面的例子发现我们执行的顺序为:

    //1.读取配置文件
            InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建SqlSessionFactory工厂
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);
            //3.使用工厂生产SqlSession对象
            SqlSession session = factory.openSession();
            //4.使用SqlSession创建Dao接口的代理对象
            IUserDao userDao = session.getMapper(IUserDao.class);
            //5.使用代理对象执行方法
            List<User> users = userDao.findAll();
            for(User user : users){
                System.out.println(user);
            }
            //6.释放资源
            session.close();
            in.close();

    2.分析上述流程,首先我们读取的是SqlMapConfig.xml配置文件,此时是一个解析xml的过程(dom4j技术解析),然后通过封装之前JDBC模式进行数据库连接创建,sql执行等操作。

    //2.创建SqlSessionFactory工厂
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);

       此处涉及构造者模式(Builder 模式)。

    (1)读取xml文件转输入流

    // 1.读取配置文件
            InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

    (2)读取SqlMapConfig.xml配置文件数据库相关内容,解析xml,封装Configuration。

      读取内如:

        <environments default="mysql">
            <!-- 配置mysql环境 -->
            <environment id="mysql">
                <!-- 配置事务类型 -->
                <transactionManager type="JDBC"></transactionManager>
                <!-- 配置数据源 -->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                    <property name="url" value="jdbc:mysql://localhost:3306/easy_mybatis?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;zeroDateTimeBehavior=CONVERT_TO_NULL&amp;useSSL=false&amp;serverTimezone=CTT" />
                    <property name="username" value="root" />
                    <property name="password" value="root" />
                </dataSource>
            </environment>
        </environments>

    (3)同时读取SqlMapConfig.xml配置文件数据库相关内容,解析xml

      读取SqlMapConfig.xml中的映射配置信息:

    <!-- 配置映射文件 -->
        <mappers>
            <mapper class="com.xhbjava.dao.IUserDao" />
        </mappers>

      然后通过映射配置文件读取sql:

    <?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.xhbjava.dao.IUserDao">
        <select id="findAll" resultType="com.xhbjava.domain.User">
            select * from user;
        </select>
    
    </mapper>

    读取这些配置文件解析,并将对应的类方法作为key,sql和返回类型封装成map作为value再添加进去map中,然后作为Configuration属性返回

    (4)生产SqlSession对象,其实就是建立数据库连接并返回数据库连接。

    //3.使用工厂生产SqlSession对象
            SqlSession session = factory.openSession();

    (5)使用动态代理,增强方法,将结果封装成List,返回代理对象

    //4.使用SqlSession创建Dao接口的代理对象
            IUserDao userDao = session.getMapper(IUserDao.class);

    (6)执行查询方法,返回结果

    //5.使用代理对象执行方法
            List<User> users = userDao.findAll();

    二、具体代码分析实现(重点)

    项目结构:

     

    (1)从上面测试类分析我们发现涉及class Resources、class SqlSessionFactoryBuilder、interface SqlSessionFactory和interface SqlSession,我们按照流程进行代码完善。

    (1)创建Maven工程

    (2)引入程序依赖jar包

    <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.xhbjava</groupId>
        <artifactId>Mybatis01</artifactId>
        <packaging>war</packaging>
        <version>0.0.1-SNAPSHOT</version>
        <name>Mybatis01 Maven Webapp</name>
        <url>http://maven.apache.org</url>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.15</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.12</version>
            </dependency>
            <!-- 解析 xml 的 dom4j -->
            <dependency>
                <groupId>dom4j</groupId>
                <artifactId>dom4j</artifactId>
                <version>1.6.1</version>
            </dependency>
            <!-- dom4j 的依赖包 jaxen -->
            <dependency>
                <groupId>jaxen</groupId>
                <artifactId>jaxen</artifactId>
                <version>1.1.6</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>3.8.1</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <build>
            <finalName>Mybatis01</finalName>
        </build>
    </project>

    (3)编写Resources类

    package com.xhbjava.mybatis.io;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    /**
     * 使用类加载器读取配置文件的类
     * 
     * @author Mr.wang
     * @date 2020年1月13日
     */
    public class Resources {
        /**
         * 根据出入参数获取一个字节流输入
         * 
         * @param filePath
         * @return
         * @throws IOException
         */
        public static InputStream getResourceAsStream(String filePath) throws IOException {
            return Resources.class.getClassLoader.getResourceAsStream(filePath);
        }
    
    }

    (4)编写SqlSessionFactoryBuilder类,同时创建SqlSessionFactory接口。

    package com.xhbjava.mybatis.sqlsession;
    
    import java.io.InputStream;
    
    /**
     * 该类用于创建SqlSessionFactory对象
     * 
     * @author Mr.wang
     * @date 2020年1月13日
     */
    public class SqlSessionFactoryBuilder {
        /**
         * 创建build方法
         * 
         * @param config
         * @return
         */
        public SqlSessionFactory build(InputStream config) {
            return null;
    
        }
    
    }
    package com.xhbjava.mybatis.sqlsession;
    
    
    public interface SqlSessionFactory {
        /**
         * 用于打开一个新的SqlSession对象
         * 
         * @return
         */
        SqlSession openSession();
    
    }

    (5)创建SqlSession接口

    package com.itheima.mybatis.sqlsession;
    
    
    /**
     * 
     * @author Mr.wang
     * @date 2020年1月13日
     */
    public interface SqlSession {
    
        /**
         * 根据参数创建一个代理对象
         * @param daoInterfaceClass dao的接口字节码
         * @param <T>
         * @return
         */
        <T> T getMapper(Class<T> daoInterfaceClass);
    
        /**
         * 释放资源
         */
        void close();
    }

    (6)创建XMLConfigBuilder工具类以及关联的类

      关联类:

    package com.xhbjava.mybatis.cfg;
    
    /**
     * 用于封装执行的SQL语句和结果类型的全限定类名
     * 
     * @author Mr.wang
     * @date 2020年1月13日
     */
    public class Mapper {
    
        private String queryString;
        private String resultType;
        public String getQueryString() {
            return queryString;
        }
        public void setQueryString(String queryString) {
            this.queryString = queryString;
        }
        public String getResultType() {
            return resultType;
        }
        public void setResultType(String resultType) {
            this.resultType = resultType;
        }
        
    
    }
    package com.xhbjava.mybatis.cfg;
    
    import java.util.Map;
    
    /**
     * 自定义配置类
     * 
     * @author Mr.wang
     * @date 2020年1月13日
     */
    public class Configuration {
        private String driver;
        private String url;
        private String username;
        private String password;
        private Map<String,Mapper> mappers = new HashMap<String,Mapper>();;
        
        public Map<String, Mapper> getMappers() {
            return this.mappers;
        }
    
        public void setMappers(Map<String, Mapper> mappers) {
            this.mappers.putAll(mappers);
        }
    
        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;
        }
    
    }
    package com.xhbjava.mybatis.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * c查询注解
     * 
     * @author Mr.wang
     * @date 2020年1月13日
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Select {
    
        /**
         * 配置SQL语句的
         * 
         * @return
         */
        String value();
    }

    工具类:

    package com.xhbjava.mybatis.util;
    
    import org.dom4j.Attribute;
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import com.xhbjava.mybatis.annotation.Select;
    import com.xhbjava.mybatis.cfg.Configuration;
    import com.xhbjava.mybatis.cfg.Mapper;
    import com.xhbjava.mybatis.io.Resources;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 用于解析配置文件
     * 
     * @author Mr.wang
     * @date 2020年1月13日
     */
    public class XMLConfigBuilder {
    
        /**
         * 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方 使用的技术: dom4j+xpath
         */
        public static Configuration loadConfiguration(InputStream config) {
            try {
                // 定义封装连接信息的配置对象(mybatis的配置对象)
                Configuration cfg = new Configuration();
    
                // 1.获取SAXReader对象
                SAXReader reader = new SAXReader();
                // 2.根据字节输入流获取Document对象
                Document document = reader.read(config);
                // 3.获取根节点
                Element root = document.getRootElement();
                // 4.使用xpath中选择指定节点的方式,获取所有property节点
                List<Element> propertyElements = root.selectNodes("//property");
                // 5.遍历节点
                for (Element propertyElement : propertyElements) {
                    // 判断节点是连接数据库的哪部分信息
                    // 取出name属性的值
                    String name = propertyElement.attributeValue("name");
                    if ("driver".equals(name)) {
                        // 表示驱动
                        // 获取property标签value属性的值
                        String driver = propertyElement.attributeValue("value");
                        cfg.setDriver(driver);
                    }
                    if ("url".equals(name)) {
                        // 表示连接字符串
                        // 获取property标签value属性的值
                        String url = propertyElement.attributeValue("value");
                        cfg.setUrl(url);
                    }
                    if ("username".equals(name)) {
                        // 表示用户名
                        // 获取property标签value属性的值
                        String username = propertyElement.attributeValue("value");
                        cfg.setUsername(username);
                    }
                    if ("password".equals(name)) {
                        // 表示密码
                        // 获取property标签value属性的值
                        String password = propertyElement.attributeValue("value");
                        cfg.setPassword(password);
                    }
                }
                // 取出mappers中的所有mapper标签,判断他们使用了resource还是class属性
                List<Element> mapperElements = root.selectNodes("//mappers/mapper");
                // 遍历集合
                for (Element mapperElement : mapperElements) {
                    // 判断mapperElement使用的是哪个属性
                    Attribute attribute = mapperElement.attribute("resource");
                    if (attribute != null) {
                        System.out.println("使用的是XML");
                        // 表示有resource属性,用的是XML
                        // 取出属性的值
                        String mapperPath = attribute.getValue();// 获取属性的值"com/itheima/dao/IUserDao.xml"
                        // 把映射配置文件的内容获取出来,封装成一个map
                        Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
                        // 给configuration中的mappers赋值
                        cfg.setMappers(mappers);
                    } else {
                        System.out.println("使用的是注解");
                        // 表示没有resource属性,用的是注解
                        // 获取class属性的值
                        String daoClassPath = mapperElement.attributeValue("class");
                        // 根据daoClassPath获取封装的必要信息
                        Map<String, Mapper> mappers = loadMapperAnnotation(daoClassPath);
                        // 给configuration中的mappers赋值
                        cfg.setMappers(mappers);
                    }
                }
                // 返回Configuration
                return cfg;
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                try {
                    config.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        /**
         * 根据传入的参数,解析XML,并且封装到Map中
         * 
         * @param mapperPath 映射配置文件的位置
         * @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
         *         以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
         */
        private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) throws IOException {
            InputStream in = null;
            try {
                // 定义返回值对象
                Map<String, Mapper> mappers = new HashMap<String, Mapper>();
                // 1.根据路径获取字节输入流
                in = Resources.getResourceAsStream(mapperPath);
                // 2.根据字节输入流获取Document对象
                SAXReader reader = new SAXReader();
                Document document = reader.read(in);
                // 3.获取根节点
                Element root = document.getRootElement();
                // 4.获取根节点的namespace属性取值
                String namespace = root.attributeValue("namespace");// 是组成map中key的部分
                // 5.获取所有的select节点
                List<Element> selectElements = root.selectNodes("//select");
                // 6.遍历select节点集合
                for (Element selectElement : selectElements) {
                    // 取出id属性的值 组成map中key的部分
                    String id = selectElement.attributeValue("id");
                    // 取出resultType属性的值 组成map中value的部分
                    String resultType = selectElement.attributeValue("resultType");
                    // 取出文本内容 组成map中value的部分
                    String queryString = selectElement.getText();
                    // 创建Key
                    String key = namespace + "." + id;
                    // 创建Value
                    Mapper mapper = new Mapper();
                    mapper.setQueryString(queryString);
                    mapper.setResultType(resultType);
                    // 把key和value存入mappers中
                    mappers.put(key, mapper);
                }
                return mappers;
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                in.close();
            }
        }
    
        /**
         * 根据传入的参数,得到dao中所有被select注解标注的方法。 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息
         * 
         * @param daoClassPath
         * @return
         */
        private static Map<String, Mapper> loadMapperAnnotation(String daoClassPath) throws Exception {
            // 定义返回值对象
            Map<String, Mapper> mappers = new HashMap<String, Mapper>();
    
            // 1.得到dao接口的字节码对象
            Class daoClass = Class.forName(daoClassPath);
            // 2.得到dao接口中的方法数组
            Method[] methods = daoClass.getMethods();
            // 3.遍历Method数组
            for (Method method : methods) {
                // 取出每一个方法,判断是否有select注解
                boolean isAnnotated = method.isAnnotationPresent(Select.class);
                if (isAnnotated) {
                    // 创建Mapper对象
                    Mapper mapper = new Mapper();
                    // 取出注解的value属性值
                    Select selectAnno = method.getAnnotation(Select.class);
                    String queryString = selectAnno.value();
                    mapper.setQueryString(queryString);
                    // 获取当前方法的返回值,还要求必须带有泛型信息
                    Type type = method.getGenericReturnType();// List<User>
                    // 判断type是不是参数化的类型
                    if (type instanceof ParameterizedType) {
                        // 强转
                        ParameterizedType ptype = (ParameterizedType) type;
                        // 得到参数化类型中的实际类型参数
                        Type[] types = ptype.getActualTypeArguments();
                        // 取出第一个
                        Class domainClass = (Class) types[0];
                        // 获取domainClass的类名
                        String resultType = domainClass.getName();
                        // 给Mapper赋值
                        mapper.setResultType(resultType);
                    }
                    // 组装key的信息
                    // 获取方法的名称
                    String methodName = method.getName();
                    String className = method.getDeclaringClass().getName();
                    String key = className + "." + methodName;
                    // 给map赋值
                    mappers.put(key, mapper);
                }
            }
            return mappers;
        }
    
    }

    (7)SqlSessionFactory接口的实现类

    package com.xhbjava.mybatis.sqlsession.defaults;
    
    import com.xhbjava.mybatis.cfg.Configuration;
    import com.xhbjava.mybatis.sqlsession.SqlSession;
    import com.xhbjava.mybatis.sqlsession.SqlSessionFactory;
    
    /**
     * SqlSessionFactory接口的实现类
     * 
     * @author Mr.wang
     * @date 2020年1月14日
     */
    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
        private Configuration cfg;
    
        public DefaultSqlSessionFactory(Configuration cfg) {
            this.cfg = cfg;
        }
    
        /**
         * 用于创建一个新的操作数据库对象
         * 
         * @return
         */
        public SqlSession openSession() {
            return new DefaultSqlSession(cfg);
        }
    }

    (8)完善SqlSessionFactoryBuilder中bulid方法

    public SqlSessionFactory build(InputStream config) {
            Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
    
            return new DefaultSqlSessionFactory(cfg);
    
        }

    (9)完善SqlSession接口实现类,实现获取数据库链接

        private Configuration cfg;
        private Connection connection;
    
        public DefaultSqlSession(Configuration cfg){
            this.cfg = cfg;
            connection = DataSourceUtil.getConnection(cfg);
        }

    添加DataSourceUtil类

    package com.xhbjava.mybatis.util;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    
    import com.xhbjava.mybatis.cfg.Configuration;
    
    /**
     * 用于创建数据源的工具类
     * 
     * @author Mr.wang
     * @date 2020年1月14日
     */
    public class DataSourceUtil {
    
        /**
         * 用于获取一个连接
         * 
         * @param cfg
         * @return
         */
        public static Connection getConnection(Configuration cfg) {
            try {
                Class.forName(cfg.getDriver());
                return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    (10)创建MapperProxy类,完善DefaultSqlSession类实现SqlSession接口中的方法

    package com.xhbjava.mybatis.sqlsession.defaults;
    
    
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    
    import com.xhbjava.mybatis.cfg.Configuration;
    import com.xhbjava.mybatis.sqlsession.SqlSession;
    import com.xhbjava.mybatis.util.DataSourceUtil;
    
    /**
     * SqlSession接口的实现类
     * 
     * @author Mr.wang
     * @date 2020年1月14日
     */
    public class DefaultSqlSession implements SqlSession {
    
        private Configuration cfg;
        private Connection connection;
    
        public DefaultSqlSession(Configuration cfg){
            this.cfg = cfg;
            connection = DataSourceUtil.getConnection(cfg);
        }
    
        /**
         * 用于创建代理对象
         * @param daoInterfaceClass dao的接口字节码
         * @param <T>
         * @return
         */
        public <T> T getMapper(Class<T> daoInterfaceClass) {
            return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
                    new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
        }
    
        /**
         * 用于释放资源
         */
     
        public void close() {
            if(connection != null) {
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    (11)添加Executor类,完善MapperProxy类

    package com.xhbjava.mybatis.util;
    
    import java.beans.PropertyDescriptor;
    import java.lang.reflect.Method;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.util.ArrayList;
    import java.util.List;
    
    import com.xhbjava.mybatis.cfg.Mapper;
    
    /**
     * 负责执行SQL语句,并且封装结果集,selectList实现
     * @author Mr.wang
     *@date    2020年1月14日
     */
    public class Executor {
    
        public <E> List<E> selectList(Mapper mapper, Connection conn) {
            PreparedStatement pstm = null;
            ResultSet rs = null;
            try {
                // 1.取出mapper中的数据
                String queryString = mapper.getQueryString();// select * from user
                String resultType = mapper.getResultType();// com.xhb.domain.User
                Class domainClass = Class.forName(resultType);
                // 2.获取PreparedStatement对象
                pstm = conn.prepareStatement(queryString);
                // 3.执行SQL语句,获取结果集
                rs = pstm.executeQuery();
                // 4.封装结果集
                List<E> list = new ArrayList<E>();// 定义返回值
                while (rs.next()) {
                    // 实例化要封装的实体类对象
                    E obj = (E) domainClass.newInstance();
    
                    // 取出结果集的元信息:ResultSetMetaData
                    ResultSetMetaData rsmd = rs.getMetaData();
                    // 取出总列数
                    int columnCount = rsmd.getColumnCount();
                    // 遍历总列数
                    for (int i = 1; i <= columnCount; i++) {
                        // 获取每列的名称,列名的序号是从1开始的
                        String columnName = rsmd.getColumnName(i);
                        // 根据得到列名,获取每列的值
                        Object columnValue = rs.getObject(columnName);
                        // 给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
                        PropertyDescriptor pd = new PropertyDescriptor(columnName, domainClass);// 要求:实体类的属性和数据库表的列名保持一种
                        // 获取它的写入方法
                        Method writeMethod = pd.getWriteMethod();
                        // 把获取的列的值,给对象赋值
                        writeMethod.invoke(obj, columnValue);
                    }
                    // 把赋好值的对象加入到集合中
                    list.add(obj);
                }
                return list;
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                release(pstm, rs);
            }
        }
    
        private void release(PreparedStatement pstm, ResultSet rs) {
            if (rs != null) {
                try {
                    rs.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            if (pstm != null) {
                try {
                    pstm.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    (12)此时基本完成代码工作,可以点击测试类进行测试。

    总结:记录本代码逻辑,还需多看多理解多动手。

  • 相关阅读:
    JZ36 两个链表的第一个公共结点
    程序员的表达能力
    Git学习(2)-使用Git 代码将本地文件提交到 GitHub
    初识模块
    三元表达式、递归、匿名函数
    CSRF
    XSS前置课程--同源策略
    XSS
    SQL注入基础入门
    Linux下ettercap的安装,make安装软件步骤
  • 原文地址:https://www.cnblogs.com/xhbJava/p/12170662.html
Copyright © 2011-2022 走看看