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

用java语言完成手写mybatis框架(第2章)

        免责声明:本内容均参考B站up小傅哥的代码文档编写而来,写作的目的仅为交流学习。针小傅哥没有讲明白的点,进行更详细的讲解。

第2章主要完成的内容:实现映射器的注册和使用,我们这一章针对的是

我们先展示下mybtis的sqlseesion的使用,应该这种方式现在都很少人在用了。

效果展示

这是实际mybatis的使用过程,我们用于参考进行去手写

public interface UserService {UserEntity getById(int userId) throws IOException;
}@Service
public class UserServiceImpl implements UserService {@Overridepublic UserEntity getById(int userId) throws IOException {SqlSessionFactory sqlSessionFactory= MyBatisJavaConfig.getSqlSessionFactory();try (SqlSession sqlSession = sqlSessionFactory.openSession()) {// 方式1:直接通过 statement 标识调用UserEntity user1 = sqlSession.selectOne("com.buu.gulimall.thirdparty.mapper.UserMapper.selectById", userId);System.out.println(user1); // 输出id=1的用户信息// 方式2:通过 Mapper 接口调用(更推荐,类型安全)
//            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//            UserEntity user2 = userMapper.selectById(1); // 底层仍通过 selectOne 实现
//            System.out.println(user2);return user1;}}
}

构建sqlsessionFactory

public class MyBatisJavaConfig {public static SqlSessionFactory getSqlSessionFactory() throws IOException {// 配置数据源PooledDataSource dataSource = new PooledDataSource();dataSource.setDriver("com.mysql.cj.jdbc.Driver"); // 适配 MySQL 8.0+dataSource.setUrl("jdbc:mysql://localhost:3306/gulimall_pms?useSSL=false&serverTimezone=UTC");dataSource.setUsername("root");dataSource.setPassword("250058");dataSource.setPoolMaximumActiveConnections(20); // 优化连接池// 2. 配置事务管理器JdbcTransactionFactory transactionFactory = new JdbcTransactionFactory();Environment environment = new Environment("development", transactionFactory, dataSource);// 3. 配置 MyBatis 核心参数Configuration configuration = new Configuration(environment);configuration.addMapper(UserMapper.class); // 注册 Mapper 接口String packageName = "com.buu.gulimall.thirdparty.mapper.UserEntity"; // 实体类所在包ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();resolverUtil.find(new ResolverUtil.IsA(Object.class), packageName); // 扫描所有类Set<Class<? extends Class<?>>> classes = resolverUtil.getClasses();// 获取别名注册器,逐个注册TypeAliasRegistry aliasRegistry = configuration.getTypeAliasRegistry();for (Class<?> clazz : classes) {aliasRegistry.registerAlias(clazz); // 别名默认为类名首字母小写(如 UserEntity → userEntity)// 若需自定义别名,可使用 registerAlias(clazz, "自定义别名")}// 4. 加载 XML 映射文件(关键:如果有自定义 SQL 必须添加)InputStream xmlInputStream = Resources.getResourceAsStream("mapper/UserMapper.xml");XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(xmlInputStream,configuration,"mapper/UserMapper.xml",configuration.getSqlFragments());xmlMapperBuilder.parse();// 5. 构建 SqlSessionFactoryreturn new SqlSessionFactoryBuilder().build(configuration);}
}

绑定mapper的接口类与xml文件

public interface UserMapper {UserEntity selectById(Integer userId);
}<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.buu.gulimall.thirdparty.mapper.UserMapper"><!-- id 必须 = 方法名:selectById --><select id="selectById" parameterType="Integer" resultType="com.buu.gulimall.thirdparty.mapper.UserEntity">SELECT * FROM user WHERE userId = #{id}</select>
</mapper>

我这里启动好服务,调用接口是直接可以成功的。

然后我们这一章就要实现这种效果。

具体手写的实现

sqlsession接口

我们先写个简易的sqlsession接口,selectOne我们是上面的实际使用可以知道,第一个参数是传的接口的全类名+需要调用的方法名,第二个传的是参数。注意selectOne是确保只返回一条记录的查询场景。

public interface SqlSession {<T> T selectOne(String statement);<T> T selectOne(String statement, Object parameter);<T> T getMapper(Class<T> type);}

有接口,那肯定有实现,我们写个DefaultSqlSession这类,我们这里是暂时不写具体的内容,但是我们可以想下大约要实现哪些内容,这里sqlsession调用返回的是一个我们自己编写的接口的对象(可以看文章开头的代码,这里截个图)

我们可以感觉出来就是一个生成一个动态代理对象UserMapper,然后调用其的selectById方法,参数是userId,然后通过映射到xml文件上,解析xml文件,然后执行具体的sql语句,最终把返回的结果封装成为对象UserEntity进行返回。我们今后几章就要实现这些内容。

public class DefaultSqlSession implements SqlSession {@Overridepublic <T> T selectOne(String statement) {return (T) ("你被代理了!" + statement);}@Overridepublic <T> T selectOne(String statement, Object parameter) {return (T) ("你被代理了!" + "方法:" + statement + " 入参:" + parameter);}@Overridepublic <T> T getMapper(Class<T> type) {return ;}@Overridepublic <T> T getMapper(Class<T> type) {return mapperRegistry.getMapper(type, this);}}

因为我们要生成接口所对应的动态代理类,所以要维护一个接口与代理工厂的映射器MapperRegistry类,其包含几个方法,addMapper将接口注册到knownMappers中,getMapper从knownMappers中提取接口class对应的代理工厂。目前来说MapperRegistry这个类和代理工厂关联起来了,但是目前来说还是不知道如何去使用,就比如:knownMappers的数据是如何初始化出来的,所以有点疑惑,暂时pass

public class MapperRegistry {/*** 将已添加的映射器代理加入到 HashMap*/private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new RuntimeException("Error getting mapper instance. Cause: " + e, e);}}public <T> void addMapper(Class<T> type) {/* Mapper 必须是接口才会注册 */if (type.isInterface()) {if (hasMapper(type)) {// 如果重复添加了,报错throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");}// 注册映射器代理工厂knownMappers.put(type, new MapperProxyFactory<>(type));}}public <T> boolean hasMapper(Class<T> type) {return knownMappers.containsKey(type);}public void addMappers(String packageName) {Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);for (Class<?> mapperClass : mapperSet) {addMapper(mapperClass);}}}

之后再给DefaultSqlSession这个类增加一个方法

工厂模式的设计思想

之后就到了本章的一个重点内容了,工厂模式的设计思想。首先我们要知道为啥要用工厂模式?就是我们想让我们的代码的扩展性比较好,我们针对一个接口A,其的多个实现进行工厂化,然后再有一个通用的工厂接口B,其用来对应接口A的生成对应的。大致的对应关系就是如下图的。

抽象接口SqlSession

public interface SqlSession {<T> T selectOne(String statement);<T> T selectOne(String statement, Object parameter);Configuration getConfiguration();<T> T getMapper(Class<T> type);}

对应的接口工厂SqlSessionFactory

public interface SqlSessionFactory {SqlSession openSession();}

SqlSession对应的实现产品类DefaultSqlSession

public class DefaultSqlSession implements SqlSession {private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}@Overridepublic <T> T selectOne(String statement) {return (T) ("你被代理了!" + statement);}@Overridepublic <T> T selectOne(String statement, Object parameter) {MappedStatement mappedStatement = configuration.getMappedStatement(statement);return (T) ("你被代理了!" + "\n方法:" + statement + "\n入参:" + parameter + "\n待执行SQL:" + mappedStatement.getSql());}@Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}@Overridepublic Configuration getConfiguration() {return configuration;}}

接口工厂SqlSessionFactory对应的实际产品 DefaultSqlSession的工厂DefaultSqlSessionFactory

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration);}}

