day02-自己实现Mybatis底层机制-01

打印 上一主题 下一主题

主题 876|帖子 876|积分 2628

自己实现Mybatis底层机制-01

主要实现:封装SqlSession到执行器+Mapper接口和Mapper.xml+MapperBean+动态代理Mapper的方法
1.Mybatis整体架构分析

对上图的解读:
1)mybatis 的核心配置文件
​        mybatis-config.xml:进行全局配置,全局只能有一个这样的配置文件
​        XxxMapper.xml 配置多个SQL,可以有多个 XxxMapper.xml 配置文件
2)通过 mybatis-config.xml 配置文件得到 SqlSessionFactory
3)通过 SqlSessionFactory 得到 SqlSession,用 SqlSession 就可以操作数据了
4)SqlSession 底层是 Executor(执行器),有两个重要的实现类
5)MappedStatement 是通过 XxxMapper.xml 来定义的,用来生成 statement 对象
6)参数输入执行并输出结果集,无需动手判断参数类型和参数下标位置,且自动将结果集映射为Java对象
2.搭建开发环境

(1)创建maven项目
(2)在pom.xml 中引入必要的依赖
  1. <properties>
  2.     <project.build.sourdeEncoding>UTF-8</project.build.sourdeEncoding>
  3.     <maven.compiler.source>1.8</maven.compiler.source>
  4.     <maven.compiler.target>1.8</maven.compiler.target>
  5.     <java.version>1.8</java.version>
  6. </properties>
  7. <dependencies>
  8.    
  9.     <dependency>
  10.         <groupId>dom4j</groupId>
  11.         <artifactId>dom4j</artifactId>
  12.         <version>1.6.1</version>
  13.     </dependency>
  14.    
  15.     <dependency>
  16.         <groupId>mysql</groupId>
  17.         <artifactId>mysql-connector-java</artifactId>
  18.         <version>5.1.49</version>
  19.     </dependency>
  20.    
  21.     <dependency>
  22.         <groupId>org.projectlombok</groupId>
  23.         <artifactId>lombok</artifactId>
  24.         <version>1.18.4</version>
  25.     </dependency>
  26.    
  27.     <dependency>
  28.         <groupId>junit</groupId>
  29.         <artifactId>junit</artifactId>
  30.         <version>4.12</version>
  31.     </dependency>
  32. </dependencies>
复制代码
(3)创建数据库和表
  1. -- 创建数据库
  2. CREATE DATABASE `li_mybatis`;
  3. USE `li_mybatis`;
  4. -- 创建monster表
  5. CREATE TABLE `monster`(
  6. `id` INT NOT NULL AUTO_INCREMENT,
  7. `age` INT NOT NULL,
  8. `birthday` DATE DEFAULT NULL,
  9. `email` VARCHAR(255) NOT NULL,
  10. `gender` TINYINT NOT NULL,-- 1 male,0 female
  11. `name` VARCHAR(255) NOT NULL,
  12. `salary` DOUBLE NOT NULL,
  13. PRIMARY KEY(`id`)
  14. )CHARSET=utf8
  15. -- insert
  16. INSERT INTO `monster` VALUES(NULL,200,'2000-11-11','nmw@qq.com',1,'牛魔王',8888);
复制代码
3.设计思路

解读:

  • 传统的方式操作数据库
    1)得到 MySession 对象
    2)调用 MyExecutor 的方法完成操作
    3)MyExecutor 的连接是从 MyConfiguration 获取
  • Mybatis 操作数据库的方式
    1)得到 MySession 对象
    2)不直接调用 MyExecutor 的方法
    3)而是通过 MyMapperProxy 获取 Mapper 对象
    4)调用 Mapper 的方法,完成对数据库的操作
    5)Mapper 最终还是动态代理方式,使用 MyExecutor 的方法完成操作
    6)这里比较麻烦的就是 MyMapperProxy 的动态代理机制如何实现
4.任务阶段1

阶段1任务:通过配置文件,获取数据库连接
4.1分析

4.2代码实现

(1)在src 的 resources目录下创建 my-config.xml,模拟原生的 mybatis 配置文件
  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <database>
  3.    
  4.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  5.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  6.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  7.     <property name="username" value="root"/>
  8.     <property name="password" value="123456"/>
  9. </database>
