MyBatis实现SQL

发布于:2025-08-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

MyBatis实现传统SQL

UserTest.java

import com.qcby.dao.UserDao;
import com.qcby.entity.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 org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class UserTest {
    private InputStream in = null;
    private SqlSession session = null;
    private UserDao userDao = null;

    @Before  //前置通知, 在方法执行之前执行
    public void init() throws IOException {
        //加载主配置文件,目的是为了构建SqlSessionFactory对象
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //通过SqlSessionFactory工厂对象创建SqlSesssion对象
        session = factory.openSession();
        //通过Session创建UserDao接口代理对象
        userDao = session.getMapper(UserDao.class);
    }

    @After  //@After: 后置通知, 在方法执行之后执行 。
    public void destory() throws IOException {
        //释放资源
        session.close();
        in.close();
    }

    @Test
    public void findAll(){
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user.toString());
        }
    }

    @Test
    public void findById(){
        User user = userDao.findById(1);
        System.out.println(user.toString());
    }

    @Test
    public void findByName(){
        List<User> users = userDao.findByName("小");
        for (User user : users) {
            System.out.println(user.toString());
        }
    }

    @Test
    public void findByUsername(){
        List<User> users = userDao.findByUsername("熊");
        for (User user : users) {
            System.out.println(user.toString());
        }
    }

    @Test
    public void add(){
        User user = new User();
        user.setUsername("小王");
        user.setSex("男");
        user.setAddress("北京");
        user.setBirthday(new Date());
        int count = userDao.add(user);
        session.commit();   //事务提交
        System.out.println(count);
    }

    @Test
    public void update(){
        User user = new User();
        user.setId(1);
        user.setUsername("小黑");
        user.setSex("男");
        user.setAddress("上海");

        int count = userDao.update(user);
        session.commit();
    }

    @Test
    public void delete(){
        int count = userDao.delete(1);
        session.commit();
    }

    @Test
    public void insertGetId(){
        User user = new User();
        user.setUsername("小李");
        userDao.insertGetId(user);
        session.commit();
        System.out.println(user.getId());
    }
]

UserDao.java

package com.qcby.dao;

import com.qcby.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface UserDao {
    List<User> findAll();

    User findById(Integer id);

    List<User> findByName(String username);

    List<User> findByUsername(String username);

    int add(User user);

    int update(User user);

    int delete(Integer id);

    int insertGetId(User user);
}

UserMapper.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="com.qcby.dao.UserDao">

    <select id="findAll" resultType="com.qcby.entity.User">
        select * from user;
    </select>

    <select id="findById" resultType="com.qcby.entity.User"
            parameterType="java.lang.Integer">
        select * from user where id = #{id};
    </select>

    <select id="findByName" resultType="com.qcby.entity.User"
            parameterType="java.lang.String">
        select * from user where username like '%${value}%';
    </select>

    <select id="findByUsername" resultType="com.qcby.entity.User"
            parameterType="java.lang.String">
        select * from user where username like concat('%',#{username},'%');
    </select>

    <insert id="add" parameterType="com.qcby.entity.User">
        insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address});
    </insert>
    
    <update id="update" parameterType="com.qcby.entity.User">
        update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id};
    </update>
    
    <delete id="delete" parameterType="java.lang.Integer">
        delete from user where id=#{id};
    </delete>

    <!--返回主键:主键需要设置自动递增-->
    <insert id="insertGetId" parameterType="com.qcby.entity.User">
        <selectKey keyProperty="id" resultType="int" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address});
    </insert>

</mapper>

关键细节讲解:

resultType和parameterType

<select> 标签的 resultTypeparameterType 本质是 MyBatis 框架利用 Java 反射机制 实现动态数据绑定的过程

  1. 针对 resultType="com.qcby.entity.User" 的反射作用时当 SQL 执行完成并返回结果集 ResultSet 后,MyBatis 需要将结果集中的字段值映射到 User 实体类的对象中:首先通过反射获取 User 类的 Class 对象(Class.forName(“com.qcby.entity.User”)),从而知晓这个类的结构,然后通过反射创建 User 类的实例(clazz.newInstance() 或通过构造方法),根据结果集中的列名找到 User 类中与数据库列名匹配的属性,反射查找 User 类中对应的 setter 方法,调用这些方法或直接访问属性,将结果集中的值设置到对象中。
  2. 针对 parameterType="java.lang.Integer" 的反射作用是当传递参数给 SQL 时,MyBatis 需要解析参数的类型和值:当传递参数给 SQL 时,MyBatis 需要解析参数的类型和值, MyBatis 会通过反射(Class.forName(“java.lang.Integer”))获取 Integer 类的 Class 对象,从而确定参数的具体类型是 Integer,反射获取参数的值,最终将值填充到 SQL 的 #{id} 占位符中。

