9.Mybatis

作业

  1. 创建名为tedu_ums的数据库;

    CREATE DATABASE tedu_ums;

  2. 在该数据库中创建名为t_user的数据表,数据表中至少包括:id、用户名、密码、年龄、手机号码、邮箱这些字段;

    CREATE TABLE t_user (
    	id INT AUTO_INCREMENT COMMENT '用户数据的id',
    	username VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
    	password VARCHAR(50) NOT NULL COMMENT '密码',
    	age INT COMMENT '年龄',
    	phone VARCHAR(20) COMMENT '手机号码',
    	email VARCHAR(50) COMMENT '邮箱',
    	PRIMARY KEY(id)
    ) DEFAULT CHARSET=UTF8;
    
  3. 向该表中插入不少于10条数据;

    INSERT INTO t_user (username,password,phone,age,email)
    VALUES (‘Henry’,‘1234’,‘13800138001’,18,‘henry@qq.com’);

  4. 删除指定id的数据,例如删除id=3的用户的数据;

    DELETE FROM t_user WHERE id=3;

  5. 将所有用户的密码全部修改为8888

    UPDATE t_user SET password=‘8888’;

  6. 将指定用户的密码修改为123456,例如将id=6的用户的密码改为123456

    UPDATE t_user SET password=‘123456’ WHERE id=6;

  7. 统计当前有多少条用户数据;

    SELECT COUNT(*) FROM t_user;

  8. 查询指定id的用户数据,例如查询id=5的用户数据;

    SELECT id,username,password,age,phone,email FROM t_user WHERE id=5;

  9. 查询用户名和密码是指定值的用户数据,例如查询用户名为root且密码是1234的用户数据;

    SELECT id,username,password,age,phone,email FROM t_user WHERE username=‘root’ AND password=‘1234’;

  10. 删除多个指定id的数据,例如一次性删除id=4,id=2,id=8的这3条数据;

    DELETE FROM t_user WHERE id IN (2,4,8);

  11. 查询年龄第2大的用户数据。

    SELECT id,username,password,age,phone,email FROM t_user ORDER BY age DESC LIMIT 1,1;

1. MyBatis

1.1. 作用

简化持久层开发,当需要开发某个增删改查功能时,程序员只需要定义好该功能对应的抽象方法,及该抽象方法的功能对应的SQL语句即可。

1.2. 创建项目

与创建SpringMVC项目的步骤相同,另外,增加添加依赖:

<!-- MyBatis -->
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.1</version>
</dependency>

<!-- MyBatis整合Spring -->
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>2.0.1</version>
</dependency>

<!-- Spring-JDBC,与spring-webmvc使用相同版本 -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
	<version>4.3.8.RELEASE</version>
</dependency>

<!-- MySQL连接 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.16</version>
</dependency>

<!-- 数据库连接池 -->
<dependency>
	<groupId>commons-dbcp</groupId>
	<artifactId>commons-dbcp</artifactId>
	<version>1.4</version>
</dependency>

1.3. 配置数据库连接

src/main/resources下创建db.properties文件,用于配置数据库连接:

url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver=com.mysql.cj.jdbc.Driver
username=root
password=root
initialSize=2
maxActive=10

关于driver属性的配置:在mysql-connector-java的jar包中,展开,找到META-INF下的services下的java.sql.Driver文件,该文件中记录的就是正确的驱动类,如果没有该文件,则使用com.mysql.jdbc.Driver

然后,可以在Spring的配置文件中,通过<util:properties>节点读取以上配置文件:

<!-- 读db.properties配置 -->
<util:properties id="dbConfig"
	location="classpath:db.properties" />

在实际连接时,应该使用BasicDataSource对象获取连接对象,在使用之前需要为BasicDataSource的相关属性注入值:

<!-- 配置数据源 -->
<bean class="org.apache.commons.dbcp.BasicDataSource">
	<property name="url" 
		value="#{dbConfig.url}" />
	<property name="driverClassName" 
		value="#{dbConfig.driver}" />
	<property name="username" 
		value="#{dbConfig.username}" />
	<property name="password" 
		value="#{dbConfig.password}" />
	<property name="initialSize" 
		value="#{dbConfig.initialSize}" />
	<property name="maxActive" 
		value="#{dbConfig.maxActive}" />
