MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
持久层:
负责将数据到保存到数据库的那一层代码。
以后开发我们会将操作数据库的Java代码作为持久层。而Mybatis就是对jdbc代码进行了封装。
JavaEE三层架构:表现层、业务层、持久层
框架:
框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
举例给大家简单的解释一下什么是半成品软件。大家小时候应该在公园见过给石膏娃娃涂鸦
如下图所示有一个石膏娃娃,这个就是一个半成品。你可以在这个半成品的基础上进行不同颜色的涂鸦,但这个娃娃的骨骼构架和性别已经设定好了是不能改变的

了解了什么是Mybatis后,接下来说说以前 JDBC代码 的缺点以及Mybatis又是如何解决的。
下面是 JDBC 代码,我们通过该代码分析都存在什么缺点:

硬编码
注册驱动、获取连接
上图标1的代码有很多字符串,而这些是连接数据库的四个基本信息,以后如果要将Mysql数据库换成其他的关系型数据库的话,这四个地方都需要修改,如果放在此处就意味着要修改我们的源代码。
SQL语句
上图标2的代码。如果表结构发生变化,SQL语句就要进行更改。这也不方便后期的维护。
操作繁琐
手动设置参数
手动封装结果集
上图标4的代码是对查询到的数据进行封装,而这部分代码是没有什么技术含量,而且特别耗费时间的。
如图所示

Mybatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作
需求:查询user表中所有的数据
分析:
创建user表,添加数据
CREATE DATABASE mybatis;
USE mybatis;
DROP TABLE IF EXISTS tb_user;
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20),
`password` VARCHAR(20),
gender CHAR(1),
addr VARCHAR(30)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO tb_user VALUES (1,'zhangsan','123','男','北京');
INSERT INTO tb_user VALUES (2,'李四','234','女','天津');
INSERT INTO tb_user VALUES (3,'王五','121','男','西安');
创建模块,导入坐标
在创建好的模块中的 pom.xml 配置文件中添加依赖的坐标
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.46version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.20version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>1.2.3version>
dependency>
dependencies>
注意:需要在项目的 resources 目录下创建logback的配置文件
编写 MyBatis 核心配置文件 – > 替换连接信息 解决硬编码问题
在模块下的 resources 目录下创建mybatis的配置文件mybatis-config.xml,内容如下:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.itheima.pojo"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="UserMapper.xml"/>
mappers>
configuration>
编写 SQL 映射文件 --> 统一管理sql语句,解决硬编码问题
在模块的 resources目录下创建映射配置文件 UserMapper.xml,内容如下:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<select id="selectAll" resultType="com.itheima.pojo.User">
select * from tb_user;
select>
mapper>
编码
在 com.itheima.pojo 包下创建 User类
public class User {
private int id;
private String username;
private String password;
private String gender;
private String addr;
//省略了 setter 和 getter
}
在 com.itheima 包下编写 MybatisDemo 测试类
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 执行selectList方法后mybatis内部会做以前jdbc中的:1.执行SQL语句2.封装结果集对象
List<User> users = sqlSession.selectList("test.selectAll");
System.out.println(users);
//4. 释放资源
sqlSession.close();
}
}
第12行参数是一个字符串,该字符串必须是映射配置文件的namespace.id->针对该案例就是test.selectAll,这样做的目的是:
解决SQL映射文件的警告提示:
在入门案例映射配置文件中存在报红的情况。问题如下:

IDEA中配置MySQL数据库连接
Database ,在展开的界面点击 + 选择 Data Source ,再选择 MySQL


而此界面就和 navicat 工具一样可以进行数据库的操作。也可以编写SQL语句

顺便提一下,解决IDEA连接Mysql数据库之后,在Mapper.xml中编写sql语句不会自动提示表信息的解决办法:


