java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > 数据库视图和MyBatis XML怎么选

通过一个真实项目的对比数据库视图和MyBatis XML到底怎么选

作者:学计算机的计算基

现在的项目是以Mybatis作为O/R映射框架,确实好用,也非常方便项目的开发,这篇文章主要介绍了如何通过一个真实项目的对比数据库视图和MyBatis XML到底怎么选的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

用一句话说清:视图和 MyBatis XML Mapper 干的其实是同一件事——把复杂 SQL 封装起来复用——只是一个把 SQL 存在数据库里,一个把 SQL 存在应用代码里。 读完你能自己判断什么时候用视图、什么时候写 Mapper,不再跟风。

很多 Java 后端写了一两年 CRUD,对视图的认知还停留在"课本上见过、项目里没用过"。其实你把 MyBatis XML 里那段复杂的 JOIN SQL 复制出来粘到数据库里起个名字,它就是视图。反过来也一样。

这篇文章不做概念堆砌,只讲三件事:视图到底解决了什么、和 MyBatis Mapper 怎么选、以及一个真实项目的对比案例。

一、视图是什么(就一句话)

视图 = 一条被起了名字、存在数据库里的 SELECT 语句。

创建:

CREATE VIEW v_student_score AS
SELECT s.name, c.name AS course_name, r.score
FROM result r
JOIN student s ON r.student_id = s.id
JOIN course  c ON r.course_id  = c.id;

查询:

SELECT * FROM v_student_score WHERE score > 60;

MySQL 在内部把它展开了跑——没有任何数据被复制、被缓存、被预计算。你每次查它,它就老老实实跑一遍底层 SELECT。

你把它理解成一个数据库层面的宏定义就够了。它不存数据,只存定义。

二、MyBatis XML Mapper 是怎么解决同样问题的

没有视图,一个多表查询需求在 MyBatis 里长这样:

<select id="listWithDept" resultType="com.xxx.vo.EmployeeVO">
    SELECT e.name, e.employee_no, d.name AS dept_name
    FROM employee e
    LEFT JOIN department d ON e.department_id = d.id
    WHERE e.status = #{status}
</select>

Service 层一行调走:

List<EmployeeVO> list = employeeMapper.listWithDept("在职");

同样的需求用视图解决就是这样:

CREATE VIEW v_employee_with_dept AS
SELECT e.name, e.employee_no, d.name AS dept_name, e.status
FROM employee e
LEFT JOIN department d ON e.department_id = d.id;

然后 Mapper 里直接查视图:

<select id="listWithDept" resultType="com.xxx.vo.EmployeeVO">
    SELECT * FROM v_employee_with_dept WHERE status = #{status}
</select>

效果一样。本质都是把 JOIN 逻辑封装起来。差别在哪?封装的位置。

三、关键区别对比(不是优缺点,是场景适用)

+----------------------+---------------------------+---------------------------+
|                      |  视图(VIEW)              |  MyBatis SQL / XML        |
+----------------------+---------------------------+---------------------------+
| SQL 存在哪            |  数据库里                  |  应用代码的 XML 文件里      |
| 创建方式              |  DBA 跑 SQL 脚本           |  开发写 XML 然后发版        |
| 生命周期              |  独立于应用,应用重启不丢    |  跟 jar 一起部署            |
| 谁可以用              |  任何连这个库的系统都能查    |  只有这个 Java 应用能用     |
| 改成本                |  改视图定义,不改代码       |  改 XML 文件,重新发版      |
| 版本管理              |  没有(纯 DDL)            |  Git 管理                  |
| 调试                  |  日志里看到的是视图名       |  日志里看到完整 SQL         |
| 对应用代码的影响       |  无感知(像查表一样)       |  需要知道接口方法名          |
+----------------------+---------------------------+---------------------------+

核心差异就一句话:视图是数据库层的封装,MyBatis XML 是应用层的封装。

四、什么时候用视图、什么时候用 MyBatis

没有哪个更好,只有哪个更合适。

用 MyBatis XML 的情况(90% 的后端 CURD)

这是绝大多数 Spring Boot 项目的默认选择。简单直接,跟着代码走,不会忘。

用视图的情况(有明确理由才用)

以下三种场景视图是正确的:

场景一:多个应用/服务共用一个数据库

你的 Java 服务、Python 报表脚本、BI 工具都要查同一份"部门统计"数据。在 XML 里写就是每个应用写一遍——视图写一次,三个都查 v_department_stats,口径天然统一。

场景二:对外系统直连数据库只读

给客户或第三方只授权查视图的权限,不让他们碰到基表。这是安全需求倒逼的,不是技术偏好。

场景三:表结构频繁重构期间做兼容层

-- 把 student 表拆成了 student_info + student_ext,但不想改所有地方的代码
CREATE VIEW student AS
SELECT si.*, se.some_field FROM student_info si LEFT JOIN student_ext se ON ...;

视图名不变,应用代码完全不用动。重构完、代码改完、视图可以删了。

用视图常见的错误动机

“视图提升性能”——不提升,底层 SQL 没变,该扫的索引还是扫。

“视图减少数据库连接”——不减,你调一次 Service 方法的消耗和查一次视图的消耗一样。

“视图让代码更简洁”——没错,但 MyBatis XML 同样能做到。这不是用视图的充分理由。

五、真实项目案例:从"分段查再拼"到一条 SQL

