(重庆)涂鸦一条街
系列文章:
文章 | 状态 | 时间 | 描述 |
---|---|---|---|
(一)Mybatis 基本使用 | 已复习 | 2022-12-14 | 对Mybtais的基本使用,能够开发 |
(二)Mybatis-config.xml的初始化 | 已复习 | 2023-02-10 | 对我们编写的mybatis配置文件的解析 |
(三)SqlSessionFactory的初始化 | 已复习 | 2023-02-11 | SqlSession会话工厂的初始化 |
(四)Mapper文件的解析 | 已复习 | 2023-02-12 | 主要对我们编写的Mapper.xml进行解析 |
(五)SqlSession的创建 | 已复习 | 2023-02-13 | 主要介绍构建DefaultSqlSessionFactory |
(六)Mapper的接口代理 | 已复习 | 2023-02-14 | 如何通过动态代理来执行我们编写的方法 |
(七)MapperMethod的INSERT分析 | 已复习 | 2023-02-15 | 通过代理对象来执行Insert语句,返回结果 |
(八)MapperMethod的Select分析 | 已复习 | 2023-02-16 | 通过代理对象来执行Select语句,返回结果 |
(九)Mybatis的PreparedStatement | 已复习 | 2023-02-17 | 预处理语句的常见,以及与数据库打交道 |
(十)Mybatis的结果隐射 | 已复习 | 2023-02-18 | 数据库结果与实体类对象的转换 |
(十一)Mybatis的一级缓存与二级缓存 | 已复习 | 2023-02-24 | Mybatis中一级缓存与二级缓存 |
(十二)Mybatis的插件开发及原理分析 | 已复习 | 2023-02-25 | Mybatis中的插件运行机制与开发 |
(十三)Mybatis的四大组件 | 已复习 | 2023-03-04 | Mybatis中的四大组件的梳理 |
(十四)Mybatis的设计模式梳理 | 已复习 | 2023-03-11 | Mybatis中设计模式的整理 |
(十五)Spring-Mybatis整理 | 已复习 | 2023-03-18 | Spring与Mybatis整合 |
Mybatis源码分析补充(一)JDBC详解 | 已复习 | 2023-03-17 | JDBC详解 |
学习的知识?
Java
类的加载机制,以及ClassLoader
的加载过程VFS(Virtual File System)
工具类,屏蔽底层磁盘系统差异ResolverUtil
工具类,完成对类的筛选 <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
package com.shu;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.awt.print.Pageable;
import java.util.List;
/**
* @description:
* @author: shu
* @createDate: 2022/12/13 19:43
* @version: 1.0
*/
@Mapper
@Repository
public interface UserMapper {
/**
* 通过ID查询单条数据
*
* @param id 主键
* @return 实例对象
*/
User queryById(Integer id);
/**
* 分页查询指定行数据
*
* @param user 查询条件
* @param pageable 分页对象
* @return 对象列表
*/
List<User> queryAllByLimit(User user);
/**
* 统计总行数
*
* @param user 查询条件
* @return 总行数
*/
long count(User user);
/**
* 新增数据
*
* @param user 实例对象
* @return 影响行数
*/
int insert(User user);
/**
* 批量新增数据
*
* @param entities List 实例对象列表
* @return 影响行数
*/
int insertBatch(@Param("entities") List<User> entities);
/**
* 批量新增或按主键更新数据
*
* @param entities List 实例对象列表
* @return 影响行数
*/
int insertOrUpdateBatch(@Param("entities") List<User> entities);
/**
* 更新数据
*
* @param user 实例对象
* @return 影响行数
*/
int update(User user);
/**
* 通过主键删除数据
*
* @param id 主键
* @return 影响行数
*/
int deleteById(Integer id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shu.UserMapper">
<resultMap type="com.shu.User" id="UserMap">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
<result property="sex" column="sex" jdbcType="INTEGER"/>
<result property="schoolname" column="schoolName" jdbcType="VARCHAR"/>
</resultMap>
<!-- 通过ID查询单条数据 -->
<select id="queryById" resultMap="UserMap">
select
id,name,email,age,sex,schoolName
from user
where id = #{id}
</select>
<!--分页查询指定行数据-->
<select id="queryAllByLimit" resultMap="UserMap">
select
id,name,email,age,sex,schoolName
from user
<where>
<if test="id != null and id != ''">
and id = #{id}
</if>
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="schoolname != null and schoolname != ''">
and schoolName = #{schoolname}
</if>
</where>
</select>
<!--统计总行数-->
<select id="count" resultType="java.lang.Long">
select count(1)
from user
<where>
<if test="id != null and id != ''">
and id = #{id}
</if>
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="email != null and email != ''">
and email = #{email}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="schoolname != null and schoolname != ''">
and schoolName = #{schoolname}
</if>
</where>
</select>
<!--新增数据-->
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into user(id,name,email,age,sex,schoolName)
values (#{id},#{name},#{email},#{age},#{sex},#{schoolname})
</insert>
<!-- 批量新增数据 -->
<insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">
insert into user(id,name,email,age,sex,schoolName)
values
<foreach collection="entities" item="entity" separator=",">
(#{entity.id},#{entity.name},#{entity.email},#{entity.age},#{entity.sex},#{entity.schoolname})
</foreach>
</insert>
<!-- 批量新增或按主键更新数据 -->
<insert id="insertOrUpdateBatch" keyProperty="id" useGeneratedKeys="true">
insert into user(id,name,email,age,sex,schoolName)
values
<foreach collection="entities" item="entity" separator=",">
(#{entity.id},#{entity.name},#{entity.email},#{entity.age},#{entity.sex},#{entity.schoolname})
</foreach>
on duplicate key update
id=values(id),
name=values(name),
email=values(email),
age=values(age),
sex=values(sex),
schoolName=values(schoolName)
</insert>
<!-- 更新数据 -->
<update id="update">
update user
<set>
<if test="id != null and id != ''">
id = #{id},
</if>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="age != null and age != ''">
age = #{age},
</if>
<if test="sex != null and sex != ''">
sex = #{sex},
</if>
<if test="schoolname != null and schoolname != ''">
schoolName = #{schoolname},
</if>
</set>
where id = #{id}
</update>
<!--通过主键删除-->
<delete id="deleteById">
delete from user where id = #{id}
</delete>
</mapper>
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
mappers>
configuration>
package com.shu;
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 org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
@SpringBootTest
class MybatisDemo02ApplicationTests {
@Test
void contextLoads() {
// 第一阶段:MyBatis的初始化阶段
String resource = "mybatis-config.xml";
// 得到配置文件的输入流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 得到SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 第二阶段:数据读写阶段
try (SqlSession session = sqlSessionFactory.openSession()) {
// 找到接口对应的实现
UserMapper userMapper = session.getMapper(UserMapper.class);
// 组建查询参数
User userParam = new User();
userParam.setSchoolname("Sunny School");
// 调用接口展开数据库操作
List<User> userList = userMapper.queryAllByLimit(userParam);
// 打印查询结果
for (User user : userList) {
System.out.println("name : " + user.getName() + " ; email : " + user.getEmail());
}
}
}
}
到这我们的代码编写完毕,下一步我们来分析其执行过程
过程:流程图
上面我们写了mybatis-config.xml文件,在代码开头我们可以看见进行配置文件的初始化
下面我们来分析一下他说如何拿到mybatis-config.xml这个文件的,在分析之前我们需要里了解一下ClassLoader。
// 第一阶段:MyBatis的初始化阶段
String resource = "mybatis-config.xml";
// 得到配置文件的输入流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
1,引导类加载器 (BootstrapClassLoader)
负责加载系统类(通常从JAR的rt.jar中进行加载),它是虚拟机不可分割的一部分,通常使用C语言实现,引导类加载器没有对应的ClassLoader对象
2,扩展类加载器 (ExtClassLoader)
扩展类加载器用于从jre/lib/txt目标加载“标准的扩展”。可以将jar文件放入该目录,这样即使没有任何类路径,扩展类加载器也可以找到其中的各个类
3,系统类加载器 (AppClassLoader)
系统类加载器用于加载应用类,它在由ClASSPATH环境变量或者-classpath命令行选项设置的类路径的目录或者是jar/ZIP文件里查找这些类
我们可以做个小测试,测试代码如下:
/**
* 类加载机制
*/
@Test
public void ResourcesTest(){
// 应用程序类加载器(Application ClassLoader):用于加载用户类路径(Classpath)上的类,是Java应用程序的类加载器。
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("应用程序类加载器:"+systemClassLoader.toString());
// 扩展类加载器(Extension ClassLoader):用于加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的类。
ClassLoader parent = systemClassLoader.getParent();
System.out.println("扩展类加载器:"+parent.toString());
// 启动类加载器(Bootstrap ClassLoader):用于加载Java运行时环境所需要的类,它加载的类是由C++编写的,并由虚拟机自身启动。
// ClassLoader parentParent = parent.getParent();
// System.out.println("启动类加载器:"+parentParent.toString());
}
注意:Bootstrap ClassLoader会报错,因为Bootstrap ClassLoader是虚拟机的一部分,由C++进行编写
加载顺序
// 第一阶段:MyBatis的初始化阶段
String resource = "mybatis-config.xml";
// 得到配置文件的输入流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
Resources
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
// 去加载我们写的mybatis-config.xml 文件
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
// 没有找到,资源不存在
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
ClassLoaderWrapper
public class ClassLoaderWrapper {
ClassLoader defaultClassLoader;
ClassLoader systemClassLoader;
ClassLoaderWrapper() {
try {
// AppClassLoader
systemClassLoader = ClassLoader.getSystemClassLoader();
} catch (SecurityException ignored) {
// AccessControlException on Google App Engine
}
}
下面一个小案例,来证实我们的猜想?
到这我们需要注意一下getClassLoaders(classLoader))方法,打个断点,调试一手
ClassLoaderWrapper
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
/**
* 获取多个ClassLoader,这一步是必须的,因为,我们就是从这个加载器中获取资源的流的
*五种类加载器:自己传入的、默认的类加载器、当前线程的类加载器、本类的类加载器、系统类加载器
* @param classLoader 我们定义的自己的类加载器
* @return 类加载器的数组
*/
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
/**
* 从一个ClassLoader中获取资源的流,这就是我们的目的
*
* @param resource 资源的地址
* @param classLoader 类加载器
* @return 流
*/
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
ClassLoader
public InputStream getResourceAsStream(String name) {
// 找到文件
URL url = getResource(name);
try {
if (url == null) {
return null;
}
URLConnection urlc = url.openConnection();
InputStream is = urlc.getInputStream();
if (urlc instanceof JarURLConnection) {
JarURLConnection juc = (JarURLConnection)urlc;
JarFile jar = juc.getJarFile();
synchronized (closeables) {
if (!closeables.containsKey(jar)) {
closeables.put(jar, null);
}
}
} else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
synchronized (closeables) {
closeables.put(is, null);
}
}
return is;
} catch (IOException e) {
return null;
}
}
public URL getResource(String name) {
URL url;
// 父类加载器能够找到该文件,由前面我们知道AppClassLoader的父类加载器是ExtClassLoader
if (parent != null) {
url = parent.getResource(name);
} else {
// 通过双亲委派机制找到文件
url = getBootstrapResource(name);
}
// 没有的话
if (url == null) {
url = findResource(name);
}
return url;
}
URLClassLoader
public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
URLClassPath
public URL findResource(String var1, boolean var2) {
// 先去缓存查询一下
int[] var4 = this.getLookupCache(var1);
Loader var3;
// 这里有点不懂,有大神可以讲解?
for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
URL var6 = var3.findResource(var1, var2);
if (var6 != null) {
return var6;
}
}
return null;
}
public InputStream getResourceAsStream(String name) {
// 找到文件
URL url = getResource(name);
try {
if (url == null) {
return null;
}
// 打开连接
URLConnection urlc = url.openConnection();
// 获取流数据
InputStream is = urlc.getInputStream();
// jar包连接
if (urlc instanceof JarURLConnection) {
JarURLConnection juc = (JarURLConnection)urlc;
JarFile jar = juc.getJarFile();
synchronized (closeables) {
if (!closeables.containsKey(jar)) {
closeables.put(jar, null);
}
}
}
// 文件连接
else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
synchronized (closeables) {
closeables.put(is, null);
}
}
return is;
} catch (IOException e) {
return null;
}
}
到这我们文件的解析就完毕了,到这我们就拿到了文件流数据,下篇文章我们来解释SqlSessionFactory的初始化。
说到输入/输出,首先想到的就是对磁盘文件的读写。在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的读操作,因此,Mybatis的io包中提供对磁盘文件读操作的支持。
磁盘文件系统分为很多种,如 FAT、VFAT、NFS、NTFS等。不同文件系统的读写操作各不相同。VFS(Virtual File System)作为一个虚拟的文件系统将各个磁盘文件系统的差异屏蔽了起来,提供了统一的操作接口。这使得上层的软件能够用单一的方式来跟底层不同的文件系统沟通。
private static final Log log = LogFactory.getLog(VFS.class);
public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };
public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<>();
private static class VFSHolder {
// 最终指定的实现类
static final VFS INSTANCE = createVFS();
/**
* 给出一个VFS实现。单例模式
* @return VFS实现
*/
static VFS createVFS() {
// 所有VFS实现类的列表。
List<Class<? extends VFS>> impls = new ArrayList<>();
// 列表中先加入用户自定义的实现类。因此,用户自定义的实现类优先级高
impls.addAll(USER_IMPLEMENTATIONS);
impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
VFS vfs = null;
// 依次生成实例,找出第一个可用的
for (int i = 0; vfs == null || !vfs.isValid(); i++) {
Class<? extends VFS> impl = impls.get(i);
try {
// 生成一个实现类的对象
vfs = impl.newInstance();
// 判断对象是否生成成功并可用
if (vfs == null || !vfs.isValid()) {
if (log.isDebugEnabled()) {
log.debug("VFS implementation " + impl.getName() +
" is not valid in this environment.");
}
}
} catch (InstantiationException | IllegalAccessException e) {
log.error("Failed to instantiate " + impl, e);
return null;
}
}
if (log.isDebugEnabled()) {
log.debug("Using VFS adapter " + vfs.getClass().getName());
}
return vfs;
}
}
在 VFSHolder类的 createVFS方法中,先组建一个 VFS实现类的列表,然后依次对列表中的实现类进行校验。第一个通过校验的实现类即被选中,在组建列表时,用户自定义的实现类放在了列表的前部,这保证了用户自定义的实现类具有更高的优先级。
DefaultVFS 作为默认的 VFS 实现类,其 isValid 函数恒返回 true。因此,只要加载DefaultVFS类,它一定能通过 VFS类中 VFSHolder单例中的校验,并且在进行实现类的校验时 DefaultVFS排在整个校验列表的最后,因此,DefaultVFS成了所有 VFS实现类的保底方案,即最后一个验证,但只要验证一定能通过。
方法:
/**
* 获取相关的路径名
* @param parent 父级路径名
* @return 相关路径名
*/
String getPathNameRelativeTo(VirtualFile parent) {
try {
return invoke(getPathNameRelativeTo, virtualFile, parent.virtualFile);
} catch (IOException e) {
// This exception is not thrown by the called method
log.error("This should not be possible. VirtualFile.getPathNameRelativeTo() threw IOException.");
return null;
}
static VirtualFile getChild(URL url) throws IOException {
Object o = invoke(getChild, VFS, url);
return o == null ? null : new VirtualFile(o);
}
这里使用代理模式来 VirtualFile内部类是 JBoss中 VirtualFile的静态代理类,同样VFS也是JBoss的静态代理类,他是如何确定来保证代理类的呀?
/** Find all the classes and methods that are required to access the JBoss 6 VFS. */
/**
* 初始化JBoss6VFS类。主要是根据被代理类是否存在来判断自身是否可用
*/
protected static synchronized void initialize() {
if (valid == null) {
// 首先假设是可用的
valid = Boolean.TRUE;
// 校验所需要的类是否存在。如果不存在,则valid设置为false
VFS.VFS = checkNotNull(getClass("org.jboss.vfs.VFS"));
VirtualFile.VirtualFile = checkNotNull(getClass("org.jboss.vfs.VirtualFile"));
// 校验所需要的方法是否存在。如果不存在,则valid设置为false
VFS.getChild = checkNotNull(getMethod(VFS.VFS, "getChild", URL.class));
VirtualFile.getChildrenRecursively = checkNotNull(getMethod(VirtualFile.VirtualFile,
"getChildrenRecursively"));
VirtualFile.getPathNameRelativeTo = checkNotNull(getMethod(VirtualFile.VirtualFile,
"getPathNameRelativeTo", VirtualFile.VirtualFile));
// 判断以上所需方法的返回值是否和预期一致。如果不一致,则valid设置为false
checkReturnType(VFS.getChild, VirtualFile.VirtualFile);
checkReturnType(VirtualFile.getChildrenRecursively, List.class);
checkReturnType(VirtualFile.getPathNameRelativeTo, String.class);
}
}
在初始化方法中,会尝试从 JBoss 的包中加载和校验所需要的类和方法,最后,还通过返回值对加载的方法进行了进一步的校验,而在以上的各个过程中,只要发现加载的类、方法不存在或者返回值发生了变化,则认为 JBoss 中的类不可用,在这种情况下,checkNotNull方法和 checkReturnType方法中会调用 setInvalid 方法将 JBoss6VFS的 valid字段设置为 false,表示 JBoss6VFS类不可用。
ResolverUtil是一个工具类,主要功能是完成类的筛选,这些筛选条件可以是:·类是否是某个接口或类的子类;类是否具有某个注解。
/**
* 筛选出指定路径下符合一定条件的类
* @param test 测试条件
* @param packageName 路径
* @return ResolverUtil本身
*/
public ResolverUtil<T> find(Test test, String packageName) {
// 获取起始包路径
String path = getPackagePath(packageName);
try {
// 找出包中的各个文件
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
// 对类文件进行测试
if (child.endsWith(".class")) { // 必须是类文件
// 测试是否满足测试条件。如果满足,则将该类文件记录下来
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
/**
* 判断一个类文件是否满足条件。如果满足则记录下来
* @param test 测试条件
* @param fqn 类文件全名
*/
@SuppressWarnings("unchecked")
protected void addIfMatching(Test test, String fqn) {
try {
// 转化为外部名称
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
// 类加载器
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
// 加载类文件
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) { // 执行测试
// 测试通过则记录到matches属性中
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a " +
t.getClass().getName() + " with message: " + t.getMessage());
}
}