</bean>

以上配置中,各<property>节点的name属性的值来自BasicDataSource中的属性名称,在Spring表达式例如#{dbConfig.driver}中的driver来自db.properties文件中各等于号左侧的属性名。

完成后,就可以编写单元测试,尝试获取连接对象:

public class Tests {
	
	@Test
	public void getConnection() throws SQLException {
		ClassPathXmlApplicationContext ac
			= new ClassPathXmlApplicationContext(
				"spring.xml");
		
		DataSource dataSource
			= ac.getBean("dataSource", DataSource.class);
		
		Connection conn = dataSource.getConnection();
		System.out.println(conn);
		
		ac.close();
	}

}

1.4. 接口与抽象方法

在开发持久层功能时,需要为对应的功能定义抽象方法,这些抽象方法应该存在于接口中,所以,先创建对应的接口文件cn.tedu.mybatis.UserMapper

public interface UserMapper {
}

然后,在接口中添加抽象方法,关于抽象方法的设计原则:

  1. 如果方法对应的功能是执行增、删、改,可以使用Integer作为返回值类型,表示“受影响的行数”,如果不关心返回值,也可以使用void,如果方法对应的功能是查询,返回值可以根据实际使用需求来设计;

  2. 方法的名称可以自定义,但绝不可以重载;

  3. 方法的参数按需设计。

以“增加新用户数据为例”,可以设计为:

Integer addnew(User user);

则还应该在项目补充创建cn.tedu.mybatis.User类。

然后,还需要在Spring的配置文件中,配置org.mybatis.spring.mapper.MapperScannerConfigurer类,以设置接口文件的位置:

<!-- MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage"
		value="cn.tedu.mybatis" />
</bean>

1.5. 配置SQL

http://doc.tedu.cn/config/mybatis-mapper.zip下载配置SQL语句的文件的压缩包,解压后得到SomeMapper.xml文件,将其重命名为UserMapper.xml

src/main/resources/下创建名为mappers的文件夹,然后将UserMapper.xml粘贴到这个文件夹中。

然后编辑UserMapper.xml,首先,添加<mapper>根节点,并配置namespace属性,取值是该XML文件对应的JAVA接口文件:

<!-- namespace:对应的接口 -->
<mapper namespace="cn.tedu.mybatis.UserMapper">
</mapper>

然后,添加子级节点,以配置与接口中抽象方法对应的SQL语句,应该根据所需要执行的操作类型来决定使用<insert><delete><update><select>节点中的某一种,子点的id属性就是对应的抽象方法的名称,然后,在节点内部配置所需要执行的SQL语句:

<!-- id:抽象方法的名称 -->
<insert id="addnew">
	INSERT INTO t_user (
		username,password,
		age,phone,
		email
	) VALUES (
		#{username},#{password},
		#{age},#{phone},
		#{email}
	)
</insert>

如果配置的是<select>节点,还必须配置resultType属性,用于表示返回值类型,如果返回值类型是List集合,则该属性配置值是List集合中的元素类型。

最后,还需要在Spring的配置文件中,配置org.mybatis.spring.SqlSessionFactoryBean类,用于指定:XML文件的位置,指定数据源:

<!-- SqlSessionFactoryBean -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- XML文件在哪里 -->
	<property name="mapperLocations"
		value="classpath:mappers/*.xml" />
	<!-- 用哪个数据源 -->
	<property name="dataSource"
		ref="dataSource" />
</bean>

验证以上功能是否完成,可以编写并执行单元测试:

@Test
public void addnew() {
	ClassPathXmlApplicationContext ac
		= new ClassPathXmlApplicationContext(
			"spring.xml");
	
	UserMapper userMapper
		= ac.getBean("userMapper", UserMapper.class);
	
	User user = new User();
	user.setUsername("MyBatis");
	user.setPassword("888888");
	Integer rows = userMapper.addnew(user);
	System.out.println("rows=" + rows);
	
	ac.close();
}

1. 查询时,需要查询结果中的列名与返回值类型中的属性名保持一致

