Java - Mybatis 框架详解(二)

1. 将id=?的数据的电子邮箱改为?

UserMapper接口中添加抽象方法:

Integer updateEmailById(
	@Param("id") Integer id, 
	@Param("email") String email
);

如果抽象方法的参数的数量超过1个,应该在每个参数之前添加@Param注解,并在注解中配置参数的名称,后续在配置SQL语句时,使用的#{}占位符中也需要写注解中配置的名称!

SomeMapper.xml中添加配置:

<update id="updateEmailById">
	UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>

在配置SQL时,如果抽象方法的参数只有1个,则在 #{} 占位符中写任意名称均可,因为框架会找唯一的那个参数!如果抽象方法的参数有多个(超过1个),由于.java文件在编译成.class文件时会丢失局部变量(参数也是一种局部变量)名称,所以,在#{}占位符中写参数名称是没有意义的,框架在执行时无法区分,可以使用#{arg0}表示第1个参数,使用#{arg1}表示第2个参数,以此类推,或者,使用#{param1}表示第1个参数,使用#{param2}表示第2个参数,以此类推……这样的做法虽然能解决问题,但是,SQL语句的代码的可读性就非常差,当抽象方法的参数都添加了@Param注解时,在SQL语句中就可以使用注解中配置的名称,以增加代码的可读性!

完成后,可以在Tests中编写单元测试:

@Test
public void updateEmailById() {
    
    
	Integer id = 1;
	String email = "[email protected]";
	Integer rows = userMapper.updateEmailById(id, email);
	System.out.println("rows=" + rows);
}

2. 动态SQL–foreach

动态SQL:在配置SQL语句时,可以添加例如<if><foreach>等节点,实现在SQL语句中的动态生成效果,例如使用<if>时,就可以对参数进行判断,最终可能生成不同的SQL语句,使用<foreach>对参数进行遍历,根据参数不同,生成的SQL语句也会不一样,这种机制就称之为“动态SQL”。

例如存在“一次性删除若干条数据”的需求,到底要删除多少条数据,这些数据的id分别是多少,对于开发人员来说是未知的!则SQL语句就是无法确定的!就需要使用动态SQL中的foreach的做法!即要求用户提供若干个id的集合,然后遍历该集合,形成SQL语句中where id in ()括号中的若干个问号!

首先,还是应该在UserMapper接口中添加抽象方法:

Integer deleteByIds(List<Integer> ids);
<delete id="deleteByIds">
	DELETE FROM t_user WHERE id IN
	<foreach collection="list" 
		item="id" separator=","
		open="(" close=")">
		#{id}
	</foreach>
</delete>

参数需要表示若干个id值,可以使用List集合,也可以使用数组,或者声明为可变参数(在处理过程中,等同于数组)。

然后,在SomeMapper.xml中添加配置:

在配置<foreach>节点时,相关属性的配置:

  • collection:被遍历的参数对象。当抽象方法的参数只有1个,没有添加@Param注解时,当对象是List集合类型时,取值为list,当对象是数组类型时,取值为array;当抽象方法的参数有多个,并且添加了@Param注解时,该属性的值就是注解中定义的名称;

  • item:在遍历过程中,被遍历到的数据的名称,是自定义的,等同于for (Integer id : ids)中的id,并且,在子级的文本中使用#{}占位符时,使用的名称就是该item属性的值;

  • separator:分隔符,生成遍历结果时,每个数据之间的分隔符,即若干个id值之间的逗号;

  • openclose:遍历生成的SQL语句部分的最左侧和最右侧的字符串。

3. 动态SQL–if

在配置SQL时,可以使用<if>对参数进行判断,例如:

<select id="find" resultType="cn.tedu.mybatis.User">
	SELECT * FROM t_user 
	<if test="where != null">
		WHERE ${where}
	</if>
	<if test="orderBy != null">
		ORDER BY ${orderBy}
	</if>
	<if test="offset != null and count != null">
		LIMIT #{offset}, #{count}
	</if>
</select>

