Mybatis动态代理与池化技术
概述
在mybatis中通过动态代理的方式,实例化Mapper接口,请求Mapper接口方法时,可直接与该方法对应的xml标签中的sql语句并执行,接下来我们来梳理一下请求的流程
动态代理技术原理
// 定义一个service接口
public interface IUserService {
String getUserNameById(Integer id);
}
// 编译代理类
public class ServiceProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(proxy instanceof IUserService) {
System.out.println("IUserService的实例");
System.out.println("执行方法名称为:" + method.getName());
}
return null;
}
}
// 通过动态代理创建接口实例
public class Application {
public static void main(String[] args) {
// 生成代理类实例 参数解析 1.需要生成实例的接口类加载器 2.类类型实例 3.实现了InvocationHandler接口的实现类
IUserService userService = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},new ServiceProxy());
userService.getUserNameById(1);
}
}
执行结果如下
通过以上的代码实践和执行结果得出结论:动态代理技术虽然不编写接口实现类,但是需要编写代理类。被代理的接口方法执行是通过 invoke(Object target,Method method,Object[] args)统一调用
Mybatis生成Mapper接口实现类
将接口与XML绑定
- 定义接口及其接口方法
- 定义与之对应的xml文件
- 配置xml文件的nameSpace属性为接口类全路径
- 定义Select|Update|Delete|insert 标签,并且将该标签的id值设置为方法的名称
接口执行
在Mybatis中,IUserDao接口的代理类为一个叫MapperProxy类,当该接口调用方法时,通过invoke方法中的Method实例,可以知道接口调用的是那个方法,然后我们通过方法名称,可以获取到xml中与之对应的sql语句
public class MapperProxy <T> implements InvocationHandler, Serializable {
// sql的实际执行者
private SqlSession sqlSession;
// Mapper接口类型
private Class<T> mapperInterface;
// Mapper接口方法映射
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface,Map<Method,MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object类的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 先获取mapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
//缓存和获取MapperMethod
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if(mapperMethod == null) {
mapperMethod = new MapperMethod(sqlSession.getConfiguration(), mapperInterface, method);
methodCache.put(method,mapperMethod);
}
return mapperMethod;
}
}
通过上面的源码,我们可以看到,调用接口方法后,会通过method实例去获取MapperMethod实例,并且最终执行调用
小结
Mybatis通过动态代理技术创建接口实例,并且在代理类存储与方法对应的sql语句。动态代理技术在这里的使用场景为,为一组具有相同操作的接口抽象调用过程。数据库的表不论是那种模型,仅有查询,删除,更新等操作,并且这些操作语法相同,在mybatis中通过编写xmlsql语句,再通过动态代理进行隐藏调用过程进行解耦。
Mybatis的数据源池化技术
无论是使用jpa技术,hibernate,mybatis,都会通过配置数据库连接池的方式来管理数据库连接,可对连接复用,资源控制。在传统的JDBC操作中是通过java.sql.Connection实例来对数据库进行操作,而JDBC的Java标准接口中,是通过实现javax.sqlDataSource接口来获取Connection实例。
Mybatis的数据源配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
// 配置非池化的数据源
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/User_Mapper.xml"/>
</mappers>
在上面的配置中我们可以看到数据源的类型为UNPOOLED,该配置是一个数据源类型的简要名称,我们可以在别名配置器中找到
public Configuration() {
// 增加jdbc 事务工厂
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
// 数据源工厂
typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);
// 无池化的数据源工厂
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
// 池化的数据源工厂
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
}
数据源的类型与对应的类型被封装在别名注册器中,当Mybatis加载配置时,可通过该名称找到对应类型,最后通过反射的形式创建数据源的实例类型
池化数据源
public class PooledDataSource implements DataSource {
/**
* 无池化的数据源
* 基于包装模式,增强UnpooledDataSource的功能
*/
private final UnpooledDataSource dataSource;
private final PoolState state = new PoolState(this);
/**
* 最大的活跃连接数
*/
protected int poolMaximumActiveConnections = 10;
/**
* 最大的空闲连接数
*/
protected int poolMaximumIdleConnections = 5;
/**
* 在强制返回之前,池中连接被检查的时间
* ns数 默认为2s
*/
protected int poolMaximumCheckoutTime = 20000;
/**
* 连接等待时间
*/
protected int poolTimeToWait = 20000;
/**
* 发送到数据的侦测查询
*/
protected String poolPingQuery = "NO PING QUERY SET";
/**
* 是否开启数据侦测查询 默认关闭
*/
protected boolean poolPingEnabled = false;
/**
* 用来配置 poolPingQuery 多久使用一次
*/
protected int poolPingConnectionsNotUsedFor = 0;
private int expectedConnectionTypeCode;
以上是Mybatis池化数据源的连接池配置信息,通过这些配置可以控制连接池的资源使用。
但是在上面的属性中,最重要的属性是UnpooledDataSource类属性和PoolState类属性。
装饰器模式的应用
UnpooledDataSource是一个非池化的数据源,它的作用就是直接获取Connection,在这里是使用装饰器模式,UnpooledDataSource是被装饰的类,而PooledDataSource是装饰器类,它为UnpooledDataSource提供连接池化的额外能力。
享元模式的应用
PoolState类是一个管理活跃Connection和空闲Connection的实例
在该类中,我们可以看到两个分别存储空闲连接和活跃连接的列表,这就是数据源池化的最底层实现,并且下面还有很多属性,用于存储连接池的运行指标
动态代理Connection实现连接复用
通过上面的介绍,我们知道了连接池的实现是通过PoolState类实现的,但是为什么保存连接的列表类型是PooledConnection呢,不是应该是Connection吗?带着这个疑问我们继续深究源码。
发现,这个类竟然是一个实现了InvocationHandler接口的代理类,并且其中竟然还有两个Connection实例,分别是真实的实例和代理实例。接下来我们来梳理一下该类的运行逻辑
通过使用代理模式,监听是否调用了connection.close方法,调用了该方法就会将该方法放回连接池,然后通知其他线程来获取连接,以达到连接的复用。在这里自己代理的方式,可以理解为自己包装自己,自己给自己增加额外的职责,可以从装饰器模式的原理来理解
小结
在mybatis数据源中,基于装饰器模式,享元模式,代理模式实现,不得不说,设计模式的强大