资讯专栏INFORMATION COLUMN

数据库操作(jdbc)

trigkit4 / 663人阅读

摘要:直接通过获取在中我们可以通过配置文件生成或者获得了就可以拿他进行数据库操作了。这样类通过这个方法获得进行数据库操作。扒开外衣,还原本质其实三种实现数据库操作的方式最终都是通过来操作数据库的。

前言

在构建一个系统的过程中难免需要对数据存储,而存储一般会有缓存(内存)、数据库(硬盘)两种存储介质。

本篇文章我们主要来介绍下在我们通过spring构建应用的过程中如何进行数据库连接、以及数据库连接的几种方式进行简单介绍。

spring中连接数据库有如下几种方式:

直接通过驱动连接数据库的方式

spring提供的JdbcTemplate

spring集成Mybatis,通过Mybatis的方式进行数据库连接

原始JDBC方式

一般初学者在学到jdbc这个阶段都会动手写下下面这样的链接数据库的代码,只需三步就可以从数据库总拿到数据,这个时候是不是在窃喜终于按照教程把数据拿出来了。见下面代码:

Class.forName("org.apache.phoenix.jdbc.PhoenixDriver");
Connection connection = DriverManager.getConnection("jdbc:phoenix:10.1.168.1:2181/hbase");
ResultSet rs = connection.createStatement().executeQuery("select * from table limit 10 ");

针对上面的链接数据的代码来深入挖下为什么这样就能连接上数据库:

org.apache.phoenix.jdbc.PhoenixDriver这个类在在加载(也就是执行Class.forName(driver))的过程中会执行其静态代码块DriverManager.registerDriver(INSTANCE);

执行registerDriver方法后会往静态registeredDrivers list中添加PhoenixDriver类。

public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

后面就是进行连接操作DriverManager.getConnection(url)方法源代码如下:

//  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application"s
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection("" + url + "")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }


}

for(DriverInfo aDriver : registeredDrivers)去遍历所有的Drivers然后创建连接,获得连接了后面就可以开始进行数据库操作了。

JdbcTemplate方式

这种方式是spring针对原始的JDBC的方式进行了一层封装将所有的操作都托管给了DataSource。

1、直接通过JdbcTemplate
获取JdbcTemplate

在spring中我们可以通过配置文件生成JdbcTemplate:


    

或者

@Bean
public JdbcTemplate getJdbcTemplate() {
    DruidDataSource druidDataSource = new DruidDataSource();
    return new JdbcTemplate(druidDataSource);
}

获得了JdbcTemplate就可以拿他进行数据库操作了。

操作数据库
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql);

通过上面的范式就可以获取数据库中的数据了。这个可以换成queryForXXX(sql)更多查询方式

2、间接通过JdbcTemplate的方式

对于一个普通的类通过继承org.springframework.jdbc.core.support.JdbcDaoSupport这个类,然后向类中注入DataSource就可以实现JDBC的功能了。


    

这样类exampleDao 通过org.springframework.jdbc.core.support.JdbcDaoSupport#getJdbcTemplate这个方法获得JdbcTemplate进行数据库操作。

下面看下为什么会这样?

其实原理比较简单JdbcDaoSupport这个类组合了JdbcTemplate在进行DataSource注入的时候会去创建一个JdbcTemplate,后面就可以通过JdbcDaoSupport#getJdbcTemplate方法拿到创建好的实例操作数据库了。

Mybatis的方式 1、SqlSessionTemplate的方式


通过上面的方式获得操作数据的句柄(sqlSessionTemplate),示例通过句柄操作数据获得数据。
sqlSessionTemplate.selectList()这里换成selectxxx()等,具体可以参见org.mybatis.spring.SqlSessionTemplate中的方法。

2、MapperFactoryBean生成mapper

  
  

使用上面的方式会生成一个MapperFactoryBean在Spring中获取userMapper对象的时候会自动通过MapperFactoryBean创建出来,这样就可以直接使用userMapper中的接口方法去查询数据库。

注意1:mapperInterface接口中的接口名称必须和mapper.xml配置中id一致,这样才能匹配到具体的sql语句。

注意2:同时如果接口中参数名称和sql语句中参数不一致可以通过在接口中加入注解@Param("code")来进行参数匹配List findUserById(@Param("id") String userId);

注意3:Mybatis从数据库中拿到数据会自动进行a_b => aB的匹配,所以代码中用驼峰数据库中用下划线的方式,Mybatis能够进行自动匹配

3、MapperScannerConfigurer自动生成mapper

    
    
    

这种方式原理和上面那种方式很相似都是通过MapperFactoryBean来生成mapperMapperScannerConfigurerspring启动的过程中会去扫描basePackage下面所有的接口动态生成MapperFactoryBean

注:sqlSessionFactoryBeanName这个参数不是必须得,如果spring容器中有多个sqlSessionFactory才需要明确指出来

为什么要使用这种方式?

这种方式主要是解决第二种方式针对每个mapper接口都要进行一次匹配操作,而导致配置拖沓。