写个测试类测试下

    @Testpublic void test_MapperProxyFactory() {// 1. 注册 MapperMapperRegistry registry = new MapperRegistry();registry.addMappers("cn.bugstack.mybatis.test.dao");// 2. 从 SqlSession 工厂获取 SessionSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);SqlSession sqlSession = sqlSessionFactory.openSession();// 3. 获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 4. 测试验证String res = userDao.queryUserName("10001");logger.info("测试结果:{}", res);}

我们本节就设计好了,我们现在还欠缺xml解析的内容。以及xml的sql与接口方法的对应。

http://www.lryc.cn/news/626015.html

相关文章:

  • 借助AI将infoNES移植到HarmonyOS平台的详细方案介绍
  • Linux操作系统编程——进程间的通信
  • 极海APM32F107V6 gpio模拟串口
  • 决策树算法学习总结
  • 【Vivado TCL 教程】从零开始掌握 Xilinx Vivado TCL 脚本编程(三)
  • UML常见图例
  • 一文精通 Swagger 在 .NET 中的全方位配置与应用
  • Java NIO 核心精讲(上):Channel、Buffer、Selector 详解与 ByteBuffer 完全指南
  • 【3-3】流量控制与差错控制
  • Linux资源管理
  • JUC之CompletableFuture【上】
  • Orbbec---setBoolProperty 快捷配置设备行为
  • 设备树下的LED驱动实验
  • 从数据表到退磁:Ansys Maxwell中N48磁体磁化指南
  • 谷歌为什么要将Android的页面大小(Page Size)从传统的4KB升级至16KB
  • Go 进阶学习路线
  • 测试 Next.js 应用:工具与策略
  • 仲裁器设计(三)-- Weighted Round Robin 权重轮询调度
  • ASP4644稳压器的特性分析与系统测试方法研究
  • GPT-4.1旗舰模型:复杂任务的最佳选择及API集成实践
  • 【RustFS干货】RustFS的智能路由算法与其他分布式存储系统(如Ceph)的路由方案相比有哪些独特优势?
  • 2025杭电多校第九场 乘法逆元、阿斯蒂芬、计算几何 个人题解
  • 宿主获取插件View流程原理 - fetchViewByLayoutName
  • LWIP协议栈实现ARP协议
  • Python脚本每天爬取微博热搜-终版
  • Spring Cloud 微服务架构:Eureka 与 ZooKeeper 服务发现原理与实战指南 NO.1
  • Stream API-怎么理解流
  • Day13_【DataFrame数据组合merge连接】【案例】
  • Redis(11)如何通过命令行操作Redis?
  • 反向代理实现服务器联网