【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 ...
随机推荐
- 网络QoS的平衡之道——音视频弱网对抗策略介绍
作者:网易智企云信资深音视频引擎开发工程师 王兴鹤 随着AI和5G的到来,音视频应用将变得越来越广泛,人们对音视频的品质需求也越来越高,视频分辨率已经从高清发展为超高清.VR,视频帧率也已出现60fp ...
- Codeforces Edu Round 66 A-E
A. From Hero to Zero 通过取余快速运行第一步即可.由于\(a \% b (a >= b) <= \frac{a}{2}\).所以总复杂度不超过\(O(log_2n)\) ...
- 开源抓包工具PowerSniff(支持lua,c语言作为脚本实时分析)
做这个程序的意图是wireshark插件编写复杂(虽然也支持lua),而轻量级的工具如smartsniff,minisniff不支持插件化数据分析,各种工具用下来或多或少不顺手.以前写的外挂也都是手工 ...
- Nginx(一):安装与常用命令
简介 Nginx ("engine x") 是一个高性能的 HTTP 和反向代理服务器,特点是占有内存少,并发能 力强,事实上nginx的并发能力确实在同类型的网页服务器中表现 ...
- HCIP --- BGP综合实验
实验要求: 实验拓扑: 一.配置IP地址 L:代表环回地址(loop back 0) Y:代表业务网段的地址(loop back 1) 二.因为BGP基于IGP之上,给AS 2内配置OSPF 在R2上 ...
- 【程序包管理】Linux程序包管理之yum源安装
yum源安装是我们工作中常用的一种方式,它是在Fedora和RedHat以及SUSE中基于rpm的软件包管理器,它可以使系统管理人员交互和自动化地更细与管理RPM软件包,能够从指定的服务器自动下载RP ...
- 测试平台MeterSphere源码入门
前端代码结构 ├── package.json #Vue的模块依赖定义 ├── pom.xml #Maven前后端打包的定义与依赖 ├── public ├── src #前端核心代码 │ ├── a ...
- 10天,从.Net转Java,并找到月薪2W的工作(三)
放弃Offer之后,压力一天比一天打 好点的公司,除了技术追根问底,还对你平时代码实践问的很多.比如问你,redis缓存一般设置多大.问你项目内容的细节,业务流程. 集合.锁.Jvm.多线程高并发.微 ...
- Flowable学习入门
一.Flowable简介 1.Flowable是什么 Flowable是一个使用Java编写的轻量级业务流程引擎.Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标 ...
- Cassandra与Kafka的集成
Cassandra和Kafka经常一起用于微服务架构中.本文将介绍几种Cassandra和Kafka常见的集成模式. 简介 如果您的开发团队乐于接纳微服务架构的优点,那么您就会了解到,Kafk ...