扒开外衣,还原本质(Mybatis)

其实Mybatis三种实现数据库操作的方式最终都是通过sqlSessionTemplate来操作数据库的。为什么这么说,下面来一层层剖析。
org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry这个方法在spring容器启动的过程中会被调用,函数体:

@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

定义了一个scanner(ClassPathMapperScanner),最后会调用scanner.scan进行扫描basePackage,跟踪调用层次关系最后会调用到org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions方法

private void processBeanDefinitions(Set beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name "" + holder.getBeanName() 
          + "" and "" + definition.getBeanClassName() + "" mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name "" + holder.getBeanName() + "".");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

实例化一个GenericBeanDefinition放到容器,从方法体可以看到definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);很明显了这就是在实例化一个MapperFactoryBean对象。

下面转战MapperFactoryBean类,发现进行实例化的时候,设置SqlSessionFactory对象的时候进行了SqlSessionTemplate的实例化。

    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

这里就生成了一个SqlSessionTemplate 来进行数据库操作。

再来彻底点转战SqlSessionTemplate的实现,new SqlSessionTemplate(sqlSessionFactory)
最后会调用到

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property "sqlSessionFactory" is required");
    notNull(executorType, "Property "executorType" is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

这个方法体重点是this.sqlSessionProxy这个属性,由JDK提供的动态代理来动态实例恶化SqlSession.class这里SqlSessionInterceptor通过openSession创建SqlSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

主要是实例化DefaultSqlSession执行器executor

调用sqlSessionTemplate.selectList方法,最终调用

@Override
  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

调用链继续,后面调用:

@Override
  public  List doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection);
      handler.parameterize(stmt);
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

看到了什么?

ConnectionStatementHandler,又回到了我们最开始讨论的纯JDBC方式从数据库中获取数据。再往下异步就可以看到

@Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler. handleResultSets(ps);
  }

获取结果的步骤了。

总结

好吧!写了这么多,感觉也有点乱了,各位看官能看到这里也说明足够有耐性了。JdbcTemplate的深挖就不继续了,比起Mybatis这种封装方式轻量级太多,往下扒两层就出来了。

坚持深挖源码的习惯,保持好的学习方式。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67444.html

相关文章

  • 如何有效地记录 Java SQL 日志?

    摘要:本文主要介绍如何使用这个可以直接显示完整的日志框架,希望对大家能有所帮助。当设置为或时,意味关闭记录。 在常规项目的开发中可能最容易出问题的地方就在于对数据库的处理了,在大部分的环境下,我们对数据库的操作都是使用流行的框架,比如 Hibernate 、 MyBatis 等。由于各种原因,我们有时会想知道在这些框架下实际执行的 SQL 究竟是什么。 虽然 Hibernate 可以在配置...

    godiscoder 评论0 收藏0
  • 如何有效地记录 Java SQL 日志?

    摘要:本文主要介绍如何使用这个可以直接显示完整的日志框架,希望对大家能有所帮助。当设置为或时,意味关闭记录。 在常规项目的开发中可能最容易出问题的地方就在于对数据库的处理了,在大部分的环境下,我们对数据库的操作都是使用流行的框架,比如 Hibernate 、 MyBatis 等。由于各种原因,我们有时会想知道在这些框架下实际执行的 SQL 究竟是什么。 虽然 Hibernate 可以在配置...

    FleyX 评论0 收藏0
  • JDBC常见面试题

    摘要:常见面试题操作数据库的步骤操作数据库的步骤注册数据库驱动。可以防止注入,安全性高于。只有隔离级别才能防止产生幻读。对象维护了一个游标,指向当前的数据行。一共有三种对象。 以下我是归纳的JDBC知识点图: showImg(https://segmentfault.com/img/remote/1460000013312769); 图上的知识点都可以在我其他的文章内找到相应内容。 JDBC...

    Yuqi 评论0 收藏0
  • JDBC【介绍JDBC、使用JDBC连接据库、简单的工具类】

    摘要:常用的方法创建向数据库发送的对象。创建执行存储过程的对象设置事务自动提交提交事务回滚事务对象对象用于向数据库发送语句,对数据库的增删改查都可以通过此对象发送语句完成。 1.什么是JDBC JDBC全称为:Java Data Base Connectivity,它是可以执行SQL语句的Java API 2.为什么我们要用JDBC 市面上有非常多的数据库,本来我们是需要根据不同的数据库学...

    MSchumi 评论0 收藏0
  • Sharding-Jdbc实现mysql分库分表

    摘要:实现数据库分库分表可以自己实现,也可以使用和实现。分布式数据库的自增不是自增的。分布式数据库分页查询需要使用插入时间实现。包含分库分片和读写分离功能。 Sharding-Jdbc实现mysql分库分表 简单介绍 数据库分库分表和读写分离区别,分库分表是在多个库建相同的表和同一个库建不同的表,根据随机或者哈希等方式查找实现。读写分离是为了解决数据库的读写性能不足,使用主库master进行...

    go4it 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<