一、MyBatis的基本结构
MyBatis是一个用于操作数据库的持久层框架,其核心组件包括:
- SqlSessionFactory:创建
SqlSession
的工厂,负责管理数据库连接和事务。 - SqlSession:执行SQL语句的接口,封装了对数据库的操作,提供了CRUD方法。
- Mapper:定义SQL语句和映射结果的接口或XML文件,负责将Java对象与数据库表之间的映射关系。
- Configuration:MyBatis的配置类,包含数据库连接信息、映射文件、插件等配置信息。
二、MyBatis的工作流程
MyBatis的基本工作流程如下:
-
创建SqlSessionFactory:在应用启动时,加载MyBatis的配置文件,创建
SqlSessionFactory
。// 使用SqlSessionFactoryBuilder加载MyBatis配置文件并构建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
-
获取SqlSession:通过
SqlSessionFactory
获取SqlSession
,用于与数据库交互。// 从SqlSessionFactory中获取SqlSession实例 SqlSession sqlSession = sqlSessionFactory.openSession();
-
调用Mapper方法:通过Mapper接口调用数据库操作方法,动态代理会拦截该调用。
-
执行SQL:动态代理获取SQL语句并通过
SqlSession
执行。 -
返回结果:执行结果返回给调用者,使用完后关闭
SqlSession
。
三、动态代理的实现
MyBatis使用动态代理来为Mapper接口生成实现类,主要通过以下几个关键类实现:
1. MapperProxy
MapperProxy
实现了InvocationHandler
接口,负责拦截对Mapper接口方法的调用。在invoke
方法中,调用SqlSession
执行对应的SQL语句。
public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession; // SqlSession实例,用于执行SQL
private final Class<T> mapperInterface; // Mapper接口的Class对象
// 构造函数,初始化SqlSession和Mapper接口
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用SqlSession执行SQL语句,返回结果
return sqlSession.selectOne(getStatement(method), args);
}
// 生成SQL语句的ID,格式为"Mapper接口名.方法名"
private String getStatement(Method method) {
return mapperInterface.getName() + "." + method.getName();
}
}
2. MapperProxyFactory
MapperProxyFactory
用于创建MapperProxy
的实例,持有Mapper接口的类信息。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface; // Mapper接口的Class对象
// 构造函数,初始化Mapper接口
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 创建MapperProxy实例
public T newInstance(SqlSession sqlSession) {
// 通过动态代理创建Mapper接口的代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class<?>[]{mapperInterface},
new MapperProxy<>(sqlSession, mapperInterface));
}
}
四、MyBatis与Spring的整合
整合MyBatis与Spring的步骤如下:
1. Spring配置
在Spring的配置文件中配置数据源、SqlSessionFactory
和SqlSessionTemplate
。
xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <!-- 数据库驱动 -->
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <!-- 数据库连接URL -->
<property name="username" value="root"/> <!-- 数据库用户名 -->
<property name="password" value="password"/> <!-- 数据库密码 -->
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/> <!-- 设置数据源 -->
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/> <!-- 使用SqlSessionFactory创建SqlSessionTemplate -->
</bean>
2. 使用Mapper接口
定义Mapper接口,并使用@Mapper
注解。
@Mapper // 表示这是一个Mapper接口
public interface UserMapper {
User selectUserById(int id); // 根据ID查询用户
}
3. 注入Mapper
在Service层中,通过@Autowired
注解将Mapper接口注入到服务中。
@Service // 表示这是一个Service类
public class UserService {
@Autowired // 自动注入UserMapper
private UserMapper userMapper;
// 根据ID获取用户信息
public User getUserById(int id) {
return userMapper.selectUserById(id); // 调用Mapper方法
}
}
4. 启动类
Spring Boot应用的启动类通常需要使用@SpringBootApplication
注解,以便Spring能够正确地配置和启动应用。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 组合注解,包含@Configuration、@EnableAutoConfiguration和@ComponentScan
public class Application {
public static void main(String[] args) {
// 启动Spring Boot应用
SpringApplication.run(Application.class, args);
}
}
五、缓存机制
MyBatis支持一级缓存和二级缓存,以提高查询性能。
1. 一级缓存
一级缓存是SqlSession级别的缓存,默认开启。它的生命周期与SqlSession
一致。在同一SqlSession
中,相同的查询只会访问一次数据库。
User user1 = userMapper.selectUserById(1); // 第一次查询,访问数据库
User user2 = userMapper.selectUserById(1); // 第二次查询,从缓存中获取
2. 二级缓存
二级缓存是跨SqlSession的缓存,默认关闭。需要在Mapper XML中配置。
2.1 配置二级缓存
要启用二级缓存,首先需要在Mapper XML文件中添加<cache/>
元素。示例:
<mapper namespace="com.example.mapper.UserMapper">
<cache /> <!-- 开启二级缓存 -->
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
2.2 二级缓存的底层实现
MyBatis的二级缓存是一个跨SqlSession
的缓存机制,允许将查询结果存储在内存中,以减少对数据库的访问次数。二级缓存的实现涉及以下几个关键方面:
-
缓存实现类:MyBatis提供了默认的二级缓存实现(
org.apache.ibatis.cache.impl.PerpetualCache
),但也允许开发者实现自定义的缓存类。开发者可以通过实现org.apache.ibatis.cache.Cache
接口来创建自己的缓存实现。 -
查询操作:当执行查询时,MyBatis首先检查二级缓存中是否存在该查询的结果。如果存在,则直接返回缓存中的数据;如果不存在,则执行实际的SQL查询,并将结果存储到二级缓存中。
-
缓存的失效机制:当对数据库进行插入、更新或删除操作时,相应的缓存会被清除,以确保数据的一致性。虽然MyBatis的默认二级缓存没有内置的过期策略,但开发者可以在自定义的缓存实现中添加过期机制。
2.3 示例代码
下面是一个简单的自定义二级缓存实现的示例:
import org.apache.ibatis.cache.Cache;
public class MyCustomCache implements Cache {
private final String id; // 缓存的唯一标识
private final Map<Object, Object> cacheMap = new ConcurrentHashMap<>(); // 存储缓存数据
// 构造函数,初始化缓存ID
public MyCustomCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id; // 返回缓存的唯一标识
}
@Override
public void putObject(Object key, Object value) {
cacheMap.put(key, value); // 将对象放入缓存
}
@Override
public Object getObject(Object key) {
return cacheMap.get(key); // 根据键获取缓存中的对象
}
@Override
public Object removeObject(Object key) {
return cacheMap.remove(key); // 从缓存中移除对象
}
@Override
public void clear() {
cacheMap.clear(); // 清空缓存
}
@Override
public int getSize() {
return cacheMap.size(); // 返回缓存中的对象数量
}
}
六、延迟加载
MyBatis支持延迟加载,即在需要时才加载关联对象。可以通过在映射文件中配置lazy
属性来实现。
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/> <!-- 映射用户ID -->
<result property="name" column="name"/> <!-- 映射用户姓名 -->
<association property="address" column="address_id" select="com.example.mapper.AddressMapper.selectById" lazy="true"/> <!-- 延迟加载地址 -->
</resultMap>
在这个例子中,address
字段的加载是延迟的,只有在实际访问address
时才会查询数据库。
七、映射文件
MyBatis支持使用XML文件来定义SQL语句和映射关系。映射文件通常与Mapper接口对应,命名规则为Mapper接口名.xml
。
1. 映射文件结构
映射文件的基本结构如下:
xml
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserById" resultType="User">
SELECT * FROM user WHERE id = #{id} <!-- 查询用户信息 -->
</select>
</mapper>
2. 映射文件的使用
在Mapper接口中,定义与映射文件中SQL语句对应的方法。例如:
@Mapper
public interface UserMapper {
User selectUserById(int id); // 对应映射文件中的select语句
}
八、调用流程详解
-
获取Mapper:调用
sqlSession.getMapper(UserMapper.class)
,MyBatis返回一个UserMapper
的代理对象。 -
方法调用:当调用
userMapper.selectUserById(1)
时,MapperProxy
的invoke
方法被触发。 -
生成SQL语句ID:
MapperProxy
通过getStatement
方法生成SQL语句的ID,例如UserMapper.selectUserById
。 -
执行SQL:调用
sqlSession.selectOne
方法执行SQL语句,并将结果返回。 -
返回结果:最终,执行结果返回给调用者。
九、接口层与核心处理层
1. 接口层
接口层主要负责定义与数据库交互的方法。Mapper接口通过注解或XML文件定义SQL语句及其参数和返回值的映射关系。使用接口的好处在于可以利用Java的类型安全性和IDE的自动补全。
2. 核心处理层
核心处理层主要负责数据库操作的具体实现,包括事务管理、连接管理等。MyBatis通过SqlSession
来管理数据库操作,确保每个操作都在一个事务上下文中进行。
十、基础模块
基础模块包括:
- 配置模块:管理MyBatis的各种配置,包括数据库连接、映射文件位置等。
- 插件模块:支持MyBatis的插件机制,允许开发者自定义拦截器,增强MyBatis的功能。
- 缓存模块:支持一级和二级缓存,提高查询性能,减少数据库访问次数。
十一、总结
通过动态代理,MyBatis能够在运行时为Mapper接口生成实现类,并将方法调用转发到SqlSession
中执行。同时,MyBatis与Spring的整合使得开发者能够更方便地进行数据库操作,利用Spring的依赖注入和管理功能,提高了代码的可维护性和灵活性。整个架构灵活且易于扩展。