复制代码
(2)创建 MyConfiguration 类,用来读取xml文件,建立连接
因为这里重点是实现 Mybatis 的底层机制,为了简化操作,就不使用数据库连接池了,直接使用原生的connection 连接
  1. package com.li.limybatis.sqlsession;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.io.SAXReader;import java.io.InputStream;import java.sql.Connection;import java.sql.DriverManager;/** * @author 李 * @version 1.0 * 用来读取xml文件,建立连接 */public class MyConfiguration {    //属性-类的加载器    private static ClassLoader loader = ClassLoader.getSystemClassLoader();    //读取xml文件并处理    public Connection build(String resource) {        Connection connection = null;        try {            //先加载配置文件 my-config.xml,获取对应的InputStream            InputStream stream = loader.getResourceAsStream(resource);            //解析 my-config.xml文件            SAXReader reader = new SAXReader();            Document document = reader.read(stream);            //获取 xml文件的根元素             Element root = document.getRootElement();            System.out.println("root=" + root);            //根据root解析,获取Connection            connection = evalDataSource(root);        } catch (Exception e) {            e.printStackTrace();        }        return connection;    }    //解析 my-config.xml 的信息,并返回 Connection    private Connection evalDataSource(Element node) {        if (!"database".equals(node.getName())) {            throw new RuntimeException("root节点应该是");        }        //连接DB的必要参数        String driverClassName = null;        String url = null;        String username = null;        String password = null;        //遍历node下的子节点,获取其属性值        for (Object item : node.elements("property")) {            //i就是对应的 property节点            Element i = (Element) item;            //property节点的 name属性的值            String name = i.attributeValue("name");            //property节点的 value属性的值            String value = i.attributeValue("value");            //判断值是否为空            if (name == null || value == null) {                throw new RuntimeException("property节点没有设置name或value属性!");            }            switch (name) {                case "url":<?xml version="1.0" encoding="UTF-8" ?>
  2. <database>
  3.    
  4.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  5.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  6.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  7.     <property name="username" value="root"/>
  8.     <property name="password" value="123456"/>
  9. </database>url = value;<?xml version="1.0" encoding="UTF-8" ?>
  10. <database>
  11.    
  12.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  13.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  14.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  15.     <property name="username" value="root"/>
  16.     <property name="password" value="123456"/>
  17. </database>break;                case "username":<?xml version="1.0" encoding="UTF-8" ?>
  18. <database>
  19.    
  20.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  21.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  22.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  23.     <property name="username" value="root"/>
  24.     <property name="password" value="123456"/>
  25. </database>username = value;<?xml version="1.0" encoding="UTF-8" ?>
  26. <database>
  27.    
  28.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  29.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  30.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  31.     <property name="username" value="root"/>
  32.     <property name="password" value="123456"/>
  33. </database>break;                case "driverClassName":<?xml version="1.0" encoding="UTF-8" ?>
  34. <database>
  35.    
  36.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  37.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  38.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  39.     <property name="username" value="root"/>
  40.     <property name="password" value="123456"/>
  41. </database>driverClassName = value;<?xml version="1.0" encoding="UTF-8" ?>
  42. <database>
  43.    
  44.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  45.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  46.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  47.     <property name="username" value="root"/>
  48.     <property name="password" value="123456"/>
  49. </database>break;                case "password":<?xml version="1.0" encoding="UTF-8" ?>
  50. <database>
  51.    
  52.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  53.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  54.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  55.     <property name="username" value="root"/>
  56.     <property name="password" value="123456"/>
  57. </database>password = value;<?xml version="1.0" encoding="UTF-8" ?>
  58. <database>
  59.    
  60.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  61.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  62.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  63.     <property name="username" value="root"/>
  64.     <property name="password" value="123456"/>
  65. </database>break;                default:<?xml version="1.0" encoding="UTF-8" ?>
  66. <database>
  67.    
  68.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  69.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  70.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  71.     <property name="username" value="root"/>
  72.     <property name="password" value="123456"/>
  73. </database>throw new RuntimeException("属性名没有匹配到..");            }        }        //获取连接        Connection connection = null;        try {            Class.forName(driverClassName);            connection = DriverManager.getConnection(url, username, password);        } catch (Exception e) {            e.printStackTrace();        }        return connection;    }}
复制代码
5.任务阶段2

阶段2任务:通过实现执行器机制,对数据表进行操作
5.1分析

我们把对数据库的操作封装到一套Executor机制中,程序具有更好的拓展性,结构更加清晰。这里我们先实现传统的方式连接数据库,即通过MyExecutor直接操作数据库。
5.2代码实现