注意:在动态SQL中,虽然有<if>节点,但是,并没有所谓的else的节点!如果需要考虑else对应的情况,可以使用2个<if>写上完全相反的2个条件,例如<if test="where != null"><if test="where == null">,也可以使用<choose><when><otherwise>的组合。

注意:凡是能在Java程序中解决的问题,就尽量不要通过动态SQL来解决!

4. 关于#{}和${}占位符

在MyBatis中配置SQL语句时,参数可以使用#{}格式的占位符,也可以使用${}格式的占位符!

使用#{}格式的占位符,是用于对进行占位的,并不能表示SQL语句结构中的某个部分,也不可以表示某个表达式!而${}格式的占位符可以表示任何内容!

使用#{}格式的占位符,是采取预编码的做法的,即先无视值的大小,将SQL执行编译,编译通过后,再将值代进去来执行SQL语句!使用${}格式的占位符,是先把占位符的值拼接到SQL语句,再进行编译,编译通过后再运行的!

可以简单的认为:以前学JDBC使用PreparedStatement时,可以写问号?的位置,都可以使用#{}格式的占位符,反之,不可以写问号?的位置,就不能使用#{}格式的占位符,而需要使用${}格式的占位符!

由于使用${}格式的占位符不是预编译的,所以,还存在SQL注入的风险!

简单的来说,优先使用#{}格式的占位符,如果不正确,换成${}格式的占位符!

5. 使用自定义别名解决名称不匹配的查询问题

先在t_user数据表中添加名为is_delete的字段,用于表示“某用户的数据是否标记为删除”,默认为0,表示“未删除,正常”,也可以设置为1表示“已删除”。

添加字段的SQL语句:

ALTER TABLE t_user ADD COLUMN is_delete INT;

UPDATE t_user SET is_delete=0;

然后,User类应该是与t_user表相对应的,所以,还应该在User类中补充对应的属性:

private Integer isDelete;
// SET/GET

添加之后,再次查询用户数据,会发现查询结果中isDelete的值是null

因为MyBatis在处理查询时,默认情况下,要求查询结果的列名与封装结果的属性名(其实是SET方法的名称)保持一致

请添加图片描述

既然目前名称并不一致,而属性名(SET方法名)是不可能随便调整的,就只能修改查询结果的列名,通过查询时指定别名就可以实现这样的效果:

<select id="findAll" resultType="cn.tedu.mybatis.User">
	SELECT 
		id, username, 
		password, age,
		phone, email,
		is_delete AS isDelete
	FROM 
		t_user 
	ORDER BY 
		id
</select>

6. 使用解决查询时名称不匹配的问题

区分3个名词:

字段名(Field):创建数据表时指定的各字段的名称;

列名(Column):查询结果的表格中每项数据上方的名称,默认情况下,列名就是字段名,当然,也可以在查询时指定别名,则列名就是别名;

属性名(Property):类中定义的全局变量的名称;

如果查询结果的列名与封装结果的属性名不一致,且不指定别名,还可以通过配置<resultMap>来解决!

配置<resultMap>节点的目的就是指导MyBatis框架,如何将查询结果中的数据封装到返回的对象中

配置代码如下:

<!-- id:自定义名称 -->
<!-- type:封装查询结果的类的全名 -->
<resultMap id="UserEntityMap"
	type="cn.tedu.mybatis.User">
	<!-- column与property:将查询结果中哪一列的数据封装到类的哪个属性中 -->
	<result column="id" property="id"/>
	<result column="username" property="username"/>
	<result column="password" property="password"/>
	<result column="age" property="age"/>
	<result column="phone" property="phone"/>
	<result column="email" property="email"/>
	<result column="is_delete" property="isDelete"/>
</resultMap>

配置完成后,在<select>节点中,就不必再配置resultType属性了,而是改为配置resultMap属性,例如:

<select id="findAll" resultMap="UserEntityMap">
	SELECT 
		*
	FROM 
		t_user 
	ORDER BY 
		id
</select>

