MyBatis 小技巧
#{} 与 ${}
接口方法
/** * 根据汽车类型获取汽车信息 * * @param carType 汽车类型 * @return 汽车信息组成的列表 */ List<Car> selectByCarType(String carType);
SQL
<select id="selectByCarType" resultType="cw.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = #{carType} </select> <select id="selectByCarType" resultType="cw.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ${carType} </select>
测试
@Test public void test01() { SqlSession sqlSession = SqlSessionUtil.openSession(); // 获取CarMapper接口的代理类对象 // 底层为CarMapper接口生成了字节码,同时创建了实现类的对象 CarMapper mapper = sqlSession.getMapper(CarMapper.class); List<Car> cars = mapper.selectByCarType("新能源"); cars.forEach(System.out::println); SqlSessionUtil.close(); }
#{} 结果
- 先对SQL语句进行编译,采用 ?作为占位符,然后使用查询参数填充占位符
[main] DEBUG cw.mybatis.mapper.CarMapper.selectByCarType - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ? [main] DEBUG cw.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 新能源(String) [main] DEBUG cw.mybatis.mapper.CarMapper.selectByCarType - <== Total: 2 查询出来的数据: Car{id=2, carNum='1002', brand='奔驰E300L', guidePrice=55.0, produceTime='2022-11-11', carType='新能源'} Car{id=20, carNum='4444', brand='奔驰C200&&&', guidePrice=32.0, produceTime='2000-10-10', carType='新能源'}
${} 结果
- 直接将查询参数的数值直接采用字符串拼接的方式,拼接到SQL语句中,然后再进行SQL语句的编译
[main] DEBUG cw.mybatis.mapper.CarMapper.selectByCarType - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源 [main] DEBUG cw.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause' ### The error may exist in CarMapper.xml ### The error may involve defaultParameterMap ### The error occurred while setting parameters ### SQL: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源 ### Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
#{} 与 ${} 的区别
- #{}: 底层使用PreparedStatement。
- 特点:先进行SQL语句的编译,然后给SQL语句的占位符问号?传值。
- 给 ? 传值时是否添加 ‘’ 根据传递的数据类型
- 可以避免SQL注入的风险。
- ${}:底层使用Statement。
- 特点:先进行SQL语句的拼接,然后再对SQL语句进行编译。
- 存在SQL注入的风险。
- 优先使用#{},这是原则。避免SQL注入的风险。#{}不能实现在考虑${}
${} 的使用场景 - 升序或降序排序
- 从数据库查询数据时,进行排序
接口方法
/** * 查询所有的汽车信息,并进行升序或降序排序 * * @param ascOrDesc 升序或降序排序 * @return 排序后的汽车信息组成的集合 */ List<Car> selectAllAscOrDesc(String ascOrDesc);
SQL
<select id="selectAllAscOrDesc" resultType="cw.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time #{ascOrDesc}; </select> <select id="selectAllAscOrDesc" resultType="cw.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time ${ascOrDesc}; </select>
测试
@Test public void test02() { SqlSession sqlSession = SqlSessionUtil.openSession(); CarMapper mapper = sqlSession.getMapper(CarMapper.class); // 查询所有的汽车信息,并降序排序 List<Car> cars = mapper.selectAllAscOrDesc("desc"); cars.forEach(System.out::println); SqlSessionUtil.close(); }
#{} 结果
- 会先进行SQL语句的编译,将#{}的位置使用占位符?替换,然后再使用查询参数进行填充
[main] DEBUG cw.mybatis.mapper.CarMapper.selectAllAscOrDesc - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time ?; [main] DEBUG cw.mybatis.mapper.CarMapper.selectAllAscOrDesc - ==> Parameters: desc(String) 填充后的SQL为 select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time 'desc'; 存在语法错误 org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''desc'' at line 8 ### The error may exist in CarMapper.xml ### The error may involve defaultParameterMap ### The error occurred while setting parameters ### SQL: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time ?; ### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''desc'' at line 8
${} 结果
- 先将查询参数与SQL语句进行字符串拼接,然后再进行SQL语句的编译
[main] DEBUG cw.mybatis.mapper.CarMapper.selectAllAscOrDesc - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time desc; 符合预期,可以进行数据的查询并进行排序 [main] DEBUG cw.mybatis.mapper.CarMapper.selectAllAscOrDesc - ==> Parameters: [main] DEBUG cw.mybatis.mapper.CarMapper.selectAllAscOrDesc - <== Total: 17
${} 小结
- 如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句当中的。
${} 的使用场景 - 拼接表名
- 向SQL语句当中拼接表名,就需要使用${}
- 现实业务当中,可能会存在分表存储数据的情况。因为一张表存的话,数据量太大。查询效率比较低。可以将这些数据有规律的分表存储,这样在查询的时候效率就比较高。因为扫描的数据量变少了。
- 如,日志表:专门存储日志信息的。如果t_log只有一张表,这张表中每一天都会产生很多log,慢慢的,这个表中数据会很多。我们可以每天生成一个新表。每张表以当天日期作为名称,例如:t_log_20220901、t_log_20220902…当我们需要知道某一天的日志信息,假设今天是20220901,那么我们可以直接查:t_log_20220901的表即可。
数据库表准备
use dbtest; create table t_log_20220901 ( id int primary key auto_increment, log varchar(255), time datetime ); INSERT INTO `t_log_20220901` (`id`, `log`, `time`) VALUES (1, '日志信息日志信息日志信息', '2022-09-01 11:06:31'); INSERT INTO `t_log_20220901` (`id`, `log`, `time`) VALUES (2, '安全警告安全警告安全警告', '2022-09-01 11:07:20'); create table `t_log_20220902` ( id int primary key auto_increment, log varchar(255), time datetime ); INSERT INTO `t_log_20220902` (`id`, `log`, `time`) VALUES (1, '插入数据', '2022-09-02 11:08:31'); INSERT INTO `t_log_20220902` (`id`, `log`, `time`) VALUES (2, '删除数据', '2022-09-02 11:08:45');
数据库表对应的Java类
package cw.mybatis.pojo; /** * ClassName: Log * Package: cw.mybatis.pojo * Description: * * @Author tcw * @Create 2023-05-25 11:24 * @Version 1.0 */ public class Log { private Integer id; private String log; private String time; public Log() { } public Log(Integer id, String log, String time) { this.id = id; this.log = log; this.time = time; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLog() { return log; } public void setLog(String log) { this.log = log; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } @Override public String toString() { return "Log{" + "id=" + id + ", log='" + log + '\'' + ", time='" + time + '\'' + '}'; } }
接口声明
public interface LogMapper { /** * 根据日期查询不同的数据库表中的全部信息 * * @param date 日期字符串 * @return 与日期对应的数据表中全部信息组成的集合 */ List<Log> selectAllByTable(String date); }
SQL映射文件
<mapper namespace="cw.mybatis.mapper.LogMapper"> <select id="selectAllByTable" resultType="cw.mybatis.pojo.Log"> select id, log, `time` from t_log_${date} </select> </mapper>
修改 MyBatis 核心配置文件
<mappers> <mapper resource="CarMapper.xml"/> <mapper resource="LogMapper.xml"/> </mappers>
测试
@Test public void selectAllByTable() { SqlSession sqlSession = SqlSessionUtil.openSession(); // 获取代理对象 LogMapper mapper = sqlSession.getMapper(LogMapper.class); // 查询数据 List<Log> logs20220901 = mapper.selectAllByTable("20220901"); logs20220901.forEach(System.out::println); // 打印信息 List<Log> logs20220902 = mapper.selectAllByTable("20220902"); logs20220902.forEach(System.out::println); // 打印信息 // 关闭资源 SqlSessionUtil.close(); }
直接将日期字符串以字符串拼接的方式与SQL语句进行拼接,然后再进行SQL语句的编译 [main] DEBUG cw.mybatis.mapper.LogMapper.selectAllByTable - ==> Preparing: select id, log, `time` from t_log_20220901 [main] DEBUG cw.mybatis.mapper.LogMapper.selectAllByTable - ==> Parameters: [main] DEBUG cw.mybatis.mapper.LogMapper.selectAllByTable - <== Total: 2 Log{id=1, log='日志信息日志信息日志信息', time='2022-09-01 11:06:31'} Log{id=2, log='安全警告安全警告安全警告', time='2022-09-01 11:07:20'} [main] DEBUG cw.mybatis.mapper.LogMapper.selectAllByTable - ==> Preparing: select id, log, `time` from t_log_20220902 [main] DEBUG cw.mybatis.mapper.LogMapper.selectAllByTable - ==> Parameters: [main] DEBUG cw.mybatis.mapper.LogMapper.selectAllByTable - <== Total: 2 Log{id=1, log='插入数据', time='2022-09-02 11:08:31'} Log{id=2, log='删除数据', time='2022-09-02 11:08:45'} 如果是 #{} 会先进行SQL语句的编译, 编译后的SQL语句为:Preparing: select id, log, `time` from t_log_? 使用日期字符串填充后,SQL语句变为:Preparing: select id, log, `time` from t_log_'20220902'
${} 的使用场景 - 批量删除(一次删除多条记录)
- 批量删除的SQL语句有两种写法:
-- 第一种 or : delete from t_car where id=1 or id=2 or id=3; -- 第二种 in : delete from t_car where id in(1,2,3);
接口声明
/** * 根据id批量删除数据库表中的信息 * * @param ids 需要进行删除操作的信息的id组成的字符串 * @return 删除记录的条数 */ int deleteBatch(String ids);
SQL
<delete id="deleteBatch"> delete from t_car where id in (${ids}); </delete> <!-- 如果要采用 delete from t_car where id=1 or id=2 or id=3; 这种形式则需要使用动态SQL -->
测试
@Test public void test03() { SqlSession sqlSession = SqlSessionUtil.openSession(); CarMapper mapper = sqlSession.getMapper(CarMapper.class); int deleteCount = mapper.deleteBatch("2, 3, 4"); System.out.println(deleteCount); sqlSession.commit() SqlSessionUtil.close(); }
先进行字符串拼接,然后再进行SQL语句的编译 [main] DEBUG cw.mybatis.mapper.CarMapper.deleteBatch - ==> Preparing: delete from t_car where id in (2, 3, 4); [main] DEBUG cw.mybatis.mapper.CarMapper.deleteBatch - ==> Parameters: [main] DEBUG cw.mybatis.mapper.CarMapper.deleteBatch - <== Updates: 3 如果使用 #{} 先进行SQL语句的编译 编译后的SQL为 delete from t_car where id in (?); 使用查询参数填充占位符后SQL为 delete from t_car where id in ('2, 3, 4'); 会报语法错误
模糊查询(like)
- 需求:根据汽车品牌进行模糊查询
- SQL如下:
select * from t_car where brand like '%奔驰%'; select * from t_car where brand like '%比亚迪%';
可以采取的方式分析
- 由于#{}是先对SQL语句进行编译,将#{}的位置使用JDBC中的占位符?进行替换,然后再使用参数来填充占位符,如果用于填充占位符的参数为字符串类型,则填充后会携带有引号;${}是先采用字符串拼接的方式,将参数和SQL语句字符串进行拼接,然后再对SQL语句进行编译,所以用于填充占位符的参数为字符串类型,与SQL语句拼接后不会有引号
- 注意:SQL语句中,引号内的 ? ,如
'%?%'
不会被认为是JDBC的占位符,而会被认为是字符串的一部分 - 因此可以有如下四种方案:
- 第一种方案:
'%${brand}%'
,字符串拼接后为'%brand%'
- 第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接
concat('%',#{brand},'%')
,SQL编译后为concat('%', ?,'%')
- 第三种方案:
concat('%','${brand}','%')
,字符串拼接后为concat('%','brand','%')
- 第四种方案:
"%"#{brand}"%"
,SQL编译后为"%"?"%"
- 让JDBC可以识别出充当占位符的 ?
- MySQL会自动进行字符串的拼接
- 也可以在传入的字符串中将模糊匹配写好,如
'%宝马%'
,将这一整个字符串传递
接口声明
/** * 根据汽车的品牌进行模糊查询 * * @param brand 汽车品牌字符串 * @return 查询出来的汽车信息 */ List<Car> selectByBrandLike(String brand);
SQL
<select id="selectByBrandLike" resultType="cw.mybatis.pojo.Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like concat('%', #{brand}, '%'); </select>
测试
@Test public void test04() { SqlSession sqlSession = SqlSessionUtil.openSession(); CarMapper mapper = sqlSession.getMapper(CarMapper.class); List<Car> cars = mapper.selectByBrandLike("比亚迪"); cars.forEach(System.out::println); SqlSessionUtil.close(); }
先进行SQL语句的编译 select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like concat('%', ?, '%'); 使用数据填充占位符(采用 #{} ) select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like concat('%', '比亚迪', '%'); [main] DEBUG cw.mybatis.mapper.CarMapper.selectByBrandLike - ==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like concat('%', ?, '%'); [main] DEBUG cw.mybatis.mapper.CarMapper.selectByBrandLike - ==> Parameters: 比亚迪(String) [main] DEBUG cw.mybatis.mapper.CarMapper.selectByBrandLike - <== Total: 5 Car{id=9, carNum='1111', brand='比亚迪汉', guidePrice=10.0, produceTime='2020-11-11', carType='电车'} Car{id=15, carNum='1333', brand='比亚迪汉', guidePrice=10.0, produceTime='2020-11-11', carType='电车'} Car{id=16, carNum='1333', brand='比亚迪汉', guidePrice=10.0, produceTime='2020-11-11', carType='电车'} Car{id=17, carNum='1333', brand='比亚迪汉', guidePrice=10.0, produceTime='2020-11-11', carType='电车'} Car{id=18, carNum='1333', brand='比亚迪汉', guidePrice=10.0, produceTime='2020-11-11', carType='电车'}
别名机制:typeAliases
- 我们之前在XxxxMapper.xml文件书写查询语句,指定返回结果集中数据元素的类型时,每次都需要写全类名,太过于复杂
- resultType属性用来指定查询结果集的封装类型,这个名字太长
<select id="selectByCarType" resultType="cw.mybatis.pojo.Car"><select/> <select id="selectAllAscOrDesc" resultType="cw.mybatis.pojo.Car"><select/> <select id="selectByBrandLike" resultType="cw.mybatis.pojo.Car"><select/>
在核心配置文件中配置别名
- MyBatis中为我们提供了别名机制,使用别名机制,我们可以为查询语句中指定的返回值类型的全类名取别名
- 在MyBatis核心配置文件中,在
typeAliases
标签中使用typeAliase
标签起别名
- 注意:settings标签写在properties标签后,typeAliases标签写在settings标签后
- typeAliases标签中的typeAlias可以写多个
<!-- 起别名 --> <typeAliases> <!-- 为 cw.mybatis.pojo.Car 起别名,别名为 Car --> <typeAlias type="cw.mybatis.pojo.Car" alias="Car"/> </typeAliases>
在XxxxMapper.xml文件中使用别名
- 在 CarMapper.xml 文件中使用别名
<select id="selectByCarType" resultType="Car"> select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = #{carType} </select>
- 测试
@Test public void test01() { SqlSession sqlSession = SqlSessionUtil.openSession(); // 获取CarMapper接口的代理类对象 // 底层为CarMapper接口生成了字节码,同时创建了实现类的对象 CarMapper mapper = sqlSession.getMapper(CarMapper.class); List<Car> cars = mapper.selectByCarType("新能源"); cars.forEach(System.out::println); SqlSessionUtil.close(); }
注意点
- 别名不缺分大小写
<select id="selectByCarType" resultType="caR">
- namespace不能使用别名机制,必须写全限定接口名称,带有包名的。
<!-- 起别名 --> <typeAliases> <!-- 为 cw.mybatis.pojo.Car 起别名,别名为 Car --> <typeAlias type="cw.mybatis.pojo.Car" alias="Car"/> <typeAlias type="cw.mybatis.mapper.CarMapper" alias="CarMapper"/> </typeAliases>
<mapper namespace="CarMapper">
- typeAlias 标签中的 alias 属性是可以省略的,有默认的别名,省略alias之后,别名就是类的简名,比如:cw.mybatis.pojo.Car 的别名就是 Car/car/cAR/cAr,不区分大小写。
<!-- 起别名 --> <typeAliases> <!-- 为 cw.mybatis.pojo.Car 起别名,别名为 Car --> <typeAlias type="cw.mybatis.pojo.Car"/> </typeAliases>
<select id="selectByCarType" resultType="caR">
别名机制:package
- 在起别名时,我们可以直接指定要起别名的类所在的包,MyBatis会自动为这个包下的所有的类全部起别名,别名就是类简名,不区分大小写。
<!-- 起别名 --> <typeAliases> <!-- 为 cw.mybatis.pojo 包下面的所有类起别名,别名默认为类的简名 --> <package name="cw.mybatis.pojo"/> </typeAliases>
<select id="selectByCarType" resultType="cAR">
核心配置文件中 mappers 标签的配置
mapper 标签中指定配置文件的三种方式
- 在MyBatis核心配置文件mappers标签中的mapper标签指定XxxxMapper.xml配置文件位置的方式有三种:
<mapper resource="CarMapper.xml"/>
- 要求类的根路径下必须有:CarMapper.xml
- 使用 resource 属性指定配置文件的存放位置,这种方式是从类的根路径下开始查找资源。
- 采用这种方式的话,你的配置文件需要放到类路径当中才行。
<mapper url="file:///d:/CarMapper.xml"/>
- 要求在d:/下有CarMapper.xml文件
- 使用 url 属性指定配置文件的存放位置,这种方式是一种绝对路径的方式
- 这种方式不要求配置文件必须放到类路径当中,哪里都行,只要提供一个绝对路径就行。
- 这种方式使用极少,移植性太差。
<mapper class="全限定接口名,带有包名"/>
- class:该属性的属性值需要我们提供的是 mapper 接口的全限定接口名,必须带有包名的。
- 使用 class 属性指定全限定接口名,MyBatis就会去该接口所在包下查找与接口相对应与接口同名的 XxxMapper.xml SQL映射配置文件
- 如:
<mapper class="com.powernode.mybatis.mapper.CarMapper"/>
,mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件 - 如果你采用这种方式,那么必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下,并且名字必须一致。
- CarMapper接口 -> CarMapper.xml
- LogMapper接口 -> LogMapper.xml
- …
接口和配置文件的存放目录
- 将类和配置文件放在同一个目录下,可以采用如下方式建包,因为resources目录相当于类的根路径,就是打包后包的根路径,蓝色的java目录也相当于类的根路径,所以打包完成后 resources 下与 java 下在相同名字的文件夹下的文件会被放在一起
- 注意:在IDEA的 resources 目录下如果要一次新建多重目录的话,在创建目录时必须采用如下的形式:
- com/powernode/mybatis/mapper
- 不能这样采用如下的形式,如下的形式只能用于软件包的创建:
- com.powernode.mybatis.mapper
- 如果采用上述的形式,则
com.powernode.mybatis.mapper
这个会被整个当成一个目录名,resources目录下没有建包的概念,只有创建目录的概念
mapper 直接指定接口及配置文件所在的包
- 采用 mapper 的 class 属性指定接口对应的配置文件的存放位置,可以直接指定接口所在包名,需要使用相应配置文件中的SQL语句时,MyBatis就会去接口所在的包自动查询同名的配置文件
- 这种方式在实际开发中是使用的,必须满足XML文件必须和接口放在一起,并且名字必须一致。
<mappers> <!-- 指定 xml 文件放在 com.powernode.mybatis.mapper 包下 --> <package name="com.powernode.mybatis.mapper"/> </mappers>