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. linux调度全景指南

  2. QT现场同步

    // 1线程同步 QFutureSynchronizer<void> synchronizer; //2线程1 synchronizer.addFuture(QtConcurrent::r ...

  3. Go语言实现布谷鸟过滤器

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/453 介绍 在我们工作中,如果遇到如网页 URL 去重.垃圾邮件识别 ...

  4. Mac创建Root用户

    1.打开Mac终端管理工具 前往-实用工具-终端 2.用命令的形式创建账户 sudo passwd root 3.输入当前登录用户密码 4.输入root用户密码并验证

  5. C++入门(2):为何还学C++?

    本文首发 | 公众号:lunvey 提及编程语言,最近很火的当属Python和Java,似乎C++没落了,真的是这样吗? 转行做程序员,掌握一门编程语言,也就是职业技能,我相信更多的是在乎未来发展而不 ...

  6. HDOJ-6645(简单题+贪心+树)

    Stay Real HDOJ-6645 由小根堆的性质可以知道,当前最大的值就在叶节点上面,所以只需要排序后依次取就可以了. #include<iostream> #include< ...

  7. Java 学习阶段性感想

    阶段性感想·操千曲而后晓声 回顾 从2月17日 到 今天 4月19日,我算是暂时完成了Java入门的学习了. 从基本语法到面向对象,从常见API到字符串集合,从文件处理到多线程,我学到了很多,很多很多 ...

  8. slickgrid ( nsunleo-slickgrid ) 1 开篇有益

    slickgrid (nsunleo-slickgrid)  1 开篇有益  作为专职的程序猿,自认为是老菜鸟或老民工,以前一直在某浪上写博客,上知天文,下达地理.做了N年的.net,又转Java,从 ...

  9. 2019 GDUT Rating Contest II : Problem B. Hoofball

    题面: 传送门 B. Hoofball Input file: standard input Output file: standard output Time limit: 5 second Memor ...

  10. 攻防世界 reverse Mysterious

    Mysterious  BUUCTF-2019 int __stdcall sub_401090(HWND hWnd, int a2, int a3, int a4) { char v5; // [e ...