1.MyBatis的动态SQL--foreach
动态SQL:根据参数值的不同,将生成不同的SQL语句。
假设存在需求:根据若干个id删除相册数据,即批量删除。
需要执行的SQL语句大致是:
delete from pms_album where id=? or id=? or id=? ...
或者:
delete from pms_album where id in (?, ?, ?, ... ?);
当实现以上功能时,关于抽象方法,可以设计为:
int deleteByIds(Long[] ids);
或者:
int deleteByIds(Long... ids);
或者:
int deleteByIds(List<Long> ids);
在配置SQL时,需要使用到<foreach>
节点对参数进行遍历:
<!-- int deleteByIds(Long[] ids); -->
<delete id="deleteByIds">
DELETE FROM pms_album
WHERE id IN (
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
关于<foreach>
节点的配置:
collection
属性:当抽象方法的参数只有1个且没有添加@Param
注解时,当参数是数组类型时(包括类型为可变参数时),此属性取值为array
,当参数是List
集合类型时,此属性取值为list
item
属性:遍历过程中的每个元素的变量名,是自定义的名称separator
属性:遍历过程中各元素之间的分隔符号
练习:批量插入相册数据
需要执行的SQL语句大致是:
insert into pms_album (name,description,sort) values (?,?,?), (?,?,?), (?,?,?)
在AlbumMapper
中添加抽象方法:
int insertBatch(List<Album> albums);
在AlbumMapper.xml
中配置SQL语句:
<!-- int insertBatch(List<Album> albums); -->
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
INSERT INTO pms_album (
name, description, sort
) values
<foreach collection="list" item="album" separator=",">
(#{album.name}, #{album.description}, #{album.sort})
</foreach>
</insert>
2.使用Mybaits修改数据
通常,修改数据时,也会使用到动态SQL的机制,当传入某个字段对应的值时,SQL中才会包含修改此字段的部分,反之,如果没有传入某个字段对应的值,则SQL语句不会包含修改此字段的部分!
这样的功能可以通过动态SQL的<if>
标签来实现!
假设需要实现修改相册数据,传入的参数中包含哪些数据,就修改哪些数据,不包含的部分将不会被修改。
在AlbumMapper
接口中添加抽象方法:
int update(Album album);
在AlbumMapper.xml
中配置SQL语句:
<!-- int update(Album album); -->
<update id="update">
UPDATE pms_album
<set>
<if test="name != null">
name=#{name},
</if>
<if test="description != null">
description=#{description},
</if>
<if test="sort != null">
sort=#{sort},
</if>
</set>
WHERE id=#{id}
</update>
2.1使用Mybatis查询--统计
假设需要实现:统计相册表中的数据的数量
需要执行的SQL语句大致是:
select count(*) from pms_album
关于抽象方法:在查询时,方法的返回值类型只要求能够存入查询结果即可。
则在AlbumMapper
中添加抽象方法:
int count();
然后,在AlbumMapper.xml
中配置SQL语句,将使用<select>
节点,此节点必须配置resultType
或resultMap
这2个属性中的某1个,当使用resultType
时,此属性的值取决于抽象方法的返回值类型,如果是基本数据类型(例如int
等),则resultType
属性的值就是类型名,如果是引用数据类型(例如String
、Album
等),则resultType
属性的值就是类型的全限定名(在java.lang
包下的可以省略包名)。
<!-- int count(); -->
<select id="count" resultType="int">
SELECT count(*) FROM pms_album
</select>
如果既没有配置resultType
又没有配置resultMap
,将会出现以下错误:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'cn.tedu.csmall.product.mapper.AlbumMapper.count'. It's likely that neither a Result Type nor a Result Map was specified.
2.2使用Mybatis查询--根据id查询
假设需要实现:根据id查询相册详情
需要执行的SQL语句大致是:
select id, name, description, sort from pms_album where id=?
关于抽象方法的返回值类型,原则上,只需要能够“放得下”就行,所以,可以使用Album
作为此次查询的返回值类型,但是,并不建议这样处理!通常,建议另创建类型,用于封装查询结果!另外创建的类型,通常并不会称之为实体类,并且,这种类型会添加一些后缀,关于后缀的使用,阿里的文档的参考:
- 数据对象:xxxDO,xxx 即为数据表名
- 数据传输对象:xxxDTO,xxx 为业务领域相关的名称
- 展示对象:xxxVO,xxx 一般为网页名称
- POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO
关于以上后缀:
- DO:Data Object
- DTO:Data Transfer Object
- VO:View Object / Value Object
对于本次查询,可以使用VO
作为类型的后缀,完整的类名可以使用AlbumStandardVO
,此类应该放在项目的根包的pojo.vo
包下:
@Data
public class AlbumStandardVO implements Serializable {
// 除了gmtCreate和gmtModified以外的所有属性
}
提示:此前涉及的“实体类编写规范”本质上是POJO的规范!
接下来,在AlbumMapper
接口中添加抽象方法:
AlbumStandardVO getStandardById(Long id);
在AlbumMapper.xml
中配置SQL:
<!-- AlbumStandardVO getStandardById(Long id); -->
<select id="getStandardById" resultType="xx.xx.xx.AlbumStandardVO">
select id, name, description, sort from pms_album where id=#{id}
</select>
Mybatis在封装查询结果时,会自动的将**列名(Column)与属性名(Property)**匹配的结果进行封装,例如查询结果中的name
值将封装到返回值对象的name
属性中去,对于名称不匹配的,将放弃。
可以在配置SQL时,为查询的字段自定义列名,使得“查询结果中的列名”与“封装结果的类型中的属性名”是一致的,例如:
<!-- BrandStandardVO getStandardById(Long id); -->
<select id="getStandardById" resultType="cn.tedu.csmall.product.pojo.vo.BrandStandardVO">
SELECT
id, name, pinyin, logo, description,
keywords, sort, sales,
product_count AS productCount,
comment_count AS commentCount,
positive_comment_count AS positiveCommentCount,
enable
FROM pms_brand
WHERE id=#{id}
</select>
提示:在SQL语句中,自定义别名时,
AS
关键字并不是必须的,只需要有1个空格即可。
除了以上做法以外,还可以在application.properties
中添加配置,使得Mybatis能自动处理“全小写且使用下划线分隔的字段名对应的列名”与“驼峰命名法的属性名”之间的对应关系(例如此做法时,不必在查询时自定义别名):
mybatis.configuration.map-underscore-to-camel-case=true
或者,还可以选择自定义ResultMap,用于指导Mybatis如何封装查询结果,其基本方式是:
<resultMap id="自定义的ResultMap名称" type="封装查询结果的类型的全限定名">
<!-- 配置 -->
</resultMap>
<select id="xxx" resultMap="自定义的ResultMap名称">
</select>
在<resultMap>
内部,使用<result>
节点,配置其column
与property
属性,用于指定列名与属性名的对应关系,例如:
<resultMap id="自定义的ResultMap名称" type="封装查询结果的类型的全限定名">
<result column="product_count" property="productCount" />
<result column="comment_count" property="commentCount" />
<result column="positive_comment_count" property="positiveCommentCount" />
</resultMap>
提示:在普通的单表查询中,列名与属性名本身就对应的部分,并不需要在<resultMap>
中配置。
另外,在开发实践中,建议将查询的字段列表使用<sql>
节点进行封装,然后,在配置的SQL语句中,使用<include>
节点进行调用即可:
<!-- BrandStandardVO getStandardById(Long id); -->
<select id="getStandardById" resultMap="StandardResultMap">
SELECT
<include refid="StandardQueryFields"/>
FROM pms_brand
WHERE id=#{id}
</select>
<sql id="StandardQueryFields">
id, name, pinyin, logo, description,
keywords, sort, sales, product_count, comment_count,
positive_comment_count, enable
</sql>
<resultMap id="StandardResultMap"
type="cn.tedu.csmall.product.pojo.vo.BrandStandardVO">
<result column="product_count" property="productCount" />
<result column="comment_count" property="commentCount" />
<result column="positive_comment_count" property="positiveCommentCount" />
</resultMap>
当在<select>
上使用resultType
属性,取值却是<resultMap>
的id时,将出现以下错误:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is org.springframework.core.NestedIOException: Failed to parse mapping resource: 'file [C:\Users\pc\IdeaProjects\jsd2204-csmall-product-teacher\target\classes\mapper\BrandMapper.xml]'; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'file [C:\Users\pc\IdeaProjects\jsd2204-csmall-product-teacher\target\classes\mapper\BrandMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'StandardResultMap'. Cause: java.lang.ClassNotFoundException: Cannot find class: StandardResultMap
当在<select>
上使用了resultMap
,取值错误时(例如取值为类型的全限定名),将出现以下错误:
java.lang.IllegalArgumentException: Result Maps collection does not contain value for cn.tedu.csmall.product.pojo.vo.BrandStandardVO
2.3使用Mybatis查询数据列表
查询列表与查询某1个数据的开发过程相差不大,主要区别在于:
- 查询列表时,需要查询的字段通常更少
- Mybatis会自动使用
List
集合来封装查询到的多个数据,所以,抽象方法的返回值类型必须是List
类型的
假设需要实现:查询品牌列表(不考虑分页问题)
需要执行的SQL语句大致是:
select * from pms_brand order by sort desc, pinyin, id desc
注意:如果执行的查询的结果可能超过1条(即2条或以上),必须显式的指定order by
进行排序!
在vo
包中创建BrandListItemVO
类:
@Data
public class BrandListItemVO implements Serializable {
// id, name, logo
}
然后,在BrandMapper
接口中添加抽象方法:
List<BrandListItemVO> list();
在BrandMapper.xml
中配置SQL:
<!-- List<BrandListItemVO> list(); -->
<select id="list" resultMap="ListItemResultMap">
SELECT
<include refid="ListItemQueryFields"/>
FROM pms_brand
ORDER BY sort DESC, pinyin, id DESC
</select>
<sql id="ListItemQueryFields">
id, name, logo
</sql>
<resultMap id="ListItemResultMap" type="cn.tedu.csmall.product.pojo.vo.BrandListItemVO">
</resultMap>
注意:即使是查询列表,无论使用resultType
,还是配置<resultMap>
,关于数据类型,都只需要指定为List
中的元素类型即可!
作业
完成以下数据表的功能:“根据id查询数据”(已完成的则跳过)、“根据名称统计数据数量”
- 品牌表
- 相册表
- 类别表xx