MyBatis提供了自动映射功能,在默认情况下自动映射功能是开启的,使用它的好处在于能有效减少大量的映射配置,从而减少工作量。
在全局配置文件中的setting标签中有两个可以配置的选项,它们是控制自动映射和驼峰映射的开关:
默认情况下,就是PARTIAL,如果编写的SQL列名和POJO属性名保持一致,就会形成自动映射。
如果代码中都严格按照驼峰命名法(比如数据库字段名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语句和添加属性
<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>
<insert id="insertRole" parameterType="cn.entity.Role"
useGeneratedKeys="true" keyProperty="roleId">
INSERT INTO t_role(role_name, role_desc) VALUES(#{roleName}, #{roleDesc})
insert>
那么非自增主键值应该怎么获取呢?使用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>
现实的需求中常常需要传递多个参数,比如订单可以通过订单编号查询,也可以根据订单名称、日期或者价格等参数进行查询等等。假设通过role_name和role_desc对角色进行查询,这样就要传递两个参数了~
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语句表达不直观,且一旦参数顺序调整,容易出错
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);
缺点:
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括号里指定的名称~
此时,代码的可读性大大提高~推荐使用!
先定义一个关于参数的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);
这种方法很直观,但缺点是需要建一个实体类,扩展不容易,需要加属性,看情况使用。
总结:
MyBatis中使用resultMap完成高级输出结果映射。
什么是高级映射?高级映射就是多表关联映射:
以项目中的实体类为例:
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>
然后我们在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>
这样就能完成一对一、一对多的高级映射了~
什么是延迟加载?
先从单表查询,需要时再从关联表去关联查询,大大提高数据库的性能,因为查询单表要比关联查询多张表速度要快。association、collection具备延迟加载的功能。
MyBatis提供对SQL语句动态的组装能力,使用XML的几个简单的元素,便能完成动态SQL的功能。大量的判断都可以在MyBatis的映射XML里面配置,以达到许多需要大量代码才能实现的功能,大大减少了代码量,体现了MyBatis的灵活、高度可配置性和可维护性。
MyBatis的动态SQL包括以下几种元素:
下面是项目中用到的if元素和foreach元素的代码实例~
场景:在更新商品信息的时候,只有当传入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>
场景:批量添加商品图片
<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>
场景:对输入的店铺名称进行模糊查询
...
<if test="shopCondition.shopName != null">
and s.shop_name like '%${shopCondition.shopName}%'
if>
...
s.shop_name LIKE '%${shopCondition.shopName}%'
可能会引起sql的注入,尽量避免使用${…}
s.shop_name LIKE "%"#{shopCondition.shopName}"%"
因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ‘,所以这里 % 需要使用双引号" ",不能使用单引号’ ',否则查不到任何结果。
s.shop_name LIKE CONCAT('%', '${shopCondition.shopName}', '%')
---------------------------------------------------------------
s.shop_name LIKE CONCAT('%', #{shopCondition.shopName}, '%')