【MyBatis】自定义 MyBatis
自定义 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 sql
和String 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
引入工具类
- XMLConfigBuilder.java 用于解析配置文件
- Executor.java 负责执行SQL语句,并且封装结果集
- DataSourceUtil.java 用于创建数据源的
编写 主配置文件
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 操作。
- 第一步:在 SqlSessionImpl 对象的
- 第五步:封装结果集,变成 Java 对象返回给调用者。
【MyBatis】自定义 MyBatis的更多相关文章
- Mybatis自定义分布式二级缓存实现与遇到的一些问题解决方案!
先说两句: 我们都知道Mybatis缓存分两类: 一级缓存(同一个Session会话内) & 二级缓存(基于HashMap实现的以 namespace为范围的缓存) 今天呢, 我们不谈一级缓存 ...
- springboot多数据源动态切换和自定义mybatis分页插件
1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...
- 自定义Mybatis框架
项目结构: https://files-cdn.cnblogs.com/files/mkl7/ownMybatis.zip 1. 创建maven工程并引入坐标: <?xml versi ...
- Mybatis框架(9)---Mybatis自定义插件生成雪花ID做为表主键项目
Mybatis自定义插件生成雪花ID做为主键项目 先附上项目项目GitHub地址 spring-boot-mybatis-interceptor 有关Mybatis雪花ID主键插件前面写了两篇博客作为 ...
- 简单自定义mybatis流程!!
----简单自定义mybatis流程----一.首先封装daoMapperxml文件和sqlMapconfig配置文件,如何封装:(1).封装我们的Mapper.xml文件,提取名称空间namespa ...
- 自定义MyBatis
自定义MyBatis是为了深入了解MyBatis的原理 主要的调用是这样的: //1.读取配置文件 InputStream in = Resources.getResourceAsStream(&qu ...
- 【Mybatis】MyBatis之配置自定义数据源(十一)
本例是在[Mybatis]MyBatis之配置多数据源(十)的基础上进行拓展,查看本例请先学习第十章 实现原理 1.扩展Spring的AbstractRoutingDataSource抽象类(该类充当 ...
- mybatis 自定义缓存 cache
缓存不管哪个框架都是显得特别的重要,今天自己测试实现了mybatis自定义缓存,从而理解mybatis缓存的工作原理. 首先缓存类要实现Cache接口:具体实现如下package com.ibatis ...
- 自定义Mybatis自动生成代码规则
前言 大家都清楚mybatis-generate-core 这个工程提供了获取表信息到生成model.dao.xml这三层代码的一个实现,但是这往往有一个痛点,比如需求来了,某个表需要增加字段,肯定需 ...
- 自定义 Mybatis 框架
分析流程 1. 引入dom4j <dependencies> <!--<dependency> <groupId>org.mybatis</groupI ...
随机推荐
- 【APIO2020】交换城市(Kruskal重构树)
Description 给定一个 \(n\) 个点,\(m\) 条边的无向连通图,边带权. \(q\) 次询问,每次询问两个点 \(x, y\),求两点间的次小瓶颈路.不存在输出 -1. Hint \ ...
- git 远端版本回退
情景:本地更改推送远端后,想要回退到自己推送之前的某个版本. 比如想回退的分支为 test 分支. 风险:远端回退到某一版本后,之后的所有推送都没了(对应的日志记录也没了).如果是团队开发,不仅自己推 ...
- 详解Java中的IO输入输出流!
目录 本片要点 基本分类 发展史 文件字符流 输出的基本结构 流中的异常处理 异常处理新方式 读取的基本结构 运用输入与输出完成复制效果 文件字节流 缓冲流 字符缓冲流 装饰设计模式 转换流(适配器) ...
- 开源OLAP引擎对比
什么是olap 01.绝大多数请求都是读请求 02.数据以相当大的批次(>1000行)更新,而不是单行更新;或者它根本没有更新 03.数据已添加到数据库,但不会进行修改 04.对于读取,每次查询 ...
- Sqlmap 学习笔记1:sqlmap参数
SQLMP参数分析 1 目录 1.Target Options 2.Requests Options 3.Injection Options 4.Detection Options 5.Techniq ...
- 无法获得VMCI驱动程序的版本:句柄无效 (亲测有效! )
今天在学习Linux 的时候 启动VM时出现了这个问题, 搞了很久终于弄好了, 就写篇博客来记录一下,帮助一下大家,如果对大家有帮助,还请各位哥哥姐姐点个关注,你的支持就是我坚持下去的动力 ! 文章目 ...
- tep用户手册帮你从unittest过渡到pytest
unittest和pytest是Python的2个强大的测试框架,经常用来做UI自动化或接口自动化.unittest是PyCharm的默认集成工具,也是我们大多数人入门自动化的首选框架.pytest提 ...
- 卡尔曼滤波学习笔记1-Matlab模拟温度例子--代码比较乱,还需优化
温度模拟参数选取 xk 系统状态 实际温度 A 系统矩阵 温度不变,为1 B.uk 状态的控制量 无控制量,为0 Zk 观测值 温度计读数 H 观测矩阵 直接读出,为1 wk 过程噪声 温度变化偏差, ...
- python初学者-从键盘获取信息
name = input(">>> 姓名:") QQ = input(">>>QQ: ") phone_num = inpu ...
- Demo分享丨看ModelArts与HiLens是如何让车自己跑起来的
摘要:基于HiLens Kit已经基本开发完成,可部署到HiLens Kit,模型的选择为基于DarkNet53的YOLOv3模型,权重为基于COCO2014训练的数据集,而车道线的检测是基于Open ...