MyBatis SQL日志源码分析

源码

BaseJdbcLogger

打印日志的抽象类。

PreparedStatementLogger

PreparedStatement的日志。

ResultSetLogger

ResultSet的日志。

打印debug的日志

1
2
3
4
5
protected void debug(String text, boolean input) {
if (statementLog.isDebugEnabled()) {
statementLog.debug(prefix(input) + text);
}
}

字符串前缀

1
2
3
4
5
6
7
8
9
10
11
private String prefix(boolean isInput) {
char[] buffer = new char[queryStack * 2 + 2];
Arrays.fill(buffer, '=');
buffer[queryStack * 2 + 1] = ' ';
if (isInput) {
buffer[queryStack * 2] = '>';
} else {
buffer[0] = '<';
}
return new String(buffer);
}

JVM堆内存,堆外内存

概念

JVM可以使用的内存分外2种:堆内存和堆外内存。

堆内存

由JVM负责分配和释放,如果程序没有缺陷代码导致内存泄露,那么就不会遇到java.lang.OutOfMemoryError(OOM)这个错误。

堆外内存

直接分配和释放内存,提高效率。堆外内存意味着把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机)。这样做的结果就是能保持一个较小的堆,以减少垃圾收集对应用的影响。JDK5.0之后,代码中能直接操作本地内存的方式有2种:使用未公开的UnsafeNIOByteBuffer。存储对象生命周期比较长,对象数据结构比较单一。

堆外内存优点

  • 可以扩展至更大的内存空间。
  • 理论上能减少GC暂停时间。因为垃圾回收会暂停其他的工作。
  • 可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。
  • 它的持久化存储可以支持快速重启,同时还能够在测试环境中重现生产数据。

    堆外内存缺点

  • 堆外内存难以控制,如果内存泄漏,那么很难排查。
  • 数据结构不能复杂化,结构单一,最好是基础类型。使用序列化和反序列化,性能比堆内对象还差。
  • 使用堆外内存,不用考虑JVM内存限制,但需要考虑磁盘系统。(HDD或者SSD)

迷宫(回溯算法)

概述

以一个M×N的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。

解题思路

可使用回溯方法,即从入口出发,顺着某一个方向进行探索,若能走通,则继续往前进;否则沿着原路退回,换一个方向继续探索,直至出口位置,求得一条通路。假如所有可能的通路都探索到而未能到达出口,则所设定的迷宫没有通路。
探索迷宫的四个方向:RIGHT为向右,DOWN向下,LEFT向左,UP向上,输出从入口到出口的行走路径。

Kryo

介绍

Kryo是一个快速有效的对象图序列化Java库。它的目标是快速、高效、易使用。该项目适用于对象持久化到文件或数据库中或通过网络传输。Kryo还可以自动实现深浅的拷贝/克隆。 就是直接复制一个对象对象到另一个对象,而不是对象转换为字节然后转化为对象。
Kryo如果设置References=true,序列化的时候会从缓存中获取之前序列化过的对象,从而提高效率。

特点

  1. 序列化、反序列化速度快。
  2. 序列化和反序列化,第1次加载类的全部信息,如果使用代码里reference=true。默认会生成和对象相对应的id值。但第2次、第N次加载类时,会根据id值去缓存中查找相对应的对象。

    示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Kryo kryo = new Kryo(); 
    //是否使用引用,默认开启,使用引用对象会和id进行关联
    kryo.setReferences(false);
    //注册对象,使用java的原生序列化对象
    kryo.register(obj.getClass(), new JavaSerializer());
    // 序列化
    Output output = new Output(new FileOutputStream("file.bin"));
    SomeClass someObject = ...
    kryo.writeObject(output, someObject);
    output.close();
    // 反序列化
    Input input = new Input(new FileInputStream("file.bin"));
    SomeClass someObject = kryo.readObject(input, SomeClass.class);
    input.close();

MyBatis基于接口编程的原理分析

源码

UserMapper

1
2
3
4
5
6
7
8
9
10
11
package org.denger.mapper;  

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.denger.po.User;

public interface UserMapper {

@Select("select * from tab_uc_account where id=#{userId}")
User getUser(@Param("userId") Long userId);
}

application-context.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">


<!-- Provided by annotation-based configuration -->
<context:annotation-config/>

<!--JDBC Transaction Manage -->
<bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg>
<ref bean="dataSource" />
</constructor-arg>
</bean>

<!-- The JDBC c3p0 dataSource bean-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/noah" />
<property name="user" value="root" />
<property name="password" value="123456" />
</bean>

<!--MyBatis integration with Spring as define sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="basePackage" value="org.denger.mapper"></property>
</bean>
</beans>

test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.denger.mapper;  

import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

@ContextConfiguration(locations = { "/application-context.xml"})
public class UserMapperTest extends AbstractJUnit4SpringContextTests{

@Autowired
public UserMapper userMapper;

@Test
public void testGetUser(){
Assert.assertNotNull(userMapper.getUser(300L));
}
}

原理分析

对于以上极其简单代码看上去并无特殊之处,主要亮点在于 UserMapper 居然不用实现类,而且在调用 getUser 的时候,也是使用直接调用了UserMapper实现类,那么Mybatis是如何去实现 UserMapper的接口的呢?
可能你马上能想到的实现机制就是通过动态代理方式,看看MyBatis整个的代理过程吧。
首先在Spring的配置文件中看到下面的Bean。

1
2
3
4
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="basePackage" value="org.denger.mapper"></property>
</bean>