(1)生成 entity 类 Monster.java
  1. package com.li.entity;
  2. import lombok.*;
  3. import java.util.Date;
  4. /**
  5. * @author 李
  6. * @version 1.0
  7. * Monster类和 monster有映射关系
  8. *
  9. * 注解说明:
  10. * @Getter 给所有属性生成 getter方法
  11. * @Setter 给所有属性生成 setter方法
  12. * @ToString 生成toString方法
  13. * @NoArgsConstructor 生成一个无参构造器
  14. * @AllArgsConstructor 生成一个全参构造器
  15. * @Data 会生成上述除了无参/全参构造器的所有方法,此外还会生成equals,hashCode等方法
  16. */
  17. @Getter
  18. @Setter
  19. @ToString
  20. @NoArgsConstructor
  21. @AllArgsConstructor
  22. public class Monster {
  23.    
  24.     private Integer id;
  25.     private Integer age;
  26.     private String name;
  27.     private String email;
  28.     private Date birthday;
  29.     private double salary;
  30.     private Integer gender;
  31. }
复制代码
(2)Executor 接口
  1. package com.li.limybatis.sqlsession;
  2. /**
  3. * @author 李
  4. * @version 1.0
  5. */
  6. public interface Executor {
  7.     //泛型方法
  8.     public <T> T query(String statement, Object parameter);
  9. }
复制代码
(3)执行器实现类 MyExecutor.java
  1. package com.li.limybatis.sqlsession;import com.li.entity.Monster;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;/** * @author 李 * @version 1.0 */public class MyExecutor implements Executor {    private MyConfiguration myConfiguration = new MyConfiguration();    /**     * 根据sql,返回查询结果     *     * @param sql     * @param parameter     * @param      * @return     */    @Override    public  T query(String sql, Object parameter) {        //获取连接对象        Connection connection = getConnection();        //查询返回的结果集        ResultSet set = null;        PreparedStatement pre = null;        try {            //构建PreparedStatement对象            pre = connection.prepareStatement(sql);            //设置参数,如果参数多,可以使用数组处理            pre.setString(1, parameter.toString());            //查询返回的结果集            set = pre.executeQuery();            //把结果集的数据封装到对象中-monster            //说明:这里做了简化处理,认为返回的结果就是一个monster记录,完善的写法应该使用反射机制            Monster monster = new Monster();            //遍历结果集,将数据封装到monster对象中            while (set.next()) {                monster.setId(set.getInt("id"));                monster.setName(set.getString("name"));                monster.setEmail(set.getString("email"));                monster.setAge(set.getInt("age"));                monster.setGender(set.getInt("gender"));                monster.setBirthday(set.getDate("birthday"));                monster.setSalary(set.getDouble("salary"));            }            return (T) monster;        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (set != null) {<?xml version="1.0" encoding="UTF-8" ?>
  2. <database>
  3.    
  4.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  5.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  6.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  7.     <property name="username" value="root"/>
  8.     <property name="password" value="123456"/>
  9. </database>set.close();                }                if (pre != null) {<?xml version="1.0" encoding="UTF-8" ?>
  10. <database>
  11.    
  12.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  13.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  14.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  15.     <property name="username" value="root"/>
  16.     <property name="password" value="123456"/>
  17. </database>pre.close();                }                if (connection != null) {<?xml version="1.0" encoding="UTF-8" ?>
  18. <database>
  19.    
  20.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  21.     <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
  22.     useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
  23.     <property name="username" value="root"/>
  24.     <property name="password" value="123456"/>
  25. </database>connection.close();                }            } catch (Exception e) {                e.printStackTrace();            }        }        return null;    }    //编写方法,通过myConfiguration对象返回连接    private Connection getConnection() {        Connection connection = myConfiguration.build("my-config.xml");        return connection;    }}
复制代码
(4)进行测试
  1. @Test
  2. public void query() {
  3.     Executor executor = new MyExecutor();
  4.     Monster monster =
  5.             (Monster) executor.query("select * from monster where id = ?", 1);
  6.     System.out.println("monster--" + monster);
  7. }
复制代码
测试结果:
6.任务阶段3

阶段3任务:将执行器封装到SqlSession
6.1代码实现

(1)创建 MySqlSession 类,将执行器封装到SqlSession中。
  1. package com.li.limybatis.sqlsession;
  2. /**
  3. * @author 李
  4. * @version 1.0
  5. * MySqlSession:搭建Configuration(连接)和Executor之间的桥梁
  6. */
  7. public class MySqlSession {
  8.     //执行器
  9.     private Executor executor = new MyExecutor();
  10.     //配置
  11.     private MyConfiguration myConfiguration = new MyConfiguration();
  12.     //编写方法selectOne,返回一条记录
  13.     public <T> T selectOne(String statement,Object parameter){
  14.         return executor.query(statement, parameter);
  15.     }
  16. }
复制代码
(2)测试
  1. @Test
  2. public void selectOne() {
  3.     MySqlSession mySqlSession = new MySqlSession();
  4.     Monster monster =
  5.             (Monster) mySqlSession.selectOne("select * from monster where id=?", 1);
  6.     System.out.println("monster=" + monster);
  7. }
复制代码
测试结果:

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表