MyBatis 中的反射机制,本质是利用 Java 反射的动态性,运行时获取类信息、操作类成员,实现了框架对任意实体类和参数类型的通用支持,无需硬编码适配特定类,而是通过反射动态处理各种类型,这也是 MyBatis 灵活性的重要体现。

SQL注入

正常情况下,程序会将用户输入作为数据传递给数据库;但如果程序没有对用户输入做严格过滤,用户输入就可能被当作 SQL 代码 的一部分被执行

假设某登录功能的 SQL 查询语句是:

select* from user where username = "+username+" and password = "+password+"

当 password 部分传入的参数为 1’ or’1’ = '1 时,该代码会变成:

select* from user where username = '123' and password ='1' or'1' = '1'

这样整个 sql 的判断语句始终为 True,无需正确密码就能登录系统

解决方式:

  1. ? 是JDBC原生的标准的参占位符,由数据库驱动直接处理。SQL 语句会先被预编译,? 位置的参数会被当作数据处理,而非 SQL 语句的一部分,JDBC 会根据参数的 Java 类型自动转换为对应数据库类型,例如字符串加引号、数字进行直接拼接,能有效防止 SQL 注入。
<select id="getUser" resultType="User">
	SELECT * FROM user WHERE id = ?
</select>
  1. #{id}:MyBatis的预编译占位符,底层会转换为JDBC的?占位符,同时也会自动根据参数值进行类型转换和转义,完全防SQL注入风险,安全性高,是MyBatis中推荐的参数传递方式。
<select id="getUser" resultType="User">
	SELECT * FROM user WHERE id = #{id}
</select>

等价于JDBC的 SELECT * FROM user WHERE id = ?,MyBatis会自动将 id 的参数值替换到?的位置上

补充 ${value}${value} 用于MyBatis的字符串拼接,将变量或表达式的结果插入到字符串中,底层不会转换为 ? 且不进行参数类型转换和转义,参数值会被当作SQL的一部分执行,仍然存在SQL注入风险,仅在必须动态生成SQL结构时使用。

