【手搓mybatis-4】数据源 事务 配置解析

在上一节中,我们解析了mapperXML文件中的SQL配置信息,将其填充如Configuration中的MapperRegistry和mappedStatements中,可以在调用代理对象的方法返回其在XML文件中配置的sql语句

我们今天的需求是:

  • 解析配置文件中关于数据源的信息
  • JDBC事务管理接口(一个成熟的数据库访问操作应该具有事务管理能力,比如commit、close、rollback),并用工厂方法模式包装JDBC事务实现,为每一个事务实现提供一个对应工厂
  • 引入 Druid 连接池
  • 在 SqlSession 执行SQL操作时顺利访问数据源。

0 依赖介绍

Druid

Druid 是阿里巴巴开发的数据库连接池和数据库监控组件,用于管理和优化 Java 应用程序中的数据库连接,可以用于增强 JDBC 的功能。

主要职责

  • 数据库连接池管理:Druid 作为数据库连接池,能够高效地管理数据库连接的创建、分配和销毁,从而提升应用的并发性能和资源利用效率。
  • SQL 监控:Druid 内置了 SQL 监控功能,支持实时查看 SQL 执行频率、执行时间、慢查询等统计数据,可以帮助开发人员优化 SQL 性能。
  • 防 SQL 注入:Druid 可以检测并拦截 SQL 注入攻击,增强数据库访问的安全性。
  • 扩展 JDBC:Druid 兼容 JDBC API,可以无缝替换 JDBC 使用的 DriverManager,通过代理模式拦截 JDBC 操作,加入监控、日志等功能。

1 事务管理接口设计与JDBC事务实现

定义标准的事务管理接口,可以有不同的事务实现方式。比如:

  • JDBC事务
  • 托管事务(框架提供的)
public interface Transaction {  

    Connection getConnection() throws SQLException;  

    void rollback() throws SQLException;  

    void close() throws SQLException;  

    void commit() throws SQLException;  
}

JDBC事务实现

JDBC 事务是基于 JDBC API 的原生事务管理方式,直接在代码中控制数据库连接的事务行为。

  • 原理:开发者在代码中通过 JDBC 的 Connection 对象手动开始、提交和回滚事务。通过设置 Connection 的 autoCommit 属性为 false,可以手动控制事务的开始和结束。

回顾一下 JDBC事务隔离级别:

事务并发执行时可能会遇到脏读、不可重复读、幻读的现象:

  • 脏读:读到其他事务未提交的数据
  • 不可重复读:在一个事务中前后读取的数据不一致
  • 幻读:在一个事务中读区的记录数量不一致(就像出现幻觉一样)

JDBC提供5种事务隔离级别接口(具体的事务管理由InnoDB存储引擎执行)来规避这些现象,隔离级别越高,性能效率越低:

  • NONE:没有事务管理,不提供事务隔离。每条 SQL 语句都独立执行,立即生效,无法rollback或commit。
  • READ_UNCOMMITTED:读未提交,允许读取未提交的更改,可能发生脏读。
  • READ_COMMITTED:读提交,只能读取已提交的更改,防止脏读。
  • REPEATABLE_READ:可重复读,防止脏读和不可重复读,但可能发生幻读。
  • SERIALIZABLE:完全隔离的级别,防止脏读、不可重复读和幻读,是最严格的隔离级别。

在这里我们创建枚举对象来管理JDBC提供的事务隔离级别,方便代码中清晰便捷使用。

【工厂方法模式】为每一种事务实现提供工厂

如果不提供工厂,每次创建 Transaction 实例都需要直接调用构造方法,这样做会导致下面问题:

  1. 高耦合性:调用方必须直接依赖 Transaction 的具体实现,这会导致代码难以维护或扩展。例如,如果要切换 Transaction 的实现,则需要修改所有调用的地方。
  2. 重复代码:每次创建 Transaction 实例时都要重复设置隔离级别等属性,增加代码重复性和维护成本。
  3. 难以扩展:未来若要增加新的 Transaction 类型或配置方案,就需要大幅度修改现有代码,而不是通过简单地添加新的工厂实现。
public interface TransactionFactory {  

    /**  
     * 根据 Connection 创建 Transaction  
     * @param conn Existing database connection  
     * @return Transaction     
     */    
    Transaction getTransaction(Connection conn);  

    /**  
     * 根据数据源和事务隔离级别创建 Transaction  
     * @param dataSource DataSource to take the connection from  
     * @param level Desired isolation level  
     * @param autoCommit Desired autocommit  
     * @return Transaction     
     */    
     Transaction getTransaction(DataSource dataSource, Boolean autoCommit, TransactionIsolationLevel level);  
}

