引言
MyBatis 提供了灵活的原生开发方式和高效的 Mapper 代理开发方式。在现代企业级开发中,Mapper 代理方式逐渐成为主流选择,因为它通过动态代理的方式将 SQL 与业务逻辑分离,极大地减少了 SQL 硬编码带来的维护成本。
1 使用 MyBatis 的原生方式开发
以下是使用 Mybatis 原生方式开发的代码:
pom.xml文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--mybatis 依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!--junit 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- 添加slf4j日志api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
<!-- 添加logback-classic依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- 添加logback-core依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>
mybatis-config.xml文件:
xml
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 数据库连接信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
UserMapper.xml文件:
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="selectAll" resultType="com.pojo.User">
select * from tb_user;
</select>
</mapper>
com.pojo.User类
java
package com.pojo;
public class User {
private Integer id;
private String password;
private String gender;
private String addr;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
}
com.entity.MyBatisDemo类:
java
package com.entity;
import com.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
// 1.加载mybatis的核心配置文件,获取SqlSessionFactory
String resource="mybatis-config.xml";
InputStream inputStream= Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
// 2.获取SqlSession对象,用它换行sql
SqlSession sqlSession=sqlSessionFactory.openSession();
// 3.执行sql
List<User> users=sqlSession.selectList("test.selectAll");
System.out.println(users);
// 4.释放资源
sqlSession.close();
}
}
通过观察以上代码,我们可以发现,在原生开发方式中,开发者需要直接通过 SqlSession
来执行 SQL 语句,并手动管理 SQL 语句的 ID、参数和结果映射。
【例如】
java
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
sqlSession.close();
在上述代码中,selectList
方法会执行 SQL 映射文件中 id
为 selectAll
的 SQL 语句。
①优点
- 开发者对 SQL 语句的执行流程有较高的控制权。
- 适合简单的数据库操作场景。
②缺点
- SQL 硬编码,维护成本高。
- 随着项目规模增大,难以管理大量的 SQL 语句。
2 使用 MyBatis 的 Mapper 代理开发
为了简化开发 ,MyBatis 提供了 Mapper 代理开发模式 。
通过该模式,开发者可以将 SQL 语句与 Java 方法分离 ,并通过 Mapper 接口与 SQL 映射文件的结合,实现 SQL 的自动执行。
2.1 Mapper 代理的底层工作原理
MyBatis 使用了动态代理模式来实现 Mapper 接口的代理对象。当调用 sqlSession.getMapper()
时,MyBatis 会为 UserMapper
接口创建一个代理对象,并在方法调用时执行相应的 SQL 语句。
具体流程如下:
- Mapper 接口与 SQL 映射文件的
namespace
对应。 - 调用
sqlSession.getMapper()
方法时,MyBatis 会为接口生成动态代理对象。 - 调用 Mapper 接口的方法时,MyBatis 通过方法名找到对应的 SQL 语句,并执行该 SQL。
- MyBatis 根据 SQL 结果集自动将其映射为 Java 对象。
2.2 具体步骤
1.定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下
在resource目录下建包格式:
复制路径步骤:
2.设置SQL映射文件的namespacel属性为Mappert接口全限定名
UserMapper.xml
改进前:
xml
<mapper namespace="test">
<select id="selectAll" resultType="com.pojo.User">
select * from tb_user;
</select>
</mapper>
改进后:
xml
<mapper namespace="com.mapper.UserMapper">
<select id="selectAll" resultType="com.pojo.User">
select * from tb_user;
</select>
</mapper>
【改进对比】
特性 | 改进前 | 改进后 |
---|---|---|
namespace 的设置 |
使用简单的 test ,不与实际 Java 接口关联 |
使用 Mapper 接口的全限定名,强绑定 Mapper 接口 |
维护性 | 当项目中有多个 Mapper 文件时,容易混淆或冲突 | 明确 Mapper 文件与接口的绑定,易于维护 |
扩展性 | 由于命名简单,增加新接口时需要手动跟踪 namespace 是否冲突 |
随着项目增长,使用全限定名可以轻松扩展多个接口 |
动态代理 | 无法自动将接口与 SQL 映射文件绑定,需要手动管理 | 动态代理可以根据 namespace 自动找到对应的接口 |
开发效率 | 开发速度快,但后期维护代价较高 | 初期开发需要花费更多时间设置,但长期维护更简单 |
3.在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和版回值
类型一致
UserMapper
接口
java
package com.mapper;
import com.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectAll();
}
4.编码
①通过SqlSession的getMapper方法获取Mapper接口的代理对象
②调用对应方法完成sql的执行
MyBatisDemo
类
改进前:
java
// 3.执行sql
List<User> users=sqlSession.selectList("test.selectAll");
改进后:
java
// 3.执行sql,获取 UserMapper接口的代理对象
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
List<User> users=userMapper.selectAll();
【改进对比】
特性 | 改进前 | 改进后 |
---|---|---|
SQL 调用方式 | 通过硬编码的 SQL ID 来调用 SQL 语句 | 通过接口方法来调用 SQL,减少硬编码 |
代码可读性 | 代码中存在硬编码 SQL ID,降低了可读性 | 通过接口调用,代码更清晰、更具可读性 |
维护性 | 当 SQL ID 变化时,需要手动修改代码中的 SQL ID | 通过接口绑定 SQL,不易出错 |
扩展性 | 增加新的 SQL 语句时需要在多个地方硬编码维护 | 通过增加接口方法,可以轻松扩展,不影响现有代码 |
类型安全性 | 需要手动指定返回类型,存在潜在的类型转换风险 | 通过 Mapper 接口,方法签名明确,类型安全 |
开发效率 | 初期开发速度较快,但项目规模增大时维护成本高 | 初期需要编写更多代码,但后期扩展和维护更加方便 |
mybatis-config.xml
改进前:
xml
<mappers>
<!--加载sql映射文件-->
<mapper resource="UserMapper.xml"/>
</mappers>
改进后:
方式一:通过指定映射文件路径加载 SQL 映射文件
xml
<mappers>
<!--加载sql映射文件-->
<mapper resource="com/mapper/UserMapper.xml"/>
</mappers>
方式二:通过包扫描的方式加载 SQL 映射文件
xml
<mappers>
<!--使用包扫描的方式简化SQL映射文件的加载-->
<package name="com.mapper"/>
</mappers>
【改进对比】
特性 | 改进前 | 改进后 |
---|---|---|
SQL 映射文件管理 | 使用相对路径 UserMapper.xml ,文件管理较简单 |
使用全限定路径 com/mapper/UserMapper.xml ,更加明确 |
命名冲突的风险 | 如果不同模块或包中有同名文件,容易引发冲突 | 使用全限定路径可以有效避免命名冲突 |
可维护性 | 当项目规模增大,SQL 文件变多时,管理会变得复杂 | 使用全路径可明确指向特定的 Mapper 文件,便于管理 |
扩展性 | 随着项目增加,需要逐一检查文件路径 | 路径清晰,随着项目扩展也能保持一致 |
【加载映射文件方式】
特性 | 指定映射文件路径 | 包扫描方式 |
---|---|---|
映射文件加载方式 | 需要逐一指定每个 SQL 映射文件的路径 | 通过指定包路径,自动扫描该包及其子包下的所有 Mapper 类 |
配置简洁度 | 当有多个 Mapper 文件时,必须手动添加每个映射文件 | 只需指定包路径,MyBatis 会自动扫描包下所有 Mapper 类 |
适用场景 | 适用于映射文件较少时的简单场景 | 适用于映射文件较多时,适合大型项目 |
灵活性 | 开发者可以明确控制加载的 SQL 映射文件 | 自动扫描所有 Mapper 类,可能加载不必要的映射文件 |
易维护性 | 需要手动维护 SQL 映射文件,项目大时维护成本较高 | 自动扫描包下的映射文件,维护成本低,适合大规模项目 |
加载控制 | 只加载指定的文件,开发者有更强的加载控制权 | 自动加载包下的所有映射文件,可能会加载不需要的文件 |
性能影响 | 只加载指定的映射文件,性能可控 | 包扫描需要遍历整个包目录,但对现代应用性能影响较小 |
运行结果:
4 两种开发方式的对比
特性 | 原生开发方式 | Mapper 代理开发方式 |
---|---|---|
SQL 执行 | 通过 sqlSession.selectList 手动执行 SQL |
通过调用 Mapper 接口中的方法执行 SQL |
SQL 硬编码 | 需要手动提供 SQL 语句 | 将 SQL 放在 XML 配置文件中,避免硬编码 |
SQL 映射文件 | 手动指定 SQL 映射文件及 SQL 语句 ID | 自动根据 Mapper 接口匹配 SQL 映射文件 |
参数和返回值 | 需要手动管理 SQL 的参数和返回类型 | 自动匹配 Mapper 接口的参数和返回值 |
动态代理 | 不涉及动态代理 | 使用动态代理来生成 Mapper 的实现类 |
维护性 | 由于涉及大量 SQL 硬编码,维护较复杂 | SQL 和 Java 代码分离,易于维护 |
适合场景 | 适用于小型项目或简单的查询 | 适用于中大型项目,增强代码的可维护性和可扩展性 |
- 原生方式开发:灵活性高,适合简单项目,但随着项目规模增大,SQL 硬编码难以维护。
- Mapper 代理开发:通过动态代理简化了 SQL 的管理和执行,适合中大型项目的开发,代码维护性和可扩展性更好。
在实际项目中,Mapper 代理开发是更推荐的方式,尤其是在需要管理大量 SQL 语句的中大型项目中,它能有效提高代码的可读性和维护性。