假设在t_user表中添加了新的名为is_delete的字段:

alter table t_user add column is_delete int;

则对应的User类中也应该添加新的属性,以与对应:

public class User {

	private Integer id;
	private String username;
	private String password;
	private Integer age;
	private String phone;
	private String email;
	private Integer isDelete;

当查询数据时,需要自定义别名,使得查询结果中的列名与返回值类型中的属性名保持一致:

<select id="findById"
	resultType="cn.tedu.mybatis.User">
	SELECT 
		id,username,
		password,age,
		phone,email,
		is_delete AS isDelete
	FROM 
		t_user 
	WHERE 
		id=#{id}
</select>

其对应关系如图所示:

[外链图片转存失败(img-8oRP3lUl-1563608116247)(01.png)]

2. 简单的关联表查询数据

假设需要创建“部门信息表”:

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

然后,添加一些模拟数据:

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

并且,每个用户都归属于某个部门:

ALTER TABLE t_user ADD COLUMN department_id INT;

最后,为用户分配部门:

UPDATE t_user SET department_id=1 WHERE id IN (21,28,32);
UPDATE t_user SET department_id=2 WHERE id IN (24,25,26);
UPDATE t_user SET department_id=3 WHERE id IN (22,27,30);
UPDATE t_user SET department_id=4 WHERE id IN (23,29,31);

假设存在需求“查询某用户的信息,并显示该用户的部门的名称”,必须通过关联查询才可以得到所需要的结果,需要执行的SQL语句大致是:

select 
	* 
from 
	t_user 
left join 
	t_department 
on 
	t_user.department_id=t_department.id 
where 
	t_user.id=25;

如果需要使用MyBatis开发该功能,首先,目前并没有某个类型可以封装查询结果!因为创建的User类是与t_user表相对应的,例如User这样的类称之为实体类(entity),实体类肯定不满足多表查询需求的,所以,对于这种情况,需要自行另创建VO类(value object),这种类的设计应该与查询结果或查询需求相对应:

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;
}

VO类的设计方式与实体类基本一致,只是这2种类的定位不同而已,实体类是与数据表对应的,VO类是与查询结果对应的。

然后,可以将此前的findById(Integer id)的返回值类型修改为UserVO

UserVO findById(Integer id);

且映射的配置:

<select id="findById"
	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>

3. 使用处理1对多的关联查询

假设存在需求“查询某部门信息,且显示出该部门所有的员工”,需要执行的SQL语句大致是:

select 
	*
from
	t_department
left join
	t_user
on
	t_department.id=t_user.department_id
where
	t_department.id=?

这类查询可能查到多条结果,取决于该部门的员工数量,但是,查询的需求却是查询“某1个”部门的信息!在设计查询的方法时,就需要某1个类型可以封装多条查询结果,可以:

public class DepartmentVO {
	private Integer id;
	private String name;
	private List<User> users;
}

则对应的抽象方法可以是:

public interface DepartmentMapper {
	DepartmentVO findById(Integer id);
}

此次查询必然可能出现多个结果,MyBatis并不知道如何将多个结果封装到1个对象中,所以,按照此前的查询做法,必然会出错!例如:

Caused by: org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3

在这种情况下,需要自定义<resultMap>节点,用于指导MyBatis如何将结果进行封装:

<!-- id:自定义名称 -->
<!-- type:返回结果的类型 -->
<resultMap id="DepartmentVO_Map"
	type="cn.tedu.mybatis.DepartmentVO">
	<!-- id节点:用于配置主键 -->
	<!-- result节点:用于配置非主键 -->
	<!-- column:查询结果的列名 -->
	<!-- property:返回结果类型中的属性名 -->
	<!-- 无论哪个节点,都是用于告之MyBatis将查询结果中哪一列的数据放到返回类型中的哪个属性中 -->
	<id column="id" property="id"/>
	<result column="name" property="name"/>
	<!-- collection节点:用于配置1对多关系 -->
	<!-- ofType:集合中的元素类型 -->
	<collection property="users"
		ofType="cn.tedu.mybatis.User">
		<id column="uid" 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"/>
	</collection>
