JDK常用核心原理

概述

在 Mybatis 中,常用的作用就是讲数据库中的表的字段映射为对象的属性,在进入Mybatis之前,原生的 JDBC 有几个步骤:导入 JDBC 驱动包,通过 DriverManager 注册驱动,创建连接,创建 Statement,增删改查,操作结果集,关闭连接

过程详解

首先进行类的加载,通过 DriverManager 注册驱动

Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("");

为什么在这里可以直接注册进去,com.mysql.jdbc.Driver 被加载到 Driver.class ,在 DriverManager 中,首先有一个静态代码块来进行初始化加载 Driver

static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

通过 loadInitialDrivers(),来加载 Driver,拿出 jdbc.drivers,通过 ServiceLoader 读取 Driver.class,读取拿出 driver 和 所有迭代器,一直迭代

private static void loadInitialDrivers() {
String drivers;
// 访问修饰符,在这里把 jdbc.drivers 拿出来
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
} AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 读取拿出 driver 和 所有迭代器
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator(); // 一直进行迭代
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
}); println("DriverManager.initialize: jdbc.drivers = " + drivers); if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

从 Driver 加载完后,就可以得到一个和数据库的连接 connection ,connection 就可以创建一个 Statement,Statement 就可以进行执行 sql 语句,将结果返回一个结果集,获取出来的结果集遍历放进一个 List 集合中

Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from mybatis.user");
while (resultSet.next()) {
int id = resultSet.getInt(1);
String username = resultSet.getString(2);
list.add(new User(id,username));
}

在原生的 JDBC 直接操作中,繁杂的步骤在业务代码中不会使用,而 Mybatis 可以在更好的便利度上使用

JDK动态代理

sql语句解析替换

在 JDK 动态代理中,利用了 Proxy 这个类来实现,在 Proxy 中,有着 newProxyInstance() 方法,创建一个动态代理实例

interface UserMapper {
@Select("select * from mybatis.user where id =#{id}")
List<User> selectUserList();
} public static void main(String[] args) {
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(
JDKMybatis.class.getClassLoader(),
new Class<?>[]{UserMapper.class},
new InvocationHandler() {
/**
* 在 invoke() 方法中就可以进行查找 method,args
* @param proxy 动态代理
* @param method 方法
* @param args 参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 把注解类获取,可以查出注解的值等多种其他值
Select annotation = method.getAnnotation(Select.class);
if (annotation != null) {
String[] value = annotation.value();
System.out.println(Arrays.toString(value));
}
return null;
}
}); userMapper.selectUserList(1);
}

newProxyInstance() 的创建需要三个参数,查看源码,可以知道需要 ClassLoader 类加载器,interfaces 接口(Mapper 接口),InvocationHandler 处理器,来进行处理

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)



把 sql 语句中的参数取出来放进 args,这时需要一个 Map 来进行传值

问题

当在通过反射获取方法的参数名,method.getParameters() 获取出来的参数都是 arg0,arg1...无意义参数



在Java8之前,代码编译为class文件后,方法参数的类型是固定的,但参数名称却丢失了,在编译的时候,需要有编译的选项,javac -parameters 默认是关闭的,需要在 idea 中设置开启,开启完成后,重新编译源文件



这种方式只能临时解决当前环境设置,在其他人运行代码时还是要重新设置

另一种解决方式,在pom文件中添加编译参数:

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>

编译完成后,重新执行,再次通过method.getParameters()获取参数:



解析原来的 sql ,就要把 #{} 给替换掉,这时候可以使用 StringBuffer 类来实现替换

private static String parseSql(String sql, Map<String, Object> argsNameMap) {
// 定义为常量数组
char[] str = {'#', '{'};
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < sql.length(); i++) {
char aloneParseSql = sql.charAt(i); if (str[0] == aloneParseSql) {
int nextIndex = i + 1;
char nextChar = sql.charAt(nextIndex); // # 后应该是 { ,不匹配直接抛出异常
if (str[1] != nextChar) {
throw new RuntimeException(String.format(
"此处应该是:#{\n sql:%s\n index:%d",
stringBuilder.toString(), nextIndex));
}
/*
1 已经解析完的下标
2 解析完的 #{} 内的参数名
3 把对应的 argsName 的值 argsValue 取出来
4 追加到原来的 stringBuilder 中的 sql 语句后面
*/
StringBuilder partStringBuilder = new StringBuilder();
i = partParseSql(partStringBuilder, sql, nextIndex);
String argsName = partStringBuilder.toString();
Object argsValue = argsNameMap.get(argsName);
stringBuilder.append(argsValue.toString());
} // 如果没有条件,直接追加
stringBuilder.append(aloneParseSql);
}
return stringBuilder.toString();
}

在其中需要把需要替换的值,再用 StringBuffer 类来实现

