跳到正文
Kale的数字花园
目录 文章
/7 分钟/3 阅读/0 评论

JDBC

- JDBC API中提供的DriverManager类获取 - DataSource接口的实现类获取 先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结果封装成Java类。 用于描述MyBatis的主配置信息,其他组件需要获

Connection

  • 一个Connection对象表示通过JDBC驱动与数据源建立的连接
  • Connection对象表示客户端会话,因此它需要一些相关的状态信息,例如用户Id、一组SQL语句和会话中使用的结果集以及事务隔离级别等信息。
  • connection的获取可以从两个方面获取
    • JDBC API中提供的DriverManager类获取
        Connection connection = DriverManager.getConnection(
          "jdbc:mysql://127.0.0.1:3306/data?user=root&password=123456");
- DataSource接口的实现类获取
        DataSource dataSource = new UnpooledDataSource(
                "com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://127.0.0.1:3306/data?user=root&password=123456&AllowPublicKeyRetrieval=true",
                "root","qwer1234");

Statement

  • Statement 不支持输入参数,有sql注入的风险
  • PreparedStatement:增加了设置SQL参数的方法
  • CallableStatement:增加了调用存储过程以及检索存储过程调用结果的方法
不同Statement执行的方式

		Connection connection = DriverManager.getConnection("");
        String sql = "SELECT * FROM admin WHERE username = ? AND password = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1,"username");
        preparedStatement.setString(2,"password");
        ResultSet resultSet = preparedStatement.executeQuery();


        String sql2 = "SELECT * FROM admin WHERE username = 'username' AND password = 'password'";
        Statement statement2 = connection.createStatement();
        ResultSet resultSet2 = statement.executeQuery(sql);

Mybatis的工作原理

先封装SQL,接着调用JDBC操作数据库,最后把数据库返回的表结果封装成Java类。

主要组件

SqlSession

  • 它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。
  • SqlSession是MyBatis提供的面向用户的API,表示和数据库交互时的会话对象,用于完成数据库的增删改查功能。SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的数据库操作接口。

Executor(可扩展插件)

  • Executor是MyBatis的SQL执行器,MyBatis中对数据库所有的增删改查操作都是由Executor组件完成的。

StatementHandler(可扩展插件)

  • StatementHandler组件封装了对JDBC Statement的操作,例如设置Statement对象的fetchSize属性、设置查询超时时间、调用JDBC Statement与数据库交互等。
  • SimpleStatementHandler :封装了JDBC的 Statement 对象
  • PreparedStatementHandler:封装了JDBC的 PreparedStatement 对象 (可添加sql参数)
  • CallableStatementHandler:封装了JDBC的 CallableStatement 对象

ParameterHandler(可扩展插件)

  • 当MyBatis框架使用的Statement类型为CallableStatement和PreparedStatement时,ParameterHandler用于为Statement对象参数占位符设置值。

ResultSetHandler(可扩展插件)

  • ResultSetHandler封装了对JDBC中的ResultSet对象操作,当执行SQL类型为SELECT语句时,ResultSetHandler用于将查询结果转换成Java对象。

TypeHandler(自定义转化处理器)

  • 处理JDBC类型与Java类型之间的转换

Configuration

用于描述MyBatis的主配置信息,其他组件需要获取配置信息时,直接通过Configuration对象获取。除此之外,MyBatis在应用启动时,将Mapper配置信息、类型别名、TypeHandler等注册到Configuration组件中,其他组件需要这些信息时,也可以从Configuration对象中获取。

MappedStatement

  • MappedStatement用于描述Mapper中的SQL配置信息,是对Mapper XML配置文件中<select|update|delete|insert>等标签或者@Select/@Update等注解配置信息的封装。

Mybatis描述文件的属性

type name desc
通用 id 在命名空间中唯一的标识符,可以被用来引用这条配置信息
parameterType 用于指定这条语句的参数类的完全限定名或别名
statementType 参数可选值为STATEMENT、PREPARED或CALLABLE,这会让MyBatis分别使用Statement、PreparedStatement或CallableStatement与数据库交互,默认值为PREPARED。
flushCache 用于控制是否刷新缓存。如果将其设置为 true,则任何时候只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值为false。
timeout 驱动程序等待数据库返回请求结果的秒数,超时将会抛出异常。
databaseId (databaseIdProvider:根据不同数据库执行不同的sql语句)
如果配置了 databaseIdProvider,MyBatis会加载所有不带databaseId或匹配当前 databaseId 的语句
selelct resultType 从这条语句中返回的期望类型的类的完全限定名或别名。注意,如果返回结果是集合类型,则resultType属性应该指定集合中可以包含的类型,而不是集合本身。
resultMap 用于引用通过<resultMap>标签配置的实体属性与数据库字段之间建立的结果集的映射(注意:resultMap和resultType属性不能同时使用)
resultSetType 参数可选值为FORWARD_ONLY、SCROLL_SENSITIVE或SCROLL_INSENSITIVE,用于设置ResultSet对象的特征
useCache 是否使用二级缓存。如果将其设置为 true,则会导致本条语句的结果被缓存在MyBatis的二级缓存中,对应<select>标签,该属性的默认值为true
fetchSize 用于设置JDBC中 Statement对象的fetchSize属性,该属性用于指定SQL执行后返回的最大行数
insert
update
useGeneratedKeys Statement对象的getGeneratedKeys()方法来取出由数据库内部生成的键值,例如MySQL自增主键。
keyProperty 用于将数据库自增主键或者<insert>标签中<selectKey>标签返回的值填充到实体的属性中,如果有多个属性,则使用逗号分隔。

