Configuration组成
Mapper映射器
3个部分组成:
-
MappedStatement
保存一个节点(select | insert | update | delete) ,包括我们配置的sql,,sql的id,,缓存信息,,resultMap,parameterType,resultType,languageDriver 等 -
SqlSource : 提供BoundSql对象的地方,是MappedStatement的一个属性
-
BoundSql : 建立sql和参数的地方
三个常用属性:
- sql : sql语句
- parameterObject : 参数本身
- parameterMappings : 是一个List,,由ParameterMapping组成,,这个对象会描述我们的参数,,比如:属性,名称,表达式,javaType,jdbcType,typeHandler等重要信息,,一个参数对应一个ParameterMapping
SqlSession下的四大对象
Executor执行器
调度StatementHandler,ParameterHandler,ResultSetHandler等来执行sql
mybatis存在三种执行器:
- simple : 简易执行器,,默认的
- reuse : 重用预处理语句
- batch : 重用语句 和 批量更新
在配置文件中设置setting元素defaultExecutorType
StatementHandler 数据库会话器
使用数据库的Statement(PreparedStatement)执行操作
三种StatementHandler:
- SimpleStatementHandler
- PreparedStatementHandler
- CallableStatementHandler
创建一个RoutingStatementHandler对象,,RoutingStatementHandler不是我们真实的服务对象,,他是通过适配模式找到对应的StatementHandler来执行的,,初始化RoutingStatementHandler对象的时候,他会根据上下文环境决定创建哪个StatementHandler对象
方法:
- prepare() : 进行预编译sql语句,,同时设置一些基本运行的参数
- parameterize() : 启用
ParameterHandler
设置参数,完成预编译 - query() : 执行查询
ParameterHandler 参数处理器
对sql参数的处理,,对预编译语句进行参数设置
方法:
- getParameterObject() : 返回参数对象
- setParameters() : 设置预编译sql语句的参数
DefaultParameterHandler
从parameterObject对象中取出参数,使用typeHandler进行参数处理
ResultSetHandler 结果处理器
最后数据集ResultSet的处理
方法:
- handleOutputParameters() : 处理存储过程输出参数
- handlerResultSets() : 包装结果集
SqlSession运行总结
SqlSession执行过程:
SqlSession是通过 Executor 创建StatementHandler 来运行的,而StatementHandler要经过下面三个步骤:
- prepared : 预编译sql
- parameterize : 设置参数
- query/update : 执行sql
其中parameterize
是调用 ParameterHandler的方法去设置的,而且参数是根据类型处理器typeHandler处理。
query/update
是通过resultHandler进行处理结果的封装,如果是update就返回整数,否则就通过typeHandler处理,,最后用 ObjectFactory 提供的规则组装对象,返回给调用者
插件
插件接口Interceptor
:
- intercept() : 直接覆盖你所拦截对象原有的方法,,
- plugin() :给被拦截的对象生成一个代理对象
Plugin.wrap()
- setProperties() : 配置所需要的参数
Plugin.wrap() : 生成这个对象的动态代理对象
调用方法的时候,会从最后一个
代理对象的invoke方法运行到第一个
代理对象的invoke方法,直至四大对象的真实方法
MetaObject 工具类使用
MetaObject这个工具类可以通过其他的技术手段读取或者修改这些重要对象的属性
方法:
SystemMetaObject.forObject()
: 获取MetaObject- getValue() : 获取对象属性值
- setValue() : 设置值
public void test01(){
User user = new User();
MetaObject metaObject = SystemMetaObject.forObject(user);
// 设置属性
metaObject.setValue("userName","sb");
metaObject.setValue("id",100L);
// 设置list
Role role = new Role();
List<Role> roleList = Arrays.asList(role);
metaObject.setValue("roleList",roleList);
metaObject.setValue("roleList[0].roleName","test");
System.out.println("user = " + user);
}
引用:https://blog.csdn.net/Linging_24/article/details/126355031
插件开发
@Intercepts
: 说明他是一个拦截器
@Signature
: 注册拦截器签名的地方。。。。type是四大对象中的一个,method代表要拦截四大对象中的某一种接口的方法,,args表示参数
导包:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
插件类:
@Intercepts(@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class}))
public class QueryLimitPlugin implements Interceptor {
// 最大查询条数
private int limit;
// 数据库类型
private String dbType;
// 限制表中间别名,,避免表重名
private static final String LMT_TABLE_NAME="limit_table_name_xxx";
// private static final limit_
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截对象
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject statementMetaObject = SystemMetaObject.forObject(statementHandler);
// StatementHandler下面有一个 BoundSql对象 ,,
// 获取原来的sql
String sql = (String) statementMetaObject.getValue("boundSql.sql");
String limitSql;
// 数据库是mysql,并且这个sql没有被修改过
if ("mysql".equals(dbType) && sql.indexOf(LMT_TABLE_NAME)==-1){
limitSql = "select * from ( "+sql+" ) "+LMT_TABLE_NAME+" limit "+limit;
// 设置sql
statementMetaObject.setValue("boundSql.sql",limitSql);
}
// 调度真实的StatementHandler的prepare()方法来完成sql预编译
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 返回代理对象
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
this.dbType = properties.getProperty("dbType","mysql");
this.limit = Integer.parseInt(properties.getProperty("limit","1"));
}
}
配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 数据库-->
<properties resource="jdbc.properties"/>
<!-- <settings>-->
<!-- <setting name="mapUnderscoreToCamelCase" value="true"/>-->
<!-- </settings>-->
<settings>
<!-- autoMappingBehavior-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- <setting name="lazyLoadingEnabled" value="true"/>-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 定义别名 -->
<typeAliases>
<package name="com.cj.model"/>
</typeAliases>
<plugins>
<plugin interceptor="com.cj.plugin.QueryLimitPlugin">
<property name="dbtype" value="mysql"/>
<property name="limit" value="1"/>
</plugin>
<!-- <plugin interceptor="com.cj.plugin.MyPlugin">-->
<!-- <property name="dbType" value="mysql"/>-->
<!-- </plugin>-->
</plugins>
<!-- 定义数据库信息,默认使用 development 数据库构建环境-->
<environments default="development">
<environment id="development">
<!-- jdbc事务管理-->
<transactionManager type="JDBC">
<property name="autoCommit" value="true"/>
</transactionManager>
<!-- 数据库信息-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
<!-- 定义映射器-->
<mappers>
<!-- sql和pojo 的映射规则定义,,mybatis解析这个xml,生成映射器 -->
<!-- <mapper resource="mapper/UserMapper.xml"/>-->
<package name="com.cj.mapper"/>
</mappers>
</configuration>
log4j.properties
log4j.rootLogger=DEBUG,stdout
log4j.logger.org.mybatis=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///mybatisplus?serverTimezone=UTC
username=root
password=root
测试:
public interface UserMapper {
@Select("select * from t_user")
List<User> getAllUser();
}
@Test
public void test01() throws IOException {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis02.xml"));
SqlSession sqlSession = factory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> allUser = userMapper.getAllUser();
System.out.println("allUser = " + allUser);
}
引入插件:
<plugin interceptor="com.cj.plugin.QueryLimitPlugin">
<property name="dbtype" value="mysql"/>
<property name="limit" value="1"/>
</plugin>
总结
- 尽量不要用插件,插件生成的是层层代理对象的责任链模式,通过反射方法运行,性能不高
- 插件的代码要考虑全面,特别是多个插件层层代理的时候,要保证逻辑的正确性