XMl基本操作

引言

使⽤Mybatis的注解⽅式,主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能,建议使⽤XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中.

之前,我们学习了,用注解的方式来实现MyBatis


接下来我们学习XML的⽅式
MyBatis XML的⽅式需要以下两步

  1. 配置数据库连接字符串和MyBatis
  2. 写持久层代码

MyBatis XML配置⽂件

配置连接字符串和MyBatis

此步骤需要进⾏两项设置,数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。

html 复制代码
# 数据库连接配置
spring:
 datasource:
 url: jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
 username: root
 password: root
 driver-class-name: com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件

//mapper是目录,自己设置
//文件名的后缀部分必须是Mapper.xml不能修改,前面可以添加内容
//这里的*代表通配符
mybatis.mapper-locations=classpath:/myBatis/*Mapper.xml
mybatis:
 mapper-locations: classpath:mapper/**Mapper.xml

写持久层代码

持久层代码分两部分

  1. ⽅法定义 Interface
  2. ⽅法实现: XXX.xml
添加 mapper 接⼝

数据持久层的接⼝定义:

java 复制代码
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMlMapper {
 List<UserInfo> queryAllUser();
}
添加 UserInfoXMLMapper.xml

在resources目录下添加前面在配置文件里面写的xml路径

在xml文件里面添加如下内容:

其中,mapper标签 里面的namespace属性 里面填的是 mapper接口的路径

java 复制代码
<?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.example.demo.mapper.UserMapper">
    
</mapper>

查询所有⽤⼾的具体实现 :

java 复制代码
<?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.example.demo.mapper.UserInfoXMlMapper">
 <select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
 select username,`password`, age, gender, phone from userinfo
 </select>
</mapper>
单元测试
java 复制代码
@SpringBootTest
class UserInfoMapperTest {
 @Autowired
 private UserInfoMapper userInfoMapper;
 @Test
 void queryAllUser() {
 List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
 System.out.println(userInfoList);
 }
 
}

增删改查操作

增(Insert)

UserInfoMapper接⼝

java 复制代码
    Integer insertUser(UserInfo userInfo);

UserInfoMapper.xml实现

java 复制代码
<insert id="insertUser">
 insert into userinfo (username, `password`, age, gender, phone) values (#
{username}, #{password}, #{age},#{gender},#{phone})
</insert>

如果使⽤@Param设置参数名称的话, 使⽤⽅法和注解类似UserInfoMapper接⼝:

java 复制代码
 Integer insertUser(@Param("userinfo") UserInfo userInfo);

UserInfoMapper.xml实现:

java 复制代码
<insert id="insertUser">
 insert into userinfo (username, `password`, age, gender, phone) values
 (#{userinfo.username},#{userinfo.password},#{userinfo.age},#
{userinfo.gender},#{userinfo.phone})
</insert>

返回⾃增 id
接⼝定义不变, Mapper.xml 实现 设置useGeneratedKeys 和keyProperty属性

java 复制代码
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
 insert into userinfo (username, `password`, age, gender, phone) values
 (#{userinfo.username},#{userinfo.password},#{userinfo.age},#
{userinfo.gender},#{userinfo.phone})
</insert
删(Delete)

UserInfoMapper接⼝:

java 复制代码
Integer deleteUser(Integer id);

UserInfoMapper.xml实现:

java 复制代码
<delete id="deleteUser">
 delete from userinfo where id = #{id}
</delete>
改(Update)

UserInfoMapper接⼝:

java 复制代码
Integer updateUser(UserInfo userInfo);

UserInfoMapper.xml实现:

java 复制代码
<update id="updateUser">
 update userinfo set username=#{username} where id=#{id}
</update>
查(Select)

同样的, 使⽤XML 的⽅式进⾏查询, 也存在数据封装的问题我们把SQL语句进⾏简单修改, 查询更多的字段内容.

java 复制代码
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
 select id, username,`password`, age, gender, phone, delete_flag, 
create_time, update_time from userinfo
</select>

运⾏结果:

解决办法和注解类似:

  • 起别名
  • 结果映射
  • 开启驼峰命名

其中1,3的解决办法和注解⼀样,不再多说, 接下来看下xml如果来写结果映射
Mapper.xml

java 复制代码
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
 <id column="id" property="id"></id>
    <result column="delete_flag" property="deleteFlag"></result>
 <result column="create_time" property="createTime"></result>
 <result column="update_time" property="updateTime"></result>
</resultMap>
<select id="queryAllUser" resultMap="BaseMap">
 select id, username,`password`, age, gender, phone, delete_flag, 
create_time, update_time from userinfo
</select>

#{} 和 ${}

MyBatis 参数赋值有两种⽅式, 咱们前⾯使⽤了 #{} 进⾏赋值, 接下来我们看下⼆者的区别

#{} 和${} 使⽤

先看Interger类型的参数

java 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where 
id= #{id} ")
UserInfo queryById(Integer id);

观察我们打印的⽇志

发现我们输出的SQL语句:

sql 复制代码
select username, `password`, age, gender, phone from userinfo where id= ?

我们输⼊的参数并没有在后⾯拼接,id的值是使⽤ ? 进⾏占位. 这种SQL 我们称之为"预编译SQL"
我们把 #{} 改成 ${} 再观察打印的⽇志:

sql 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where 
id= ${id} ")
UserInfo queryById(Integer id);


可以看到, 这次的参数是直接拼接在SQL语句中了.
接下来我们再看String类型的参数

java 复制代码
@Select("select username, `pasword`, age, gender, phone from userinfo where 
username= #{name} ")
UserInfo queryByName(String name);

观察我们打印的⽇志, 结果正常返回

我们把 #{} 改成 ${} 再观察打印的⽇志

java 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where 
username= ${name} ")
UserInfo queryByName(String name);

可以看到, 这次的参数依然是直接拼接在SQL语句中了, 但是字符串作为参数时, 需要添加引号 '' , 使
⽤ ${} 不会拼接引号 '' , 导致程序报错.
修改代码如下:

java 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where 
username= '${name}' ")
UserInfo queryByName(String name);

再次运⾏, 结果正常返回

从上⾯两个例⼦可以看出:
#{} 使⽤的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进⾏编译, 然后把参数填充到SQL语句中.
#{} 会根据参数类型, ⾃动拼接引号 '' .
${} 会直接进⾏字符替换, ⼀起对SQL进⾏编译. 如果参数为字符串, 需要加上引号 '' .
参数为数字类型时, 也可以加上, 查询结果不变, 但是可能会导致索引失效, 性能下降.

#{} 和 ${}区别

#{} 和 ${} 的区别就是预编译SQL即时SQL的区别.
简单回顾:
当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下:

  1. 解析语法和语义, 校验SQL语句是否正确
  2. 优化SQL语句, 制定执⾏计划
  3. 执⾏并返回结果

⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)

性能更⾼
绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐ 如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.

预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译(只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率
更安全(防⽌SQL注⼊)
SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。
由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些
SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。
sql 注⼊代码: ' or 1='1

java 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where 
username= '${name}' ")
List<UserInfo> queryByName(String name);

测试代码:
正常访问情况:

java 复制代码
@Test
void queryByName() {
 List<UserInfo> userInfos = userInfoMapper.queryByName("admin");
 System.out.println(userInfos);
}

结果运⾏正常

SQL注⼊场景:

java 复制代码
@Test
void queryByName() {
 List<UserInfo> userInfos = userInfoMapper.queryByName("' or 1='1");
 System.out.println(userInfos);
}

结果依然被正确查询出来了, 其中参数 or被当做了SQL语句的⼀部分
可以看出来, 查询的数据并不是⾃⼰想要的数据. 所以⽤于查询的字段,尽量使⽤ #{} 预查询的⽅式

SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀. 如果发⽣在⽤⼾登录的场景中, 密码输⼊为 ' or 1='1 , 就可能完成登录(不是⼀定会发⽣的场景, 需要看登录代码如何写)

排序功能

从上⾯的例⼦中, 可以得出结论: {} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询 既然如此, 是不是 {} 就没有存在的必要性了呢?
当然不是.
Mapper实现

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from userinfo order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);

使⽤ {sort} 可以实现排序查询, ⽽使⽤ #{sort} 就不能实现排序查询了. **注意: 此处 sort 参数为String类型, 但是SQL语句中, 排序规则是不需要加引号 '' 的, 所以此时的{sort} 也不加引号**
我们把 ${} 改成 #{}

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from userinfo order by id #{sort} ")
List<UserInfo> queryAllUserBySort(String sort);


可以发现, 当使⽤ #{sort} 查询时, asc 前后⾃动给加了引号, 导致 sql 错误

#{} 会根据参数类型判断是否拼接引号 ''
如果参数类型为String, 就会加上 引号.
除此之外, 还有表名作为参数时, 也只能使⽤ ${}

like 查询

like 使⽤ #{} 报错

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from userinfo where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);

把 #{} 改成 {} 可以正确查出来, 但是{}存在SQL注⼊的问题, 所以不能直接使⽤ ${}.
解决办法: 使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from userinfo where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);

数据库连接池

数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,⽽不是再重新建⽴⼀个.

  • 没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQ语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源
  • 使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾ 请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把 Connection归还给连接池
    优点:
  1. 减少了⽹络开销
  2. 资源重⽤
  3. 提升了系统的性能

常⻅的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari

⽬前⽐较流⾏的是 Hikari, Druid
Hikari : SpringBoot默认使⽤的数据库连接池

相关推荐
渣哥1 小时前
原来 Java 里线程安全集合有这么多种
java
间彧2 小时前
Spring Boot集成Spring Security完整指南
java
间彧2 小时前
Spring Secutiy基本原理及工作流程
java
Java水解3 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆5 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学6 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole6 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊6 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端
程序员鱼皮6 小时前
刚刚 Java 25 炸裂发布!让 Java 再次伟大
java·javascript·计算机·程序员·编程·开发·代码