一.Mybatis概述
MyBatis 是一款优秀的**持久层框架**,它支持定制化 SQL、存储过程以及高级映射。**MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集**。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 JavaBean 为数据库中的记录。
1.1 Mybatis执行流程
三、快速开始
需求: 查询用户表中 id 为 1 的数据
3.1_环境准备
1、添加相关依赖包
<!-- mybatis 包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
<scope>runtime</scope> <!-- 运行和测试阶段才使用 -->
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope><!-- 测试阶段才用 -->
</dependency>
<!-- Lombok 包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope><!-- 编译阶段和测试阶段才使用 -->
</dependency>
2、创建一张用户表user
CREATE TABLE `user` (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int DEFAULT NULL,
`salary` decimal(10,0) DEFAULT NULL
);
INSERT INTO user(name,age,salary) VALUES('willie',18,800);
INSERT INTO user(name,age,salary) VALUES('小狼',17,8000);
3、创建实体类User
3.2_配置文件书写
mybatis 框架需要用到一个核心配置文件来配置项目的基本信息以及一种 Mapper 文件来书写 SQL 语句相关内容。
3.2.1_主配置文件 mybatis-config.xml
-
在 resources下创建 mybatis-config.xml 配置文件
-
拷贝 xml 的约束
-
添加环境配置(事务管理器 / 连接池 / 映射文件)
3.2.2_mapper 映射文件: UserMapper.xml
mybatis 中,访问数据库的 SQL语句是编写在 mapper配置文件中的,程序员按照这个文件约定的格式进行配置即可
-
在目录 cn/wolfcode/mybatisdemo/mapper 中创建配置文件: UserMapper.xml
-
拷贝约束信息
-
添加 SQL 语句
<!--
一个项目可以操作多张表
每张表都需要一个mapper配置文件来编写SQL语句
每条SQL语句都需要有一个唯一的标识
这个唯一的标识由 namespace + sqlid 组成
使用下面的 namespace+sqlid 就得到了查询用户信息的唯一标识: cn.wolfcode.mybatis.mapper.UserMapper.get
接下来,我们就可以使用上面的标识找到这条SQL语句了
-->
<mapper namespace="cn.wolfcode.mybatis.mapper.UserMapper">
<!--
id:该mepper 文件中SQL的唯一标识
resultType:把一行数据封装成什么类型的对象
#{}:OGNL表达式
如果参数是八大基本数据类型,直接获取数据
如果参数是JavaBean,调用属性的getter方法来获取数据
如果参数是Map,调用Map 的get方法通过key 来获取数据
-->
<select id="get" resultType="cn.wolfcode.mybatis.domain.User">
SELECT * FROM user WHERE id=#{id}
</insert>
</mapper>
3.2.3_核心配置文件关联映射文件
<mappers>
<!-- 这里是mapper文件的路径,所以使用/分割-->
<mapper resource="cn/wolfcode/mybatis/mapper/UserMapper.xml"/>
</mappers>
3.3_启用MyBatis框架功能
3.3.1_DAO 接口开发
在 cn.wolfcode.mybatis.dao 包中创建 dao 接口: IUserDAO.java
public interface IUserDAO {
/**
* 根据用户 id 查询用户信息
* @param id 要查询的用户 id
* @result User 查询到的用户信息
*/
User get(Long id);
}
3.3.2_DAO 实现
在 cn.wolfcode.mybatis.dao.impl 包中创建 dao 的实现类: UserDAOImpl.java
public class UserDAOImpl implements IUserDAO {
public User get(Long id) {
try {
// 获取 classpath 下的 xml配置文件转化为字节流
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 解析xml文件,获取框架未知信息并封装成对象然后构建出工厂对象 --> 等价之前的 DataSource
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 获取和数据库交流的会话对象 --> 等价之前的 Connection
SqlSession session = factory.openSession();
// cn.wolfcode.mybatis.mapper.UserMapper.get <==> UserMapper.xml中的 namespace + sql id
User user = session.selectOne("cn.wolfcode.mybatis.mapper.UserMapper.get", id);
session.commit();
session.close();
return user;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
3.4_测试
public class UserDAOTest {
private IUserDAO dao = new UserDAOImpl();
3.5_其他 API
session.insert(String statement,Object param); // 添加
session.update(String statement,Object param); // 修改
session.delete(String statement,Object param); // 删除
session.selectOne(String statement,Object param); // 查询一个
session.selectList(String statement); // 查询多个
session.selectList(String statement,Object param); // 查询多个带条件
3.6_流程步骤
3.6.1_MyBatis 执行流程(理解)
-
加载主配置文件 (mybatis-config.xml 和 XxxMapper.xml) 到内存中,将数据封装成对象 Configuration/Environment/TransactionManager/DataSource/Statement Collection等
-
通过操作拿到访问数据库的基本信息,根据这些数据创建 SqlSessionFactory 对象
-
从 SqlSessionFactory 对象中获取到 SqlSession 对象
-
调用方法执行 SQL 语句
-
获取语句 SELECT * FROM user WHERE id=#{id}
-
获取 id 值并翻译语句为:SELECT * FROM user WHERE id=?
-
使用 PreparedStatement 来执行指定的 SQL
-
-
处理结果数据
-
获取 resultType 中的类的全限定名
-
通过反射和内省技术创建出对象并封装数据到对象中返回
-
3.6.2_操作步骤回顾
1 创建项目,导入 jar 包
2 创建表和模型
3 创建 mybatis-config.xml 存放到 resource 目录下, 配置环境
4 创建 UserMapper.xml 文件 存在 mapper 包中,配置 sql 语句
5 创建 dao 接口和实现类
6 启动 MyBatis 去执行插入操作
7 测试插入操作
四、CRUD 功能实现
4.1_Mapper 接口和原理(掌握)
4.1.1_之前代码问题
之前代码
public User get(Long id) {
try {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = factory.openSession();
User user = session.selectOne("cn.wolfcode.mybatis.mapper.UserMapper.get", id);
session.commit();
session.close();
return user;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
问题:
-
namespace.id 使用的是 String 类型(第一个参数),一旦编写错误,只有等到运行代码才能报错;
-
传入的实际参数类型不被检查,因为第二个参数类型是 Object
4.1.2_Mapper接口使用(掌握)
1、使用注意
类似之前的 DAO,在接口中定义 CRUD 等操作方法。Mapper 组件 = Mapper 接口 + Mapper XML 文件。
-
接口的命名为实体名 Mapper,一般和其对应 XML 文件放一起(只要编译之后字节码文件和 XML 文件在一起);
-
XML 命名空间用其对应接口的全限定名;
-
Mapper 接口的方法名要和 Mapper XML 文件元素(select | update | delete | insert) id 值一样;
-
方法的返回类型对应 SQL 元素中定义的 resultType / resultMap 类型;
-
方法的参数类型对应 SQL 元素中定义的 paramterType 类型(一般不写)。
2、定义 Mapper 接口
package cn.wolfcode.mapper;
public interface UserMapper {
User get(Long id);
}
3、使用 Mapper 接口
修改 UserMapperTest 代码,如下:
public void testGet() throws Exception{
SqlSession session = MyBatisUtil.getSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.get(1L);
System.out.println(user);
session.close();
}
4.1.3_Mapper 接口的原理(了解)
通过打印接口,发现打印的是:class com.sun.proxy.$Proxy5,底层使用的是动态代理(后面 Spring 再学)生成 Mapper 接口的实现类对象。
接口是规范,实质做的实现还是要由实现类对象来做,而这个实现类不需要我们写,实现类对象也不由我们创建,这些都 MyBatis 使用动态代理帮我们做了; 我们只需提供 Mapper 接口对应 Mapper XML 文件,获取实现类对象的时候传入 Mapper 接口就可以了(不然 MyBatis 也不知道你要获取哪个 Mapper 接口的实现类对象);
至于实现类中操作方式底层还是和之前的一样,因为 Mapper XML 命名空间是使用 Mapper 接口的全限定名,方法名又与对应 XML 元素 id 一致,所以可以通过获取调用方法所在 Mapper 接口的全限名和方法名,拼接出 namespace + id,再配合调用方法的实参就可以像之前一样操作了。
4.2 CRUD 功能实现
4.2.1_DML 实现
1、Mapper 组件书写
UserMapper.xml
<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
INSERT INTO user(name,age,salary) VALUES(#{name}, #{age}, #{salary})
</insert>
<update id="update">
UPDATE user
SET name=#{name}, age = #{age}, salary = #{salary}
WHERE id = #{id}
</update>
<delete id="delete">
delete from user where id = #{id}
</delete>
UserMapper.java
public interface UserMapper{
void insert(User user);
void update(User user);
void delete(Long id);
}
2、测试类书写
UserMapperTest.java
4.2.2_DQL 实现
1、Mapper 组件书写
UserMapper.xml
<select id="selectAll" resultType="cn.wolfcode.mybatis.domain.User">
SELECT * FROM user
</insert>
UserMapper.java
public interface UserMapper{
void insert(User user);
void update(User user);
void delete(Long id);
User get(Long id);
List<User> selectAll();
}
2、测试类书写
UserMapperTest.java
4.3_代码重构
5.1_MyBatisUtil 工具类抽取
和 JDBC 中的连接池使用一样,在整个项目中,我们只需要一个 SqlSessionFactory 对象,而不需要每次都创建一个新的,
所以,我们将SqlSessionFactory的创建和SqlSession对象的获取都抽取到工具类中: MyBatisUtil
注意:
加载配置文件没有填写
5.2_db.properties 工具类抽取
在主配置文件中配置的内容较多, 其中包括我们修改频率较高的数据库连接信息(driver/url/username/password)等,我们可以在修改这些信息的过程中误改或者误删到其他的配置,为了解决这个问题,我们仍然是将这些信息配置到db.propertie 配置文件中,然后再合并到主配置文件即可,如下:
db.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/javaweb
username=root
password=admin
mybatis-cofnig.xml
<configuration>
<!-- 关联db.properties:为了把连接数据库的信息单独放到一个配置文件 -->
<properties resource="db.properties" />
<typeAliases>
<package name="cn.wolfcode.mybatis.domain"/>
</typeAliases>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--
使用${key}取出db.properties中配置的信息
key和db.properties文件中的可以一直即可
-->
<property name="driver" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 关联映射文件 -->
<mappers>
<mapper resource="cn\wolfcode\pmis\mapper\ProductMapper.xml"/>
</mappers>
</configuration>
五、MyBatis 进阶
5.1_MyBatis 的参数处理
5.1.1_需求
实现一个登录需求,本质就是调用 Mapper 接口中的方法根据用户名和密码查询用户。
5.1.2_代码编写
修改 UserMapper 接口及 UserMapper.xml,里面提供对应的查询方法及 SQL。
User login(String username, String password);
<select id="login" resultType="User">
SELECT id, username, password FROM user
WHERE username = #{username} AND password = #{password}
</select>
5.1.3_问题
发现调用时会抛出异常,原因本质接口的底层还是之前的方法,就只支持一个参数的。
5.1.4_问题解决
1、封装成一个参数
修改 UserMapper 接口及 UserMapper.xml。
User login1(Map<String, Object> param);
User login2(User param);
<select id="login1" resultType="User">
SELECT id, username, password FROM user
WHERE username = #{username} AND password = #{password}
</select>
<select id="login2" resultType="User">
SELECT id, username, password FROM user
WHERE username = #{username} AND password = #{password}
</select>
2、使用 @Param 注解
修改 UserMapper 接口中的 login 方法,在形参贴注解即可。
// 本质相当于构建一个 Map 对象,key 为注解 @Param 的值,value 为参数的值。
User login(
5.1.5_集合/数组参数
当传递一个 List 对象或数组对象参数给 MyBatis 时,MyBatis 会自动把它包装到一个 Map 中,此时:List 对象会以 list 作为 key,数组对象会以 array 作为 key,也可以使用注解 @Param 设置 key 名。
5.2_MyBatis 的#和$
5.2.1_相同点
都可以获取对象(Map 对象或者 JavaBean 对象)的信息。
5.2.2_不同点
-
使用 # 传递的参数会先转换为字符串,无论传递是什么类型数据都会带一个单引号;使用 $ 传递的参数,直接把值作为 SQL 语句的一部分。
-
使用 # 支持把简单类型(八大基本数据类型及其包装类、String、BigDecimal 等等)参数作为值;使用 $ 不支持把简单类型参数作为值。
-
使用 # 好比使用 PrepareStatement,没有 SQL 注入的问题,相对比较安全;使用 $ 好比使用 Statement,可能会有 SQL 注入的问题,相对不安全。
5.2.3_代码证明
修改 UserMapper 接口及 UserMapper.xml
// #
User queryByUsername1(String username);
// $
User queryByUsername2(
<select id="queryByUsername1" resultType="User">
SELECT id, username, password FROM user WHERE username = #{username}
</select>
<select id="queryByUsername2" resultType="User">
SELECT id, username, password FROM user WHERE username = ${username}
</select>
<select id="orderByDesc1" resultType="User">
SELECT id, username, password FROM user ORDER BY #{columnName} DESC
</select>
<select id="orderByDesc2" resultType="User">
SELECT id, username, password FROM user ORDER BY ${columnName} DESC
</select>
5.2.4_使用总结
若需要作为 ORDER BY 或 GROUP BY 子句获取参数值使用 $;若需要作为其它子句获取参数值使用 #。
5.3_动态 SQL
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框 架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射语句中的强大的动态 SQL 语言得以改进这种情形。动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。
数据准备
CREATE TABLE `employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`sn` varchar(20) DEFAULT NULL,
`salary` decimal(8,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
INSERT INTO `employee` VALUES ('1', '赵一', '001', '800.00');
INSERT INTO `employee` VALUES ('2', '倩儿', '002', '900.00');
INSERT INTO `employee` VALUES ('3', '孙三', '003', '800.00');
INSERT INTO `employee` VALUES ('4', '李四', '004', '1000.00');
INSERT INTO `employee` VALUES ('5', '周五', '005', '900.00');
INSERT INTO `employee` VALUES ('6', '吴六', '006', '1200.00');
INSERT INTO `employee` VALUES ('7', '郑七', '007', '1400.00');
编写对应的实体类、Mapper 接口及 Mapper XML
package cn.wolfcode.domain;
package cn.wolfcode.mapper;
public interface EmployeeMapper {
}
在 resources 目录下的 cn/wolfcode/mapper 下新建 EmployeeMapper.xml,内容如下:
5.3.1_动态 SQL 之 if 和 where
1、查询工资大于等于某个值的员工
修改 EmployeeMapper 接口及 EmployeeMapper.xml
List<Employee> queryByMinSalary(
<select id="queryByMinSalary" resultType="Employee">
SELECT id, name, sn, salary
FROM employee
<if test="minSalary != null">
WHERE salary >= #{minSalary}
</if>
</select>
编写单元测试类
public class EmployeeMapperTest {
2、按照工资范围查询员工
修改 EmployeeMapper 接口及 EmployeeMapper.xml
List<Employee> queryByMinSalaryAndMaxSalary(
<select id="queryByMinSalaryAndMaxSalary" resultType="Employee">
SELECT id, name, sn, salary
FROM employee
<where>
<if test="minSalary != null">
AND salary >= #{minSalary}
</if>
<if test="maxSalary != null">
AND salary <= #{maxSalary}
</if>
</where>
</select>
编写单元测试方法
public class EmployeeMapperTest {
5.3.2_动态 SQL 之 set
set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,若里面条件都不成立,就会去除 SET 关键字。其用来解决更新时丢失数据的问题。
1、需求
给某个员工加工资。
2、错误代码实现
<update id="update">
UPDATE employee SET
name = #{name},
sn = #{sn},
salary = #{salary}
WHERE id = #{id}
</update>
问题:传入的对象某个属性值为 null,则会造成更新丢失数据。
<update id="update">
UPDATE employee SET
<if test="name != null">
name = #{name},
</if>
<if test="sn != null">
sn = #{sn},
</if>
<if test="salary != null">
salary = #{salary}
</if>
WHERE id = #{id}
</update>
问题:虽然可以解决更新丢失数据的问题,但会造成多逗号或者少逗号的问题(比如就仅第一个条件成立)。
3、使用 set 元素实现
<update id="update">
UPDATE employee
<set>
<if test="name != null">
name = #{name},
</if>
<if test="sn != null">
sn = #{sn},
</if>
<if test="salary != null">
salary = #{salary},
</if>
</set>
WHERE id = #{id}
</update>
4、编写单元测试方法
public class EmployeeMapperTest {
5.3.3_动态 SQL 之 foreach
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候,这里就会使用到 foreach 元素。
1、需求
批量地根据员工 id 删除员工。
2、代码实现
修改 EmployeeMapper 接口及 EmployeeMapper.xml
void batchDelete(
<delete id="batchDelete">
DELETE FROM employee WHERE id IN
<!--
collection 遍历数组或集合的 key 或者属性名
open 遍历开始拼接的字符串
index 遍历索引
item 遍历元素
separator 每遍历元素拼接字符串
close 遍历结束拼接字符串
-->
<foreach collection="ids" open="(" item="id" separator="," close=")">
#{id}
</foreach>
</delete>
3、编写单元测试方法
public class EmployeeMapperTest {
小结
- 理解框架的作用
****
- 理解 MyBatis 框架的作用,能为咱们提供哪些好处
*****
- 理解 MyBatis 框架的执行流程
*****
- 掌握 MyBatis 框架的配置文件 mybatis-config.xml 和 XxxMapper.xml
****
- 理解 Mapper.xml 直接使用原理
****
- 掌握使用 Mapper 接口的方式完成 CRUD
*****
- 掌握 MyBatisUtil 工具类抽取
****
- 掌握 db.properties 抽取和配置
****
- 了解在 Mapper XML 文件中 # 和 $ 取值的区别,掌握它们各自的应用场景。
****
- 掌握使用 @Param 注解解决 Mapper 接口中方法多参数问题。
*****
- 理解动态 SQL 的好处,掌握使用 if、where、set、foreach 及知道他们的应用场景。
*****
- 了解细节处理中的类型别名,日志管理
***
练习
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', '刘备', '001', '46');
INSERT INTO `user` VALUES ('2', '诸葛亮', '002', '27');
INSERT INTO `user` VALUES ('3', '关羽', '003', '44');
INSERT INTO `user` VALUES ('4', '张飞', '004', '42');
INSERT INTO `user` VALUES ('5', '马超', '005', '33');
INSERT INTO `user` VALUES ('6', '黄忠', '006', '60');
INSERT INTO `user` VALUES ('7', '赵云', '007', '40');
根据以上表结构和数据,完成以下功能(注意只需编写 Mapper 接口、Mapper XML、实体类和单元测试类):
-
提供根据用户名称查询用户的功能。
-
提供根据列(比如传了年龄就根据年龄排序查询)降序查询用户的功能。
-
提供根据用户名称和密码查询用户的功能。
-
提供根据年龄范围查询用户的功能。
-
提供修改用户名称、密码和年龄的功能。
-
提供根据 id 批量删除用户的功能。
-
提供批量保存用户的功能。 能。