欢迎关注个人主页:逸狼
创造不易,可以点点赞吗
如有错误,欢迎指出~
MyBatisXML配置⽂件
Mybatis的开发有两种⽅式: 1. 注解 2. XML 前⾯学习了注解的⽅式,接下来我们学习XML的⽅式 使⽤Mybatis的注解⽅式,主要是来完成⼀些简单的增删改查功能.如果需要实现复杂的SQL功能,建 议使⽤XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中.
MyBatisXML的⽅式需要以下两步:
1. 配置数据库连接字符串和MyBatis
2. 写持久层代码
配置连接字符串和MyBatis
此步骤需要进⾏两项设置,数据库连接字符串设置和MyBatis的XML⽂件配置。
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
写持久层代码
持久层代码分两部分 1. ⽅法定义Interface 2. ⽅法实现:XXX.xml
添加mapper接⼝
数据持久层的接⼝定义:
package com.example.demo;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMLMapper {
List<UserInfo> selectAll();
}
添加UserInfoMapper.xml
数据持久成的实现,MyBatis的固定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.example.demo.mapper.UserInfoXMLMapper">
<select id="selectAll" resultType="com.example.demo.model.UserInfo">
select * from user_info
</select>
</mapper>
以下是对以上标签的说明:
- <mapper>标签:需要指定namespace 属性,表⽰命名空间,值为mapper接⼝的全限定 名,包括全包名.类名。
- <select>查询标签:是⽤来执⾏数据库的查询操作的:
- id :是和Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法。
- resultType :是返回的数据类型,也就是开头我们定义的实体类