#{}和${}参数占位符

  • 使用#{}参数占位符时,占位符内容会被替换成“?”,然后通过PreparedStatement对象的setXXX()方法为参数占位符设置值;而${}参数占位符内容会被直接替换为参数值。使用#{}参数占位符能够有效避免SQL注入问题,所以我们可以优先考虑使用#{}占位符,当#{}参数占位符无法满足需求时,才考虑使用${}参数占位符。
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。
  • 预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。

Mapper

  1. MyBatis中Mapper的配置分为两部分,分别为Mapper接口和Mapper SQL配置。MyBatis通过动态代理的方式创建Mapper接口的代理对象,MapperProxy类中定义了Mapper方法执行时的拦截逻辑,通过MapperProxyFactory创建代理实例,MyBatis启动时,会将MapperProxyFactory注册到Configuration对象中。另外,MyBatis通过MappedStatement类描述Mapper SQL配置信息,框架启动时,会解析Mapper SQL配置,将所有的MappedStatement对象注册到Configuration对象中。
  2. 通过Mapper代理对象调用Mapper接口中定义的方法时,会执行MapperProxy类中的拦截逻辑,将Mapper方法的调用转换为调用SqlSession提供的API方法。在SqlSession的API方法中通过Mapper的Id找到对应的MappedStatement对象,获取对应的SQL信息,通过StatementHandler操作JDBC的Statement对象完成与数据库的交互,然后通过ResultSetHandler处理结果集,将结果返回给调用者。

几种扩展的执行顺序

Executor ------> Start    sql:[select  * from  `order` where  sub_size = ? and amount = ?]
ParameterHandler  ------>   select  * from  `order` where  sub_size = ** NOT SPECIFIED ** and amount = ** NOT SPECIFIED **
StatementHandler Init ------>   query
TypeHandler getResult Before ------>   {}
ResultSetHandler  ------>   [{"amount":333,"data":"{}","gmtCreate":1639716832000,"id":2,"jsonData":{},"subSize":333}]
StatementHandler Before ------>   [{"amount":333,"data":"{}","gmtCreate":1639716832000,"id":2,"jsonData":{},"subSize":333}]
StatementHandler After  ------>   [{"amount":333,"data":"{\"StatementHandler\":\"Live\"}","gmtCreate":1639716832000,"id":2,"jsonData":{"StatementHandler":"Live"},"subSize":333}]
Executor ------>  END  [{"amount":333,"data":"{\"StatementHandler\":\"Live\"}","gmtCreate":1639716832000,"id":2,"jsonData":{"StatementHandler":"Live"},"subSize":333}]

MyBatis缓存

一级缓存

  • 一级缓存默认是开启的,而且不能关闭。
  • 一级缓存级别:参数 localCacheScope
    • SESSION 当指定localCacheScope参数值为SESSION时,缓存对整个SqlSession有效,只有执行DML语句(更新语句)时,缓存才会被清除
    • STATEMENT 当localCacheScope值为STATEMENT时,缓存仅对当前执行的语句有效,当语句执行完毕后,缓存就会被清空。

二级缓存

  • 默认二级缓存关闭
  • 作用域为 Namespaces
  • 当Namespaces内执行查询操作时,查询结果会缓存到二级缓存中,执行更新操作后,二级缓存会被清空。

第三方缓存

  • 支持使用第三方作为缓存

插件代码示例

Executor示例代码

@Intercepts({
    @Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class,
            RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update",
                args = {MappedStatement.class, Object.class})
})
public class ExecutorInterceptor implements Interceptor {

    public static int count =1;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {


        if (count>1){
            return invocation.proceed();
        }
        count++;
        System.out.println("进入ExecutorInterceptor");
        MappedStatement parameterHandler = (MappedStatement)invocation.getArgs()[0];
        Object param = invocation.getArgs()[1];
        BoundSql boundSql = parameterHandler.getBoundSql(param);
        System.out.println("Executor ------> Start    "+ StringFormatter.format("sql:[%s]",boundSql.getSql()).getValue());
//        invocation.getArgs()[0] = newMappedStatement(parameterHandler);
        Object proceed = invocation.proceed();
        System.out.println("Executor ------>  END  "+ JSON.toJSONString(proceed));
        return proceed;
    }
    
    //下面这个可以动态更改SQL

