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>
标签的 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);
测试: