/7 分钟/4 阅读/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
- MyBatis中Mapper的配置分为两部分,分别为Mapper接口和Mapper SQL配置。MyBatis通过动态代理的方式创建Mapper接口的代理对象,MapperProxy类中定义了Mapper方法执行时的拦截逻辑,通过MapperProxyFactory创建代理实例,MyBatis启动时,会将MapperProxyFactory注册到Configuration对象中。另外,MyBatis通过MappedStatement类描述Mapper SQL配置信息,框架启动时,会解析Mapper SQL配置,将所有的MappedStatement对象注册到Configuration对象中。
- 通过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代理的执行