1
2
3
4
5
/**
* BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for
* interfaces and registers them as {@code MapperFactoryBean}. Note that only interfaces with at
* least one method will be registered; concrete classes will be ignored.
* <p>

以上的MapperScannerConfigurer的注释中描述道。
从base包中搜索所有下面所有interface,并将其注册到Spring Bean容器中,其注册的class bean是MapperFactoryBean
看看它的注册过程,从MapperScannerConfigurer#Scanner类中抽取,在初始化以上application-content.xml文件时就会进行调用。
主要用于是搜索base packages下的所有mapper.class,并将其注册至spring的benfinitionHolder中。

最佳调度问题(回溯算法)

概述

掌握调度问题的处理方法,实现最佳调度问题的回溯解决。

解题思路

一个深度为NM叉树。
基本思路:

  • 搜索从开始结点(根结点)出发,以DFS搜索整个解空间。
  • 每搜索完一条路径则记录下besttimebestx[]序列
  • 开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处向纵深方向移至一个新结点,并成为一个新的活结点,也成为当前扩展结点。
  • 如果在当前的扩展结点处不能再向纵深方向扩展,则当前扩展结点就成为死结点。
  • 此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点;直至找到一个解或全部解。

八皇后(回溯算法)

概述

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上(反斜线),问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n × n,而皇后个数也变成n。当且仅当n = 1n ≥ 4时问题有解。

Mybatis一级缓存和二级缓存

一级缓存

一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSessionDefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
MyBatis的一级缓存指的是在一个Session域内,Session为关闭的时候执行的查询会根据SQL为key被缓存,单独使用MyBatis而不继承Spring,使用原生的MyBatis的SqlSessionFactory来构造sqlSession查询,是可以使用以及缓存的。
当参数不变的时候只进行一次查询,参数变更以后,则需要重新进行查询,而清空缓存以后,参数相同的查询过的SQL也需要重新查询,当执行SQL时两次查询中间发生增删改操作,则SqlSession的缓存清空。
如果集成Spring是没有使用一级缓存。原因是一个sqlSession,但是实际上因为我们的dao继承SqlSessionDaoSupport,而SqlSessionDaoSupport内部sqlSession的实现是使用用动态代理实现的,这个动态代理sqlSessionProxy使用一个模板方法封装select()等操作,每一次select()查询都会自动先执行openSession(),执行完后调用close()方法,相当于生成一个新的session实例,所以我们无需手动的去关闭这个session(),当然也无法使用MyBatis的一级缓存,也就是说MyBatis的一级缓存在Spring中是没有作用的。

官方文档

MyBatis SqlSession provides you with specific methods to handle transactions programmatically. But when using MyBatis-Spring your beans will be injected with a Spring managed SqlSession or a Spring managed mapper. That means that Spring will always handle your transactions.
You cannot call SqlSession.commit(), SqlSession.rollback() or SqlSession.close() over a Spring managed SqlSession. If you try to do so, a UnsupportedOperationException exception will be thrown. Note these methods are not exposed in injected mapper classes.

二级缓存

二级缓存,又叫自定义缓存,实现Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。

1
2
3
4
5
public class Configuration {
// ...
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
// ...
}

每次构建SqlSessionFactory对象时都会创建新的Configuration对象,因此,二级缓存的生命周期与SqlSessionFactory是相同的。在创建每个MapperedStatement对象时,都会根据其所属的namespace名称空间,给其分配Cache缓存对象。
二级缓存同样执行增删查改操作,会清空缓存。
二级缓存就是global caching,它超出session范围之外,可以被所有sqlSession共享,它的实现机制和MySQL的缓存一样,开启它只需要在MyBatis的配置文件开启settings里。

回溯算法

引言

寻找问题的解的一种可靠的方法是首先列出所有候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找到所需要的解。理论上,当候选解数量有限并且通过检查所有或部分候选解能够得到所需解时,上述方法是可行的。
不过,在实际应用中,很少使用这种方法,因为候选解的数量通常都非常大(比如指数级,甚至是大数阶乘),即便采用最快的计算机也只能解决规模很小的问题。
对候选解进行系统检查的方法有多种,其中回溯和分枝定界法是比较常用的两种方法。按照这两种方法对候选解进行系统检查通常会使问题的求解时间大大减少(无论对于最坏情形还是对于一般情形)。事实上,这些方法可以使我们避免对很大的候选解集合进行检查,同时能够保证算法运行结束时可以找到所需要的解。因此,这些方法通常能够用来求解规模很大的问题。

概念

回溯算法也叫试探法,它是一种系统地搜索问题的解的方法(一种选优搜索法,按选优条件向前搜索,以达到目标)。当搜索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术称为:回溯法,而满足回溯条件的某个状态的点称为:“回溯点”。
回溯法实际是穷举算法,按问题某种变化趋势穷举下去,如某状态的变化用完还没有得到最优解,则返回上一种状态继续穷举。
许多复杂的,规模较大的问题都可以使用回溯法,回溯算法有“通用解题方法”的美称,其采用了一种“走不通就掉头”思想作为其控制结构,用它可以求出问题的所有解和任意解。
它的应用很广泛,很多算法都用到回溯法,例如,迷宫,八皇后问题,图的m着色总是等都用到回溯法,当然其中还使用了其他策略。

原子性,临界区,互斥量,信号量

原子性

原子性就是说一个操作不可以被中途CPU暂停然后调度,即不能被中断,要不就执行完,要不就不执行。

原子操作

是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生中断之前执行完毕。
一个不正确的知识:“原子操作不需要进行同步控制”。
原子性可以应用于基本数据类型(64位的longdouble不是原子性),对于写入和读取,可以把它们当作原子操作来操作内存。但是,JVM可以将64(longdouble)的读取和写入当作两个分离的32位操作来执行,这个就产生在读取和写入操作中间发生上下文切换问题,导致不同的任务可以看到不正确结果。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×