[NeedBox项目总结]---MyBatis小结

文章目录

    • 一、MyBatis的执行流程
    • 二、自动映射和驼峰映射
    • 三、#{...}和${...}的区别
    • 四、获取自增主键值和非自增主键值
    • 五、传递多个参数的4种方式
        • 1. 顺序传参法
        • 2. 使用Map传参
        • 3. 使用注解@Param传参
        • 4. 通过JavaBean传参
    • 六、使用resultMap进行关联查询
        • resultType和resultMap实现一对一查询小结
    • 七、动态sql
        • 1. if元素
        • 2. foreach元素
    • 八、模糊查询
        • 使用CONCAT()函数进行拼接:

一、MyBatis的执行流程

  1. 首先要有MyBatis配置文件,包括MyBatis全局配置文件和MyBatis映射文件。
  2. MyBatis通过读取配置文件信息,构造出SqlSessionFactory,即会话工厂。
  3. 通过SqlSessionFactory会话工厂创建SqlSession会话,SqlSession的作用是操作数据库。
  4. SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器来操作数据库的。
  5. Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。该对象包括:SQL语句、输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括Java的简单类型、HashMap集合对象、POJO对象类型。

二、自动映射和驼峰映射

MyBatis提供了自动映射功能,在默认情况下自动映射功能是开启的,使用它的好处在于能有效减少大量的映射配置,从而减少工作量。
在全局配置文件中的setting标签中有两个可以配置的选项,它们是控制自动映射和驼峰映射的开关:

  • autoMappingBehavior
  • mapUnderscoreToCamelCase
  1. 自动映射,autoMappingBehavior选项的取值范围是:
  • NONE 不进行自动映射
  • PARTIAL 默认值,只对没有嵌套结果集进行自动映射
  • FULL 对所有结果集进行自动映射,包括嵌套结果集

默认情况下,就是PARTIAL,如果编写的SQL列名和POJO属性名保持一致,就会形成自动映射。

  1. 驼峰映射,mapUnderscoreToCamelCase

如果代码中都严格按照驼峰命名法(比如数据库字段名user_name,则POJO属性名为userName),那么只要在配置项中把mapUnderscoreToCamelCase设置为true即可。

三、#{…}和${…}的区别

MyBatis的Mapper.xml语句中parameterType向SQL语句传参有两种方式:#{…} 和 ${…}
#{…}表示一个占位符号,在预编译处理时,会把参数部分用一个占位符 ? 代替
${…}表示一个拼接符号,在动态解析过程中,简单的进行字符串替换,会导致SQL注入
举个栗子~

① SELECT * FROM tb_user WHERE user_name = #{username};
② SELECT * FROM tb_user WHERE user_name = ${username};

①预编译后,会变成SELECT * FROM tb_user WHERE user_name = ?;
②在动态解析的时候,会传入参数字符串,SELECT * FROM tb_user WHERE user_name = ‘duck’;

所以它们的区别就是,#{…} 这种取值是编译好SQL语句再取值,${…} 这种是取值以后再去编译SQL语句

  • #{} 能够很大程度防止SQL注入, ${} 无法防止SQL注入
  • $方式一般用于传入数据库对象,例如传入表名
  • 一般能用#的就别用$

四、获取自增主键值和非自增主键值

我们在数据库表设计的时候,一般都会在表中设计一个自增的id作为表的主键。这个id也会关联到其它表的外键。这就要求往表中插入数据时能返回表的自增id,用这个ID去给关联表的字段赋值。有两种方法可以获取自增主键值:使用sql语句和添加属性

  1. 使用sql语句
<insert id="insertRole" parameterType="cn.entity.Role">
	INSERT INTO t_role(role_name, role_desc) VALUES(#{roleName}, #{roleDesc})
    <selectKey keyProperty="roleId" order="AFTER" resultType="int">
    	SELECT LAST_INSERT_ID()
    selectKey>
insert>
  • SELECT LAST_INSERT_ID():得到刚刚insert进去记录的主键值,只适用于自增主键
  • keyProperty:将得到的主键值设置到parameterType指定对象的属性中
  • order:SELECT LAST_INSERT_ID()相对于insert语句的执行顺序
  1. 添加属性
<insert id="insertRole" parameterType="cn.entity.Role"
		 useGeneratedKeys="true" keyProperty="roleId">
	INSERT INTO t_role(role_name, role_desc) VALUES(#{roleName}, #{roleDesc})
insert>
  • useGeneratedKeys:采用JDBC的Statement对象的getGeneratedKeys方法返回主键
  • keyProperty:将得到的主键值设置到parameterType指定对象的属性中

那么非自增主键值应该怎么获取呢?使用Mysql的uuid()

<insert id="insertRole" parameterType="cn.entity.Role">
    <selectKey keyProperty="roleId" order="BEFORE" resultType="String">
    	SELECT UUID()
    selectKey>
    INSERT INTO t_role(role_id, role_name, role_desc) VALUES(#{roleId}, #{roleName}, #{roleDesc})
insert>
  • SELECT UUID():生成主键,将主键设置到对象的roleId属性中
  • 在执行insert时,从对象中取出roleId属性值

五、传递多个参数的4种方式

现实的需求中常常需要传递多个参数,比如订单可以通过订单编号查询,也可以根据订单名称、日期或者价格等参数进行查询等等。假设通过role_name和role_desc对角色进行查询,这样就要传递两个参数了~

1. 顺序传参法

public List<Role> findRoleByOrder(String roleName, String roleDesc);
<select id="findRoleByOrder" resultType="Role">
	SELECT * FROM tb_role
	WHERE role_name=#{0} AND role_desc=#{1}
select>

#{}里面的数字代表传入参数的顺序~
缺点:SQL语句表达不直观,且一旦参数顺序调整,容易出错

2. 使用Map传参

public List<Role> findRoleByMap(Map<String, Object> parameterMap);

此时传递给映射器的是一个map对象,使用它在SQL中设置对应的参数

<select id="findRoleByMap" parameterType="Map" resultType="Role">
	SELECT * FROM tb_role
	WHERE role_name=#{roleName} AND role_desc=#{roleDesc}
select>

#{}里面的名称对应的是Map里面的key名称~

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Map<String, Object> parameterMap = new HashMap<String, Object>();
parameterMap.put("roleName", "1");
parameterMap.put("roleDesc", "1");
Role role = roleMapper.findRoleByMap(parameterMap);

缺点:

  • Map是一个键值对应的集合,使用者要通过阅读它的键,才能明了其作用
  • 使用Map不能限定其传递的数据类型,因此业务性质不强,可读性差。

3. 使用注解@Param传参

public List<Role> findRoleByAnnotation(@Param("roleName") String roleName,
									     @Param("roleDesc") String roleDesc);
<select id="findRoleByAnnotation" resultType="Role">
	SELECT * FROM tb_role
	WHERE role_name=#{roleName} AND role_desc=#{roleDesc}
select>

#{}里面的名称对应的是注解@Param括号里指定的名称~
此时,代码的可读性大大提高~推荐使用!

4. 通过JavaBean传参

先定义一个关于参数的POJO——RoleParams

package cn.entity.RoleParams;
public class RoleParams {
	private String roleName;
	private String roleDesc;
	/** setter and getter **/
}

此时的接口方法:

public List<Role> findRoleByBean(RoleParams roleParams);

#{}里面的名称对应的是RoleParams类里面的成员属性~

<select id="findRoleByBean" parameterType="cn.entity.RoleParams" resultType="Role">
	SELECT * FROM tb_role
	WHERE role_name=#{roleName} AND role_desc=#{roleDesc}
select>

引入JavaBean定义的属性作为参数,然后查询:

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
RoleParams roleParams = new RoleParams();
roleParams.setRoleName("1");
roleParams.setRoleDesc("1");
Role role = roleMapper.findRoleByBean(roleParams);

这种方法很直观,但缺点是需要建一个实体类,扩展不容易,需要加属性,看情况使用。

总结:

  • 使用顺序传参和Map传参导致了业务可读性的丧失,导致后续扩展和维护的困难,果断废弃。
  • 使用注解@Param传参,受到参数个数的影响。当参数个数<=5时,推荐使用。当参数个数大于5,将给调用带来困难,此时不推荐。
  • 当参数个数大于5,建议使用JavaBean方式。

六、使用resultMap进行关联查询

MyBatis中使用resultMap完成高级输出结果映射。
什么是高级映射?高级映射就是多表关联映射:

  • 将关联查询的列映射到一个pojo属性中(一对一)
  • 将关联查询的列映射到一个List中(一对多)

以项目中的实体类为例:
Product商品类里除了一些简单类型的成员变量,还有Shop实体类和ProductCategory实体类对象的成员变量,还有ProductImg的List集合成员变量。

public class Product {
    private Long productId;
    private String productName;
    private String productDesc;
    private String imgAddr;
    private String normalPrice;
    private String promotionPrice;
    private Integer priority;
    private Date createTime;
    private Date lastEditTime;
    private Integer enableStatus;
    
    private List<ProductImg> productImgList;
    private ProductCategory productCategory;
    private Shop shop;
}

对应的tb_product表结构是这样的:

CREATE TABLE `tb_product` (
	`product_id` int(100) NOT NULL AUTO_INCREMENT,
	`product_name` varchar(100) NOT NULL,
	`product_desc` varchar(2000) DEFAULT NULL,
	`img_addr` varchar(2000) DEFAULT '',
	`normal_price` varchar(100) DEFAULT NULL,
	`promotion_price` varchar(100) DEFAULT NULL,
	`priority` int(2) NOT NULL DEFAULT '0',
	`create_time` datetime DEFAULT NULL,
	`last_edit_time` datetime DEFAULT NULL,
	`enable_status` int(2) NOT NULL DEFAULT '0',
	`product_category_id` int(11) DEFAULT NULL,
	`shop_id` int(20) NOT NULL DEFAULT '0',
	PRIMARY KEY (`product_id`),
	KEY `fk_product_procate` (`product_category_id`),
	KEY `fk_product_shop` (`shop_id`),
	CONSTRAINT `fk_product_procate` FOREIGN KEY (`product_category_id`) REFERENCES `tb_product_category` (`product_category_id`),
	CONSTRAINT `fk_product_shop` FOREIGN KEY (`shop_id`) REFERENCES `tb_shop` (`shop_id`)
) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=utf8

所以我们定义如下的resultMap:

<mapper namespace="com.yaya.o2o.dao.ProductDao">
    <resultMap id="productMap" type="com.yaya.o2o.entity.Product">
        <id column="product_id" property="productId"/>
        <result column="product_name" property="productName" />
        <result column="product_desc" property="productDesc" />
        <result column="img_addr" property="imgAddr" />
        <result column="normal_price" property="normalPrice" />
        <result column="promotion_price" property="promotionPrice" />
        <result column="priority" property="priority" />
        <result column="create_time" property="createTime" />
        <result column="last_edit_time" property="lastEditTime" />
        <result column="enable_status" property="enableStatus" />
       
        <association property="productCategory" column="product_category_id" javaType="com.yaya.o2o.entity.ProductCategory">
            <id column="product_category_id" property="productCategoryId" />
            <result column="product_category_name" property="productCategoryName"/>
        association>
        <association property="shop" column="shop_id" javaType="com.yaya.o2o.entity.Shop">
            <id column="shop_id" property="shopId"/>
            <result column="owner_id" property="ownerId"/>
            <result column="shop_name" property="shopName"/>
        association>
      
        <collection property="productImgList" column="product_id" ofType="com.yaya.o2o.entity.ProductImg">
            <id column="product_img_id" property="productImgId" />
            <result column="detail_img" property="imgAddr" />
            <result column="img_desc" property="imgDesc" />
            <result column="priority" property="priority" />
            <result column="create_time" property="createTime" />
            <result column="product_id" property="productId" />
        collection>
    resultMap>
    ......
mapper>
  • resultMap元素
    • id:对resultMap的唯一标识
    • type:最终映射的java对象类型
  • id元素——查询结果集中唯一标识的定义
    • column:查询出来的列名
    • property:type指定的 pojo中的属性名
  • result元素——对普通列的映射定义
    • column:查询出来的列名
    • property:type指定的 pojo中的属性名
  • association元素——用于映射关联查询单个对象的信息(“有一个”关系)
    • property:映射的属性
    • column:查询的列名
    • javaType:映射的属性的实体类
      • id,column,property同上
  • collection元素——对关联查询到的多条记录映射到集合对象中(“有很多”关系)
    • property:映射的属性
    • column:查询的列名
    • ofType:指定映射到list集合属性中pojo的类型
      • id,column,property同上

然后我们在select中使用resultMap,通过商品id查询商品信息:

<mapper namespace="com.yaya.o2o.dao.ProductDao">
    <select id="queryProductById" resultMap="productMap" parameterType="Long">
        SELECT
            p.product_id,
            p.product_name,
            p.product_desc,
            p.img_addr,
            p.normal_price,
            p.promotion_price,
            p.priority,
            p.create_time,
            p.last_edit_time,
            p.enable_status,
            p.product_category_id,
            p.shop_id,
            pm.product_img_id,
            pm.img_addr AS detail_img,
            pm.img_desc,
            pm.priority,
            pm.create_time
        FROM tb_product p
        LEFT JOIN tb_product_img pm
        ON p.product_id=pm.product_id
        WHERE p.product_id=#{productId}
        ORDER BY pm.priority DESC
    select>
mapper>

这样就能完成一对一、一对多的高级映射了~

resultType和resultMap实现一对一查询小结

  • resultType:使用较为简单。如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
  • resultMap:需要单独定义resultMap,实现麻烦。如果有对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射到pojo的属性中。
  • resultMap可以实现延迟加载,resultType无法实现。

什么是延迟加载?
先从单表查询,需要时再从关联表去关联查询,大大提高数据库的性能,因为查询单表要比关联查询多张表速度要快。association、collection具备延迟加载的功能。

七、动态sql

MyBatis提供对SQL语句动态的组装能力,使用XML的几个简单的元素,便能完成动态SQL的功能。大量的判断都可以在MyBatis的映射XML里面配置,以达到许多需要大量代码才能实现的功能,大大减少了代码量,体现了MyBatis的灵活、高度可配置性和可维护性。

MyBatis的动态SQL包括以下几种元素:

  • if 判断语句
  • choose(when, otherwise) 相当于java中的switch和case
  • trim(when, set) 辅助元素,用于处理特定的SQL拼装问题,比如去掉多余的and,or等
  • foreach 循环语句

下面是项目中用到的if元素和foreach元素的代码实例~

1. if元素

场景:在更新商品信息的时候,只有当传入pojo里属性不为空的,才进行对列名的赋值和拼接

<update id="updateProduct" parameterType="com.yaya.o2o.entity.Product">
	UPDATE tb_product
	<set>
	    <if test="productName != null">product_name=#{productName},if>
	    <if test="productDesc != null">product_desc=#{productDesc},if>
	    <if test="imgAddr != null">img_addr=#{imgAddr},if>
	    <if test="normalPrice != null">normal_price=#{normalPrice},if>
	    <if test="promotionPrice != null">promotion_price=#{promotionPrice},if>
	    <if test="priority != null">priority=#{priority},if>
	    <if test="lastEditTime != null">last_edit_time=#{lastEditTime},if>
	    <if test="enableStatus != null">enable_status=#{enableStatus},if>
	    <if test="productCategory != null and productCategory.productCategoryId != null">
	        product_category_id=#{productCategory.productCategoryId}
	    if>
	set>
	WHERE product_id=#{productId}
	AND shop_id=#{shop.shopId}
update>
  • test:在判断语句中,要与if联合使用

2. foreach元素

场景:批量添加商品图片

<insert id="batchInsertProductImg" parameterType="java.util.List">
	INSERT INTO
	     tb_product_img(img_addr, img_desc, priority, create_time, product_id)
	VALUES
	     <foreach collection="list" item="productImg" index="index" separator=",">
	         (
	         #{productImg.imgAddr},
	         #{productImg.imgDesc},
	         #{productImg.priority},
	         #{productImg.createTime},
	         #{productImg.productId}
	         )
	     foreach>
insert>
  • collection:传递进来的类型,如数组、List、Set等集合
  • item:循环中当前的元素
  • index:当前元素在集合的位置下标
  • separator:各个元素的间隔符

八、模糊查询

场景:对输入的店铺名称进行模糊查询

...
	<if test="shopCondition.shopName != null">
		and s.shop_name like '%${shopCondition.shopName}%'
	if>
...
  1. 使用${…}
s.shop_name LIKE '%${shopCondition.shopName}%'

可能会引起sql的注入,尽量避免使用${…}

  1. 使用#{…}
s.shop_name LIKE "%"#{shopCondition.shopName}"%"

因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ‘,所以这里 % 需要使用双引号" ",不能使用单引号’ ',否则查不到任何结果。

使用CONCAT()函数进行拼接:

s.shop_name LIKE CONCAT('%', '${shopCondition.shopName}', '%')
---------------------------------------------------------------
s.shop_name LIKE CONCAT('%', #{shopCondition.shopName}, '%')

你可能感兴趣的:(NeedBox项目总结)