之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:
List<User> users = sqlSession.selectList("test.selectAll");
这里调用 selectList() 方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下)则不存在硬编码问题。
//getMapper获取UserMapper的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
通过上面的描述可以看出 Mapper 代理方式的目的:
使用Mapper代理方式,必须满足以下要求:
1.定义与SQL映射文件同名的Mapper接口,并且使Mapper接口和SQL映射文件编译后在同一目录下,首先我们在itheima包下创建mapper.UserMapper接口,至于怎么使Mapper接口和SQL映射文件编译后在同一目录下,有两种办法:
法一:

<build>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>truefiltering>
resource>
resources>
build>

法二:
法一可行,但不建议用,因为真实开发中maven项目的资源文件和java代码不应该放在同一目录,否则不便于后期管理维护


因为这里改了UserMapper.xml的文件位置,所以需要在mybatis-config.xml中重新指定加载的sql映射文件的位置:
<mappers>
<mapper resource="com.itheima.mapper.UserMapper.xml"/>
mappers>
2.设置SQL映射文件的namespace属性为Mapper接口全限定名
<mapper namespace="com.itheima.mapper.UserMapper">
3.在 Mapper 接口中定义方法,方法名就是QL映射文件中sql语句的id,并保持参数类型和返回值类型一致(如果返回的是一个集合,就要用List集合,如List)

