您的位置:首页 > 汽车 > 时评 > 邢台头条新闻最新_国家知识产权局专利局_最近实时热点事件_线上线下推广方案

邢台头条新闻最新_国家知识产权局专利局_最近实时热点事件_线上线下推广方案

2024/12/28 21:46:08 来源:https://blog.csdn.net/weixin_37693760/article/details/144457797  浏览:    关键词:邢台头条新闻最新_国家知识产权局专利局_最近实时热点事件_线上线下推广方案
邢台头条新闻最新_国家知识产权局专利局_最近实时热点事件_线上线下推广方案

文章目录

  • 第5章 数据源的注册和使用
    • 背景
    • 目标
    • 设计
    • 实现
      • 工程代码
      • 类图
      • 实现步骤
        • 1.事务管理
        • 2. 类型别名注册器
        • 3. 解析数据源配置
        • 4.SQL执行和结果封装
    • 测试
      • 事先准备
      • 测试用例
      • 测试结果
    • 总结


第5章 数据源的注册和使用

![mybatis](https://raw.githubusercontent.com/swg209/my_img/main/d7410afe519f55aeabfcc21f57148bc4.jpeg

mybatis

背景

在第4章节,我们实现了解析 XML 中的 SQL 配置信息,并在代理对象调用 DefaultSqlSession 中进行获取和打印操作,从整个框架结构来看我们解决了对象的代理、Mapper的映射、SQL的初步解析,那么接下来就应该是连接数据库和执行SQL语句并返回结果了。

那么这部分内容就会涉及到解析 XML 中关于 dataSource 数据源信息配置,并建立事务管理和连接池的启动和使用。并将这部分能力在 DefaultSqlSession 执行 SQL 语句时进行调用。但为了不至于在一个章节把整个工程撑大,本章的重点放到解析配置、建立事务框架和引入 DRUID 连接池,以及初步完成 SQL 的执行和结果简单包装上。便于大家先熟悉整个框架结构,在后续章节再陆续迭代和完善框架细节。

ORM 框架核心流程如图:

image-20241213174931782

目标

解析 XML 中关于 dataSource 数据源信息配置,并建立事务管理和连接池的启动和使用,初步完成 SQL 的执行和结果简单包装。

设计

建立数据源连接池和 JDBC 事务工厂操作,并以 xml 配置数据源信息为入口,在 XMLConfigBuilder 中添加数据源解析和构建操作,向配置类configuration添加 JDBC 操作环境信息。以便在 DefaultSqlSession 完成对 JDBC 执行 SQL 的操作。

整体设计如图 :

image-20241213175105059
  • 在 parse 中解析 XML DB 链接配置信息,并完成事务工厂和连接池的注册环境到配置类的操作。
  • 改造上一章节 selectOne 方法的处理,不再是打印 SQL 语句,而是把 SQL 语句放到 DB 连接池中进行执行,并完成简单的结果封装。

实现

工程代码

image-20241213175226250

类图

image-20241213183350084
  • 通过事务接口 Transaction 和事务工厂 TransactionFactory 的实现,包装数据源 DruidDataSourceFactory 的功能。这里的数据源连接池我们采用的是阿里的 Druid,暂时还没有实现 Mybatis 的 JNDI 和 Pooled 连接池,这部分可以后续专门以数据源连接池的专项来开发。
  • 当所有的数据源相关功能准备好后,就是在 XMLConfigBuilder 解析 XML 配置操作中,对数据源的配置进行解析以及创建出相应的服务,存放到 Configuration 的环境配置中。
  • 最后在 DefaultSqlSession#selectOne 方法中完成 SQL 的执行和结果封装,最终就把整个 Mybatis 核心脉络串联出来了。

实现步骤

1.事务管理

为了保障数据的安全,一次数据库的操作应该具有事务管理能力,而不是通过 JDBC 获取链接后直接执行。还应该把控链接、提交、回滚和关闭的操作处理。所以这里我们结合 JDBC 的能力封装事务管理。

1-1 定义标准事务接口Transaction

  • 定义标准的事务接口,链接、提交、回滚、关闭,具体可以由不同的事务方式进行实现,包括:JDBC和托管事务,托管事务是交给 Spring 这样的容器来管理。
public interface Transaction {Connection getConnection() throws SQLException;void commit() throws SQLException;void rollback() throws SQLException;void close() throws SQLException;
}

1-2 实现事务接口JdbcTransaction

  • 在 JDBC 事务实现类中,封装了获取链接、提交事务等操作,其实使用的也就是 JDBC 本身提供的能力。
public class JdbcTransaction implements Transaction {protected Connection connection;protected DataSource dataSource;protected TransactionIsolationLevel level = TransactionIsolationLevel.NONE;protected boolean autoCommit;public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {this.dataSource = dataSource;this.level = level;this.autoCommit = autoCommit;}public JdbcTransaction(Connection connection) {this.connection = connection;}@Overridepublic Connection getConnection() throws SQLException {connection = dataSource.getConnection();connection.setTransactionIsolation(level.getLevel());connection.setAutoCommit(autoCommit);return connection;}@Overridepublic void commit() throws SQLException {if (connection != null && !connection.getAutoCommit()) {connection.commit();}}@Overridepublic void rollback() throws SQLException {if (connection != null && !connection.getAutoCommit()) {connection.rollback();}}@Overridepublic void close() throws SQLException {if (connection != null && !connection.getAutoCommit()) {connection.close();}}
}

1-3 定义事务工厂接口TransactionFactory

  • 在 JDBC 事务实现类中,封装了获取链接、提交事务等操作,其实使用的也就是 JDBC 本身提供的能力。
public interface TransactionFactory {/*** 根据连接Connection创建一个事务Transaction.*/Transaction newTransaction(Connection conn);/*** 根据数据源和事务隔离级别创建Transaction.*/Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

1-4 实现事务工厂接口JdbcTransactionFactory

  • 以工厂方法模式包装 JDBC 事务实现,为每一个事务实现都提供一个对应的工厂。与简单工厂的接口包装不同。
public class JdbcTransactionFactory implements TransactionFactory {@Overridepublic Transaction newTransaction(Connection conn) {return new JdbcTransaction(conn);}@Overridepublic Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {return new JdbcTransaction(dataSource, level, autoCommit);}
}
2. 类型别名注册器

在 Mybatis 框架中我们所需要的基本类型、数组类型以及自己定义的事务实现事务工厂都需要注册到类型别名的注册器中进行管理,在我们需要使用的时候可以从注册器中获取到具体的对象类型,之后在进行实例化的方式进行使用。

2-1 基础注册器TypeAliasRegistry

  • 在 TypeAliasRegistry 类型别名注册器中先做了一些基本的类型注册,以及提供 registerAlias 注册方法和 resolveAlias 获取方法。
public class TypeAliasRegistry {private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<>();public TypeAliasRegistry() {// 构造函数里注册系统内置的类型别名registerAlias("string", String.class);// 基本包装类型registerAlias("byte", Byte.class);registerAlias("long", Long.class);registerAlias("short", Short.class);registerAlias("int", Integer.class);registerAlias("integer", Integer.class);registerAlias("double", Double.class);registerAlias("float", Float.class);registerAlias("boolean", Boolean.class);}public void registerAlias(String alias, Class<?> value) {String key = alias.toLowerCase(Locale.ENGLISH);TYPE_ALIASES.put(key, value);}public <T> Class<T> resolveAlias(String input) {String key = input.toLowerCase(Locale.ENGLISH);return (Class<T>) TYPE_ALIASES.get(key);}
}

2-2 注册事务

  • 在 Configuration 配置选项类中,添加类型别名注册机,通过构造函数添加 JDBC、DRUID 注册操作。
  • 整个 Mybatis 的操作都是使用 Configuration 配置项进行串联流程,所以所有内容都会在 Configuration 中进行链接。
public class Configuration {/*** 映射注册机.*/protected MapperRegistry mapperRegistry = new MapperRegistry();/*** 映射的语句,存在Map.*/protected final Map<String, MappedStatement> mappedStatementMap = new HashMap<>();/*** 类型别名注册机.*/protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();/*** 环境.*/protected Environment environment;public Configuration() {typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);}public TypeAliasRegistry getTypeAliasRegistry() {return typeAliasRegistry;}public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);}public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}public boolean hasMapper(Class<?> type) {return mapperRegistry.hasMapper(type);}public void addMappedStatement(MappedStatement ms) {mappedStatementMap.put(ms.getId(), ms);}public MappedStatement getMappedStatement(String id) {return mappedStatementMap.get(id);}public Environment getEnvironment() {return environment;}public void setEnvironment(Environment environment) {this.environment = environment;}
}
3. 解析数据源配置

