一.解决数据库字段名和对象属性名冲突的问题.
- 产生这个问题的本质原因就是Java 属性名和数据库字段的命名规范不同. 这个问题的本质就是查询数据库返回了字段,但是不知道和Java对象的哪个属性相对应
1.注解的解决方法
- 注解的解决方式有三种:
- 方式一:给数据库字段起别名.== 本质上就是通过给数据库字段起别名的方式,让数据库的字段能供和Java对象的属性的对应关系能够对应的上.==
- 方式二:指定结果映射. 数据返回了,但是不知道和谁对应,这个方式就是用映射的方式来指定数据间的对应关系.
- 方式三:自动驼峰转换.
1.1 给数据库字段起别名的具体实现.
- mapper层代码实现:
@Select("select id,username,delete_flag as deleteFlag,create_time as createTime,update_time as updateTime from userInfo")
List<UserInfo> selectUserInfo();
- 生成对应的测试代码,运行结果:
1.2 指定结果映射的具体实现.
- mapper层代码实现:
@Results(id = "resultMap", value = {
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("select * from userInfo")
List<UserInfo> selectUserInfo2();
- 生成对应的测试代码,运行结果:
配置一次之后下一次就可以直接使用这个对应关系.
@ResultMap("resultMap")
@Select("select * from userInfo")
public List<UserInfo> getUserInfoAll();
此处的参数和指定结果映射中的id相对应.
1.3 自动驼峰转换的具体实现.(更推荐使用这种方式)
- 在yml中配置如下信息:
mybatis:
configuration:
map-underscore-to-camel-case: true #自动驼峰转换
2.XML配置文件的解决方法
- XML配置文件的解决方式和注解的方式相同也是以上三种方式:
- 方式一:给数据库字段起别名.(跟注解的方式一样这里就不做详细的叙述了)
- 方式二:指定结果映射.
- 方式三:自动驼峰转换. (跟注解的方式一样这里就不做详细的叙述了)
2.1指定结果映射的具体实现
- mapper层代码实现.
List<UserInfo> selectAll2();
- 对应的XML配置文件.
<resultMap id="BaseMap" type="com.tuanzi.ssm.springmybatis.model.UserInfo">
<id column="id" property="id"/>
<result column="delete_flag" property = "deleteFlag"/>
<result column = "create_time" property = "createTime"/>
<result column = "update_time" property = "updateTime"/>
</resultMap>
<select id="selectAll2" resultMap="BaseMap">
select * from userInfo
</select>
- 生成对应的测试代码,运行结果:
二.#{}和${}的区别和联系.
1.#{}和${}的使用.
- mapper层代码:
@Select("select * from userinfo where id=#{id}")
List<UserInfo> getUserInfoById(Integer id);
@Select("select * from userinfo where id=${id}")
List<UserInfo> getUserInfoById2(Integer id);
- #{}运行结果:
我们输入的参数并没有在后面拼接,id的值是使用 ? 进行占位. 这种SQL 我们称之为"预编译SQL", 预编译SQL的本质类似于买火车票的占位行为, 显示出来这个位置已经有人了,但具体是谁,这个人高矮胖瘦,是男是女,都不重要,那个位置已经被他预定了. 然后根据传的参数来确定这个参数的具体类型并赋值. - ${}运行结果:
从${}的运行结果我们可以看出此时的传参变成了直接拼接字符串.并会不跟据传入参数的类型来进行灵活转化.
2.#{}和${}参数解析之间的区别
- mapper层代码:
@Select("select * from userinfo where username = #{userName}")
List<UserInfo> getUserInfoByUsername(String userName);
@Select("select * from userinfo where username = ${userName}")
List<UserInfo> getUserInfoByUsername2(String userName);
-
#{}运行结果:
此时代码正常执行,正确执行的sql语句为: select * from userinfo where username = ‘admin’ -
${}运行结果:
此时代码执行会报错, 报的是SQLSyntaxErrorException, 由上述的图片我们可以看出直接拼接的内容与正确的sql的差别, 缺少’'/“”
#{} 使用的是预编译SQL, 通过 ? 占位的方式, 提前对SQL进行编译, 然后把参数填充到SQL语句中. #{} 会根据参数类型, 自动拼接引号 ‘’ .${} 会直接进行字符替换, 一起对SQL进行编译. 如果参数为字符串, 需要加上引号 ‘’
3.#{} 和 ${}区别
1. #{}的性能更高
- 绝大多数情况下, 某⼀条 SQL 语句可能会被反复调用执行, 或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同, update 的 set 子句值不同,insert 的 values 值不同). 如果每次都需要经过上面的语法解析, SQL优化、SQL编译等,则效率就明显不行了.
- 预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同), 省去了解析优化等过程, 以此来提高效率
2.#{}更安全可以防止sql注入(最重要的区别)
- SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时在参数中添加⼀些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
- sql注入的代码:
' or 1='1
- 举个例子:
@Test
void queryByName() {
List<UserInfo> userInfos = userInfoMapper.queryByName("' or 1='1");
System.out.println(userInfos);
}
可以看出来, 查询的数据并不是自己想要的数据. 假设如果这是用户的登录,那么即使在不知道用户密码的情况下,也可以通过sql注入进行登录所以用于查询的字段,尽量使用 #{} 预查询的方式. SQL注入是⼀种非常常见的数据库攻击手段, SQL注入漏洞也是网络世界中最普遍的漏洞之一.
3.排序功能(${}可以实现而#{}不可以实现)
- mapper层代码:
@Select("select * from userInfo order by id #{order} ")
List<UserInfo> selectUserInfoOrder(String order);
//也存在sql注入的问题,解决方法,可以使用穷举的方式进行校验.
//也可以定义两个接口直接写死
@Select("select * from userInfo order by id ${order} ")
List<UserInfo> selectUserInfoOrder2(String order);
- #{}的运行结果:
运行结果会报错,报错的原因是因为SQL错误, 与正确的SQL相比,desc是不需要加’'的. - ${}的运行结果:
4.like查询.
- 如果使用#{}
@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注入的风险,解决方法是使用concat函数来解决问题
@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注入:一些数据库连接池如Druid提供了防SQL注入的功能。
-
SpringBoot默认使用的是hikari数据库连接池
-
常见的数据库连接池
- C3P0:是一个开源的数据库连接池,稳定性良好。
- DBCP:Apache提供的数据库连接池,速度快但存在已知BUG。
- Druid:阿里巴巴提供的数据库连接池,集成了多种优点,并提供强大的监控功能。
-
Druid的maven依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency>
此时的运行结果:
四.动态SQL
1.标签
- 思考一种情况,当添加用户的时候,有些情况某些值是非必填的选项,如果某一项或某几项没有传的时候该如何根据输入的具体参数处理SQL. 这个处理过的SQl称为动态SQL.
- 接口定义:
Integer insertUserByCondition(UserInfo userInfo);
Mapper.xml实现:
<insert id="insertUserByCondition">
INSERT INTO userinfo (
username,
`password`,
age,
<if test="gender != null">
gender,
</if>
phone)
VALUES (
#{username},
#{age},
<if test="gender != null">
#{gender},
</if>
#{phone})
</insert>
2.trim标签.
- 标签的相关属性
- prefix:表示整个语句块,以prefix的值作为前缀
- suffix:表示整个语句块,以suffix的值作为后缀
- prefixOverrides:表示整个语句块要去除掉的前缀
- suffixOverrides:表示整个语句块要去除掉的后缀
<insert id="insertUserInfoByCondition">
insert into userinfo
<trim prefix="(" suffix=")" prefixOverrides=",">
<if test="username!=null">
username
</if>
<if test="password!=null">
,password
</if>
<if test="age!=null">
,age
</if>
<if test="gender!=null">
,gender
</if>
</trim>
values
<trim prefix="(" suffix=")" prefixOverrides=",">
<if test="username!=null">
username
</if>
<if test="password!=null">
,password
</if>
<if test="age!=null">
,age
</if>
<if test="gender != null">
,#{gender}
</if>
</trim>
</insert>
- 在以上 sql 动态解析时,会将第一个部分做如下处理:
- 基于 prefix 配置,开始部分加上 (
- 基于 suffix 配置,结束部分加上 )
- 多个组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于
suffixOverrides 配置去掉最后一个 ,注意 中的 username 是传入对象的属性
此时就可以根据传的值来动态调整SQL语句了.
3.where标签.
<select id="queryUserInfoByCondition" resultType="com.tuanzi.ssm.springmybatis.model.UserInfo">
select * from userinfo
<where>
<if test="age!=null">
age = #{age}
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
<if test="deleteFlag!=null">
and delete_flag = #{deleteFlag}
</if>
</where>
</select>
只会在子元素有内容的情况下才插入where子句,而且会自动去除子句开头的AND或OR 以上标签也可以使用 替换, 但是此种情况下, 当子元素都没有内容时, where关键字也会保留
4.标签
<update id="updateUserInfoByCondition">
update userInfo
<set>
<if test="password!=null">
password = #{password}
</if>
<if test="age!=null">
,age = #{age}
</if>
<if test="gender!=null">
,gender = #{gender}
</if>
</set>
where
id = #{id}
</update>
- :动态的在SQL语句中插入set关键字,并会删掉额外的逗号. (用于update语句中)以上标签也可以使用 替换。
5.标签
- 对集合进行遍历时可以使用该标签。标签有如下属性:
- collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
- item:遍历时的每⼀个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
<delete id="batchDeleteByIds">
delete from userinfo where id in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</delete>
6.标签
- 在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码
- 我们可以对重复的代码片段进行抽取,将其通过 标签封装到⼀个SQL片段,然后再通过 标签进行引用
- sql标签定义可重用的SQL片段
- include标签通过属性refid,指定包含的SQL片段
<sql id="selectAllUserInfo">
select * from userInfo
</sql>
<select id="selectAll" resultType="com.tuanzi.ssm.springmybatis.model.UserInfo">
<include refid="selectAllUserInfo"></include>
</select>