9. MyBatis小技巧
9.1 #{}和${}
9.1.1 基本概念
#{}
:先编译SQL语句,再给占位符传值,底层使用PreparedStatement实现。可以防止SQL注入,比较常用。${}
:先进行SQL语句拼接,然后再编译SQL语句,底层使用Statement实现。存在SQL注入风险。只有在需要进行SQL语句关键字拼接的情况下才会用到。
9.1.2 使用场景对比
9.1.2.1 普通查询场景
xml
<!-- 使用#{} -->
<select id="selectByCarType" resultType="Car">
select * from t_car where car_type = #{carType}
</select>
<!-- 使用${} -->
<select id="selectByCarType" resultType="Car">
select * from t_car where car_type = '${carType}'
</select>
执行结果对比:
- 使用#{}时,SQL语句中会带有?占位符,例如:
where car_type = ?
- 使用${}时,SQL语句会直接拼接值,例如:
where car_type = '燃油车'
9.1.2.2 排序场景
xml
<!-- 使用${}进行排序 -->
<select id="selectAll" resultType="Car">
select * from t_car order by carNum ${key}
</select>
注意事项:
- 排序场景必须使用${},因为asc/desc是SQL关键字
- 使用#{}会导致SQL语法错误,例如:
order by carNum 'desc'
9.1.2.3 动态表名场景
xml
<!-- 使用${}拼接表名 -->
<select id="selectAllByTableName" resultType="Car">
select * from ${tableName}
</select>
使用场景:
- 分表存储的场景,如按日期分表:t_user20220108
- 使用#{}会导致表名被引号包围,例如:
select * from 't_car'
9.1.2.4 批量删除场景
xml
<!-- 使用${}进行批量删除 -->
<delete id="deleteBatch">
delete from t_car where id in(${ids})
</delete>
注意事项:
- 使用#{}会导致SQL语法错误:
where id in('1,2,3')
- 使用${}才能正确执行:
where id in(1,2,3)
9.1.2.5 模糊查询场景
xml
<!-- 使用${}进行模糊查询 -->
<select id="selectLikeByBrand" resultType="Car">
select * from t_car where brand like '%${brand}%'
</select>
<!-- 使用#{}进行模糊查询(方式1:使用concat函数) -->
<select id="selectLikeByBrand" resultType="Car">
select * from t_car where brand like concat('%',#{brand},'%')
</select>
<!-- 使用#{}进行模糊查询(方式2:使用双引号) -->
<select id="selectLikeByBrand" resultType="Car">
select * from t_car where brand like "%"#{brand}"%"
</select>
9.1.3 使用原则
- 优先使用
#{}
,能使用#{}
就不用${}
- 只有在以下场景使用
${}
:- 需要动态拼接SQL关键字(如排序)
- 需要动态拼接表名
- 需要动态拼接列名
- 需要批量删除时拼接in条件
- 需要模糊查询时拼接通配符
9.1.4 安全注意事项
- 使用${}时要注意SQL注入风险
- 对用户输入进行严格验证
- 必要时对参数进行转义处理
- 避免直接使用用户输入拼接SQL语句
9.1.5 性能考虑
- #{}使用预编译,性能更好
- ${}需要每次重新编译SQL语句
- 大量使用${}会影响系统性能
- 建议在必须使用${}的场景才使用
9.2 TypeAliases(类型别名)
9.2.1 配置方式
9.2.1.1 单个类型别名配置
xml
<typeAliases>
<typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
</typeAliases>
特点:
- type属性:指定要起别名的类
- alias属性:别名(可选)
- 如果不指定alias,则使用类的简类名作为别名
- 别名不区分大小写
9.2.1.2 包级别别名配置
xml
<typeAliases>
<package name="com.powernode.mybatis.pojo"/>
</typeAliases>
特点:
- 自动为包下所有类创建别名
- 别名就是简类名
- 别名不区分大小写
- 可以配置多个包
9.3 Mappers(映射器)
9.3.1 配置方式
9.3.1.1 resource方式
xml
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
特点:
- 从类路径加载配置文件
- SQL映射文件必须放在resources目录下或其子目录
9.3.1.2 url方式
xml
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
特点:
- 使用绝对路径
- 对SQL映射文件位置无要求
9.3.1.3 class方式
xml
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
特点:
- SQL映射文件和mapper接口必须在同一目录
- SQL映射文件名必须和mapper接口名一致
9.3.1.4 package方式
xml
<mappers>
<package name="com.powernode.mybatis.mapper"/>
</mappers>
特点:
- 自动注册包下所有mapper接口
- 需要满足class方式的条件
9.4 IDEA配置文件模板
建议在IDE中创建以下模板:
-
mybatis-config.xml
xml<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="jdbc.properties"/> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> </settings> <typeAliases> <package name=""/> </typeAliases> <environments default="dev"> <environment id="dev"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <package name=""/> </mappers> </configuration>
-
SqlMapper.xml
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=""> </mapper>
9.5 获取自动生成的主键
9.5.1 配置方式
xml
<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
9.5.2 使用场景
- 主键是自动生成的
- 需要在插入数据后立即使用生成的主键
- 常见于一对多关系的数据插入
9.5.3 配置说明
- useGeneratedKeys="true":启用自动生成主键
- keyProperty="id":指定主键属性名
9.6 完整示例
9.6.1 项目结构
mybatis-005-antic/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── powernode/
│ │ │ └── mybatis/
│ │ │ ├── mapper/
│ │ │ │ └── CarMapper.java
│ │ │ ├── pojo/
│ │ │ │ └── Car.java
│ │ │ └── utils/
│ │ │ └── SqlSessionUtil.java
│ │ └── resources/
│ │ ├── com/
| | | └── powernode/
| | | └── mybatis/
| | | ├── mapper/
| | | └── CarMapper.xml
│ │ ├── jdbc.properties
│ │ ├── logback.xml
│ │ └── mybatis-config.xml
│ └── test/
│ └── java/
│ └── com/
│ └── powernode/
│ └── mybatis/
│ └── test/
│ └── CarMapperTest.java
9.6.2 核心代码
9.6.2.1 pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.powernode</groupId>
<artifactId>mybatis-005-antic</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--logback依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
9.6.2.2 jdbc.properties
properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/powernode
jdbc.username=root
jdbc.password=root
9.6.2.3 SqlSessionUtil.java
java
package com.powernode.mybatis.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
static {
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession openSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
public static void close(SqlSession sqlSession) {
if (sqlSession != null) {
sqlSession.close();
}
local.remove();
}
}
9.6.2.4 Car.java
java
package com.powernode.mybatis.pojo;
public class Car {
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
// 构造方法、getter、setter、toString方法
}
9.6.2.5 CarMapper.java
java
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import java.util.List;
public interface CarMapper {
List<Car> selectByCarType(String carType);
List<Car> selectAll(String ascOrDesc);
List<Car> selectAllByTableName(String tableName);
int deleteBatch(String ids);
List<Car> selectLikeByBrand(String likeBrand);
void insertUseGeneratedKeys(Car car);
}
9.6.2.6 mybatis-config.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<typeAliases>
<package name="com.powernode.mybatis.pojo"/>
</typeAliases>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.powernode.mybatis.mapper"/>
</mappers>
</configuration>
9.6.2.7 CarMapper.xml
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.powernode.mybatis.mapper.CarMapper">
<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>
<select id="selectAll" resultType="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 carNum ${key}
</select>
<select id="selectAllByTableName" resultType="Car">
select
id,car_num as carNum,brand,guide_price as guidePrice,
produce_time as produceTime,car_type as carType
from
${tableName}
</select>
<delete id="deleteBatch">
delete from t_car where id in(${ids})
</delete>
<select id="selectLikeByBrand" 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
brand like concat('%',#{brand},'%')
</select>
<insert id="insertUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
</mapper>
9.6.2.8 CarMapperTest.java
java
package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.util.List;
public class CarMapperTest {
@Test
public void testSelectByCarType() {
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectByCarType("燃油车");
cars.forEach(car -> System.out.println(car));
}
@Test
public void testSelectAll() {
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll("desc");
cars.forEach(car -> System.out.println(car));
}
@Test
public void testSelectAllByTableName() {
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAllByTableName("t_car");
cars.forEach(car -> System.out.println(car));
}
@Test
public void testDeleteBatch() {
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
int count = mapper.deleteBatch("1,2,3");
System.out.println("删除了几条记录:" + count);
SqlSessionUtil.openSession().commit();
}
@Test
public void testSelectLikeByBrand() {
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectLikeByBrand("奔驰");
cars.forEach(car -> System.out.println(car));
}
@Test
public void testInsertUseGeneratedKeys() {
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = new Car();
car.setCarNum("5262");
car.setBrand("BYD汉");
car.setGuidePrice(30.3);
car.setProduceTime("2020-10-11");
car.setCarType("新能源");
mapper.insertUseGeneratedKeys(car);
SqlSessionUtil.openSession().commit();
System.out.println(car.getId());
}
}
9.7 常见问题及解决方案
9.7.1 SQL注入问题
- 问题:使用${}时可能引发SQL注入
- 解决方案:
- 优先使用#{}
- 必须使用${}时,确保参数安全
- 对用户输入进行严格验证
9.7.2 配置文件位置问题
- 问题:找不到配置文件
- 解决方案:
- 确保配置文件在正确的位置
- 使用正确的配置方式(resource/url/class/package)
- 检查文件路径是否正确
9.7.3 类型别名问题
- 问题:找不到类型别名
- 解决方案:
- 检查typeAliases配置是否正确
- 确保包路径正确
- 检查类名是否正确
9.7.4 主键生成问题
- 问题:无法获取自动生成的主键
- 解决方案:
- 确保数据库支持自增主键
- 正确配置useGeneratedKeys和keyProperty
- 检查实体类属性名是否正确
9.8 性能优化建议
9.8.1 SQL优化
- 合理使用索引
- 避免使用select *
- 优化复杂查询
- 使用批量操作
9.8.2 缓存优化
- 合理使用一级缓存
- 配置二级缓存
- 注意缓存更新策略
9.8.3 连接池优化
- 合理配置连接池参数
- 及时释放连接
- 避免连接泄漏