在 com.itheima.mapper 包下创建 UserMapper接口,代码如下:
public interface UserMapper {
List<User> selectAll();
}
在 resources 下创建 com/itheima/mapper 目录,并在该目录下创建 UserMapper.xml 映射配置文件
<mapper namespace="com.itheima.mapper.UserMapper">
<select id="selectAll" resultType="com.itheima.pojo.User">
select *
from tb_user;
select>
mapper>
在 com.itheima 包下创建 MybatisDemo2 测试类,代码如下:
public class MyBatisDemo2 {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3 获取UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
System.out.println(users);
//4. 释放资源
sqlSession.close();
}
}
注意:
如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。也就是将核心配置文件的加载映射配置文件的配置修改为
<mappers>
<package name="com.itheima.mapper"/>
mappers>
在核心配置文件的 environments 标签中其实是可以配置多个 environment ,使用 id 给每段环境起名,在 environments 中使用 default='环境id' 来指定使用哪儿段配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
dataSource>
environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
dataSource>
environment>
environments>=
在映射配置文件中的 resultType 属性需要配置数据封装的类型(类的全限定名)。而每次这样写是特别麻烦的,Mybatis 提供了 类型别名(typeAliases) 可以简化这部分的书写。
首先需要现在核心配置文件中配置类型别名,也就意味着给pojo包下所有的类起了别名(别名就是类名),不区分大小写。内容如下:
<typeAliases>
<package name="com.itheima.pojo"/>
typeAliases>
通过上述的配置,我们就可以简化映射配置文件中 resultType 属性值的编写
<mapper namespace="com.itheima.mapper.UserMapper">
<select id="selectAll" resultType="user">
select * from tb_user;
select>
mapper>
配置各个标签时需要遵循前后顺序,常用的几个标签的顺序:
外部文件
日志
别名
环境
注册sql映射文件
比如数据库中字段是brand_name而实体类中属性是brandName,就会造成这个数据封装不成功,解决办法有两种:
1.起别名:
<select id="selectAll" resultType="brand">
select brand_name as brandName from user;
select>
这里我是省略了很多字段,实际开发中可能有很多字段需要查询,而且如果其他功能也需要查询这些子段,用这种方式就显得代码不够精炼,Mybatis提供了sql片段可以提高sql的复用性:
<sql id="brand_column">username as userNamesql>
<select id="selectAll" resultType="brand">
select <include refid="brand_column" /> from user;
select>
2.使用ResultMap:
<resultMap id="brandResultMap" type="brand">
<result column="brand_name" property="brandName"/>
resultMap>
在上面只需要定义 字段名 和 属性名 不一样的映射,而一样的则不需要专门定义出来
然后SQL语句即可正常编写(注意将resultType属性改为resultMap属性)
<select id="selectAll" resultMap="brandResultMap">
select brand_name from tb_brand;
select>
<select id="selectById" paramType="int" resultMap="brandResultMap">
select *
from tb_brand where id = #{id};
select>
1.#{id}和${id}:
2.paramType可以省略不写
3.sql语句中特殊字符:
<select id="selectById" paramType="int" resultMap="brandResultMap">
select *
from tb_brand where id < #{id};
select>
上述代码的<会报错,因为<在xml中是特殊字符,解决办法有两种:
将特殊字符转义
<select id="selectById" paramType="int" resultMap="brandResultMap">
select *
from tb_brand where id < #{id};
select>
使用
<select id="selectById" paramType="int" resultMap="brandResultMap">
select *
from tb_brand where id
#{id};
select>
比如说前端有2个输入框进行多条件查询:status,bandName,其一我们要考虑怎么才能在后端进行多条件查询,其二要考虑:用户可能不会把品牌名输完整,所以bandName在后台实现时应该是模糊查询
1.怎么才能实现模糊查询呢:
将从前端接收到的参数brandName进行拼接字符串:
companyName = "%" + companyName + "%";
编写sql语句:
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where status = #{status}
and brand_name like #{brandName}
</select>
2.SQL语句设置有多个参数有几种方式:
散装参数:需要使用@Param(“SQL中的参数占位符名称”)
List<Brand> selectByCondition(@Param("status") int status, @Param("brandName") String brandName);
实体类封装参数:需要SQL中的参数占位符名称和实体类属性名对应上
List<Brand> selectByCondition(Brand brand);
Brand brand = new Brand();
brand.setStatus(status);
brand.setBrandName(brandName);
List<Brand> brands = brandMapper.selectByCondition(brand);
map集合:需要SQL中的参数占位符名称和map集合的键的名称对应上
List<Brand> selectByCondition(Map map);
Map map = new HashMap();
map.put("status" , status);
map.put("brandName" , brandName);
List<Brand> brands = brandMapper.selectByCondition(map);
如果前端的2个输入框用户只输入了一个,那么按照2.5.3的处理方式结果一个也查询不到,这显然是不符合业务需求的,接下来我们用动态SQL一步一步进行改进:
给sql语句加上if标签;
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where
<if test="status != null">
status = #{status}
if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
if>
select>
注意:brandName是前端传来的字符串,除了判断不为null,还需要判断不为空字符串
改进后的动态SQL语句,如果用户只输入了status那么拼接后的SQL就是:
select * from tb_brand where status = ?
这样的话如果用户只输入了status代码仍能正常执行,但是有个问题:如果用户只输入了brandName那么拼接后的SQL就变为:
select * from tb_brand where and brand_name like ?
where后面多了一个and,这条sql语句显然不能正常执行,针对这种情况有两种解决办法:
给sql加上恒等式:
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where 1=1
<if test="status != null">
and status = #{status}
</if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
</if>
</select>
这样的话即使用户只输入了brandName那么拼接后的SQL就变为:
select * from tb_brand where 1=1 and brand_name like ?
可以知道sql语句仍能正常执行
将sql的where关键字改为where标签:
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
<where>
<if test="status != null">
and status = #{status}
if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
if>
where>
select>
如果用户只输入了brandName那么拼接后的SQL就变为:
select * from tb_brand where brand_name like ?
可以知道sql语句仍能正常执行