private static int partParseSql(StringBuilder partStringBuilder, String sql, int nextIndex) {
// 由于 nextIndex 当前指针指向的是 { 所以要加一位,把后面内容解析
nextIndex++;
char[] str = {'}'};
for (; nextIndex < sql.length(); nextIndex++) {
char indexSql = sql.charAt(nextIndex);
if (str[0] != indexSql) {
partStringBuilder.append(indexSql);
}
if (str[0] == indexSql) {
return nextIndex;
}
}
throw new RuntimeException(String.format(
"缺少:}\n index:%d",
nextIndex));
}

再重新在 invoke 方法中进行调用,完成 sql 语句的动态拼装

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 把注解类获取,可以查出注解的值等多种其他值
Select annotation = method.getAnnotation(Select.class);
Map<String, Object> argsNameMap = MapBuildArgsName(method, args);
if (annotation != null) {
String[] value = annotation.value();
String sql = value[0];
sql = parseSql(sql, argsNameMap);
System.out.println(sql);
}
return null;
}

Mybatis(一)Porxy动态代理和sql解析替换的更多相关文章

  1. jdk动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)

    代理模式是一种很常见的模式,本文主要分析cglib动态代理的过程 1. 举例 使用cglib代理需要引入两个包,maven的话包引入如下 <!-- https://mvnrepository.c ...

  2. 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  3. Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。

    借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...

  4. Spring <tx:annotation-driven>注解 JDK动态代理和CGLIB动态代理 区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  5. 通过一个工具类更深入理解动态代理和Threadlocal

    动态代理和Threadlocal 一个代理类返回指定的接口,将方法调用指定的调用处理程序的代理类的实例.返回的是一个代理类,由指定的类装载器的定义和实现指定接口指定代理实例调用处理程序最近用到一个工具 ...

  6. JDK动态代理和CGLIB的区别

    Aspect默认情况下不用实现接口,但对于目标对象,在默认情况下必须实现接口 如果没有实现接口必须引入CGLIB库 我们可以通过Advice中添加一个JoinPoint参数,这个值会由spring自动 ...

  7. JDK动态代理和CGLib动态代理简单演示

    JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...

  8. SpringAOP-JDK 动态代理和 CGLIB 代理

    在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类. 1.JDK 动态代理 那么接 ...

  9. JDK动态代理和CGLIB代理的区别

    一.原理区别: java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. 而cglib动态代理是利用asm开源包,对代理对象类的class文件 ...

随机推荐

  1. 记录vue springboot 跨域采坑

    vue配置 域名src\main.js要与config\index.js一样 var axios = require('axios')axios.defaults.baseURL = 'http:// ...

  2. Canvas 如何画一个四分之一圆

    转: Canvas 如何画一个四分之一圆 HTML: Document JS: var c = document.getElementById('ctx') var ctx = c.getContex ...

  3. Vmware虚拟机CentOS7、Ubuntu20系统设置静态IP,且主机和虚拟机系统能相互ping通。

    目录 前言 一.VMware虚拟系统centos7设置静态IP 1.1 打开VMware虚拟网络配置窗口 1.2 方法1:通过DHCP服务给主机动态分配IP,同时设置centos静态IP 1.2.1 ...

  4. CloudQuery v1.3.4 版本更新

    Hello,大家好久不见! 上一个版本(v1.3.3)发布已是春节前的事情了,此次 v1.3.4 是 CloudQuery 社区版在辛丑牛年的第一个版本发布.本次更新增加了新功能,优化了原有功能点.同 ...

  5. 从零学脚手架(四)---babel

    如果此篇对您有所帮助,在此求一个star.项目地址: OrcasTeam/my-cli 接下来介绍一个打包编译过程中一个极为重要的工具--babel. ES6的枷锁 细心的朋友可以知道,在之前打包编译 ...

  6. 01-Spring概述(总览)

    Spring概述 前言 Spring 发展至现在,俨然成为一个生态,但要理解其余的 Spring Boot.Spring Cloud 等框架,需要先对 Spring 的整个体系有一定的理解,因为其余的 ...

  7. vue 实现页面嵌套pdf之vue-pdf插件

    近期vue移动端项目中遇到了页面内,嵌套展示pdf内容.实现方法很多种,可以用iframe嵌套,但不利于引擎优化seo.所以在网上找到了vue-pdf这个插件,这个插件非常好用,其中封装的方法也很多, ...

  8. addEventListener的第三个参数详解

    示例代码 element.addEventListener("mousedown", func, { passive: true });  element.addEventList ...

  9. SQL练习——LeetCode解题和总结(1)

    只用于个人的学习和总结. 178. Rank Scores 一.表信息 二.题目信息 对上表中的成绩由高到低排序,并列出排名.当两个人获得相同分数时,取并列名次,且名词中无断档. Write a SQ ...

  10. mongodb为什么比mysql效率高

    首先是内存映射机制,数据不是持久化到存储设备中的,而是暂时存储在内存中,这就提高了在IO上效率以及操作系统对存储介质之间的性能损耗.(毕竟内存读取最快) 其次,NoSQL并不是不使用sql,只是不使用 ...