自定义 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 对象返回给调用者。

【MyBatis】自定义 MyBatis的更多相关文章

  1. Mybatis自定义分布式二级缓存实现与遇到的一些问题解决方案!

    先说两句: 我们都知道Mybatis缓存分两类: 一级缓存(同一个Session会话内) & 二级缓存(基于HashMap实现的以 namespace为范围的缓存) 今天呢, 我们不谈一级缓存 ...

  2. springboot多数据源动态切换和自定义mybatis分页插件

    1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...

  3. 自定义Mybatis框架

    项目结构:      https://files-cdn.cnblogs.com/files/mkl7/ownMybatis.zip 1. 创建maven工程并引入坐标: <?xml versi ...

  4. Mybatis框架(9)---Mybatis自定义插件生成雪花ID做为表主键项目

    Mybatis自定义插件生成雪花ID做为主键项目 先附上项目项目GitHub地址 spring-boot-mybatis-interceptor 有关Mybatis雪花ID主键插件前面写了两篇博客作为 ...

  5. 简单自定义mybatis流程!!

    ----简单自定义mybatis流程----一.首先封装daoMapperxml文件和sqlMapconfig配置文件,如何封装:(1).封装我们的Mapper.xml文件,提取名称空间namespa ...

  6. 自定义MyBatis

    自定义MyBatis是为了深入了解MyBatis的原理 主要的调用是这样的: //1.读取配置文件 InputStream in = Resources.getResourceAsStream(&qu ...

  7. 【Mybatis】MyBatis之配置自定义数据源(十一)

    本例是在[Mybatis]MyBatis之配置多数据源(十)的基础上进行拓展,查看本例请先学习第十章 实现原理 1.扩展Spring的AbstractRoutingDataSource抽象类(该类充当 ...

  8. mybatis 自定义缓存 cache

    缓存不管哪个框架都是显得特别的重要,今天自己测试实现了mybatis自定义缓存,从而理解mybatis缓存的工作原理. 首先缓存类要实现Cache接口:具体实现如下package com.ibatis ...

  9. 自定义Mybatis自动生成代码规则

    前言 大家都清楚mybatis-generate-core 这个工程提供了获取表信息到生成model.dao.xml这三层代码的一个实现,但是这往往有一个痛点,比如需求来了,某个表需要增加字段,肯定需 ...

  10. 自定义 Mybatis 框架

    分析流程 1. 引入dom4j <dependencies> <!--<dependency> <groupId>org.mybatis</groupI ...

随机推荐

  1. 【APIO2020】交换城市(Kruskal重构树)

    Description 给定一个 \(n\) 个点,\(m\) 条边的无向连通图,边带权. \(q\) 次询问,每次询问两个点 \(x, y\),求两点间的次小瓶颈路.不存在输出 -1. Hint \ ...

  2. git 远端版本回退

    情景:本地更改推送远端后,想要回退到自己推送之前的某个版本. 比如想回退的分支为 test 分支. 风险:远端回退到某一版本后,之后的所有推送都没了(对应的日志记录也没了).如果是团队开发,不仅自己推 ...

  3. 详解Java中的IO输入输出流!

    目录 本片要点 基本分类 发展史 文件字符流 输出的基本结构 流中的异常处理 异常处理新方式 读取的基本结构 运用输入与输出完成复制效果 文件字节流 缓冲流 字符缓冲流 装饰设计模式 转换流(适配器) ...

  4. 开源OLAP引擎对比

    什么是olap 01.绝大多数请求都是读请求 02.数据以相当大的批次(>1000行)更新,而不是单行更新;或者它根本没有更新 03.数据已添加到数据库,但不会进行修改 04.对于读取,每次查询 ...

  5. Sqlmap 学习笔记1:sqlmap参数

    SQLMP参数分析 1 目录 1.Target Options 2.Requests Options 3.Injection Options 4.Detection Options 5.Techniq ...

  6. 无法获得VMCI驱动程序的版本:句柄无效 (亲测有效! )

    今天在学习Linux 的时候 启动VM时出现了这个问题, 搞了很久终于弄好了, 就写篇博客来记录一下,帮助一下大家,如果对大家有帮助,还请各位哥哥姐姐点个关注,你的支持就是我坚持下去的动力 ! 文章目 ...

  7. tep用户手册帮你从unittest过渡到pytest

    unittest和pytest是Python的2个强大的测试框架,经常用来做UI自动化或接口自动化.unittest是PyCharm的默认集成工具,也是我们大多数人入门自动化的首选框架.pytest提 ...

  8. 卡尔曼滤波学习笔记1-Matlab模拟温度例子--代码比较乱,还需优化

    温度模拟参数选取 xk 系统状态 实际温度 A 系统矩阵 温度不变,为1 B.uk 状态的控制量 无控制量,为0 Zk 观测值 温度计读数 H 观测矩阵 直接读出,为1 wk 过程噪声 温度变化偏差, ...

  9. python初学者-从键盘获取信息

    name = input(">>> 姓名:") QQ = input(">>>QQ: ") phone_num = inpu ...

  10. Demo分享丨看ModelArts与HiLens是如何让车自己跑起来的

    摘要:基于HiLens Kit已经基本开发完成,可部署到HiLens Kit,模型的选择为基于DarkNet53的YOLOv3模型,权重为基于COCO2014训练的数据集,而车道线的检测是基于Open ...