可以做淘宝推广的网站,网站域名解析错误怎么办,终身免费建站,我在海贼开发app免费引言 使⽤Mybatis的注解⽅式#xff0c;主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能#xff0c;建议使⽤XML来配置映射语句#xff0c;也就是将SQL语句写在XML配置⽂件中. 之前#xff0c;我们学习了#xff0c;用注解的方式来实现MyBatis 接下来我们…引言 使⽤Mybatis的注解⽅式主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能建议使⽤XML来配置映射语句也就是将SQL语句写在XML配置⽂件中. 之前我们学习了用注解的方式来实现MyBatis 接下来我们学习XML的⽅式 MyBatis XML的⽅式需要以下两步 配置数据库连接字符串和MyBatis 写持久层代码 MyBatis XML配置⽂件 配置连接字符串和MyBatis 此步骤需要进⾏两项设置数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。 # 数据库连接配置
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncodingutf8useSSLfalseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径在 resources/mapper 创建所有表的 xml ⽂件//mapper是目录,自己设置
//文件名的后缀部分必须是Mapper.xml不能修改,前面可以添加内容
//这里的*代表通配符
mybatis.mapper-locationsclasspath:/myBatis/*Mapper.xml
mybatis:mapper-locations: classpath:mapper/**Mapper.xml 写持久层代码 持久层代码分两部分 ⽅法定义 Interface ⽅法实现: XXX.xml 添加 mapper 接⼝ 数据持久层的接⼝定义 import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
Mapper
public interface UserInfoXMlMapper {ListUserInfo queryAllUser();
} 添加 UserInfoXMLMapper.xml 在resources目录下添加前面在配置文件里面写的xml路径 在xml文件里面添加如下内容 其中mapper标签里面的namespace属性里面填的是 mapper接口的路径 ?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.example.demo.mapper.UserMapper/mapper 查询所有⽤⼾的具体实现 ?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN
http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.example.demo.mapper.UserInfoXMlMapperselect idqueryAllUser resultTypecom.example.demo.model.UserInfoselect username,password, age, gender, phone from userinfo/select
/mapper 单元测试 SpringBootTest
class UserInfoMapperTest {Autowiredprivate UserInfoMapper userInfoMapper;Testvoid queryAllUser() {ListUserInfo userInfoList userInfoMapper.queryAllUser();System.out.println(userInfoList);}} 增删改查操作 增(Insert) UserInfoMapper接⼝ Integer insertUser(UserInfo userInfo); UserInfoMapper.xml实现 insert idinsertUserinsert into userinfo (username, password, age, gender, phone) values (#
{username}, #{password}, #{age},#{gender},#{phone})
/insert 如果使⽤Param设置参数名称的话, 使⽤⽅法和注解类似UserInfoMapper接⼝: Integer insertUser(Param(userinfo) UserInfo userInfo); UserInfoMapper.xml实现: insert idinsertUserinsert into userinfo (username, password, age, gender, phone) values(#{userinfo.username},#{userinfo.password},#{userinfo.age},#
{userinfo.gender},#{userinfo.phone})
/insert 返回⾃增 id 接⼝定义不变, Mapper.xml 实现 设置useGeneratedKeys 和keyProperty属性 insert idinsertUser useGeneratedKeystrue keyPropertyidinsert into userinfo (username, password, age, gender, phone) values(#{userinfo.username},#{userinfo.password},#{userinfo.age},#
{userinfo.gender},#{userinfo.phone})
/insert 删(Delete) UserInfoMapper接⼝ Integer deleteUser(Integer id); UserInfoMapper.xml实现: delete iddeleteUserdelete from userinfo where id #{id}
/delete 改(Update) UserInfoMapper接⼝: Integer updateUser(UserInfo userInfo); UserInfoMapper.xml实现: update idupdateUserupdate userinfo set username#{username} where id#{id}
/update 查(Select) 同样的, 使⽤XML 的⽅式进⾏查询, 也存在数据封装的问题我们把SQL语句进⾏简单修改, 查询更多的字段内容. select idqueryAllUser resultTypecom.example.demo.model.UserInfoselect id, username,password, age, gender, phone, delete_flag,
create_time, update_time from userinfo
/select 运⾏结果: 解决办法和注解类似: 起别名 结果映射 开启驼峰命名 其中1,3的解决办法和注解⼀样,不再多说, 接下来看下xml如果来写结果映射 Mapper.xml resultMap idBaseMap typecom.example.demo.model.UserInfoid columnid propertyid/idresult columndelete_flag propertydeleteFlag/resultresult columncreate_time propertycreateTime/resultresult columnupdate_time propertyupdateTime/result
/resultMap
select idqueryAllUser resultMapBaseMapselect id, username,password, age, gender, phone, delete_flag,
create_time, update_time from userinfo
/select #{} 和 ${} MyBatis 参数赋值有两种⽅式, 咱们前⾯使⽤了 #{} 进⾏赋值, 接下来我们看下⼆者的区别 #{} 和${} 使⽤ 先看Interger类型的参数 Select(select username, password, age, gender, phone from userinfo where
id #{id} )
UserInfo queryById(Integer id); 观察我们打印的⽇志 发现我们输出的SQL语句 select username, password, age, gender, phone from userinfo where id ? 我们输⼊的参数并没有在后⾯拼接id的值是使⽤ ? 进⾏占位. 这种SQL 我们称之为预编译SQL 我们把 #{} 改成 ${} 再观察打印的⽇志: Select(select username, password, age, gender, phone from userinfo where
id ${id} )
UserInfo queryById(Integer id); 可以看到, 这次的参数是直接拼接在SQL语句中了. 接下来我们再看String类型的参数 Select(select username, pasword, age, gender, phone from userinfo where
username #{name} )
UserInfo queryByName(String name); 观察我们打印的⽇志, 结果正常返回 我们把 #{} 改成 ${} 再观察打印的⽇志 Select(select username, password, age, gender, phone from userinfo where
username ${name} )
UserInfo queryByName(String name); 可以看到, 这次的参数依然是直接拼接在SQL语句中了, 但是字符串作为参数时, 需要添加引号 , 使 ⽤ ${} 不会拼接引号 , 导致程序报错. 修改代码如下: Select(select username, password, age, gender, phone from userinfo where
username ${name} )
UserInfo queryByName(String name); 再次运⾏, 结果正常返回 从上⾯两个例⼦可以看出: #{} 使⽤的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进⾏编译, 然后把参数填充到SQL语句中. #{} 会根据参数类型, ⾃动拼接引号 . ${} 会直接进⾏字符替换, ⼀起对SQL进⾏编译. 如果参数为字符串, 需要加上引号 . 参数为数字类型时, 也可以加上, 查询结果不变, 但是可能会导致索引失效, 性能下降. #{} 和 ${}区别 #{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别. 简单回顾: 当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下: 解析语法和语义, 校验SQL语句是否正确 优化SQL语句, 制定执⾏计划 执⾏并返回结果 ⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL) 性能更⾼ 绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐ 如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要经过上⾯的语法解析, SQL优化、SQL编译等则效率就明显不⾏了. 预编译SQL编译⼀次之后会将编译后的SQL语句缓存起来后⾯再次执⾏这条语句时不会再次编译(只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率 更安全(防⽌SQL注⼊) SQL注⼊是通过操作输⼊的数据来修改事先定义好的SQL语句以达到执⾏代码对服务器进⾏攻击的⽅法。 由于没有对⽤⼾输⼊进⾏充分检查⽽SQL⼜是拼接⽽成在⽤⼾输⼊参数时在参数中添加⼀些 SQL关键字达到改变SQL运⾏结果的⽬的也可以完成恶意攻击。 sql 注⼊代码 or 11 Select(select username, password, age, gender, phone from userinfo where
username ${name} )
ListUserInfo queryByName(String name); 测试代码: 正常访问情况: Test
void queryByName() {ListUserInfo userInfos userInfoMapper.queryByName(admin);System.out.println(userInfos);
} 结果运⾏正常 SQL注⼊场景: Test
void queryByName() {ListUserInfo userInfos userInfoMapper.queryByName( or 11);System.out.println(userInfos);
} 结果依然被正确查询出来了, 其中参数 or被当做了SQL语句的⼀部分 可以看出来, 查询的数据并不是⾃⼰想要的数据. 所以⽤于查询的字段尽量使⽤ #{} 预查询的⽅式 SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀. 如果发⽣在⽤⼾登录的场景中, 密码输⼊为 or 11 , 就可能完成登录(不是⼀定会发⽣的场景, 需要看登录代码如何写) 排序功能 从上⾯的例⼦中, 可以得出结论: ${} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询 既然如此, 是不是 ${} 就没有存在的必要性了呢? 当然不是. Mapper实现 Select(select id, username, age, gender, phone, delete_flag, create_time,
update_time from userinfo order by id ${sort} )
ListUserInfo queryAllUserBySort(String sort); 使⽤ ${sort} 可以实现排序查询, ⽽使⽤ #{sort} 就不能实现排序查询了. 注意: 此处 sort 参数为String类型, 但是SQL语句中, 排序规则是不需要加引号 的, 所以此时的${sort} 也不加引号 我们把 ${} 改成 #{} Select(select id, username, age, gender, phone, delete_flag, create_time,
update_time from userinfo order by id #{sort} )
ListUserInfo queryAllUserBySort(String sort); 可以发现, 当使⽤ #{sort} 查询时, asc 前后⾃动给加了引号, 导致 sql 错误 #{} 会根据参数类型判断是否拼接引号 如果参数类型为String, 就会加上 引号. 除此之外, 还有表名作为参数时, 也只能使⽤ ${} like 查询 like 使⽤ #{} 报错 Select(select id, username, age, gender, phone, delete_flag, create_time,
update_time from userinfo where username like %#{key}% )
ListUserInfo queryAllUserByLike(String key); 把 #{} 改成 ${} 可以正确查出来, 但是${}存在SQL注⼊的问题, 所以不能直接使⽤ ${}. 解决办法: 使⽤ mysql 的内置函数 concat() 来处理实现代码如下 Select(select id, username, age, gender, phone, delete_flag, create_time,
update_time from userinfo where username like concat(%,#{key},%))
ListUserInfo queryAllUserByLike(String key); 数据库连接池 数据库连接池负责分配、管理和释放数据库连接它允许应⽤程序重复使⽤⼀个现有的数据库连接⽽不是再重新建⽴⼀个. 没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQ语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾ 请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把 Connection归还给连接池 优点: 减少了⽹络开销 资源重⽤ 提升了系统的性能 常⻅的数据库连接池 C3P0 DBCP Druid Hikari ⽬前⽐较流⾏的是 Hikari, Druid Hikari : SpringBoot默认使⽤的数据库连接池