</resultMap>

<select id="findById"
	resultMap="DepartmentVO_Map">
	SELECT 
		t_department.id, name,
		t_user.id AS uid, username,
		password, age,
		phone, email,
		is_delete
	FROM
		t_department
	LEFT JOIN
		t_user
	ON
		t_department.id=t_user.department_id
	WHERE
		t_department.id=#{id}
</select>

查询结果例如:

DepartmentVO [
	id=1, name=软件研发部, 
	users=[
		User [id=21, username=Mike01, password=8888, age=18, phone=13800138001, email=Mike@qq.com, isDelete=0], 
	
		User [id=28, username=Mike08, password=8888, age=25, phone=13800138001, email=Mike@qq.com, isDelete=0], 

		User [id=32, username=Mike11, password=8888, age=26, phone=13800138001, email=Mike@qq.com, isDelete=0]
	]
]

4. 动态SQL

动态SQL指的是可以在配置SQL语句添加一些特殊的标签,例如<if><foreach>等,可以根据参数的不同,最终生成不同的SQL语句,则称之为动态SQL。

例如:根据若干个id删除数据,大致的SQL语句是:

delete from t_user where id in (1,3,5,7,9)

在实际应用中,以上IN关键字后侧的括号中的值是不确定的,不光是值本身,值的数量也是不确定,并且各值之间需要使用逗号进行分隔,当值的数量不确定时,逗号的数量也是无法确定的!

首先,需要在UserMapper.java接口中添加抽象方法:

Integer deleteByIds(List<Integer> ids);

在设计参数时,可以使用List集合类型,也可以使用数组类型。

然后,在UserMapper.xml中配置以上抽象方法的映射:

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

在配置<foreach>节点时:

  • collection:需要被遍历的集合或数据,如果抽象方法只有1个参数时,如果参数的类型是List集合,则取值为list,如果参数类型是数组,则取值为array;如果抽象方法有多个参数,则该属性取值为@Param("xx")注解中使用的名称。

  • item:遍历过程中,集合中的元素的名称,在<foreach>子级位置,可以使用#{item值}表示被遍历到的元素的值。

  • separator:分隔符。

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

练习1:根据若干个id查询用户列表,例如查询id=26和id=29和id=30的用户的数据:

List<User> findByIds(Integer[] ids);

<select id="findByIds"
	resultType="cn.tedu.mybatis.User">
	SELECT
		id, username,
		password, age,
		phone, email,
		is_delete AS isDelete
	FROM
		t_user
	WHERE
		id IN
		<foreach collection="array"
			item="id" separator=","
			open="(" close=")">
			#{id}
		</foreach>
</select>

练习2:将多个用户的密码改成某个值,例如将id=25、id=27、id=28的用户的密码都改成123456,该功能中,id值是由用户指定的,新密码也是由用户指定的:

Integer updatePasswordByIds(
	@Param("ids") Integer[] ids, 
	@Param("password") String password);

<update id="updatePasswordByIds">
	UPDATE 
		t_user
	SET
		password=#{password}
	WHERE
		id IN
		<foreach collection="ids"
			item="id" separator=","
			open="(" close=")">
			#{id}
		</foreach>
</update>

5. #{}与${}占位符

在MyBatis中,配置SQL语句时,可以使用#{}${}这2种占位符。

使用#{}占位符,可以用于占位某些值,也就是在SQL中写值的位置,都可以使用这种占位符(此前在学习JDBC时使用?的位置);而${}可以表示SQL语句的任何部分!

在使用#{}对某个值进行占位时,框架对整个SQL语句是有预编译处理的,无需考虑该值的数据类型的问题;而使用${}占位时,框架的处理方式其实就是非常单纯的字符串拼接,需要考虑数据类型的问题,如果占位的值中包括字符串类型的值,则必须使用''框住值!

由于#{}只能对某个值进行占位,SQL语句本身是相对固定的,所以,这种做法实现的功能的局限性就非常明显,由于是预编译的,没有SQL注入风险,且工作效率较高!而${}可以随意占位,功能可以非常灵活,但是,不是预编译的,有SQL注入风险,工作效率较低。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员西柚柚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值