动态SQL执行流程
新增示例
// 动态条件查询接口示例
List<User> searchUsers(@Param("name") String name,
@Param("age") Integer age,
@Param("email") String email);
<select id="searchUsers" resultType="user">
SELECT * FROM users
<where>
<if test="name != null">AND name LIKE #{name}if>
<if test="age != null">AND age = #{age}if>
<if test="email != null">AND email = #{email}if>
where>
select>
最佳实践
动态SQL是指在SQL语句中根据不同的条件动态拼接SQL语句。在实际开发中,我们经常需要根据用户输入的不同条件来构建不同的SQL语句。
批量删除
delete from t_car where id in(1,2,3,4,5,6,...)
多条件查询
select * from t_car where brand like '丰田%' and guide_price > 30 and .....
动态更新
update t_user set username=?, email=? where id=?
if标签用于条件判断,根据条件动态拼接SQL语句。它是最基础也是最常用的动态SQL标签。
<if test="条件表达式">
SQL片段
if>
// Mapper接口
List<Car> selectByMultiCondition(@Param("brand") String brand,
@Param("guidePrice") Double guidePrice,
@Param("carType") String carType);
<select id="selectByMultiCondition" resultType="car">
select * from t_car where
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
if>
select>
条件判断问题
where 1=1
空值处理
!= null
和!= ''
双重判断,确保参数不为空参数命名
where标签是if标签的增强版,专门用于处理where子句。
自动处理where关键字
自动处理连接词
<select id="selectByMultiConditionWithWhere" resultType="car">
select * from t_car
<where>
<if test="brand != null and brand != ''">
and brand like "%"#{brand}"%"
if>
<if test="guidePrice != null and guidePrice != ''">
and guide_price >= #{guidePrice}
if>
<if test="carType != null and carType != ''">
and car_type = #{carType}
if>
where>
select>
trim标签提供了更灵活的条件处理方式,可以自定义前缀、后缀以及要删除的内容。
<select id="selectByMultiConditionWithTrim" resultType="car">
select * from t_car
<trim prefix="where" suffixOverrides="and|or">
<if test="brand != null and brand != ''">
brand like "%"#{brand}"%" and
if>
<if test="guidePrice != null and guidePrice != ''">
guide_price >= #{guidePrice} and
if>
<if test="carType != null and carType != ''">
car_type = #{carType}
if>
trim>
select>
set标签专门用于update语句,处理set子句。
<update id="updateWithSet">
update t_car
<set>
<if test="carNum != null and carNum != ''">car_num = #{carNum},if>
<if test="brand != null and brand != ''">brand = #{brand},if>
<if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},if>
<if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},if>
<if test="carType != null and carType != ''">car_type = #{carType},if>
set>
where id = #{id}
update>
这三个标签一起使用,实现类似if-else if-else的逻辑。
<select id="selectWithChoose" resultType="car">
select * from t_car
<where>
<choose>
<when test="brand != null and brand != ''">
brand like #{brand}"%"
when>
<when test="guidePrice != null and guidePrice != ''">
guide_price >= #{guidePrice}
when>
<otherwise>
produce_time >= #{produceTime}
otherwise>
choose>
where>
select>
foreach标签用于循环处理集合或数组,常用于批量操作。
<delete id="deleteBatchByForeach">
delete from t_car where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
foreach>
delete>
<delete id="deleteBatchByForeach2">
delete from t_car where
<foreach collection="ids" item="id" separator="or">
id = #{id}
foreach>
delete>
<insert id="insertBatchByForeach">
insert into t_car values
<foreach collection="cars" item="car" separator=",">
(null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
foreach>
insert>
这两个标签用于SQL代码复用,提高代码的可维护性。
<sql id="carCols">
id,
car_num carNum,
brand,
guide_price guidePrice,
produce_time produceTime,
car_type carType
sql>
<select id="selectAllRetMap" resultType="map">
select <include refid="carCols"/> from t_car
select>
<select id="selectByIdRetMap" resultType="map">
select <include refid="carCols"/> from t_car where id = #{id}
select>
参数处理
性能优化
代码维护
错误处理
多对一映射指多个实体对象关联到同一个实体对象的情况(如多个学生属于同一个班级)。
三种实现方式:
1. 级联属性映射(单SQL)
2. association标签(单SQL)
3. 分步查询(双SQL,推荐)
实现步骤:
1. 在Student类中添加Clazz属性:
public class Student {
private Integer sid; // 学生ID
private String sname; // 学生姓名
private Clazz clazz; // 关联的班级对象
// getter和setter方法
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
}
2. 在StudentMapper.xml中配置:
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
resultMap>
<select id="selectBySid" resultMap="studentResultMap">
select s.*, c.*
from t_student s
join t_clazz c on s.cid = c.cid
where sid = #{sid}
select>
特点:
性能考虑:
使用association标签可以更清晰地表达对象之间的关联关系:
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
association>
resultMap>
特点:
最佳实践:
1. 使用有意义的别名,提高SQL可读性
2. 明确指定需要查询的字段,避免使用select *
3. 合理使用索引,提高查询效率
分步查询是最常用的方式,它通过两条SQL语句完成查询:
优点:
1. 代码复用性增强:关联查询的SQL可以被其他查询复用
2. 支持延迟加载:可以按需加载关联数据,提高性能
3. 更好的可维护性:每个查询职责单一
4. 更灵活的控制:可以独立优化每个查询
实现步骤:
1. 修改StudentMapper.xml:
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<association property="clazz"
select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"/>
resultMap>
<select id="selectBySid" resultMap="studentResultMap">
select s.* from t_student s where sid = #{sid}
select>
2. 在ClazzMapper接口中添加方法:
public interface ClazzMapper {
/**
* 根据班级ID查询班级信息
* @param cid 班级ID
* @return 班级对象
*/
Clazz selectByCid(Integer cid);
}
3. 在ClazzMapper.xml中配置:
<select id="selectByCid" resultType="Clazz">
select * from t_clazz where cid = #{cid}
select>
性能优化建议:
1. 为关联字段建立索引
2. 合理使用缓存
3. 控制查询的字段数量
4. 考虑使用批量查询优化性能
延迟加载(Lazy Loading)是指只有在真正使用到关联数据时才会执行查询,这样可以提高系统性能。
在association标签中添加fetchType=“lazy”:
<association property="clazz"
select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"
fetchType="lazy"/>
使用场景:
@Test
public void testSelectBySid(){
StudentMapper mapper = SqlSessionUtil.openSession().getMapper(StudentMapper.class);
Student student = mapper.selectBySid(1);
// 此时只查询学生信息,不查询班级信息
String sname = student.getSname();
System.out.println("学生姓名:" + sname);
// 当访问班级信息时,才会执行班级查询
String cname = student.getClazz().getCname();
System.out.println("班级名称:" + cname);
}
延迟加载的注意事项:
1. 确保SqlSession在使用完之前不要关闭
2. 注意N+1查询问题
3. 考虑使用批量加载优化性能
4. 合理设置延迟加载的触发条件
在mybatis-config.xml中配置全局延迟加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
settings>
如果某个SQL不想使用延迟加载,可以设置fetchType=“eager”:
<association property="clazz"
select="com.powernode.mybatis.mapper.ClazzMapper.selectByCid"
column="cid"
fetchType="eager"/>
全局延迟加载的配置选项:
1. lazyLoadingEnabled:是否开启延迟加载
2. aggressiveLazyLoading:是否启用积极的延迟加载
3. lazyLoadTriggerMethods:指定触发延迟加载的方法
一对多映射是指一个实体对象包含多个其他实体对象的情况。例如:一个班级包含多个学生。
1. 在Clazz类中添加List属性:
public class Clazz {
private Integer cid; // 班级ID
private String cname; // 班级名称
private List<Student> stus; // 学生列表
// getter和setter方法
public List<Student> getStus() {
return stus;
}
public void setStus(List<Student> stus) {
this.stus = stus;
}
}
2. 使用collection标签实现:
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus" ofType="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
collection>
resultMap>
<select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
select c.*, s.*
from t_clazz c
join t_student s on c.cid = s.cid
where c.cid = #{cid}
select>
一对多映射的性能考虑:
1. 数据量大的情况下,建议使用分步查询
2. 合理使用索引提高查询效率
3. 考虑使用缓存减少数据库访问
4. 控制返回的数据量
1. 修改ClazzMapper.xml:
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<collection property="stus"
select="com.powernode.mybatis.mapper.StudentMapper.selectByCid"
column="cid"/>
resultMap>
<select id="selectClazzAndStusByCid" resultMap="clazzResultMap">
select * from t_clazz c where c.cid = #{cid}
select>
2. 在StudentMapper接口中添加方法:
/**
* 根据班级ID查询学生列表
* @param cid 班级ID
* @return 学生列表
*/
List<Student> selectByCid(Integer cid);
3. 在StudentMapper.xml中配置:
<select id="selectByCid" resultType="Student">
select * from t_student where cid = #{cid}
select>
分步查询的优化建议:
1. 使用批量查询代替循环查询
2. 合理设置缓存策略
3. 优化SQL语句,使用索引
4. 控制返回的字段数量
一对多延迟加载的实现方式与多对一相同:
1. 局部延迟加载:在collection标签中添加fetchType="lazy"
2. 全局延迟加载:在mybatis-config.xml中配置lazyLoadingEnabled=true
3. 如果开启全局延迟加载后,某个SQL不想使用延迟加载,可以设置fetchType="eager"
使用建议:
1. 对于频繁访问的关联数据,建议使用立即加载(eager)
2. 对于不常访问的关联数据,建议使用延迟加载(lazy)
3. 在开发阶段可以通过日志查看SQL执行情况,优化加载策略
4. 注意处理延迟加载可能带来的性能问题
性能优化技巧:
1. 使用批量查询代替单条查询
2. 合理设置缓存策略
3. 优化数据库索引
4. 控制返回的数据量
5. 使用合适的连接池配置
常见问题及解决方案:
1. N+1查询问题:使用批量查询或缓存解决
2. 性能问题:优化SQL语句,使用索引
3. 内存问题:控制返回的数据量
4. 事务问题:确保在事务范围内使用延迟加载
缓存(cache)是一种临时存储机制,用于存储频繁访问的数据,以减少对原始数据源的访问次数,从而提高系统性能。
MyBatis的缓存机制:将select语句的查询结果存储到缓存(内存)中,当再次执行相同的select语句时,直接从缓存中获取数据,不再查询数据库。这样既减少了IO操作,又避免了重复执行复杂的查找算法,从而显著提升性能。
一级缓存(本地缓存)
二级缓存(全局缓存)
第三方缓存
重要说明:
一级缓存是MyBatis默认开启的缓存机制,它存在于SqlSession的生命周期中。
@Test
public void testSelectById() throws Exception {
// 获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 使用同一个SqlSession进行两次查询
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
Car car1 = mapper1.selectById(83L);
System.out.println(car1);
CarMapper mapper2 = sqlSession1.getMapper(CarMapper.class);
Car car2 = mapper2.selectById(83L);
System.out.println(car2);
// 使用不同的SqlSession进行查询
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CarMapper mapper3 = sqlSession2.getMapper(CarMapper.class);
Car car3 = mapper3.selectById(83L);
System.out.println(car3);
CarMapper mapper4 = sqlSession2.getMapper(CarMapper.class);
Car car4 = mapper4.selectById(83L);
System.out.println(car4);
}
使用不同的SqlSession对象
查询条件发生变化
手动清空缓存
sqlSession.clearCache();
执行增删改操作
二级缓存是SqlSessionFactory级别的缓存,多个SqlSession可以共享二级缓存。
全局配置开启缓存(默认已开启):
<setting name="cacheEnabled" value="true"/>
在Mapper.xml文件中添加配置:
<cache/>
实体类必须实现Serializable接口
public class Car implements Serializable {
// 实现序列化接口
}
SqlSession关闭或提交后,一级缓存数据才会写入二级缓存
当两次查询之间出现任何增删改操作时:
<cache eviction="LRU"
flushInterval="60000"
readOnly="true"
size="1024"/>
eviction:缓存淘汰策略
flushInterval:刷新间隔(毫秒)
readOnly:
size:二级缓存最大存储对象数量(默认1024)
二级缓存适合:
二级缓存不适合:
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.2version>
dependency>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="e:/ehcache"/>
<defaultCache eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU"/>
ehcache>
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
diskStore:磁盘存储配置
defaultCache:默认缓存策略
eternal:是否永久有效
maxElementsInMemory:内存中最大缓存对象数
overflowToDisk:是否缓存到磁盘
diskPersistent:是否持久化到磁盘
timeToIdleSeconds:对象空闲时间(秒)
0:表示一直有效
0:表示空闲指定时间后失效
timeToLiveSeconds:对象存活时间(秒)
0:表示一直有效
0:表示创建后指定时间失效
memoryStoreEvictionPolicy:缓存清理策略
缓存穿透:查询不存在的数据
缓存雪崩:大量缓存同时失效
缓存击穿:热点数据失效