类似以上这种单表的数据查询中,如果查询结果的列名与类中的属性名是完全相同的,其实可以不必配置,所以,以上<resultMap>可以简化为:

<resultMap id="UserEntityMap" type="cn.tedu.mybatis.User">
	<result column="is_delete" property="isDelete"/>
</resultMap>

另外,除了使用<result>节点配置对应关系以外,还可以使用<id>节点进行配置,只不过,<id>节点应该只用于配置主键字段!例如配置为:

<resultMap id="UserEntityMap" type="cn.tedu.mybatis.User">
	<id column="id" property="id"/>
	<result column="is_delete" property="isDelete"/>
</resultMap>

从查询结果看来,使用<id><result>这2种节点的配置效果完全相同,但是,使用<id>配置有利于MyBatis框架实现数据缓存!推荐将主键使用<id>来配置!

7. 简单的关联表查询

创建t_department部门信息表:

CREATE TABLE t_department (
	id INT AUTO_INCREMENT COMMENT '部门id',
	name VARCHAR(20) NOT NULL COMMENT'部门名称',
	PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;

然后,还应该在项目中创建Department类:

public class Department {
    
    
	private Integer id;
	private String name;
	// SET/GET/toString
}

如果要使得用户数据与以上部门的数据产生关联,还应该先在用户数据表中添加“用户所归属的部门”字段:

ALTER TABLE t_user ADD COLUMN department_id INT;

修改了用户数据表,则应该在User类中也补充添加相应的字段:

private Integer departmentId;

然后,在2张中调整一下测试数据:

INSERT INTO t_department (name) VALUES ('软件研发部'), ('人力资源部'), ('财务部');

UPDATE t_user SET department_id=1 WHERE id IN (1,8,15);
UPDATE t_user SET department_id=2 WHERE id IN (4,11,14);
UPDATE t_user SET department_id=3 WHERE id IN (9,10,13);

假设存在需求:查询某个用户的信息,并显示该用户归属的部门的名称

需要执行的SQL语句大致大致是:

select 
	t_user.id, username,
	password, age,
	phone, email,
	is_delete, department_id,
	name 
from 
	t_user 
left join 
	t_department 
on 
	t_user.department_id=t_department.id 
where 
	t_user.id=?

与数据表相对应的是实体类(Entity),当涉及关联表查询时,必然没有任何一个实体类可以封装查询结果,则需要自定义新的**VO类(Value Object)**来封装查询结果!这种类与实体类只是定位不同,它是与查询结果对应的,或者是根据查询结果来定制的,编码方式几乎一致!

所以,需要先创建VO类,表示此次的查询结果:

public class UserVO {
    
    
	private Integer id;
	private String username;
	private String password;
	private Integer age;
	private String phone;
	private String email;
	private Integer isDelete;
	private Integer departmentId;
	private String departmentName;
}

UserMapper接口中定义抽象方法:

UserVO findVOById(Integer id);

然后SomeMapper.xml中配置以上方法对应的SQL语句:

<select id="findVOById" resultType="cn.tedu.mybatis.UserVO">
	SELECT
		t_user.id, username,
		password, age,
		phone, email,
		is_delete AS isDelete,
		department_id AS departmentId,
		name AS departmentName
	FROM
		t_user
	LEFT JOIN
		t_department
	ON
		t_user.department_id=t_department.id
	WHERE
		t_user.id=#{id}
</select>

8. 练习

创建DepartmentMapper接口,用于定义管理部门数据的抽象方法;

SomeMapper.xml重命名为UserMapper.xml

UserMapper.xml复制得到DepartmentMapper.xml,并清空其中的配置,该DepartmentMapper.xml将用于配置管理部门数据的SQL语句;

通过MyBatis框架实现以下功能:

  1. 增加新的部门;

  2. 修改部门的名称;

  3. 根据某个id删除部门;

  4. 批量删除部门;

  5. 查询部门列表;

  6. 根据某个id查询部门。

猜你喜欢

转载自blog.csdn.net/Kevinblant/article/details/120915309
今日推荐