@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class ,Object.class})})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// implement pre-processing if needed
Object returnObject = invocation.proceed();
// implement post-processing if needed
return returnObject;
}
}
Intercepts (mybatis 3.5.11 API)
支持四大组件的处理拦截,实际使用拦截器主要是 Executor。即上述的 @Intercepts.type
ParameterHandler
StatementHandler
Executor
ResultSetHandler
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
/**
* Executor 初始化,同时进行插件增强。
* @see org.apache.ibatis.session.Configuration#newExecutor()
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
Executor executor = new SimpleExecutor(this, transaction);
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 基于JDK Proxy, executor 是经过各个interceptor 层层代理后的结果
// 代理生成是通过 Plugin.wrap(Object target, Interceptor interceptor) 完成的
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public class Plugin implements InvocationHandler {
public static Object wrap(Object target, Interceptor interceptor) {
// 解析@Intercepts ,得到期望拦截的组件和方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果有匹配拦截器的组件和方法,那么就进行代理。 例如: Executor.update()
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 关键实现,关注target 和 interceptor
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 通过 interceptor 把参数暴露出去,代码增强在interceptor 完成。
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
/*
* Look up or generate the designated proxy class.
* Return the cached copy or create the proxy class via the ProxyClassFactory
* Generate the specified proxy class via ProxyGenerator
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* InvocationHandler 作为构造入参,生成代理实例
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
}
}
One of the design goals of logback is to audit and debug complex distributed applications.
It lets the developer place information in a diagnostic context that can be subsequently retrieved by certain logback components.
Chapter 8: Mapped Diagnostic Context
Java 常用的日志工具,logback log4j slf4j 都支持MDC 特性。本文分析 logback MDC。
// MDC operations such as put() and get() affect only the MDC of the current thread, and the children of the current thread.
// Thus, there is no need for the developer to worry about thread-safety or synchronization when programming with the MDC because it handles these issues safely and transparently.
public class LogbackMDCAdapter implements MDCAdapter {
// The internal map is copied so as
// We wish to avoid unnecessarily copying of the map. To ensure
// efficient/timely copying, we have a variable keeping track of the last
// operation.
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
}
ShardingSphere-JDBC 支持分库分表和读写分离。
是一种Datasource 实现,通过托管原有的Datasource,根据策略实现请求分发/路由。本文关注读写分离的主要实现。
spring
shardingsphere
master-slave:
name: ds_ms
master-data-source-name: ds_master
slave-data-source-names:
- ds_slave0
- ds_slave1
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 4.0.0-RC2 之后版本 负载均衡策略配置方式 -->
<master-slave:load-balance-algorithm id="randomStrategy" type="RANDOM" />
<master-slave:data-source id="masterSlaveDataSource" master-data-source-name="ds_master" slave-data-source-names="ds_slave0, ds_slave1" strategy-ref="randomStrategy">
<master-slave:props>
<prop key="sql.show">true</prop>
<prop key="executor.size">10</prop>
</master-slave:props>
</master-slave:data-source>
</beans>
监控、动态配置等辅助功能,在开发和测试场景中,并不需要。但是会在spring 启动加载过程中拖慢启动速度,开发、自测效率也受影响。
方案:配置spring profile,排除这些配置文件。在开发环境中,只加载关注的配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
profile="!speed">
<!-- 开发环境不需要的配置 -->
<!-- 例如:缓存,连接redis io操作 -->
<!-- 例如:mq 消费,连接broker io操作 -->
</beans>
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
JVM 配置 -Dspring.profiles.active="speed,profile2"
Bean definition profiles provide a mechanism in the core container that allows for registration of different beans in different environments.
关键类:org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
/**
* 解析并注册beans 标签下声明的bean。 Since:3.1
*/
protected void doRegisterBeanDefinitions(Element root) {
if (this.delegate.isDefaultNamespace(root)) {
// "profile"
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
// profile 格式解析,支持分隔符",; "
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 判断profile 是否激活,如果不符合快速失败,直接return
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 正常解析和注册 bean
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
}
注解可以在类和方法上使用。
// Spring condition match 特性实现选择性加载, Since:4.0
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
profile 思想应用广泛,常见的还有maven profile。
spring profile 提供一整套环境切换的方案,更加简化了工程治理。
本文作为 Code Insight 目录,梳理 h2 数据库的知识点以及感兴趣的实现细节。
作为一个教学演示用的数据库,性能优化肯定不是其优势,重点在协议、SQL规范实现的思路和解决方案上。❌
h2 数据库提供DBMS 完整的实现、丰富的特性和其简练的实现,通过了解其设计思路,举一反三,在日常开发设计、MySQL 数据库深度研究都有借鉴意义。✔
核心实现的原理分析会持续更新、补充链接。
h2 数据库支持嵌入式(开源产品 demo 演示)和服务器模式,可以使用磁盘存储(B 树索引存储引擎、日志结构存储引擎)或内存数据库,并提供事务支持和多版本并发控制(MVCC)。
同时还提供了一个基于浏览器的控制台应用程序(JavaWeb),支持加密数据库和全文搜索(based on Apache Lucene)等扩展特性。功能非常丰富,使用Java 编写,可以作为学习数据库原理和架构的范例。
H2 使用教程 10min 带你玩转 H2 数据库 😀
H2 支持的SQL 语法 增删改查、存储过程、触发器等,兼容 ANSI-SQL89 规范。
H2 特性介绍和原理 Code Insight 主要参考资料
目录结构参考 官网介绍架构 的Top-down Overview。
结合关系型数据库和 DBMS 相关的知识点,理论结合实际,深入理解数据库思想。
org.h2.Driver
org.h2.engine.SessionRemote
参考 Database URL Overview URL 连接和配置示例
org.h2.command.Parser
SQL 语法解释器使用递归下降分析器(recursive-descent),按照语法规则解析输入的文本。有性能问题,优点是易于实现和理解。
package org.h2.command.dml
org.h2.expression.Expression
h2 没有生成查询 IR(中间表示)这一中间步骤,而是直接生成一个命令执行对象。然后对命令对象进行一些优化步骤(org.h2.expression.Expression#optimize),生成更有效的命令。
Insight H2 database auto increment
org.h2.table.RegularTable
org.h2.mvstore.db.MVTable
org.h2.index.PageBtreeIndex
RegularTable 采用常用的数据表实现方案,使用B Tree 索引结构,聚集索引数据存储等。
MVTable 是基于新一代的存储引擎 MVStore 实现的数据表实现。
Undo log, 撤销日志是每个会话独立的,用于回滚操作或撤销失败的更改。
redo log, 重做日志也是每个会话独立的,用于在崩溃后恢复数据库。
transaction log, 事务日志是在所有会话之间共享的(MVCC),用于记录数据库的所有更改。
基于 B-tree 的存储引擎,使用磁盘页面(块)存储数据。
B树是一种树状数据结构,用于组织和存储数据,可用于在大量数据集中快速查找和访问数据。针对磁盘存储介质,实现数据快速检索和更新。
笔记 h2database BTree 设计实现与查询优化思考
MVStore 是一种持久化的、基于日志结构的键值存储。用作新版本 H2 的默认存储引擎。
H2 数据库官网特有一章节用来描述 MVStore。
这种引擎将修改的数据缓存在内存中,然后在累积足够的修改后,将它们一次性写入磁盘。这种方式可以提高写入性能,特别是对于不支持小随机写入的文件系统和存储系统(如Btrfs),以及SSD。
每个修改集合称为一个“chunk”,其中包含了所有被修改的B树的父节点和根节点,以及元数据。
为了重用磁盘空间,会压缩具有最少活动数据的chunk。与传统存储引擎相比,这种引擎更简单、更灵活,并且通常需要更少的磁盘操作。
org.h2.store.FileStore
org.h2.mvstore.OffHeapStore
文件抽象层,方便存储引擎层进行操作。封装了seek、readFully、write、sync 等方法,屏蔽了具体存储(也可以使用堆外存储)的实现细节。
ByteBuffer.allocateDirect