测试
package com.example.demo.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserInfoXMLMapperTest {
@Autowired
private UserInfoXMLMapper userInfoXMLMapper;
@Test
void selectAll() {
userInfoXMLMapper.selectAll().forEach(x -> System.out.println(x));
}
}
使用xml的方式增删改查操作
接下来,我们来实现⼀下⽤⼾的增加、删除和修改的操作.
增(Insert)
UserInfoMapper接⼝:
Integer insert(UserInfo userInfo);
UserInfoMapper.xml实现:
<insert id="insert">
insert into user_info(username, `password`, age, gender, phone)
values(#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
如果使⽤@Param设置参数名称的话,使⽤⽅法和注解类似
返回⾃增id
接⼝定义不变,Mapper.xml实现设置useGeneratedKeys和keyProperty属性
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into user_info(username, `password`, age, gender, phone)
values(#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
删(Delete)
UserInfoMapper接⼝:
Integer delete(Integer id);
UserInfoMapper.xml实现:
<delete id="delete">
delete from user_info where id = #{id}
</delete>
改(Update)
UserInfoMapper接⼝:
Integer update(UserInfo userInfo);
UserInfoMapper.xml实现:
<update id="update">
update user_info set username = #{username} where id = #{id}
</update>
查(Select)
同样的,使⽤XML的⽅式进⾏查询,也存在数据封装的问题 我们把SQL语句进⾏简单修改,查询更多的字段内容
解决办法和注解类似: 1. 起别名 2. 结果映射 3. 开启驼峰命名
其中1,3的解决办法和注解⼀样
接下来看下xml如果来写结果映射
<resultMap id="BeanMap" type="com.example.demo.model.UserInfo" >
<result column="delete_flag" property="deleteFlag"></result>
<result column="create_time" property="createTime"></result>
<result column="update_time" property="updateTime"></result>
</resultMap>
<select id="selectAll" resultMap="BeanMap">
select * from user_info
</select>
一个xml可以定义多个resultMap,注解也是同样,id不能重复,建议把字段写全(否则可能会出现问题)
select username, `password`, age, gender, phone from userinfo where username='' or 1='1'
多表查询
多表查询和单表查询类似,只是SQL不同⽽已 5.1.1 准备⼯作 上⾯建了⼀张⽤⼾表,我们再来建⼀张⽂章表,进⾏多表关联查询. ⽂章表的uid,对应⽤⼾表的id.
对应Model:
package com.example.demo.model;
import lombok.Data;
import java.util.Date;
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private Integer uid;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
查询的sql语句
SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender
FROM article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id WHERE ta.id = 1
对应的接口
package com.example.demo.mapper;
import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface ArticleMapper {
@Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +
"FROM article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id WHERE ta.id = #{id}")
ArticleInfo selectById(Integer id);
}
如果名称不⼀致的,采⽤ResultMap,或者别名的⽅式解决,和单表查询⼀样 Mybatis不分单表还是多表,主要就是三部分:SQL,映射关系和实体类
测试
package com.example.demo.mapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleMapperTest {
@Autowired
private ArticleMapper articleMapper;
@Test
void selectById() {
System.out.println(articleMapper.selectById(1).toString());
}
}
测试结果
这里where
子句中的条件username='' or 1='1'
,由于1='1'
恒成立,所以不管用户名是什么,该条件始终为真。这就导致原本应该根据特定用户名查询的操作,变成了查询表中的所有记录,结果返回了 7 条用户信息记录,展示了用户名、密码、年龄、性别和电话号码等字段。
#{}和${}
MyBatis参数赋值有两种⽅式,咱们前⾯使⽤了#{} 进⾏赋值,接下来我们看下⼆者的区别
#{}和${}使⽤对比
Interger类型的参数
@Select("select * from user_info where id = #{id};")
List<UserInfo> selectById(@Param("id") Integer userId);
我们输⼊的参数并没有在后⾯拼接,id的值是使⽤? 进⾏占位.这种SQL我们称之为"预编译SQL"
我们把 #{} 改成 ${} 再观察打印的⽇志:
@Select("select * from user_info where id = ${id};")
List<UserInfo> selectById(@Param("id") Integer userId);
这次的参数是直接拼接在SQL语句中了.
String类型的参数
@Select("select * from user_info where username = #{name};")
List<UserInfo> selectByName(String name);
我们把 #{} 改成 ${} 再观察打印的⽇志:
可以看到,这次的参数依然是直接拼接在SQL语句中了,但是字符串作为参数时,需要添加引号 '' ,使 ⽤ ${} 不会拼接引号 '' ,导致程序报错.
修改代码如下
@Select("select * from user_info where username = '${name}';")
List<UserInfo> selectByName(String name);
结果测试成功
从上⾯两个例⼦可以看出:
- #{} 使⽤的是预编译SQL,通过? 占位的⽅式,提前对SQL进⾏编译,然后把参数填充到SQL语句 中.#{} 会根据参数类型,⾃动拼接引号'' .
- ${} 会直接进⾏字符替换,⼀起对SQL进⾏编译.如果参数为字符串,需要加上引号 '' . 参数为数字类型时,也可以加上,查询结果不变,但是可能会导致索引失效,性能下降.
#{}和${}区别
#{}和${}的区别就是预编译SQL和即时SQL的区别.
当用⼾发送⼀条SQL语句给服务器后,⼤致流程如下:
1. 解析语法和语义,校验SQL语句是否正确
2. 优化SQL语句,制定执⾏计划
3. 执⾏并返回结果 ⼀条SQL如果⾛上述流程处理,我们称之为ImmediateStatements(即时SQL)
绝⼤多数情况下,某⼀条SQL语句可能会被反复调⽤执⾏,或者每次执⾏的时候只有个别的值不同(⽐ 如select的where⼦句值不同,update的set⼦句值不同,insert的values值不同).如果每次都需要 经过上⾯的语法解析,SQL优化、SQL编译等,则效率就明显不⾏了.
预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译 (只是输⼊的参数不同),省去了解析优化等过程,以此来提⾼效率
更安全(防⽌SQL注⼊)
SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的 ⽅法
sql注⼊代码: ' or 1='1
可以看出来,查询的数据并不是⾃⼰想要的数据.所以⽤于查询的字段,尽量使⽤#{} 预查询的⽅式 SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段,SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀. 如果发⽣在⽤⼾登录的场景中,密码输⼊为 ' or 1='1 ,就可能完成登录(不是⼀定会发⽣的场景, 需要看登录代码如何写)
${}的使用场景
排序功能
使⽤ ${sort} 可以实现排序查询,⽽使⽤#{sort} 就不能实现排序查询了.
注意:此处sort参数为String类型,但是SQL语句中,排序规则是不需要加引号 '' 的,所以此时的 ${sort} 也不加引号
@Select("select * from user_info order by id ${order}")
List<UserInfo> selectByOrder(String order);
我们把${} 改成#{}
可以发现,当使⽤#{sort} 查询时,asc前后⾃动给加了引号,导致sql错误 #{} 会根据参数类型判断是否拼接引号 '' 如果参数类型为String,就会加上引号
like查询
like使⽤#{}报错
@Select("select id, username, age, gender, phone, delete_flag, create_time,
update_time " +
"from user_info where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);
把#{}改成${}可以正确查出来,但是${}存在SQL注⼊的问题,所以不能直接使⽤${}. 解决办法:使⽤mysql的内置函数concat()来处理,实现代码如下:
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time "
+ "from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);
数据库连接池
在上⾯Mybatis的讲解中,我们使⽤了数据库连接池技术,避免频繁的创建连接,销毁连接
数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接, ⽽不是再重新建⽴⼀个.
- 没有使⽤数据库连接池的情况:每次执⾏SQL语句,要先创建⼀个新的连接对象,然后执⾏SQL语句,SQL 语句执⾏完,再关闭连接对象释放资源.这种重复的创建连接,销毁连接⽐较消耗资源
- 使⽤数据库连接池的情况:程序启动时,会在数据库连接池中创建⼀定数量的Connection对象,当客⼾ 请求数据库连接池,会从数据库连接池中获取Connection对象,然后执⾏SQL,SQL语句执⾏完,再把 Connection归还给连接池.
优点: 1. 减少了⽹络开销 2. 资源重⽤ 3. 提升了系统的性能
使⽤
常⻅的数据库连接池: • C3P0 • DBCP • Druid • Hikari
⽬前⽐较流⾏的是Hikari,Druid
Hikari:SpringBoot默认使⽤的数据库连接池
Druid: 如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引⼊相关依赖即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency>
- Druid连接池是阿⾥巴巴开源的数据库连接池项⽬
- 功能强⼤,性能优秀,是Java语⾔最好的数据库连接池之⼀
MySQL开发企业规范总结
1. 表名,字段名使⽤⼩写字⺟或数字,单词之间以下划线分割.尽量避免出现数字开头或者两个下划线 中间只出现数字.数据库字段名的修改代价很⼤,所以字段名称需要慎重考虑。
MySQL在Windows下不区分⼤⼩写,但在Linux下默认是区分⼤⼩写.因此,数据库名,表名,字 段名都不允许出现任何⼤写字⺟,避免节外⽣枝
- 正例:aliyun_admin,rdc_config,level3_name
- 反例:AliyunAdmin,rdcConfig,level_3_name
2.表必备三字段:id,create_time,update_time
id必为主键,类型为bigintunsigned,单表时⾃增,步⻓为1 create_time,update_time的类型均为datetime类型,create_time表⽰创建时间, update_time表⽰更新时间 有同等含义的字段即可,字段名不做强制要求
3.在表查询中,避免使⽤*作为查询的字段列表,标明需要哪些字段.
- 1. 增加查询分析器解析成本
- 2. 增减字段容易与resultMap配置不⼀致
- 3. ⽆⽤字段增加⽹络消耗,尤其是text类型的字段