跳至主要內容

MyBatis

HeChuangJun约 1847 字大约 6 分钟

MyBatis、JDBC、Statement与PreparedStatement区别?#{}、${}区别? √模糊查询?一对一、一对多查询?批量操作?动态SQL?作用?原理?

半ORM(对象关系映射)框架,封装了JDBC,开发时只关注SQL语句,不用处理加载驱动、创建连接等繁杂的过程,灵活度高。但sql语句编写工作量大,要求高,数据库移植性差

Java DataBase Connectivity用于执行SQL的API接口
//1.注册驱动
Class.forName(com.mysql.jdbc.Driver);
//2.获得链接
connection conn = DirverManger.getConnection(jdbc:mysql://ip:port/test,,密码);
String sql = "select * from t where a = ?";
PreparedStatement psmt = conn.prepareStatement(sql);//3.获得预处理对象
//void setXxx(int index, Xxx xx); 
psmt.setString(1,'张三');//4.设置实际参数
//int executeUpdate(); --执行insert update delete语句.
//ResultSet executeQuery(); --执行select语句.
//boolean execute(); --执行select返回true 否则返回false
ResultSet rs = psmt.executeQuery();//5.执行sql
while( rs.next() ){//6.处理结果集
	Object getObject(int index/String name) 获得任意对象
	String getString(int index/String name) 获得字符串
	int getInt(int index/String name) 获得整形
	double getDouble(int index/String name) 获得双精度浮点型
}
rs.close();//7.释放资源
stmt.close();
con.close();

用途:不~;支持参数化查询
性能:每次都解析和编译,性能差;预编译,高性能
批量操作:不支持,支持
安全性:不能~;防止SQL注入

#{}是占位符,预编译处理;${}是拼接符,没有~
#{}能防止SQL注入,提高系统安全性;${}不能~
#{}的变量替换是在DBMS中;${} 的~外
#{}自动转换java类型和jdbc类型。自动加上'';${}不~~不超过最大限制max_allowed_packet(1M)

LIKE CONCAT('%',#{question},'%')或者"%"#{question}"%"

association一对一关联对象,collection一对多查询关联集合对象的延迟加载。

用foreach标签:属性item,index,collection,open,separator,close。
用ExecutorType.BATCH

以XML标签的形式编写,完成逻辑判断和动态拼接SQL的功能。
动态SQL标签:<if />、<choose />、<when />、<otherwise />、<trim />、<where />、<set />、<foreach />、<bind />
原理:使用OGNL的表达式,从SQL参数对象中计算表达式的值

Mybatis延迟加载?原理?

配置文件:lazyLoadingEnabled=true
使用CGLIB创建目标对象的代理对象,调用目标方法时进入拦截器方法,如a.getB().getName(),拦截器invoke()方法发现a.getB()是null则发送事先保存好的查询关联B对象的sql查询B并调用a.setB(b)
Hibernate同理

MyBatis事务管理形式?

使用JDBC的事务管理机制。利用java.sql.Connection对象
使用MANAGED的事务管理机制。让容器如WebLogic、JBOSS

MyBatis一级缓存和二级缓存?

不推荐,因为分布式环境下缓存基于本地,会有脏数据,不如直接使用Redis、Memcached等分布式缓存
都是基于PerpetualCache没有容量限定的HashMap缓存

一级缓存(默认开):存储作用域为sqlSession或者statement,建议设为Statement。配置:<setting name="localCacheScope" value="SESSION|STATEMENT"/>

二级缓存:~Mapper(Namespace),可自定义存储源Ehcache。配置:<setting name="cacheEnabled" value="true"/>在映射XML中配置cache或者cache-ref

C/U/D操作后清除作用域下select缓存

MyBatis执行顺序√SQLSession 流程,核心组件

读取mybatis-config.xml,生成 Configuration,存储全局配置。
加载 Mapper.xml 文件:生成 MappedStatement 和其他映射信息,存储到 Configuration。
SqlSessionFactoryBuilder 通过Configuration创建 SqlSessionFactory:
SqlSessionFactory创建 SqlSession 对象:用于执行SQL、处理事务
Executor根据MappedStatement动态生成 SQL,执行 SQL 和维护缓存:处理参数绑定、缓存管理,并调用数据库。
输入参数映射:通过 MappedStatement 将传入参数映射到 SQL。
输出结果映射:通过 MappedStatement 将结果集映射为 Java 对象。

调用Mapper接口方法执行SQL
SqlSession 根据调用的 StatementID(Mapper接口的全限定名+方法名)找到MappedStatement对象(包含 SQL 的配置信息)
Executor根据MappedStatement对象生成动态SQL、维护一二级缓存
SQL参数转化、动态SQL拼接,生成JDBC Statement 对象。
使用 ParameterHandler 将参数填充到SQL占位符中,
使用 StatementHandler 将参数绑定到JDBC Statement对象。
JDBC statement对象执行 SQL 语句。返回 ResultSet结果集。
ResultSetHandler 使用 MappedStatement 中的结果映射关系将ResultSet结果集映射为Java对象
关闭SqlSession,释放资源

Configuration:保存全局配置信息(如jdbc数据源、SQL映射文件等)
SqlSession:面向用户的接口,封装了JDBC操作
Executor:用于和数据库交互
MappedStatement:用于描述SQL配置信息,存储 SQL 映射信息,如 SQL 语句、参数映射、结果映射等
StatementHandler:封装了对JDBC中Statement 对象的操作
TypeHandler:类型处理器,用于Java类型与基础类型之间的转换。
ParameterHandler:用于为SQL的参数占位符设置值。
ResultSetHandler:将结果集ResultSet对象转换为Java实体对象

Mapper接口与XML对应关系?Mapper接口方法能重载吗?映射Enum枚举类?Executor执行器分类及区别?

接口的全限名、方法名、方法参数映射文件namespace值、MappedStatement的id值、SQL的参数
不能重载,因为是全限名+方法名的保存和寻找策略。

原理:select、insert、update、delete标签都解析为MappedStatement对象。Mapper接口的实现类通过使用JDK动态代理自动生成代理对象Proxy时会拦截接口方法,根据接口全限名+方法名拼接字符串作为key值,唯一定位一个对应的MappedStatement执行SQL

sqlexecutionprocedure.png
sqlexecutionprocedure.png

EnumTypeHandler基于Enum.name枚举名称(String)。默认。
EnumOrdinalTypeHandler基于Enum.ordinal枚举数值(int)。
设置<setting name="defaultEnumTypeHandler" value="EnumOrdinalTypeHandler" />

通过自定义TypeHandler类实现#setParameter()和getResult()接口完成从javaType和jdbcType双向转换

SimpleExecutor:每次执行update或select都创建Statement对象,用完后立刻关闭
ReuseExecutor:执行update或select时以SQL作为key查找缓存的Statement对象,存在就使用,不存在就创建;用完后放入缓存Map<String, Statement>内
BatchExecutor:执行update操作调用addBatch方法将所有SQL都添加到批处理中,等待executeBatch方法统一执行
CachingExecutor :在三个执行器之上增加二级缓存功能
<setting name="defaultExecutorType" value="SIMPLE、REUSE、BATCH"> 分别使用上面三个执行器
<setting name="cacheEnabled" value=""> value=true时创建 CachingExecutor执行器

Mybatis插件原理?自定义插件?分页插件原理?

基于JDK动态代理,拦截ParameterHandler、ResultSetHandler、StatementHandler、Executor对象的方法

实现Interceptor接口实现intercept(Invocation invocation)方法,给插件添加注解指定要拦截哪接口的方法,在配置文件中配置插件
@Intercepts({
    @Signature(
        type = StatementHandler.class, // 拦截对象类型
        method = "prepare",           // 拦截方法
        args = {Connection.class, Integer.class} // 方法参数
    )
})
public class MyPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 被拦截方法名
        System.out.println("拦截方法: " + invocation.getMethod().getName());
        long startTime = System.currentTimeMillis();
        
        // 执行目标方法
        Object result = invocation.proceed();
        
        long endTime = System.currentTimeMillis();
        System.out.println("方法执行时间: " + (endTime - startTime) + "ms");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        // 判断是否需要生成代理对象
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
        // 读取配置文件的属性
    }
}
Configuration configuration = new Configuration();
MyPlugin myPlugin = new MyPlugin();
Properties properties = new Properties();
properties.setProperty("param1", "value1");
myPlugin.setProperties(properties);
configuration.addInterceptor(myPlugin);

不推荐:使用RowBounds对象对ResultSet结果集执行内存分页,而非数据库分页
推荐:手动或用分页插件给SQL添加分页参数
原理:用插件接口拦截Executor的query方法添加分页参数
分页插件:Mybatis-PageHelper MyBatis-Plus