手写MyBatis底层机制
读取配置文件,得到数据库连接
思路
- 引入必要的依赖
- 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
- 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
实现
- 引入必要的依赖
<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>
- 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,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/hsp_mybatis?
useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="zy"/>
</database>
- 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
public class ZyConfiguration {
//属性 类加载器
private ClassLoader classLoader =
ClassLoader.getSystemClassLoader();
//读取xml文件信息并处理
public Connection build(String resource) {
Connection connection = null;
//加载配置文件,获取对应的InputStream流
InputStream resourceAsStream =
classLoader.getResourceAsStream(resource);
//解析xml文件
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resourceAsStream);
Element root = document.getRootElement();
//解析rootElement
System.out.println("root= "+root);
return evalDataSource(root);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
//解析xml文件 并返回一个连接
private Connection evalDataSource(Element node) {
Iterator property = node.elementIterator("property");
String driverClassName = null;
String url = null;
String username = null;
String password = null;
//遍历node子节点 获取属性值
while(property.hasNext()){
Element pro = (Element)property.next();
String name = pro.attributeValue("name");
String value = pro.attributeValue("value");
//判断是否得到了name 和 value
if (name == null || value == null){
throw new RuntimeException("property 节点没有设置name 或 value属性");
}
switch (name){
case "driverClassName":
driverClassName = value;
break;
case "url":
url = value;
break;
case "username":
username = 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) {
throw new RuntimeException(e);
}
return connection;
}
}
编写执行器,输入SQL语句,完成操作
思路
- 需要写一个实体类,对应monster表
- 编写接口executor
- 实现接口,编写自己的执行器
- 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作
实现
- 需要写一个实体类,对应monster表
@Setter
@Getter
@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;
}
- 编写接口executor
public interface Executor {
public <T> T query(String statement,Object parameter);
}
- 实现接口,编写自己的执行器
public class ZyExecutor implements Executor{
private ZyConfiguration zyConfiguration = new ZyConfiguration();
@Override
public <T> T query(String sql, Object parameter) {
Connection connection = getConnection();
//查询返回的结果集
ResultSet set = null;
PreparedStatement pre = null;
try {
pre = connection.prepareStatement(sql);
//设置参数,如果参数多,用数组处理
pre.setString(1, parameter.toString());
set = pre.executeQuery();
//把set数据封装到对象 -- 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 (SQLException e) {
throw new RuntimeException(e);
}finally {
try {
if (set != null) {
set.close();
}
if (pre != null) {
pre.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public Connection getConnection(){//Configuration类 返回连接,通过连接对数据库进行操作
return zyConfiguration.build("zy_mybatis.xml");
}
}
- 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作
将Sqlsession封装到执行器
思路
- 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
- 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
实现
- 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
public class ZySqlSession {//搭建连接和执行器之间的桥梁
//执行器
private Executor executor = new ZyExecutor();
//配置
private ZyConfiguration zyConfiguration = new ZyConfiguration();
//操作DB 的具体方法
//SelectOne 返回一条记录-对象
public <T> T selectOne(String statement,Object parameter){
return executor.query(statement,parameter);
}
}
- 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
//操作DB 的具体方法
//SelectOne 返回一条记录-对象
public <T> T selectOne(String statement,Object parameter){
return executor.query(statement,parameter);
}
开发Mapper接口和Mapper.xml
思路
- 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
- 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
- monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
实现
- 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
public interface MonsterMapper {
public Monster getMonsterById(Integer id);
}
- 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
- monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.code_study.mapper.MonsterMapper">
<!-- 实现配置接口方法 getMonsterById-->
<select id="getMonsterById" resultType="com.code_study.entity.Monster">
SELECT * FROM monster WHERE id = ?
</select>
</mapper>
开发MapperBean,可以和Mapper接口相映射
思路
- 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
- 开发 MapperBean类,记录接口信息和接口下的所有方法
- Function类 对应 monsterMapper.xml中的信息
- MapperBean类 对应 MonsterMapper 接口中的信息
实现
- 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
//对应 monsterMapper.xml中的信息
public class Function {
private String sqlType;//sql类型,比如select,insert,update,delete
private String funcName;//方法名
private String sql;//执行的sql语句
private Object resultType;//返回类型
private String parameterType;//入参类型
}
- 开发 MapperBean类,记录接口信息和接口下的所有方法
//对应 MonsterMapper 接口中的信息
public class MapperBean {
private String interfaceName;//接口名
// 接口下的所有方法
private List<Function> functions;
}
- Function类 对应 monsterMapper.xml中的信息
- MapperBean类 对应 MonsterMapper 接口中的信息
在Configuration中解析MapperXML获取MapperBean对象
思路
- 在Configuration 添加方法readMapper(String path)
- 通过 path 读取接口对应的Mapper方法
- 保存接口下所有的方法信息
- 封装成 MapperBean对象
实现
- 在Configuration 添加方法readMapper(String path)
- 通过 path 读取接口对应的Mapper方法
- 保存接口下所有的方法信息
- 封装成 MapperBean对象
//解析MapperXML获取MapperBean对象
//path = xml的路径+文件名 是从类的加载路径计算的(如果放在resource目录下 之间传xml文件名即可)
public MapperBean readMapper(String path) {
MapperBean mapperBean = new MapperBean();
InputStream resourceAsStream = classLoader.getResourceAsStream(path);
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resourceAsStream);
Element root = document.getRootElement();
String namespace = root.attributeValue("namespace");
mapperBean.setInterfaceName(namespace);
List<Function> list = new ArrayList<>();//保存接口下所有的方法信息
//得到root的迭代器
Iterator iterator = root.elementIterator();
while(iterator.hasNext()){
Element e = (Element)iterator.next();
String sqlType = e.getName().trim();
String sql = e.getText().trim();
String funcName = e.attributeValue("id");
String resultType = e.attributeValue("resultType");
//ResultType 返回的是一个Object对象 ->反射
Object instance = Class.forName(resultType).newInstance();
//封装 function 对象
Function function = new Function();
function.setSql(sql);
function.setSqlType(sqlType);
function.setFuncName(funcName);
function.setResultType(instance);
//将封装好的function对象 放入 list中
list.add(function);
mapperBean.setFunctions(list);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return mapperBean;
}
动态代理Mapper方法
思路
- 在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
- 编写动态代理类 实现 InvocationHandler 接口
- 取出mapperBean的functions 遍历
- 判断 当前要执行的方法和function.getFunctionName是否一致
- 调用方法返回 动态代理对象
- 编写SqlSessionFactory 会话工厂,可以返回SqlSession
实现
-
编写动态代理类 实现 InvocationHandler 接口
-
在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
//返回mapper的动态代理对象
public <T> T getMapper(Class<T> clazz){
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[]{clazz},
new ZyMapperProxy(zyConfiguration,clazz,this));
}
- 取出mapperBean的functions 遍历
- 判断 当前要执行的方法和function.getFunctionName是否一致
- 调用方法返回 动态代理对象
public class ZyMapperProxy implements InvocationHandler {
private ZySqlSession zySqlSession;
private String mapperFile;
private ZyConfiguration zyConfiguration;
public ZyMapperProxy(ZySqlSession zySqlSession, Class clazz, ZyConfiguration zyConfiguration) {
this.zySqlSession = zySqlSession;
this.zyConfiguration = zyConfiguration;
this.mapperFile = clazz.getSimpleName() + ".xml";
}
//当执行Mapper接口的代理对象方法时,会执行到invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperBean mapperBean = zyConfiguration.readMapper(this.mapperFile);
//判断是否为当前xml文件对应的接口
if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){
return null;
}
//取出mapperBean的functions
List<Function> functions = mapperBean.getFunctions();
//判断当前的mapperBean 解析对应的MapperXML后,有方法
if (null != functions || 0 != functions.size()){
for (Function function : functions) {
//当前要执行的方法和function.getFunctionName
if (method.getName().equals(function.getFuncName())){
if ("SELECT".equalsIgnoreCase(function.getSqlType())){
return zySqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
}
}
}
}
return null;
}
}
- 编写SqlSessionFactory 会话工厂,可以返回SqlSession
public class ZySqlSessionFactory {
public static ZySqlSession open(){
return new ZySqlSession();
}
}
测试
@Test
public void openSession(){
ZySqlSession zySqlSession = ZySqlSessionFactory.openSession();
System.out.println("zySqlSession= "+zySqlSession);
MonsterMapper mapper = zySqlSession.getMapper(MonsterMapper.class);
Monster monster = mapper.getMonsterById(1);
System.out.println("monster= "+monster);
}