通过在 XML 解析器 XMLConfigBuilder 中,扩展对环境信息的解析,我们这里把数据源、事务类内容称为操作 SQL 的环境。解析后把配置信息写入到 Configuration 配置项中,便于后续使用。

XML解析器XMLConfigBuilder

  • XMLConfigBuilder#parse 解析扩展对数据源解析操作, environmentsElement 方法中包括事务管理器解析和从类型注册器中读取到事务工程的实现类,以及数据源
  • 最后把事务管理器和数据源的处理,通过环境构建 Environment.Builder 存放到 Configuration 配置项中,也就可以通过 Configuration 存在的地方都可以获取到数据源了。
public class XMLConfigBuilder extends BaseBuilder {private Element root;public XMLConfigBuilder(Reader reader) {// 1. 调用父类初始化Configurationsuper(new Configuration());// 2. dom4j 处理 xmlSAXReader saxReader = new SAXReader();try {Document document = saxReader.read(new InputSource(reader));root = document.getRootElement();} catch (DocumentException e) {e.printStackTrace();}}//从xml文件解析Configuration.public Configuration parse() {try {// 解析环境配置.environmentsElement(root.element("environments"));// 解析映射器,读取XML文件中的mappers标识.mapperElement(root.element("mappers"));} catch (Exception e) {throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}return configuration;}/*** <environments default="development">* <environment id="development">* <transactionManager type="JDBC">* <property name="..." value="..."/>* </transactionManager>* <dataSource type="POOLED">* <property name="driver" value="${driver}"/>* <property name="url" value="${url}"/>* <property name="username" value="${username}"/>* <property name="password" value="${password}"/>* </dataSource>* </environment>* </environments>*/private void environmentsElement(Element context) throws Exception {//获取环境String environment = context.attributeValue("default");//获取environments标签下的所有environment标签.List<Element> environmentList = context.elements("environment");for (Element e : environmentList) {//获取environment标签的id属性.String id = e.attributeValue("id");if (environment.equals(id)) {//创建事务管理器.TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(e.element("transactionManager").attributeValue("type")).newInstance();// 数据源.Element dataSourceElement = e.element("dataSource");DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();List<Element> propertyList = dataSourceElement.elements("property");Properties props = new Properties();for (Element property : propertyList) {props.setProperty(property.attributeValue("name"), property.attributeValue("value"));}dataSourceFactory.setProperties(props);DataSource dataSource = dataSourceFactory.getDataSource();//构建环境Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());}}}/*** XMLmapper 格式.* <mappers>* <mapper resource="mapper/User_Mapper.xml"/>* </mappers>*** <mapper namespace="cn.suwg.mybatis.test.dao.IUserDao">** <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.suwg.mybatis.test.po.User">* SELECT id, user_id, user_head, create_time* FROM user* where id = #{id}* </select>** </mapper>** @param mappers* @throws Exception*/private void mapperElement(Element mappers) throws Exception {//获取mappers标签下的所有mapper标签.List<Element> mapperList = mappers.elements("mapper");for (Element mapper : mapperList) {//获取mapper标签的resource属性.String resource = mapper.attributeValue("resource");Reader reader = Resources.getResourceAsReader(resource);SAXReader saxReader = new SAXReader();Document document = saxReader.read(new InputSource(reader));Element root = document.getRootElement();//命名空间String namespace = root.attributeValue("namespace");//SELECT  解析语句.List<Element> selectNodes = root.elements("select");for (Element node : selectNodes) {String id = node.attributeValue("id");String parameterType = node.attributeValue("parameterType");String resultType = node.attributeValue("resultType");String sql = node.getText();// ?匹配Map<Integer, String> parameter = new HashMap<>();Pattern pattern = Pattern.compile("(#\\{(.*?)})");Matcher matcher = pattern.matcher(sql);//匹配到的参数替换为?for (int i = 1; matcher.find(); i++) {String g1 = matcher.group(1);String g2 = matcher.group(2);parameter.put(i, g2);sql = sql.replace(g1, "?");}String msId = namespace + "." + id;String nodeName = node.getName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));// 构建BoundSql.BoundSql boundSql = new BoundSql(sql, parameter, parameterType, resultType);MappedStatement mappedStatement = new MappedStatement.Builder(configuration, msId, sqlCommandType,boundSql).build();// 添加解析SQLconfiguration.addMappedStatement(mappedStatement);}// 注册Mapper映射器.configuration.addMapper(Resources.classForName(namespace));}}}
4.SQL执行和结果封装

在上一章节中在 DefaultSqlSession#selectOne 只是打印了 XML 中配置的 SQL 语句,现在把数据源的配置加载进来以后,就可以把 SQL 语句放到数据源中进行执行以及结果封装。

DefaultSqlSession默认sql会话

  • 在 selectOne 方法中获取 Connection 数据源链接,并简单的执行 SQL 语句,并对执行的结果进行封装处理。
  • 因为目前这部分主要是为了大家串联出整个功能结构,所以关于 SQL 的执行、参数传递和结果封装都是写死的,后续我们进行扩展。
public class DefaultSqlSession implements SqlSession {/*** 配置项.*/private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}public Configuration getConfiguration() {return configuration;}/*** 根据给定的执行SQL获取一条记录的封装对象.** @param statement* @param <T>* @return*/@Overridepublic <T> T selectOne(String statement) {return (T) ("你的操作被代理了," + statement);}@Overridepublic <T> T selectOne(String statement, Object parameter) {try {//映射语句MappedStatement mappedStatement = configuration.getMappedStatement(statement);//环境Environment environment = configuration.getEnvironment();//连接Connection connection = environment.getDataSource().getConnection();BoundSql boundSql = mappedStatement.getBoundSql();PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());preparedStatement.setLong(1, Long.parseLong(((Object[]) parameter)[0].toString()));//执行查询ResultSet resultSet = preparedStatement.executeQuery();List<T> objectList = resultSet2Obj(resultSet, Class.forName(boundSql.getResultType()));return objectList.get(0);} catch (Exception e) {e.printStackTrace();return null;}}private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {List<T> list = new ArrayList<>();try {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();// 每次遍历行值while (resultSet.next()) {T obj = (T) clazz.newInstance();for (int i = 1; i <= columnCount; i++) {Object value = resultSet.getObject(i);String columnName = metaData.getColumnName(i);String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + StrUtil.toCamelCase(columnName.substring(1));java.lang.reflect.Method method;if (value instanceof java.sql.Timestamp) {method = clazz.getMethod(setMethod, java.util.Date.class);} else {method = clazz.getMethod(setMethod, value.getClass());}method.invoke(obj, value);}list.add(obj);}} catch (Exception e) {e.printStackTrace();}return list;}@Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}}

测试

事先准备

创建库表

-- 建表
CREATE TABLE `my_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',`user_id` varchar(9) DEFAULT NULL COMMENT '用户ID',`user_head` varchar(16) DEFAULT NULL COMMENT '用户头像',`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',`user_name` varchar(64) DEFAULT NULL COMMENT '用户名',`user_password` varchar(64) DEFAULT NULL COMMENT '用户密码',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;-- 插入数据
INSERT INTO my_user (user_id, user_head, create_time, update_time, user_name, user_password) VALUES('1', '头像', '2024-12-13 18:00:12', '2024-12-13 18:00:12', '小苏', 's123asd');

定义一个数据库接口 IUserDao

IUserDao

public interface IUserDao {String queryUserInfoById(String uid);}

配置数据源