<select id="findByUsername" resultType="com.qcby.entity.User" parameterType="java.lang.String">
	select * from user where username like concat('%',#{username},'%');
</select>

总结:

类型 底层实现 安全性 适用场景 自动转义
? JDBC原生 高(防注入) 替换参数值(JDBC直接使用)
#{id} 转换为? 高(防注入) 替换参数值(MyBatis推荐)
${value} 字符串直接拼接 低(有注入风险) 替换SQL关键字、表名、列名等

此外还有一个需要注意的内容

原数据库内容如下:

在这里插入图片描述

当执行update()方法时,传入的参数是

User user = new User();
user.setId(1);
user.setUsername(“小黑”);
user.setSex(“男”);
user.setAddress(“上海”);

发现这里并没有设置birthday这个属性,执行update()方法后数据库的值为:

在这里插入图片描述

这样birthday这个属性值直接为Null了,这是有问题的,我们使用非动态SQL的方式就能完美解决了,下面我们来详细讲解动态SQL。

MyBatis实现动态SQL

什么是动态sql

动态sql是在不同条件下拼接不同的sql。
Mybatis框架的动态sql技术是一种根据特定条件动态拼接SQl语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。

动态sql的使用

  1. <where> --- <if> 标签

Mapping:

	<select id="selectUser" parameterType="com.qcby.entity.User" resultType="com.qcby.entity.User">
        select * from user
        <where>
            <if test="username!=null">
                and username like concat('%',#{username},'%')
            </if>
            <if test="sex!=null">
                and sex = #{sex}
            </if>
            <if test="address!=null">
                and address like concat('%',#{address},'%')
            </if>
            <if test="birthday!=null">
                and birthday = #{birthday}
            </if>
        </where>
    </select>

如果标签返回的内容是以 and 或 or 开头的,则它会剔除掉

UserDao.java

List<User> selectUser(User user);

测试:

在这里插入图片描述

  1. <set>、<if>标签

Mapping:

	<update id="updateUser" parameterType="com.qcby.entity.User">
        update user
        <set>
            <if test="username!=null">
                username = #{username},
            </if>
            <if test="birthday!=null">
                birthday = #{birthday},
            </if>
            <if test="sex!=null">
                sex = #{sex},
            </if>
            <if test="address!=null">
                address = #{address},
            </if>
        </set>
            where id = #{id}
    </update>

用于 UPDATE 语句,自动处理逗号分隔问题

UserDao.java

public int update(User user);

测试:

在这里插入图片描述

  1. <choose>、<when>和<otherwise> 标签

这个标签相当于是java当中的 if…elseif…else,<choose> 标签是这个标签组合当中的父标签,<when> 标签就相当于 if 和 elseif,<othrtwise>标签相当于是 else,只执行第一个满足条件的 when

mapping:

    <select id="selectUserByChoose" parameterType="com.qcby.entity.User" resultType="com.qcby.entity.User">
        select * from user
        <where>
            <choose>
                <when test="id!='' and id!=null">
                    id=#{id}
                </when>
                <when test="username!='' and username!=null">
                    and username=#{username}
                </when>
                <otherwise>
                    and sex=#{sex}
                </otherwise>
            </choose>
        </where>
    </select>

UserDao.java

    List<User> selectUserByChoose(User user);

测试:

在这里插入图片描述

  1. <trim> 标签

trim标记是一个格式化的标记,可以完成set或者是where标记的功能,自定义字符串截取规则,可替代 where 和 set 标签
① 用 trim 改写上面第二点的 if+where 语句

	<select id="trimSelectUser" parameterType="com.qcby.entity.User" resultType="com.qcby.entity.User">
        select * from user
        <trim prefix="where" prefixOverrides="and | or">
            <if test="username!=null">
                and username like concat('%',#{username},'%')
            </if>
            <if test="sex!=null">
                and sex = #{sex}
            </if>
            <if test="address!=null">
                and address like concat('%',#{address},'%')
            </if>
            <if test="birthday!=null">
                and birthday = #{birthday}
            </if>
        </trim>
    </select>

prefix:在 trim 包裹的内容前添加指定前缀(如 “WHERE”、“SET”)
suffix:在 trim 包裹的内容后添加指定后缀
prefixOverrides:去除内容前面多余的指定字符(如 “AND”、“OR”、“,”)
suffixOverrides:去除内容后面多余的指定字符(如 “,”)

② 用 trim 改写上面第三点的 if+set 语句

    <update id="trimUpdateUser" parameterType="com.qcby.entity.User">
        update user
        <trim prefix="set" suffixOverrides=",">
            <if test="username!=null">
                username = #{username},
            </if>
            <if test="birthday!=null">
                birthday = #{birthday},
            </if>
            <if test="sex!=null">
                sex = #{sex},
            </if>
            <if test="address!=null">
                address = #{address},
            </if>
        </trim>
        where id = #{id}
    </update>
  1. <foreach> 标签

有些时候数据是以数组的形式出现的,比如进行批量删除和批量添加的时候

①:批量删除
mapping:

    <delete id="deleteAll">
        delete from user where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")" >
            #{id}
        </foreach>
    </delete>

collection:当前要循环的数组或者集合,item: 我们指定要循环的数组的每一个元素,separator:每一个元素应该用什么来做分割,open:当前循环是以什么开始,close:当前循环是以什么结束

UserDao.java

    int deleteAll(@Param("ids") Integer[] ids);

测试:

在这里插入图片描述

②:批量添加
mapping:

    <insert id="insertAll">
        insert into user(username,address,sex,birthday) values
        <foreach collection="users" item="user" separator=",">
            (#{user.username},#{user.address},#{user.sex},#{user.birthday})
        </foreach>
    </insert>

UserDao.java

    int insertAll(@Param("users") List<User> users);

测试:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到