MyBatis实现SQL
MyBatis实现SQL
- MyBatis实现传统SQL
- resultType和parameterType
- SQL注入
- MyBatis实现动态SQL
- 什么是动态sql
- 动态sql的使用
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();}@Testpublic void findAll(){List<User> users = userDao.findAll();for (User user : users) {System.out.println(user.toString());}}@Testpublic void findById(){User user = userDao.findById(1);System.out.println(user.toString());}@Testpublic void findByName(){List<User> users = userDao.findByName("小");for (User user : users) {System.out.println(user.toString());}}@Testpublic void findByUsername(){List<User> users = userDao.findByUsername("熊");for (User user : users) {System.out.println(user.toString());}}@Testpublic 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);}@Testpublic void update(){User user = new User();user.setId(1);user.setUsername("小黑");user.setSex("男");user.setAddress("上海");int count = userDao.update(user);session.commit();}@Testpublic void delete(){int count = userDao.delete(1);session.commit();}@Testpublic 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 mapperPUBLIC "-//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>
标签的 resultType
和 parameterType
本质是 MyBatis 框架利用 Java 反射机制 实现动态数据绑定的过程
- 针对
resultType="com.qcby.entity.User"
的反射作用时当 SQL 执行完成并返回结果集 ResultSet 后,MyBatis 需要将结果集中的字段值映射到 User 实体类的对象中:首先通过反射获取 User 类的 Class 对象(Class.forName(“com.qcby.entity.User”)),从而知晓这个类的结构,然后通过反射创建 User 类的实例(clazz.newInstance() 或通过构造方法),根据结果集中的列名找到 User 类中与数据库列名匹配的属性,反射查找 User 类中对应的 setter 方法,调用这些方法或直接访问属性,将结果集中的值设置到对象中。 - 针对
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,无需正确密码就能登录系统
解决方式:
?
是JDBC原生的标准的参占位符,由数据库驱动直接处理。SQL 语句会先被预编译,? 位置的参数会被当作数据处理,而非 SQL 语句的一部分,JDBC 会根据参数的 Java 类型自动转换为对应数据库类型,例如字符串加引号、数字进行直接拼接,能有效防止 SQL 注入。
<select id="getUser" resultType="User">SELECT * FROM user WHERE id = ?
</select>
#{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的使用
<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);
测试:
<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);
测试:
<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);
测试:
<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>
<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);
测试: