当前位置: 首页 > news >正文

手写mybatis

手写mybatis

https://gitee.com/laomaodu/handwritten-mybatis

内容回顾

sqlsession线程不安全
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { ​ notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); ​ /// public abstract class TransactionSynchronizationManager { /// private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources"); /// private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations"); /// private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name"); /// private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status"); /// private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level"); /// private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active"); /// /// public TransactionSynchronizationManager() { /// } //从 Spring 的事务上下文里,取出当前线程绑定的 SqlSession SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); ​ SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } ​ LOGGER.debug(() -> "Creating a new SqlSession"); session = sessionFactory.openSession(executorType); ​ registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); ​ return session; } ​

线程私有

session = sessionFactory.openSession(executorType); //把 SqlSession 放进 SqlSessionHolder //绑定到 Spring 的事务资源管理器 //注册事务同步回调(事务结束时自动清理) registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
​ /** * 将新创建的 SqlSession 注册到 Spring 的事务同步管理器中 * * 作用: * 1)让 SqlSession 绑定到当前线程事务上下文(ThreadLocal) * 2)保证事务内复用同一个 SqlSession * 3)事务提交/回滚时自动关闭 SqlSession */ private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; if (TransactionSynchronizationManager.isSynchronizationActive()) { // 获取 MyBatis 的运行环境对象 Environment // Environment 内部包含: // * - DataSource(数据源) // * - TransactionFactory(事务工厂) Environment environment = sessionFactory.getConfiguration().getEnvironment(); //事务由spring来实现 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]"); /// 创建 SqlSessionHolder,封装 SqlSession holder = new SqlSessionHolder(session, executorType, exceptionTranslator); // 核心操作:绑定资源到 Spring 的事务管理器中 //当前线程的事务上下文中存入 SqlSessionHolder // 后续 Mapper 调用都会复用这个 SqlSession TransactionSynchronizationManager.bindResource(sessionFactory, holder); //册事务同步回调 SqlSessionSynchronization TransactionSynchronizationManager .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); holder.requested(); } else { if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional"); } else { throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } } else { LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } ​ }

线程缓存使用

一个关掉一个自减

手写mybatis v1.0

加深理解

jdbc单纯操作

1.重复代码

2.耦合度高

3.返回值无处理

借鉴

定义sqlSession

​ public class GPSqlSession { private GPConfiguration configuration; ​ private GPExecutor executor; ​ public GPSqlSession(GPConfiguration configuration, GPExecutor executor){ this.configuration = configuration; this.executor = executor; } /// 获取数据的地方 public <T> T selectOne(String statementId, Object paramater){ // 根据statementId拿到SQL String sql = GPConfiguration.sqlMappings.getString(statementId); if(null != sql && !"".equals(sql)){ return executor.query(sql, paramater ); } return null; } //返回代理的proxy->invoke计算出sql com.gupaoedu.mybatis.v1.mapper.BlogMapper.selectBlogById=select * from blog where bid = %d public <T> T getMapper(Class clazz){ return configuration.getMapper(clazz, this); } }

定义Executor

package com.gupaoedu.mybatis.v1; ​ import com.gupaoedu.mybatis.v1.mapper.Blog; ​ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; ​ public class GPExecutor { public <T> T query(String sql, Object paramater) { Connection conn = null; Statement stmt = null; Blog blog = new Blog(); ​ try { // 注册 JDBC 驱动 Class.forName("com.mysql.jdbc.Driver"); ​ // 打开连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "123456"); ​ // 执行查询 stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(String.format(sql, paramater)); ​ // 获取结果集 while (rs.next()) { Integer bid = rs.getInt("bid"); String name = rs.getString("name"); Integer authorId = rs.getInt("author_id"); blog.setAuthorId(authorId); blog.setBid(bid); blog.setName(name); } System.out.println(blog); ​ rs.close(); stmt.close(); conn.close(); } catch (SQLException se) { se.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (stmt != null) stmt.close(); } catch (SQLException se2) { } try { if (conn != null) conn.close(); } catch (SQLException se) { se.printStackTrace(); } } return (T)blog; } } ​

mapper

属性文件解析

com.gupaoedu.mybatis.v1.mapper.BlogMapper.selectBlogById=select * from blog where bid = %d

config保存所有信息

解析sql资源文件

sqlsession

1.提供单条查询

executor

需要sql语句再config里面

​ public class TestMain { public static void main(String[] args) { GPConfiguration configuration = new GPConfiguration(); GPExecutor executor = new GPExecutor(); GPSqlSession sqlSession = new GPSqlSession(configuration, executor); Blog blog = sqlSession.selectOne("com.gupaoedu.mybatis.v1.mapper.BlogMapper.selectBlogById", 1); System.out.println(blog); } }

executor执行

先跑通

最基础

代理方式调用

V1Mapper mapper = sqlSession.getMapper(V1Mapper.class); String sql = mapper.selectBlogById(1);

接下来

public <T> T getMapper(Class clazz){ return configuration.getMapper(clazz, this); }
public class GPConfiguration { public static final ResourceBundle sqlMappings; ​ static{ sqlMappings = ResourceBundle.getBundle("v1sql"); } ​ public <T> T getMapper(Class clazz, GPSqlSession sqlSession) { return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, new GPMapperProxy(sqlSession)); } } ​
public class GPMapperProxy implements InvocationHandler { private GPSqlSession sqlSession; ​ public GPMapperProxy(GPSqlSession sqlSession){ this.sqlSession = sqlSession; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String mapperInterface = method.getDeclaringClass().getName(); String methodName = method.getName(); String statementId = mapperInterface + "." + methodName; return sqlSession.selectOne(statementId, args[0]); } } ​

迁移

继续改造

手写2.0

statem细化

参数处理

结果集处理

数据库硬编码

public static void main(String[] args) { SqlSessionFactory factory = new SqlSessionFactory(); DefaultSqlSession sqlSession = factory.build().openSqlSession(); // 获取MapperProxy代理 BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog = mapper.selectBlogById(1); ​ System.out.println("第一次查询: " + blog); System.out.println(); blog = mapper.selectBlogById(1); System.out.println("第二次查询: " + blog); }
StatementHandler

解决硬编码过多

/** * 获取连接 * @return * @throws SQLException */ private Connection getConnection() { String driver = Configuration.properties.getString("jdbc.driver"); String url = Configuration.properties.getString("jdbc.url"); String username = Configuration.properties.getString("jdbc.username"); String password = Configuration.properties.getString("jdbc.password"); Connection conn = null; try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; }

无连接器

职责细分

缓存和默认

@Override public <T> T query(String statement, Object[] parameter, Class pojo) { // 计算CacheKey CacheKey cacheKey = new CacheKey(); cacheKey.update(statement); cacheKey.update(joinStr(parameter)); // 是否拿到缓存 if (cache.containsKey(cacheKey.getCode())) { // 命中缓存 System.out.println("【命中缓存】"); return (T)cache.get(cacheKey.getCode()); }else{ // 没有的话调用被装饰的SimpleExecutor从数据库查询 Object obj = delegate.query(statement, parameter, pojo); cache.put(cacheKey.getCode(), obj); return (T)obj; } }
@Override public <T> T query(String statement, Object[] parameter, Class pojo) { StatementHandler statementHandler = new StatementHandler(); return statementHandler.query(statement, parameter, pojo); }

Executor 负责“调度流程”,Handler 负责“具体干活”,这是典型的职责拆分设计。

参数处理paramteHadnle

结果集处理

private ResultSetHandler resultSetHandler = new ResultSetHandler(); ​ public <T> T query(String statement, Object[] parameter, Class pojo){ // JDBC数据库连接对象 Connection conn = null; // 预编译SQL执行对象 PreparedStatement preparedStatement = null; // 查询结果临时存储 Object result = null; ​ try { conn = getConnection(); ​ preparedStatement = conn.prepareStatement(statement); // 参数处理器封装 ​ ParameterHandler parameterHandler = new ParameterHandler(preparedStatement); //将参数填充到SQL占位符 ? 中 // //Java 默认行为: ​ // //对象作为参数传递时,传的是引用值(指针) parameterHandler.setParameters(parameter); preparedStatement.execute(); try { //结果处理 result = resultSetHandler.handle(preparedStatement.getResultSet(), pojo); } catch (Exception e) { e.printStackTrace(); } return (T)result; } catch (Exception e){ e.printStackTrace(); } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } // 只在try里面return会报错 return null; }

config增强

public class Configuration { // SQL映射关系配置,使用注解时不用重复配置 public static final ResourceBundle sqlMappings; // 全局配置 public static final ResourceBundle properties; // 维护接口与工厂类关系 public static final MapperRegistry MAPPER_REGISTRY = new MapperRegistry(); // 维护接口方法与SQL关系 public static final Map<String, String> mappedStatements = new HashMap<>(); ​ // 插件 private InterceptorChain interceptorChain = new InterceptorChain(); // 所有Mapper接口 private List<Class<?>> mapperList = new ArrayList<>(); // 类所有文件 private List<String> classPaths = new ArrayList<>(); ​ static{ sqlMappings = ResourceBundle.getBundle("v2sql"); properties = ResourceBundle.getBundle("mybatis"); }

注解实现

缓存实现

public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; // 根据全局配置决定是否使用缓存装饰 this.executor = configuration.newExecutor(); }
/** * 创建执行器,当开启缓存时使用缓存装饰 * 当配置插件时,使用插件代理 * @return */ public Executor newExecutor() { Executor executor = null; if (properties.getString("cache.enabled").equals("true")) { executor = new CachingExecutor(new SimpleExecutor()); }else{ executor = new SimpleExecutor(); } ​ // 目前只拦截了Executor,所有的插件都对Executor进行代理,没有对拦截类和方法签名进行判断 if (interceptorChain.hasPlugin()) { return (Executor)interceptorChain.pluginAll(executor); } return executor; } ​

插件

MyBatis V2 的插件系统采用*责任链模式 + JDK 动态代理*实现,允许用户在 SQL 执行的关键节点插入自定义逻辑。

┌─────────────────────────────────────────────────────────────┐ │ 插件核心组件 │ ├─────────────────────────────────────────────────────────────┤ │ @Intercepts → 注解标记,指定拦截的方法名 │ │ Interceptor → 拦截器接口,定义拦截逻辑 │ │ InterceptorChain → 拦截器链,管理多个插件的层层代理 │ │ Plugin → JDK动态代理类,执行实际的拦截判断 │ │ Invocation → 方法调用封装,包装目标方法信息 │ └─────────────────────────────────────────────────────────────┘
/** * 拦截器接口,所有自定义拦截器必须实现此接口 * @Author: qingshan */ public interface Interceptor { /** * 插件的核心逻辑实现 * @param invocation * @return * @throws Throwable */ Object intercept(Invocation invocation) throws Throwable; ​ /** * 对被拦截对象进行代理 * @param target * @return */ Object plugin(Object target); } ​
  • intercept(): 执行拦截逻辑,通过Invocation.proceed()调用原方法

  • plugin(): 生成代理对象,通常调用Plugin.wrap(target, this)

@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Intercepts { String value(); // 指定要拦截的方法名 }
@Intercepts("query") // 拦截名为 "query" 的方法 public class MyPlugin implements Interceptor { // ... }

Plugin 代理类

public class Plugin implements InvocationHandler { private Object target; // 被代理对象 private Interceptor interceptor; // 拦截器 // 创建代理对象 public static Object wrap(Object obj, Interceptor interceptor) { Class clazz = obj.getClass(); return Proxy.newProxyInstance( clazz.getClassLoader(), clazz.getInterfaces(), new Plugin(obj, interceptor) ); } // 方法调用拦截 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 检查是否有@Intercepts注解 if (interceptor.getClass().isAnnotationPresent(Intercepts.class)) { // 判断方法名是否匹配 if (method.getName().equals( interceptor.getClass().getAnnotation(Intercepts.class).value())) { // 进入拦截逻辑 return interceptor.intercept(new Invocation(target, method, args)); } } // 非拦截方法,执行原逻辑 return method.invoke(target, args); } }

Invocation 封装类

public class Invocation { private Object target; // 目标对象 private Method method; // 目标方法 private Object[] args; // 方法参数 public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); // 调用原方法 } // Getter 方法... }

作用:封装被拦截方法的完整信息,让拦截器可以获取参数并控制是否执行原方法。

InterceptorChain 拦截器链

public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<>(); // 添加拦截器 public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } // 层层代理:对每个拦截器都进行a一次代理包装 public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); // 代理包裹代理 } return target; } }
原始 Executor ↓ proxy Plugin3(拦截器3) ← 最外层代理 ↓ proxy Plugin2(拦截器2) ↓ proxy Plugin1(拦截器1) ← 最内层代理 ↓ invoke SimpleExecutor ← 原始对象
plugin.path=com.gupaoedu.mybatis.v2.interceptor.MyPlugin

初始化

文件位置:com.gupaoedu.mybatis.v2.session.Configuration

String pluginPathValue = properties.getString("plugin.path"); String[] pluginPaths = pluginPathValue.split(","); for (String plugin : pluginPaths) { // 反射实例化拦截器 Interceptor interceptor = (Interceptor) Class.forName(plugin).newInstance(); interceptorChain.addInterceptor(interceptor); }
// 创建执行器时应用插件代理 public Executor newExecutor() { Executor executor = new SimpleExecutor(); // 如果有插件,对 Executor 进行层层代理 if (interceptorChain.hasPlugin()) { return (Executor) interceptorChain.pluginAll(executor); } return executor; }
┌────────────────┐ │ 加载配置文件 │ └───────┬────────┘ ↓ ┌────────────────┐ │ 解析plugin.path │ └───────┬────────┘ ↓ ┌────────────────┐ ┌──────────────────┐ │ 反射创建拦截器 │────→│ InterceptorChain │ └───────┬────────┘ │ .addInterceptor()│ ↓ └──────────────────┘ ┌────────────────┐ │ newExecutor() │ └───────┬────────┘ ↓ ┌────────────────┐ ┌──────────────────┐ │ 创建SimpleExecutor│──→│ interceptorChain │ └───────┬────────┘ │ .pluginAll() │ ↓ └────────┬─────────┘ ┌────────────────┐ ↓ │ 返回代理后的Executor│←──层层包装Plugin代理 └────────────────┘

流程

com.gupaoedu.mybatis.v2.interceptor.MyPlugin`

package com.gupaoedu.mybatis.v2.interceptor; ​ import com.gupaoedu.mybatis.v2.annotation.Intercepts; import com.gupaoedu.mybatis.v2.plugin.Interceptor; import com.gupaoedu.mybatis.v2.plugin.Invocation; import com.gupaoedu.mybatis.v2.plugin.Plugin; ​ import java.util.Arrays; ​ /** * 自定义插件 - 拦截 query 方法,打印 SQL 和参数 */ @Intercepts("query") // 指定拦截 Executor.query() 方法 public class MyPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 获取方法参数 String statement = (String) invocation.getArgs()[0]; Object[] parameter = (Object[]) invocation.getArgs()[1]; Class pojo = (Class) invocation.getArgs()[2]; // 自定义逻辑:打印 SQL 信息 System.out.println("进入自定义插件:MyPlugin"); System.out.println("SQL:[" + statement + "]"); System.out.println("Parameters:" + Arrays.toString(parameter)); // 继续执行原方法(可在此处做权限校验、修改参数等) return invocation.proceed(); } ​ @Override public Object plugin(Object target) { // 使用 Plugin 工具类创建代理 return Plugin.wrap(target, this); } }

http://www.jsqmd.com/news/354849/

相关文章:

  • 新手PPT救星!5款零门槛生成工具实测:教育党职场人闭眼入,省时80% - 品牌测评鉴赏家
  • 分析2026年别墅门厂家十大排名,看哪家费用更合理 - 工业品牌热点
  • 分析法式装修设计费用多少钱,重庆哪家性价比高 - 工业品网
  • Java 中使用 Alibaba Fastjson 解析泛型类型 JsonResult<SysUserDTO> 的问题
  • 讲讲风机进风口价格合理的供应商,怎么选择有攻略 - mypinpai
  • 完整教程:基于python新闻数据分析可视化系统 Hadoop 新闻平台 爬虫 情感分析 舆情分析 可视化 Django框架 vue框架 机器学习 大数据毕业设计✅
  • 解读全国拉力试验机优质生产商,桌上型拉力试验机怎么收费 - 工业设备
  • 2026重庆装修公司费用对比,十二分装饰能免费上门量房设计吗 - 工业品牌热点
  • 分享ROHS2.0检测仪推荐厂家,高灵敏度产品盘点 - 工业品牌热点
  • 2025年-2026年全屋定制品牌推荐:基于长期稳定性评测,涵盖居家与办公场景痛点分析 - 十大品牌推荐
  • 2026年高新企业审计外部审计推荐,靠谱品牌供应商盘点 - 工业品网
  • 永辉超市购物卡回收变现攻略:快速兑换现金的方法! - 团团收购物卡回收
  • 选安防门关注这些优势,熊熊集团口碑好值得考虑吗? - 工业推荐榜
  • 分析浙江口碑不错的别墅门源头厂家,推荐哪家 - 工业推荐榜
  • 2026年诺丁山艺术中心婚礼价格哪家性价比高,灯光效果揭秘 - 工业品网
  • Java语言提供了八种基本类型。六种数字类型【函数函数123】
  • 2026年口碑好的一站式婚礼企业盘点,费用怎么收 - 工业设备
  • 摆脱论文困扰! 降AIGC平台 千笔·专业降AIGC智能体 VS 笔捷Ai 专科生专属
  • 2026年河北靠谱的牧草膜品牌推荐,口碑好的牧草膜源头厂家全解析 - myqiye
  • 2026年上海值得关注的检测设备品牌,聊聊台硕检测影像测量仪性价比 - 工业设备
  • 2025年-2026年云电脑推荐:基于办公与生产场景评价,针对数据泄露与运维复杂痛点 - 十大品牌推荐
  • 2026年浙江地区粗糙度轮廓仪选购攻略,按需定制费用如何 - 工业设备
  • 格式总出错?AI论文写作软件 千笔·专业论文写作工具 VS PaperRed
  • 2026年肉骨粉干燥机厂家推荐及选购参考:饲料烘干机/豆渣烘干机/羽毛粉干燥机/羽毛粉烘干机/餐厨垃圾烘干机 - 品牌策略师
  • 上海智推时代GEO联系方式整理!2026最新官方渠道汇总 - 速递信息
  • 2026年羽毛粉生产设备公司实力排行:羽毛粉水解罐/鸭毛加工设备/水解羽毛粉设备/鸡毛粉生产设备/高蛋白羽毛粉生产设备 - 品牌策略师
  • 2025年-2026年云电脑推荐:技术趋势与合规评测,涵盖远程办公与软件部署核心痛点 - 十大品牌推荐
  • 一天一个Python库:jinja2 - 强大灵活的Python模板引擎
  • 2026年豆渣烘干机厂家最新推荐,饲料烘干机/肉骨粉烘干机/羽毛粉烘干机/肉骨粉干燥机/餐厨垃圾烘干机 - 品牌策略师
  • 2026年爆破片安全装置厂家TOP5名单出炉,权威数据揭示 - 孟哥商业圈