    public MappedStatement newMappedStatement(MappedStatement parameterHandler){
        StaticSqlSource sqlSource = new StaticSqlSource(parameterHandler.getConfiguration(),
            "select  * from  `order` where  id = 1");

        MappedStatement.Builder builder = new MappedStatement.Builder(
            parameterHandler.getConfiguration(),
            parameterHandler.getId(),
            sqlSource,
            parameterHandler.getSqlCommandType()
        );
        builder.resource(parameterHandler.getResource());
        builder.fetchSize(parameterHandler.getFetchSize());
        builder.statementType(parameterHandler.getStatementType());
        builder.keyGenerator(parameterHandler.getKeyGenerator());
        if (parameterHandler.getKeyProperties() != null && parameterHandler.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : parameterHandler.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(parameterHandler.getTimeout());
        builder.parameterMap(parameterHandler.getParameterMap());
        builder.resultMaps(parameterHandler.getResultMaps());
        builder.resultSetType(parameterHandler.getResultSetType());
        builder.cache(parameterHandler.getCache());
        builder.flushCacheRequired(parameterHandler.isFlushCacheRequired());
        builder.useCache(parameterHandler.isUseCache());

        return builder.build();
    }
}

ParameterHandler

@Intercepts(
    {
        @Signature(type = ParameterHandler.class, method = "setParameters",
            args = {PreparedStatement.class})
    }
)
public class ParamInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        ClientPreparedStatement ps = (ClientPreparedStatement) invocation.getArgs()[0];
        System.out.println("ParameterHandler  Start------>   "+ps.asSql());
        Object proceed = invocation.proceed();
        System.out.println("ParameterHandler  End  ------>   "+ps.asSql());

        return proceed;
    }

}

ResultSetHandler

@Intercepts(
    {
        @Signature(type = ResultSetHandler.class, method = "handleResultSets",
            args = {Statement.class})
    }
)
public class ResultIInterceptor implements Interceptor {

    @Override public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("ResultSetHandler  Start ------>   ");
        Object proceed = invocation.proceed();
        System.out.println("ResultSetHandler  End   ------>   "+JSON.toJSONString(proceed));
        return proceed;
    }
}

StatementHandler

@Intercepts(
    {
        @Signature(type = StatementHandler.class, method = "query",
                args = {Statement.class, ResultHandler.class}),
            @Signature(type = StatementHandler.class, method = "update",
                    args = {Statement.class})
    }
)
public class StatementInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();


        Method method = invocation.getMethod();

        if (method.getName().equals("update")){
            Object proceed = invocation.proceed();
            System.out.println("StatementHandler Update Result ------>   "+ JSON.toJSONString(proceed));
            return proceed;
        }

        System.out.println("StatementHandler Start ------>   "+ method.getName());

        //注解中method的值
        List<OrderDO> proceed = (List<OrderDO>)invocation.proceed();

        System.out.println("StatementHandler Before ------>   "+ JSON.toJSONString(proceed));

        for (OrderDO orderDO : proceed) {
            JSONObject jsonData = orderDO.getJsonData();
            if (jsonData!=null){
                jsonData.put("StatementHandler","Live");
                orderDO.setData(jsonData);
            }
        }

        System.out.println("StatementHandler END  ------>   "+ JSON.toJSONString(proceed));

        return proceed;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

TypeHandler

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class JsonTypeHandler implements TypeHandler<JSONObject> {


    @Override
    public void setParameter(PreparedStatement ps, int i, JSONObject parameter, JdbcType jdbcType) throws SQLException {
        System.out.println("TypeHandler setParameter Before ------>   "+ parameter);
        ps.setString(i,parameter.toJSONString());
    }

    @Override
    public JSONObject getResult(ResultSet rs, String columnName) throws SQLException {
        System.out.println("TypeHandler getResult Before ------>   "+ rs.getString(columnName));
        return JSON.parseObject(rs.getString(columnName));
    }

    @Override
    public JSONObject getResult(ResultSet rs, int columnIndex) throws SQLException {
        System.out.println("TypeHandler getResult Before ------>   "+ rs.getString(columnIndex));
        return JSON.parseObject(rs.getString(columnIndex));
    }

    @Override
    public JSONObject getResult(CallableStatement cs, int columnIndex) throws SQLException {
        System.out.println("TypeHandler getResult Before ------>   "+ cs.getString(columnIndex));
        return JSON.parseObject(cs.getString(columnIndex));
    }
}

XML中的配置

<configuration>
	<typeHandlers>
        <typeHandler jdbcType="VARCHAR" javaType="list"
                     handler="com.code.note.config.typeHandler.JsonTypeHandler"/>
        <typeHandler jdbcType="VARCHAR" javaType="String"
                     handler="com.code.note.config.typeHandler.NonoTypeHandler"/>
    </typeHandlers>

    <plugins>
        <plugin interceptor="com.code.note.config.plugins.ExecutorInterceptor"/>
        <plugin interceptor="com.code.note.config.plugins.StatementInterceptor"/>
        <plugin interceptor="com.code.note.config.plugins.ParamInterceptor"/>
        <plugin interceptor="com.code.note.config.plugins.ResultIInterceptor"/>
    </plugins>
</configuration>

Mapper的生命周期

Mapper配置文件的读取和装载

Mapper代理的执行

评论需要登录。生产环境使用 GitHub 登录,本地可前往 登录页 使用开发登录。

还没有评论,来抢沙发。