【框架学习】数据库框架 - hippowc/hippowc.github.io GitHub Wiki

数据库框架

说到数据库框架,最先会想到mybatis,hibernate等,其实这是比较上层的框架,要了解如何连接数据库,还是要从jdbc -- datasource -- 数据库应框架(mybatis)这个顺序。上层框架使用的太多,导致一些原理性的东西都不了解了

jdbc与datasource

jdbc

JDBC(Java Data Base Connectivity,Java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。

jdbc几个常用的api

java.sql.DriverManager

管理一组 JDBC 驱动程序的基本服务。(注:DataSource 接口是 JDBC 2.0 API 中的新增内容,它提供了连接到数据源的另一种方法。使用 DataSource 对象是连接到数据源的首选方法。)作为初始化的一部分,DriverManager 类会尝试加载在 “jdbc.drivers” 系统属性中引用的驱动程序类。这允许用户定制由他们的应用程序使用的 JDBC Driver。应用程序不再需要使用 Class.forName() 显式地加载 JDBC 驱动程序。当前使用 Class.forName() 加载 JDBC 驱动程序的现有程序将在不作修改的情况下继续工作。在调用getConnection方法时,DriverManager会试着从初始化时加载的那些驱动程序以及使用与当前applet或应用程序相同的类加载器显式加载的那些驱动程序中查找合适的驱动程序。

java.sql.Driver(接口)

每个驱动程序类必须实现的接口。Java SQL框架允许多个数据库驱动程序。每个驱动程序都应该提供一个实现 Driver 接口的类。DriverManager会试着加载尽可能多的它可以找到的驱动程序,然后,对于任何给定连接请求,它会让每个驱动程序依次试着连接到目标URL。

每个Driver都会有一段static程序,在类加载的时候将自己注册到DriverManager中:

static {
      try {
          java.sql.DriverManager.registerDriver(new Driver());
      } catch (SQLException E) {
          throw new RuntimeException("Can't register driver!");
      }
  }

java.sql.Connection(接口)

与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果。

datasource

数据库链接的建立和关闭是极其耗费系统资源的操作。通过DriverManager获取的数据库连接,一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完后立即关闭连接。频繁的打开、关闭连接将造成系统性能的低下。

对于共享资源的情况,有一个通用版的设计模式:资源池。用于解决资源的频繁请求、释放所造成的性能下降。为了解决数据库连接的频繁请求、释放,JDBC2.0引入了数据库连接池技术。 数据库连接池,在系统启动时,就主动建立足够的数据库连接,并将这些连接组成一个连接池。每次应用程序请求数据库连接时,无须重新打开连接,而是从连接池中取出已有的链接使用,使用完毕后不再关闭数据库连接,而是直接将连接归还给连接池。

DBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由商用服务器等提供实现,也有一些开源组织提供实现,在Spring与Hibernate、Mybatis等ORM框架的整合过程中,DataSource扮演着非常重要的角色。

mybatis使用实践

mybatis ${} 与 #{}

  • ${} 在动态解析时会被直接替换为字符串,一般用于表名,字段名的动态替换
  • #{} 在动态解析时会被替换为?,sql语句会进行预编译,变量在数据库中进行替换,通过预编译可以防止sql注入

mybatis动态sql

mybatis的动态sql指的是使用xml实现了if,choose,when等等标签功能。

通过LanguageDriver接口可以自定义sql的解析方式,可以作为sql的解析器,通过@Lang注解的方式指定解析器,mybatis已经默认实现了xml的解析器,其实我们如果想实现自己的解析方法,可以通过将我们自定义的sql语句转换成为xml的方式,这样我们可以继承XMLLanguageDriver,并重写相关方法完成sql的解析

LanguageDriver的类会在应用启动完成后执行,会生成动态的sql,这块是不会动态的变动了,所以动态的表名称还是需要通过动态sql来生成。

mybatis插件机制

目前团队多数使用的数据框架是mybatis,mybatis的拦截器可以拦截某些方法的调用,开发者可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑不再执行被拦截方法。

mybatis插件使用说明:

org.apache.ibatis.plugin.Interceptor接口,提供三个方法

plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法。

Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。这个类是java.lang.reflect.InvocationHandler接口的一个实现,其实就是一个代理类。

Interceptor是插件真正运行的方法,它将直接覆盖掉你真实拦截对象的方法。里面有一个Invocation对象,利用它可以调用你原本要拦截的对象的方法;在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。

如何使用?

@Intercepts:表明这是一个mybatis拦截器,参数为多个@Signature

@Signature:要拦截的type类,method方法以及args方法参数

type表示拦截的接口类型,有Executor、StatementHandler、ParameterHandler和ResultSetHandler method表示需要拦截的方法,mybatis有update, query, flushStatements, commit, rollback, getTransaction, close, isClosed args表示拦截的参数类型,有MappedStatement、Object、RowBounds和ResultHandler等等

springboot配置插件

有部分是在xml中进行配置,但是这样不是零侵入,需要修改自己mybatis的配置文件。需要寻找更优的方案。

仅仅使用注解@Intercepts进行声明是否可行?否则试下:

@Bean public Interceptor getInterceptor(){ }

补课 1 -- mybatis四个重要对象 Executor:mybatis中有三个执行器。SimpleExecutor,ReuserExecutor,BatchExecutor。默认使用SimpleExecutor。执行器方法中会遍历并调用插件

首先它先生成StatementHandler对象,通过prepareStatement方法调用prepare方法初始化参数,然后使用parameterize方法设置参数到运行环境,然后便通过handler.query(stmt, resultHandler);方法来完成结果组装。

StatementHandler:主要任务是和数据库对话。它会使用parameterHandler和ResultHandler绑定sql参数和组装最后的结果返回。

myBatis有四个statementHandler的实现类。常用的SimpleStatementHandler对应jdbc的statement对象,用于没有预编译参数的sql的运行。PrepareStatementHandler用于预编译参数sql的运行。

prepare方法用来编译sql。

parameterize方法用以进行设置参数。

query/update方法。sql就两种,一种进行查询 -- query,一种进行更新 -- update

statementHandler是四个对象中最重要的对象,是插件的基础。当需要改变sql的时候,要在预编译方法前加入修改的逻辑;要修改参数的时候可以在调用parameterize方法前修改逻辑,或者使用ParameterHandler设置参数;要控制组装结果集的时候,可以在query方法前后加入逻辑,或者使用ResultHandler改造组装结果。

ParameterHandler:用来设置参数规则,在StatementHandler使用prepare方法后,就用它来设置参数。

ResultSetHandler:接口只有一个方法,但是内容比较复杂。

补课 2 -- 工具类MetaObject 这个工具类可以帮我们获取以及设置对象的属性(包括私有属性),而不用去寻找getter 和setter。

使用方法:

MetaObject metaObj = SystemMetaObject.forObject(Object obj)

String strVal = (String)metaObj.getValue("attr1.attr2"); 嵌套获取内部的属性

metaObj.setValue("attr1.attr2", "str"); 设置内部属性的值

使用例子

@Intercepts({
    @Signature(type = StatementHandler.class,
            method = "prepare",
            args = {Connection.class})})
public class PagingInterceptor implements Interceptor {
 
    private int limit = 0;
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过循环可以分离出最原始的的目标类)  
        while (metaStatementHandler.hasGetter("h")) { // jdk动态代理的东西,jdk源码是这样的。h代表下一级对象
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }
        
        //BoundSql对象是处理sql语句的。
        String sql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
        //判断sql是否select语句,如果不是select语句那么就出错了。
        //如果是修改它,是的它最多返回行,这里用的是mysql,其他数据库要改写成其他
        if (sql != null && sql.toLowerCase().trim().indexOf("select") == 0 && !sql.contains("$_$limit_$table_")) {
            //通过sql重写来实现,这里我们起了一个奇怪的别名,避免表名重复.
            sql = "select * from (" + sql + ") $_$limit_$table_ limit " + this.limit;
            metaStatementHandler.setValue("delegate.boundSql.sql", sql); //重写SQL
        }
        return invocation.proceed();//实际就是调用原来的prepared方法,只是再次之前我们修改了sql
    }
 
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);//使用Plugin的wrap方法生成代理对象
    }
 
    @Override
    public void setProperties(Properties props) {
        String limitStr = props.get("page.limit").toString();
        this.limit = Integer.parseInt(limitStr);//用传递进来的参数初始化
    }
 
}

配置插件

<plugins>
        <plugin interceptor="com.ykzhen.charter3.plugin.PagingInterceptor">
            <property name="page.limit" value="100"/>
        </plugin>
    </plugins>
⚠️ **GitHub.com Fallback** ⚠️