  • 通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password
  • 这里DataSource 配置的是 DRUID,因为我们实现的是这个数据源的处理方式。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="DRUID"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="mapper/User_Mapper.xml"/></mappers>
</configuration>

定义对应的mapper xml文件

<?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="cn.suwg.mybatis.test.dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.suwg.mybatis.test.po.User">SELECT id, user_id, user_head, create_timeFROM userwhere id = #{id}</select></mapper>

测试用例

  • 在单元测试中,实现的步骤为:
    1. 从XML文件读取配置项,通过SqlSessionFactoryBuilder获取到SqlSessionFactory
    2. 从SqlSessionFactory获取SqlSession
    3. 获取映射器对象
    4. 调用Dao方法
public class ApiTest {private Logger logger = LoggerFactory.getLogger(ApiTest.class);// 测试SqlSessionFactory@Testpublic void testSqlSessionFactory() throws IOException {// 1.从xml文件读取mybatis配置项, 从SqlSessionFactory获取SqlSession.Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);SqlSession sqlSession = sqlSessionFactory.openSession();// 2.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 3.测试验证User user = userDao.queryUserInfoById(1L);logger.info("测试结果:{}", JSON.toJSONString(user));}}

测试结果

image-20241213181146482

  • 从输出的结果来看,我们实现的Mybatis手写 ORM 框架中,目前通过对数据源的解析、包装和使用,已经可以对 SQL 语句进行执行和包装返回的结果信息了。

  • 建议大家在实现过程中,可以debug调试看下调用链路,了解看每一步都是如何完成执行步骤的,也在这个过程中进行学习 Mybatis 框架的设计技巧。

总结

  • 解析 XML 配置解析为入口,添加数据源的整合和包装,引出事务工厂对 JDBC 事务的处理,并加载到环境配置中进行使用。
  • 那么通过数据源的引入就可以在 DefaultSqlSession 中从 Configuration 配置引入环境信息,把对应的 SQL 语句提交给 JDBC 进行处理并简单封装结果数据。
  • 结合本章节建立起来的框架结构,数据源、事务、简单的SQL调用,下个章节将继续这部分内容的扩展处理,让整个功能模块逐渐完善。

参考书籍:《手写Mybatis渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/book-small-mybatis

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com