自己实现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 中引入必要的依赖- <properties>
- <project.build.sourdeEncoding>UTF-8</project.build.sourdeEncoding>
- <maven.compiler.source>1.8</maven.compiler.source>
- <maven.compiler.target>1.8</maven.compiler.target>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
-
- <dependency>
- <groupId>dom4j</groupId>
- <artifactId>dom4j</artifactId>
- <version>1.6.1</version>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.49</version>
- </dependency>
-
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.4</version>
- </dependency>
-
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version>
- </dependency>
- </dependencies>
复制代码 (3)创建数据库和表- -- 创建数据库
- CREATE DATABASE `li_mybatis`;
- USE `li_mybatis`;
- -- 创建monster表
- CREATE TABLE `monster`(
- `id` INT NOT NULL AUTO_INCREMENT,
- `age` INT NOT NULL,
- `birthday` DATE DEFAULT NULL,
- `email` VARCHAR(255) NOT NULL,
- `gender` TINYINT NOT NULL,-- 1 male,0 female
- `name` VARCHAR(255) NOT NULL,
- `salary` DOUBLE NOT NULL,
- PRIMARY KEY(`id`)
- )CHARSET=utf8
- -- insert
- 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 配置文件- <?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>
复制代码 (2)创建 MyConfiguration 类,用来读取xml文件,建立连接
因为这里重点是实现 Mybatis 的底层机制,为了简化操作,就不使用数据库连接池了,直接使用原生的connection 连接
- 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" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>url = value;<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>break; case "username":<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>username = value;<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>break; case "driverClassName":<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>driverClassName = value;<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>break; case "password":<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>password = value;<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>break; default:<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </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- package com.li.entity;
- import lombok.*;
- import java.util.Date;
- /**
- * @author 李
- * @version 1.0
- * Monster类和 monster有映射关系
- *
- * 注解说明:
- * @Getter 给所有属性生成 getter方法
- * @Setter 给所有属性生成 setter方法
- * @ToString 生成toString方法
- * @NoArgsConstructor 生成一个无参构造器
- * @AllArgsConstructor 生成一个全参构造器
- * @Data 会生成上述除了无参/全参构造器的所有方法,此外还会生成equals,hashCode等方法
- */
- @Getter
- @Setter
- @ToString
- @NoArgsConstructor
- @AllArgsConstructor
- public class Monster {
-
- private Integer id;
- private Integer age;
- private String name;
- private String email;
- private Date birthday;
- private double salary;
- private Integer gender;
- }
复制代码 (2)Executor 接口- package com.li.limybatis.sqlsession;
- /**
- * @author 李
- * @version 1.0
- */
- public interface Executor {
- //泛型方法
- public <T> T query(String statement, Object parameter);
- }
复制代码 (3)执行器实现类 MyExecutor.java- 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" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>set.close(); } if (pre != null) {<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>pre.close(); } if (connection != null) {<?xml version="1.0" encoding="UTF-8" ?>
- <database>
-
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
- useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="123456"/>
- </database>connection.close(); } } catch (Exception e) { e.printStackTrace(); } } return null; } //编写方法,通过myConfiguration对象返回连接 private Connection getConnection() { Connection connection = myConfiguration.build("my-config.xml"); return connection; }}
复制代码 (4)进行测试- @Test
- public void query() {
- Executor executor = new MyExecutor();
- Monster monster =
- (Monster) executor.query("select * from monster where id = ?", 1);
- System.out.println("monster--" + monster);
- }
复制代码 测试结果:
6.任务阶段3
阶段3任务:将执行器封装到SqlSession
6.1代码实现
(1)创建 MySqlSession 类,将执行器封装到SqlSession中。- package com.li.limybatis.sqlsession;
- /**
- * @author 李
- * @version 1.0
- * MySqlSession:搭建Configuration(连接)和Executor之间的桥梁
- */
- public class MySqlSession {
- //执行器
- private Executor executor = new MyExecutor();
- //配置
- private MyConfiguration myConfiguration = new MyConfiguration();
- //编写方法selectOne,返回一条记录
- public <T> T selectOne(String statement,Object parameter){
- return executor.query(statement, parameter);
- }
- }
复制代码 (2)测试- @Test
- public void selectOne() {
- MySqlSession mySqlSession = new MySqlSession();
- Monster monster =
- (Monster) mySqlSession.selectOne("select * from monster where id=?", 1);
- System.out.println("monster=" + monster);
- }
复制代码 测试结果:

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