Mapper代理开发

引言

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&amp;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 映射文件中 idselectAll 的 SQL 语句。

①优点

  • 开发者对 SQL 语句的执行流程有较高的控制权。
  • 适合简单的数据库操作场景。

②缺点

  • SQL 硬编码,维护成本高。
  • 随着项目规模增大,难以管理大量的 SQL 语句。

2 使用 MyBatis 的 Mapper 代理开发

为了简化开发 ,MyBatis 提供了 Mapper 代理开发模式

通过该模式,开发者可以将 SQL 语句与 Java 方法分离 ,并通过 Mapper 接口与 SQL 映射文件的结合,实现 SQL 的自动执行

2.1 Mapper 代理的底层工作原理

MyBatis 使用了动态代理模式来实现 Mapper 接口的代理对象。当调用 sqlSession.getMapper() 时,MyBatis 会为 UserMapper 接口创建一个代理对象,并在方法调用时执行相应的 SQL 语句。

具体流程如下:

  1. Mapper 接口与 SQL 映射文件的 namespace 对应。
  2. 调用 sqlSession.getMapper() 方法时,MyBatis 会为接口生成动态代理对象。
  3. 调用 Mapper 接口的方法时,MyBatis 通过方法名找到对应的 SQL 语句,并执行该 SQL。
  4. 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 语句的中大型项目中,它能有效提高代码的可读性和维护性。


相关推荐
没书读了22 分钟前
ssm框架-spring-spring声明式事务
java·数据库·spring
jokerest12328 分钟前
web——sqliabs靶场——第十三关——报错注入+布尔盲注
mybatis
小二·30 分钟前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic1 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
懒洋洋大魔王1 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康1 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神2 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
qq_327342732 小时前
Java实现离线身份证号码OCR识别
java·开发语言
阿龟在奔跑3 小时前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
飞滕人生TYF3 小时前
m个数 生成n个数的所有组合 详解
java·递归