简单工厂模式 v.s. 工厂方法模式

这里用到的是工厂方法模式,工厂方法模式的优越之处体现在符合开闭原则,且提供更高的耦合。

  • 在简单工厂模式下,一个简单工厂对应所有接口实现。如果要新增新的实现,就要修改这个简单工厂中的switch代码;
  • 而在工厂方法模式下,一个工厂对应一个实现。如果要新增新的实现,只需要新增新的接口实现和工厂即可。
方面简单工厂模式工厂方法模式
扩展性不符合开闭原则,修改工厂类符合开闭原则,新增工厂类即可
复杂度简单,适合少量产品较复杂,适合产品种类多的场景
适用场景产品少,且变化不频繁产品多且频繁变化
典型使用情况通常在简单场景使用在较大系统或灵活需求场景中使用

2 类型别名注册器

创建一个类型别名注册器,将事务工厂和数据源工厂(比如JDBC、DRUID等)注册进去,方便解析配置时候直接在别名注册器中通过别名找到对应的类型

3 XML 数据源配置信息解析

  • 在 Configuration 对象中加入相应字段 environment,存储事务管理配置和数据源配置。
private void datasourceElement(Element environments) throws Exception {  
    String defaultEnvironment = environments.attributeValue("default");  
    List<Element> environmentList = environments.elements("environment");  
    for (Element e : environmentList) {  
        String id = e.attributeValue("id");  
        if (defaultEnvironment.equals(id)) {  
            // 找到了配置的环境  
            // 事务管理器  
            String transactionManagerType = e.element("transactionManager").attributeValue("type");  
            TransactionFactory transactionFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(transactionManagerType).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实例的完整性  
            Environment.Builder builder = new Environment.Builder(id)  
                    .dataSource(dataSource)  
                    .transactionFactory(transactionFactory);  

            configuration.setEnvironment(builder.build());  
        }  
    }  
}

在mybatis中Configuration对象起到流程中心的作用,负责管理和维护 MyBatis 各个模块的配置信息。可以看到mapper映射 mapperRegistry、方法映射 mappedStatements、事务管理和数据源配置 Environment 都保存在Configuration 中。之后将Configuration作为依赖注入到SqlSession,提供完整的SQL操作上下文,创建高度定制化的SQL会话。

4 SqlSession 提供完整Sql操作(select)

SqlSession按照传入的Configuration配置创建数据库连接,匹配并执行数据库操作。
这里暂时将语句写死,后面再进行完善。

@Override  
public <T> T selectOne(String statement, Object parameter) {  
    try {  
        MappedStatement mappedStatement = configuration.getMappedStatement(statement);  
        Environment environment = configuration.getEnvironment();  
        Connection conn = environment.getDataSource().getConnection();  
        BoundSql boundSql = mappedStatement.getBoundSql();  
        PreparedStatement preparedStatement = conn.prepareStatement(boundSql.getSql());  
        // todo:暂时将sql写死在这里  
        preparedStatement.setLong(1, Long.parseLong(((Object[]) parameter)[0].toString()));  
        ResultSet resultSet = preparedStatement.executeQuery();  
        List<T> objList = resultSet2Obj(resultSet, Class.forName(boundSql.getResultType()));  
        return objList.get(0);  
    } catch (Exception e) {  
        e.printStackTrace();  
        return null;  
    }  
}

5 效果测试

@Test  
public void test_MapperProxyFactory_day4() throws IOException {  

    // 获取到配置文件的Reader  
    Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");  

    // 通过sqlSessionFactory的建造者 按照配置文件Reader 得到 sqlSessionFactory;  
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();  

    /**  
     * sqlSessionFactoryBuilder.build() 去解析配置文件 装配出Configuration  
     * 将提供给sqlSessionFactory,作为定制化SqlSession的需求  
     */  
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);  

    // 得到sqlSession  
    SqlSession sqlSession = sqlSessionFactory.openSession();  

    // 得到代理对象  
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);  

    User result = userDao.queryUserInfoById("10001");  
    logger.info("测试结果:{}", JSON.toJSONString(result));  
}

完成本节后,我们可以解析配置文件中的数据源和事务管理器配置,包装成Environment对象,并注入到Configuration中。由此,SqlSession工厂创建出类的SqlSession中的Configuration内含数据源、事务的上下文信息,可以创建数据源连接并执行相应的sql操作。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