自己实现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 中引入必要的依赖
<!--指定编译器/source/target的版本-->
<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>
<!--dom4j-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--lombok-简化entity/javabean/pojo 的开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<!--junit-->
<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文件的根元素 <database>
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节点应该是<database>");
}
//连接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":
url = value;
break;
case "username":
username = value;
break;
case "driverClassName":
driverClassName = value;
break;
case "password":
password = value;
break;
default:
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 <T>
* @return
*/
@Override
public <T> 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) {
set.close();
}
if (pre != null) {
pre.close();
}
if (connection != null) {
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,返回一条记录
//说明:在原生的Mybatis中statement不是sql,而是一个要执行的接口方法
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);
}
测试结果: