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. 使用EF的Code First模式创建模型

    Entity Framework Core Entity Framework (EF) Core 是轻量化.可扩展.开源和跨平台版的常用 Entity Framework 数据访问技术. EF Cor ...

  2. 基于西门子S7-1500的大型焊接机全套程序,使用博图V14打开(带全部注释)

    程序说明:本套程序是在从事自动化行业时候的做的项目的程序,经过在设备上运行测试,其中包含20多个轴的伺服控制以及模拟量,数字量IO的控制,包括扫描枪的读取,属于大型程序,总步数有好几万步. 本程序注释 ...

  3. Oracle RMAN scripts to delete archivelog

    vi del_arch.shexport ORACLE_SID=pdcsdbrman target / cmdfile=/home/oracle/scripts/del_arch.sql log=/h ...

  4. wxWidgets源码分析(9) - wxString

    目录 wxString wxString的中文字符支持 Windows Linux Unicode Linux UTF-8 总结 wxString与通用字符串的转换 wxString对象的创建 将wx ...

  5. rabbitmq如何保证消息可靠性不丢失

    目录 生产者丢失消息 代码模拟 事务 confirm模式确实 数据退回监听 MQ事务相关软文推荐 MQ丢失信息 消费者丢失信息 之前我们简单介绍了rabbitmq的功能.他的作用就是方便我们的消息解耦 ...

  6. dom_bom学习

    1. bom是什么? browser object model(浏览器对象模型) 在浏览器中 window指的就是bom对象 //网页重定向 // window.location.href=" ...

  7. &#128681;数分工作了三年,我干了件很酷的事情

    从17年毕业来,一直都在干数据分析的工作.和很多转行的小伙伴一样,没有对口的科班学习,摸不清数据分析具体情况,起初充满着很多迷茫. 在刚开始的1年半中,都是自己从淘宝买些课程,最多时,网盘放了4-5T ...

  8. slickgrid ( nsunleo-slickgrid ) 2 修正区域选择不能跨冻结列的问题

    slickgrid( nsunleo-slickgrid )  2 修正区域选择不能跨冻结列的问题 周六的时候,留了个小小的尾巴,区域选择的问题进做到了定位: 问题原因,在slickgrid启动冻结之 ...

  9. @WebFilter("")配置servlet访问出现404的原因

    配置 servlet 一共有两种方式 直接在web.xml中配置name 和 url-parttern 使用注解配置servlet 使用注解的方式配置servlet是在servlet3.0之后新增的特 ...

  10. 用 Numba 加速 Python 代码

    原文出自微信公众号:Python那些事 一.介绍 pip install numba Numba 是 python 的即时(Just-in-time)编译器,即当你调用 python 函数时,你的全部 ...