我这个教育管理系统有个学生成绩查询功能,要返回:学生姓名 / 课程名称 / 学年 / 考试类型 / 成绩 / 绩点(GPA)。

原来的写法(你能忍算我输)

public List<ResultSearchVO> search(ResultSearchDTO dto) {
    // 第1次 DB 查:找学生
    List<Student> students = studentMapper.selectList(
        new LambdaQueryWrapper<Student>().like(Student::getStudentNo, dto.getStudentNo()));
    if (students.isEmpty()) return List.of();

    Set<Long> studentIds = students.stream().map(Student::getId).collect(Collectors.toSet());

    // 第2次 DB 查:查成绩
    List<Result> results = resultMapper.selectList(
        new LambdaQueryWrapper<Result>().in(Result::getStudentId, studentIds));
    if (results.isEmpty()) return List.of();

    // 第3次 DB 查:查课程
    Set<Long> courseIds = results.stream().map(Result::getCourseId).collect(Collectors.toSet());
    Map<Long, Course> courseMap = courseMapper.selectByIds(courseIds)
            .stream().collect(Collectors.toMap(Course::getId, Function.identity()));

    // Java 里拼数据 + 手算 GPA
    return results.stream().map(r -> {
        Course c = courseMap.get(r.getCourseId());
        return ResultSearchVO.builder()
                .studentName(nameMap.get(r.getStudentId()))
                .courseName(c != null ? c.getName() : null)
                .score(r.getScore())
                .gpa(scoreToGpa(r.getScore()))  // 手写公式
                .build();
    }).collect(Collectors.toList());
}

3 次数据库往返 + 一个 Java for 循环拼数据 + GPA 公式手写在代码里。查一个学生的成绩,数据库跑了三趟。

改法一:MyBatis XML 写 JOIN(推荐)

<select id="searchStudentScores" resultType="com.xxx.dto.ResultSearchVO">
    SELECT
        s.name          AS studentName,
        c.name          AS courseName,
        c.year          AS year,
        r.exam_type     AS examType,
        r.score         AS score,
        CASE WHEN r.score < 60 THEN 0.0
             ELSE ROUND((r.score - 50) / 10, 2) END AS gpa
    FROM result r
    JOIN student s ON r.student_id = s.id
    JOIN course  c ON r.course_id  = c.id
    WHERE 1 = 1
    <if test="studentNo != null and studentNo != ''">
        AND s.student_no = #{studentNo}
    </if>
    <if test="studentName != null and studentName != ''">
        AND s.name LIKE CONCAT('%', #{studentName}, '%')
    </if>
</select>

Service 变成:

public List<ResultSearchVO> search(ResultSearchDTO dto) {
    return resultMapper.searchStudentScores(dto.getStudentNo(), dto.getStudentName());
}

30 行变成 1 行,3 次 DB 查询变成 1 次,GPA 在 SQL 里用 CASE WHEN 一次算好。干净,可 Git 管理,跟着项目一起发版。

改法二:用视图(收益相同,多了个数据库对象)

CREATE VIEW v_student_score_report AS
SELECT
    s.student_no, s.name AS student_name,
    c.name AS course_name, c.year AS course_year,
    r.score,
    CASE WHEN r.score < 60 THEN 0.0
         ELSE ROUND((r.score - 50) / 10, 2) END AS gpa
FROM result r
JOIN student s ON r.student_id = s.id
JOIN course  c ON r.course_id  = c.id;

然后 Mapper 查视图:

<select id="search" resultType="com.xxx.dto.ResultSearchVO">
    SELECT * FROM v_student_score_report
    WHERE 1 = 1
    <if test="studentNo != null and studentNo != ''">
        AND student_no = #{studentNo}
    </if>
    <if test="studentName != null and studentName != ''">
        AND student_name LIKE CONCAT('%', #{studentName}, '%')
    </if>
</select>

效果一样。区别是 GPA 公式存在数据库视图里还是 XML 里。

为什么最终选了 XML 而非视图

这个项目是一个单体 Spring Boot,只有一个 Java 应用连它。没有 Python 脚本、没有 BI 工具、没有第三方直查。视图带来的唯一好处——“跨系统共享口径”——用不上。每多一个数据库对象就多一个运维点,没收益的事不干。

所以我的结论是:XML 写 JOIN 是这里的最优解。视图在那个规模下是多此一举。

六、一张图记住怎么选

你改这个 SQL 的时候要不要跟着发版?
    ├── 要 → 写在 MyBatis XML 里
    └── 不能发版(或者不想改代码) → 用视图

这个 SQL 是不是多个应用都要查?
    ├── 是 → 用视图(统一口径)
    └── 只有一个 Java 服务 → 写在 MyBatis XML 里

这个 SQL 需要 IF / FOREACH 动态条件吗?
    ├── 是 → 必须 XML,视图做不了动态拼接
    └── 否 → 都可以

需要 Git 版本管理和 Code Review 吗?
    ├── 是 → 写在 XML 里,跟着代码走
    └── 否 → 无所谓

结尾

大多数 Java CRUD 项目根本不需要视图。MyBatis XML 一条 JOIN SQL 就解决的事,别为了用视图而用视图。

视图真正的战场是多系统共享数据、报表引擎直连数据库、表结构迁移做兼容层——这些场景下它不可替代。你用不着的时候老老实实写 XML,比搞个视图简单得多。

到此这篇关于数据库视图和MyBatis XML到底怎么选的文章就介绍到这了,更多相关数据库视图和MyBatis XML怎么选内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文