动态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 ids) ;
在配置SQL时,需要使用到节点对参数进行遍历:
- <delete id="deleteByIds">
- DELETE FROM pms_album
- WHERE id IN (
- <foreach collection="array" item="id" separator=",">
- #{id}
- foreach>
- )
- delete>
关于节点的配置:
collection属性:当抽象方法的参数只有1个且没有添加@Param注解时,当参数是数组类型时(包括类型为可变参数时),此属性取值为array,当参数是List集合类型时,此属性取值为listitem属性:遍历过程中的每个元素的变量名,是自定义的名称separator属性:遍历过程中各元素之间的分隔符号练习:批量插入相册数据
需要执行的SQL语句大致是:
insert into pms_album (name,description,sort) values (?,?,?), (?,?,?), (?,?,?)
在AlbumMapper中添加抽象方法:
int insertBatch(List albums) ;
在AlbumMapper.xml中配置SQL语句:
- <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>
通常,修改数据时,也会使用到动态SQL的机制,当传入某个字段对应的值时,SQL中才会包含修改此字段的部分,反之,如果没有传入某个字段对应的值,则SQL语句不会包含修改此字段的部分!
这样的功能可以通过动态SQL的标签来实现!
假设需要实现修改相册数据,传入的参数中包含哪些数据,就修改哪些数据,不包含的部分将不会被修改。
在AlbumMapper接口中添加抽象方法:
int update(Album album);
在AlbumMapper.xml中配置SQL语句:
- <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>
假设需要实现:统计相册表中的数据的数量
需要执行的SQL语句大致是:
select count(*) from pms_album
关于抽象方法:在查询时,方法的返回值类型只要求能够存入查询结果即可。
则在AlbumMapper中添加抽象方法:
int count();
然后,在AlbumMapper.xml中配置SQL语句,将使用节点,此节点必须配置resultType或resultMap这2个属性中的某1个,当使用resultType时,此属性的值取决于抽象方法的返回值类型,如果是基本数据类型(例如int等),则resultType属性的值就是类型名,如果是引用数据类型(例如String、Album等),则resultType属性的值就是类型的全限定名(在java.lang包下的可以省略包名)。
- <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.
假设需要实现:根据id查询相册详情
需要执行的SQL语句大致是:
select id, name, description, sort from pms_album where id=?
关于抽象方法的返回值类型,原则上,只需要能够“放得下”就行,所以,可以使用Album作为此次查询的返回值类型,但是,并不建议这样处理!通常,建议另创建类型,用于封装查询结果!另外创建的类型,通常并不会称之为实体类,并且,这种类型会添加一些后缀,关于后缀的使用,阿里的文档的参考:
关于以上后缀:
对于本次查询,可以使用VO作为类型的后缀,完整的类名可以使用AlbumStandardVO,此类应该放在项目的根包的pojo.vo包下:
- @Data
- public class AlbumStandardVO implements Serializable {
- // 除了gmtCreate和gmtModified以外的所有属性
- }
提示:此前涉及的“实体类编写规范”本质上是POJO的规范!
接下来,在AlbumMapper接口中添加抽象方法:
AlbumStandardVO getStandardById(Long id);
在AlbumMapper.xml中配置SQL:
- <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时,为查询的字段自定义列名,使得“查询结果中的列名”与“封装结果的类型中的属性名”是一致的,例如:
- <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>
在内部,使用节点,配置其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>
提示:在普通的单表查询中,列名与属性名本身就对应的部分,并不需要在中配置。
另外,在开发实践中,建议将查询的字段列表使用节点进行封装,然后,在配置的SQL语句中,使用节点进行调用即可:
- <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>
当在上使用resultType属性,取值却是的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
当在上使用了resultMap,取值错误时(例如取值为类型的全限定名),将出现以下错误:
java.lang.IllegalArgumentException: Result Maps collection does not contain value for cn.tedu.csmall.product.pojo.vo.BrandStandardVO
查询列表与查询某1个数据的开发过程相差不大,主要区别在于:
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 list() ;
在BrandMapper.xml中配置SQL:
- <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,还是配置,关于数据类型,都只需要指定为List中的元素类型即可!
完成以下数据表的功能:“根据id查询数据”(已完成的则跳过)、“根据名称统计数据数量”