• 【MyBatis】自定义 MyBatis


    自定义 MyBatis

    文章源码

    执行查询信息的分析

    我们知道,MyBatis 在使用代理 DAO 的方式实现增删改查时只做两件事:

    • 创建代理对象
    • 在代理对象中调用 selectList()

    配置信息 1:连接数据库的信息,有了它们就能创建 Connection 对象

    ```xml
    <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatisT?useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>
    ```
    

    配置信息 2:有了它们就有了映射配置信息

    ```xml
    <mappers>
        <!--使用 xml-->
        <mapper resource="cn/parzulpan/dao/UserDAO.xml"/>
        <!--使用 注解-->
        <!--指定被注解的 DAO 全限定类名-->
        <mapper class="cn.parzulpan.dao.UserDAOA"/>
    </mappers>
    ```
    

    配置信息 3:有了它们就有了要执行 SQL 语句,即能获取 PreparedStatement 对象,并且还指定了封装的实体类全限定类名

    ```xml
    <!--持久层接口的映射文件-->
    <mapper namespace="cn.parzulpan.dao.UserDAO">
        <select id="findAll" resultType="cn.parzulpan.domain.User">
            select * from user;
        </select>
    </mapper>
    ```
    

    对于上面三个配置信息,需要读取配置文件,用到的就是解析 XML 的技术,这里选用 dom4j

    有了上面的准备,现在可以准备 第二件事 selectList()

    • 根据配置文件信息创建 Connection 对象

      • 注册驱动,获取连接等
    • 获取预处理对象 PrepareStatement

      • 执行 connection.prepareStatement(sql),此时需要 SQL 语句,可以由配置信息 3 得到
    • 执行查询

      • 执行 ResultSet rs = prepareStatement.executeQuery()
    • 遍历结果用于封装

      ArrayList<T> list = new ArrayList<>();
      while (resultSet.next()) {
          T t = (T)Class.forName(配置信息 3 的实体类全限定类名).newInstance();
          // 使用反射封装 
          list.add(t);
      }
      
    • 返回 list

      • return list;

    要想让 selectList() 执行,需要给方法提供两个信息

    • 连接信息
    • 映射信息,包括 执行的 SQL 语句 和 封装结果的实体类全限定类名,可以把这两个信息组合起来定义成一个 Mapper 对象

    这个对象可以用用一个 Map 存储起来:

    • key 是一个 String,值为 cn.parzulpan.dao.UserDAO.findAll
    • value 即这个 Mapper 对象,属性有 String sqlString domainClassPath

    现在需要准备 第一件事 创建代理对象

    // 5. 使用 SqlSession 对象 创建 DAO 接口的的代理对象
    UserDAO userDAO = session.getMapper(UserDAO.class);
    
    // 根据 DAO 接口的字节码创建 DAO 的代理对象
    public <T> getMapper(Class<T> DAOInterfaceClass) {
    
        /**
        loader,类加载器,它使用和被代理类相同的类加载,即  DAOInterfaceClass.getClass().getClassLoader()
    
        interfaces,代理对象要实现的接口字节码数组,它使用和被代理类相同的接口,即 DAOInterfaceClass.getClass().getInterfaces()
    
        handler,如何代理,它需要自己实现,写一个实现接口 InvocationHandler 的类,类中调用第二件事 selectList()
        */
    
        Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler);
    }
    

    自定义实现

    万变不离其宗,看别人是如何实现的?

    package cn.parzulpan.test;
    
    import cn.parzulpan.dao.UserDAO;
    import cn.parzulpan.domain.User;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12-15
     * @Desc :
     */
    
    public class MyBatisTest {
        public static void main(String[] args) throws IOException {
            // 1. 读取配置文件
            // 使用类加载器,它只能读取类路径的配置文件
            // 使用 ServletContext 对象的 getRealPath()
            InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    
            // 2. 创建 SqlSessionFactory 的构建者对象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            // 3. 使用构建者创建工厂对象 SqlSessionFactory
            // 创建工厂对象 使用了建造者模式
            // 优势:把对象的创建细节隐藏,使用者直接调用方法即可拿到对象
            SqlSessionFactory factory = builder.build(is);
    
            // 4. 使用 SqlSessionFactory 生产 SqlSession 对象
            // 生产 SqlSession 对象 使用了工厂模式
            // 优势:解藕,降低了类之间的依赖关系
            SqlSession session = factory.openSession();
    
            // 5. 使用 SqlSession 对象 创建 DAO 接口的的代理对象
            // 创建 DAO 接口的代理对象 使用了代理模式
            // 优势:在不修改源码的基础上对已有方法增强
            UserDAO userDAO = session.getMapper(UserDAO.class);
    
            // 6. 使用代理对象执行方法
            List<User> users = userDAO.findAll();
            users.forEach(System.out::println);
    
            // 7. 释放资源
            session.close();
            is.close();
        }
    }
    
    

    通过 上面的示例代码 和 MyBatis 入门 我们知道,需要实现以下类和接口:

    • class Resources
    • class SqlSessionFactoryBuilder
    • interface SqlSessionFactory
    • interface SqlSession

    引入工具类

    编写 主配置文件

    SqlMapConfig.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--Mybatis 的主配置文件-->
    <configuration>
        <!--配置 MyBatis 环境-->
        <environments default="mysql">
            <!--配置 MySQL 环境-->
            <environment id="mysql">
                <!--配置事务的类型-->
                <transactionManager type="JDBC"/>
                <!--配置连接数据库的信息,用的是数据源(连接池)-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatisT?useSSL=false"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
    
        <!--告知 MyBatis 映射配置的位置-->
        <mappers>
            <!--使用 xml-->
            <mapper resource="cn/parzulpan/dao/UserDAO.xml"/>
            <!--使用 注解-->
            <!--指定被注解的 DAO 全限定类名-->
            <mapper class="cn.parzulpan.dao.UserDAOA"/>
        </mappers>
    </configuration>
    

    注意:由于没有使用 MyBatis 的 jar 包,所以要把配置文件的约束删掉,否则会报错。

    编写 读取配置文件类

    package cn.parzulpan.mybatis.io;
    
    import java.io.InputStream;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : 使用类加载器读取配置文件
     */
    
    public class Resources {
    
        /**
         * 用于加载 xml 文件,并且得到一个流对象
         * @param xmlPath xml 文件路径
         * @return 流对象
         */
        public static InputStream getResourceAsStream(String xmlPath) {
            return Resources.class.getClassLoader().getResourceAsStream(xmlPath);
        }
    }
    
    

    编写 Mapper 类

    package cn.parzulpan.mybatis.cfg;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : 用于封装查询时的必要信息,包括 执行的 SQL 语句 和 封装结果的实体类全限定类名
     */
    
    public class Mapper {
        private String queryString; // sql 语句
        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;
        }
    }
    

    编写 Configuration 配置类

    package cn.parzulpan.mybatis.cfg;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : 核心配置类,包含数据库信息、sql 的 map 集合
     */
    
    public class Configuration {
        private String username;    //用户名
        private String password;    //密码
        private String url; //地址
        private String driver;  //驱动
    
        /**
         要想让 **`selectList()`** 执行,需要给方法提供两个信息
    
         * 连接信息
         * 映射信息,包括 执行的 SQL 语句 和 封装结果的实体类全限定类名,可以把这两个信息组合起来定义成一个 **Mapper 对象**
    
         这个对象可以用用一个 Map 存储起来:
    
         * **key** 是一个 String,值为 `cn.parzulpan.dao.UserDAO.findAll`
         * **value** 即这个 Mapper 对象,属性有 `String sql` 和 `String domainClassPath`
    
         */
    
        private Map<String, Mapper> mappers = new HashMap<>();
    
        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;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getDriver() {
            return driver;
        }
    
        public void setDriver(String driver) {
            this.driver = driver;
        }
    
        public Map<String, Mapper> getMappers() {
            return mappers;
        }
    
        public void setMappers(Map<String, Mapper> mappers) {
            this.mappers.putAll(mappers);   // 注意这里是追加的方式,而不是覆盖
        }
    }
    

    编写 持久层接口的映射文件

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!--持久层接口的映射文件-->
    <mapper namespace="cn.parzulpan.dao.UserDAO">
        <select id="findAll" resultType="cn.parzulpan.domain.User">
            select * from user;
        </select>
    </mapper>
    

    注意:由于没有使用 MyBatis 的 jar 包,所以要把映射文件的约束删掉,否则会报错。

    编写 SqlSessionFactoryBuilder 建造者类

    package cn.parzulpan.mybatis.session;
    
    import cn.parzulpan.mybatis.session.impl.SqlSessionFactoryImpl;
    
    import java.io.InputStream;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : 用于 SqlSessionFactory 的创建
     */
    
    public class SqlSessionFactoryBuilder {
    
        /**
         * 根据传入的流,实现对 SqlSessionFactory 的创建
         * @param is
         * @return
         */
        public SqlSessionFactory build(InputStream is) {
            SqlSessionFactoryImpl factory = new SqlSessionFactoryImpl();
            factory.setIs(is);  // //给 factory 中 is 赋值
            return factory;
        }
    }
    
    

    编写 SqlSessionFactory 接口和实现类

    package cn.parzulpan.mybatis.session;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : SqlSessionFactory 接口
     */
    
    public interface SqlSessionFactory {
    
        /**
         * 创建一个新的 SqlSession 对象
         * @return
         */
        SqlSession openSession();
    }
    
    
    package cn.parzulpan.mybatis.session.impl;
    
    import cn.parzulpan.mybatis.cfg.Configuration;
    import cn.parzulpan.mybatis.session.SqlSession;
    import cn.parzulpan.mybatis.session.SqlSessionFactory;
    import cn.parzulpan.mybatis.utils.XMLConfigBuilder;
    
    import java.io.InputStream;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : SqlSessionFactory 接口的实现类
     */
    
    public class SqlSessionFactoryImpl implements SqlSessionFactory {
        private InputStream is = null;
    
        public InputStream getIs() {
            return is;
        }
    
        public void setIs(InputStream is) {
            this.is = is;
        }
    
        /**
         * 创建一个新的 SqlSession 对象
         *
         * @return
         */
        @Override
        public SqlSession openSession() {
            SqlSessionImpl session = new SqlSessionImpl();
            Configuration cfg = XMLConfigBuilder.loadConfiguration(session, is);    // 调用工具类解析 xml 文件
            session.setCfg(cfg);
            return session;
        }
    }
    
    

    编写 SqlSession 接口和实现类

    package cn.parzulpan.mybatis.session;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : SqlSession 接口,操作数据库的核心对象
     */
    
    public interface SqlSession {
    
        /**
         * 创建 DAO 接口的的代理对象
         * @param DAOInterfaceClass
         * @param <T>
         * @return
         */
        <T> T getMapper(Class<T> DAOInterfaceClass);
    
        /**
         * 释放资源
         */
        void close();
    }
    
    
    package cn.parzulpan.mybatis.session.impl;
    
    import cn.parzulpan.mybatis.cfg.Configuration;
    import cn.parzulpan.mybatis.session.SqlSession;
    import cn.parzulpan.mybatis.session.handler.MapperInvocationHandler;
    import cn.parzulpan.mybatis.utils.DataSourceUtil;
    
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.SQLException;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : SqlSession 接口的实现类
     */
    
    public class SqlSessionImpl implements SqlSession {
        private Configuration cfg;  // 核心配置对象
        private Connection connection;  // 连接对象
    
        public Configuration getCfg() {
            return cfg;
        }
    
        public void setCfg(Configuration cfg) {
            this.cfg = cfg;
            this.connection = DataSourceUtil.getConnection(this.cfg);
        }
    
        public void setConnection(Connection connection) {
            this.connection = connection;
        }
    
        /**
         * 创建 DAO 接口的的代理对象
         *
         * @param DAOInterfaceClass DAO 接口的字节码
         * @return
         */
        @Override
        public <T> T getMapper(Class<T> DAOInterfaceClass) {
    
    //        Proxy.newProxyInstance(DAOInterfaceClass.getClassLoader(),
    //                DAOInterfaceClass.getInterfaces(),
    //                new MapperInvocationHandler(cfg.getMappers(), connection));
    
            T DAOProxy = (T)Proxy.newProxyInstance(DAOInterfaceClass.getClassLoader(),
                    new Class[]{DAOInterfaceClass},
                    new MapperInvocationHandler(cfg.getMappers(), connection));
    
            return DAOProxy;
        }
    
        /**
         * 释放资源
         */
        @Override
        public void close() {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    编写 创建 DAO 的代理对象的类

    package cn.parzulpan.mybatis.session.handler;
    
    import cn.parzulpan.mybatis.cfg.Mapper;
    import cn.parzulpan.mybatis.utils.Executor;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.sql.Connection;
    import java.util.Map;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : 用于创建代理对象是增强方法
     */
    
    public class MapperInvocationHandler implements InvocationHandler {
        private Map<String, Mapper> mappers;    //  key 包含实体类全限定类名和方法名
        private Connection connection;
    
        public MapperInvocationHandler(Map<String, Mapper> mappers, Connection connection) {
            this.mappers = mappers;
            this.connection = connection;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 1. 获取方法名
            String methodName = method.getName();
            // 2. 获取方法所在类名
            String className = method.getDeclaringClass().getName();
            // 3. 组合 key
            String key = className + "." + methodName;
            // 4. 获取 mappers 中的 Mapper 对象
            Mapper mapper = mappers.get(key);
            // 5. 判断是否有 mapper
            if (mapper == null) {
                throw new IllegalArgumentException("传入的参数有误,无法获取执行的必要条件。");
            }
            // 6. 创建 Executor 对象,负责执行 SQL 语句,并且封装结果集
            return new Executor().selectList(mapper, connection);
        }
    }
    

    自定义注解

    package cn.parzulpan.mybatis.annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : 自定义 Select 注解
     */
    
    // 生命周期为 RUNTIME,出现位置为 METHOD
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Select {
        String value(); // 配置 SQL 语句
    }
    

    总结和练习


    自定义 MyBatis 步骤总结

    • 第一步:SqlSessionBuilder 接收 SqlMapConfig.xml 文件流,构建出 SqlSessionFactory 对象;
    • 第二步:SqlSessionFactory 加载解析 SqlMapConfig.xml 文件流,得到连接信息和映射信息,用来生产出真正操作数据库的 SqlSession 对象;
    • 第三步:SqlSession 对象有两大作用,分别是生成接口代理对象和定义通用增删改查方法。
    • 第四步:
      • 第一步:在 SqlSessionImpl 对象的 getMapper() 分两步实现:1. 先用核心配置对象和连接对象;2. 通过代理模式创建出代理类对象;
      • 第二步:在 Executor 工具类 selectList() 等方法分两步实现:1. 得到连接对象;2. 得到 SQL 语句,进行 JDBC 操作。
    • 第五步:封装结果集,变成 Java 对象返回给调用者。

  • 相关阅读:
    知识工程及语义网技术 2020-03-19 (第一节)-构建本体
    知识工程及语义网技术 2020-03-12 (第二节)-构建本体
    知识工程及语义网技术 2020-03-12 (第二节)、RDF(S)、OWL
    知识工程及语义网技术(一)-XML、RDF(S)、OWL-2020.3.5第一节
    知识工程及语义网技术(一)-知识工程,万维网、语义网、本体工程 2020-02-20 (第一节)
    本体
    语义网技术及其应用(四)-欧石燕
    一文深度揭秘3GPP:2G/3G/4G/Pre-5G标准化制定流程是这样的
    3GPP更新5G标准时间表
    一张图了解3GPP
  • 原文地址:https://www.cnblogs.com/parzulpan/p/14141862.html
Copyright © 2020-2023  润新知