如上图所示,在查询时只能选择 品牌名称、当前状态、企业名称 这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句。
这种需求需要使用到 choose(when,otherwise)标签 实现, 而 choose 标签类似于Java 中的switch语句。
1.先在BrandMapper接口中编写抽象方法:
List<Brand> selectByConditionSingle(Brand brand);
2.在 BrandMapper.xml映射配置文件中编写 statement,使用 resultMap 而不是使用 resultType
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
where
<choose>
<when test="status != null">
status = #{status}
when>
<when test="companyName != null and companyName != '' ">
company_name like #{companyName}
when>
<when test="brandName != null and brandName != ''">
brand_name like #{brandName}
when>
<otherwise>
1=1
otherwise>
choose>
select>
这样的话如果用户查询的是brandName,那么sql就会拼接为:
select * from tb_brand where brand_name like ?
因为有otherwise的存在,所以即使用户什么也不输入该sql语句仍能正常执行:
select * from tb_brand where 1=1
还有一种办法:使用Mybatis提供的where标签(将where关键字改为where标签):
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
<where>
<choose>
<when test="status != null">
status = #{status}
when>
<when test="companyName != null and companyName != '' ">
company_name like #{companyName}
when>
<when test="brandName != null and brandName != ''">
brand_name like #{brandName}
when>
choose>
where>
select>
如果用户什么也不输入该sql语句则会拼接为:
select * from tb_brand
Mybatis会为我们自动将"autocommit(自动提交事务)"设为false(也就是使用Mybatis时默认是关闭事务的),解决办法有两种:
SqlSession.commit();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
数据添加成功后,有时候需要获取插入数据库数据的主键(主键是自增长)
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into......
insert>
- useGeneratedKeys:是够获取自动增长的主键值。true表示获取
- keyProperty :指定将获取到的主键值封装到实体类的哪个属性里
<update id="update">
update tb_brand
<set>
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
if>
<if test="status != null">
status = #{status}
if>
set>
where id = #{id};
update>
如果仍用Set关键字编写sql语句,有两个错误:
如果用户修改的值只有brandName那么拼接后的sql语句就会在where关键字之前多一个逗号:
update tb_brand set brand_name = ?, where id = ?;
如果用户一个值也不修改而是直接提交数据那么拼接后后的sql就会报错:
update tb_brand set where id = ?;
显然这也是错误的
使用set标签可以规避这两个错误
这里说的是批量删除:
1.先在BrandMapper接口中添加抽象方法:
void deleteByIds(int[] ids);
2.在BrandMapper.xml中编写sql映射:
<delete id="deleteByIds">
delete from tb_brand where id
in (
<foreach collection="array" item="id" separator=",">
#{id}
foreach>
);
delete>
foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符根据上面的描述我们可以做一下两点改变(这里只是说一下,实际开发):
void deleteByIds(@Param("ids") int[] ids);
<delete id="deleteByIds">
delete from tb_brand where id
in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
foreach>
;
delete>
Mybatis 接口方法中可以接收各种各样的参数,如下:
接收多个参数需要使用 @Param 注解,那么为什么要加该注解呢(Mybatis提供了ParamNameResolver类来进行参数封装,具体原理可以去这个类的源代码,单个参数的默认key名称原理也要看源码,我目前还没看懂):
如果参数是多个,那么Mybatis就会自动将参数封装为map集合,且给集合的key名称设为默认值:
以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:
map.put(“arg0”,参数值1);
map.put(“arg1”,参数值2);
以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:
map.put(“param1”,参数值1);
map.put(“param2”,参数值2);
所以说如果传入的是两个参数那么map集合中就有四对键值对.
当使用@Param注解后,Mybatis 会将 arg开头的键名替换为对应注解的属性值(为了提高代码可读性,开发中还是写注解比较好)
注解完成简单功能,配置文件完成复杂功能
@Select(select * from tb_user where id = #{id})
User selectById(int id)
查询:@Select
添加:@Insert
修改:@Update
删除:@Delete
在日后写Servlet的时候(可以等到看完Servlet再回来看这个),因为需要使用Mybatis来完成数据库的操作,所以对于Mybatis的基础操作就出现了些重复代码,如下
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(inputStream);
有了这些重复代码就会造成一些问题:
那如何来优化呢?
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
//静态代码块会随着类的加载而自动执行,且只执行一次
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
工具类抽取以后,以后在对Mybatis的SqlSession进行操作的时候,就可以直接使用
SqlSessionFactory sqlSessionFactory =SqlSessionFactoryUtils.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
从工具类中获取SqlSession的这行代码也是重复的,为什么不能抽取到工具类中呢: