1. Mybatis概述
1.1 Mybatis概念
MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github
中文官网:http://www.mybatis.cn/
持久层:
-
负责将数据到保存到数据库的那一层代码。
开发会将操作数据库的Java代码作为持久层,而Mybatis就是对jdbc代码进行了封装。
-
JavaEE三层架构:表现层、业务层、持久层
框架:
- 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
- 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展
1.2 JDBC 缺点
分析 JDBC 代码缺点:
-
硬编码
-
注册驱动、获取连接
①的代码有很多字符串,是连接数据库的四个基本信息,如果要将Mysql数据库换成其他的关系型数据库的话,都需要修改,意味着要修改我们的源代码。
-
SQL语句
②的代码,如果表结构发生变化,SQL语句要进行更改,不方便后期的维护。
-
-
操作繁琐
-
手动设置参数
-
手动封装结果集
④的代码是对查询到的数据进行封装,而这部分代码是没有技术含量,而且特别耗费时间。
-
1.3 Mybatis 优化
- 硬编码可以配置到配置文件
- 操作繁琐的地方mybatis都自动完成
2. Mybatis快速入门
需求:查询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) ); INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京'); INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津'); INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');
-
创建模块,导入坐标
在 pom.xml 配置文件中添加依赖的坐标
<dependencies> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!--junit 单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <!-- 添加slf4j日志api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.20</version> </dependency> <!-- 添加logback-classic依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- 添加logback-core依赖 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> </dependencies>
注意:需要在项目的 resources 目录下创建logback的配置文件
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%level] %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
</encoder>
</appender>
<logger name="com.start" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
</logger>
<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</configuration>
-
编写 MyBatis 核心配置文件 → \rightarrow → 替换连接信息 解决硬编码问题
在模块下的 resources 目录下创建mybatis的配置文件
mybatis-config.xml
,内容如下:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.start.pojo"/> </typeAliases> <!-- environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment --> <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> <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="root"/> </dataSource> </environment> </environments> <mappers> <!--加载sql映射文件--> <mapper resource="com/start/mapper/UserMapper.xml"/> </mappers> </configuration>
-
编写 SQL 映射文件 → \rightarrow → 统一管理sql语句,解决硬编码问题
在模块的
resources
目录下创建映射配置文件UserMapper.xml
,内容如下:<?xml version="1.0" encoding="UTF-8" ?> <!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.start.pojo.User"> select * from tb_user; </select> </mapper>
-
编码
-
在
com.start.pojo
包下创建 User类public class User { private int id; private String username; private String password; private String gender; private String addr; //省略setter 和 getter }
-
在
com.start
包下编写 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. 执行sql List<User> users = sqlSession.selectList("test.selectAll"); //参数是一个字符串,该字符串必须是映射配置文件的namespace.id System.out.println(users); //4. 释放资源 sqlSession.close(); } }
-
3. Mapper代理开发
3.1 Mapper代理开发概述
上述写的代码是基本使用方式,也存在硬编码的问题,如下:
调用 selectList()
方法传递的参数是映射配置文件中的 namespace.id值。不便于后期的维护。使用 Mapper 代理方式(如下图)则不存在硬编码问题。
Mapper 代理方式的目的:
- 解决原生方式中的硬编码
- 简化后期执行SQL
3.2 使用Mapper代理要求
使用Mapper代理方式,必须满足以下要求:
-
定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。如下图:
-
设置SQL映射文件的namespace属性为Mapper接口全限定名
- 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
3.3 案例
-
在
com.start.mapper
包下创建 UserMapper接口,代码如下:public interface UserMapper { List<User> selectAll(); User selectById(int id); }
-
在
resources
下创建com/start/mapper
目录,并在该目录下创建 UserMapper.xml 映射配置文件<!-- namespace:名称空间。必须是对应接口的全限定名 --> <mapper namespace="com.start.mapper.UserMapper"> <select id="selectAll" resultType="com.start.pojo.User"> select * from tb_user; </select> </mapper>
-
在
com.start
包下创建 MybatisDemo2 测试类,代码如下:/** * Mybatis 代理开发 */ 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. 执行sql //3.1 获取UserMapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.selectAll(); System.out.println(users); //4. 释放资源 sqlSession.close(); } }
注意:
如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。也就是将核心配置文件的加载映射配置文件的配置修改为
<mappers>
<!--加载sql映射文件-->
<!-- <mapper resource="com/start/mapper/UserMapper.xml"/>-->
<!--Mapper代理方式-->
<package name="com.start.mapper"/>
</mappers>
4. 核心配置文件
4.1 多环境配置
在核心配置文件的 environments
标签中其实是可以配置多个 environment
,使用 id
给每段环境起名,在 environments
中使用 default='环境id'
来指定使用哪儿段配置。我们一般就配置一个 environment
即可。
<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>
<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="root"/>
</dataSource>
</environment>
</environments>
4.2 类型别名
在映射配置文件中的 resultType
属性需要配置数据封装的类型(类的全限定名),每次这样写是特别麻烦。Mybatis 提供了 类型别名
(typeAliases) 可以简化这部分的书写。
首先需要现在核心配置文件中配置类型别名,意味着给pojo包下所有的类起了别名(别名就是类名),不区分大小写。
<typeAliases>
<!--name属性的值是实体类所在包-->
<package name="com.start.pojo"/>
</typeAliases>
通过上述的配置,可以简化映射配置文件中 resultType
属性值的编写
<mapper namespace="com.start.mapper.UserMapper">
<select id="selectAll" resultType="user">
select * from tb_user;
</select>
</mapper>
5. Mybatis使用中的问题
5.1 数据封装不成功
在实体类中属性名是 brandName
和 companyName
,而表中的字段名为 brand_name
和 company_name
- ① 保持两部分的名称一致
<select id="selectAll" resultType="brand">
select
id, brand_name as brandName, company_name as companyName, ordered, description, status
from tb_brand;
</select>
上面的SQL语句中的字段列表书写麻烦,如果表中还有更多的字段,同时其他的功能也需要查询这些字段时就显得我们的代码不够精炼。Mybatis提供了sql
片段可以提高sql的复用性。
- ② SQL片段
- 将需要复用的SQL片段抽取到
sql
标签中,id属性值是唯一标识,引用时也是通过该值进行引用。
<sql id="brand_column"> id, brand_name as brandName, company_name as companyName, ordered, description, status </sql>
- 在原sql语句中进行引用,使用
include
标签引用上述的 SQL 片段,而refid
指定上述 SQL 片段的id值。
<select id="selectAll" resultType="brand"> select <include refid="brand_column" /> from tb_brand; </select>
- 将需要复用的SQL片段抽取到
- ③ resultMap(推荐)
- 在映射配置文件中使用resultMap定义 字段 和 属性 的映射关系
<resultMap id="brandResultMap" type="brand"> <!-- id:完成主键字段的映射 column:表的列名 property:实体类的属性名 result:完成一般字段的映射 column:表的列名 property:实体类的属性名 --> <result column="brand_name" property="brandName"/> <result column="company_name" property="companyName"/> </resultMap>
注意:在上面只需要定义 字段名 和 属性名 不一样的映射,而一样的则不需要专门定义出来。
- SQL语句正常编写
<select id="selectAll" resultMap="brandResultMap"> select * from tb_brand; </select>
小结:
实体类属性名 和 数据库表列名 不一致,不能自动封装数据
- 起别名:在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
- 可以定义 <sql>片段,提升复用性
- resultMap(推荐使用):定义<resultMap> 完成不一致的属性名和列名的映射
5.2 参数占位符
mybatis提供了两种参数占位符:
-
#{}(推荐使用) :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。使用#{} 底层使用的是
PreparedStatement
-
${} :拼接SQL。底层使用的是
Statement
,会存在SQL注入问题。将映射配置文件中的 #{} 替换成 ${}
5.3 SQL语句中特殊字段处理
转义字符
<
就是<
的转义字符- <![CDATA[内容]]>
5.4 多条件查询
条件字段 企业名称
和 品牌名称
需要进行模糊查
- 条件表达式
- 如何连接
该功能有三个参数,需要考虑定义接口时,参数应该如何定义。
Mybatis针对多参数有多种实现:
-
使用
@Param("参数名称")
标记每一个参数,在映射配置文件中就需要使用#{参数名称}
进行占位List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName,@Param("brandName") String brandName);
-
将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和实体类属性名保持一致。List<Brand> selectByCondition(Brand brand);
-
将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用
#{内容}
时,里面的内容必须和map集合中键的名称一致。List<Brand> selectByCondition(Map map);
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName}
</select>
5.5 动态SQL
Mybatis对动态SQL有很强大的支撑:
-
if
-
choose (when, otherwise)
-
trim (where, set)
-
foreach
5.5.1 if 标签和 where 标签
if 标签:条件判断
- test 属性:逻辑表达式
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where
<if test="status != null">
and status = #{status}
</if>
<if test="companyName != null and companyName != '' ">
and company_name like #{companyName}
</if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
</if>
</select>
5.5.2 where 标签
- 作用:
- 替换where关键字
- 会动态的去掉第一个条件前的 and
- 如果所有的参数没有值则不加where关键字
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
<where>
<if test="status != null">
and status = #{status}
</if>
<if test="companyName != null and companyName != '' ">
and company_name like #{companyName}
</if>
<if test="brandName != null and brandName != '' ">
and brand_name like #{brandName}
</if>
</where>
</select>
注意:需要给每个条件前都加上 and 关键字。
5.5.3 choose(when,otherwise)
在查询时只能选择 品牌名称
、当前状态
、企业名称
这三个条件中的一个,但是用户到底选择哪儿一个,并不能确定。属于单个条件的动态SQL语句。
需要使用到 choose(when,otherwise)标签
, 而 choose
标签类似于Java 中的switch语句
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
<where>
<choose><!--相当于switch-->
<when test="status != null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName != null and companyName != '' "><!--相当于case-->
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != ''"><!--相当于case-->
brand_name like #{brandName}
</when>
</choose>
</where>
</select>
5.5.4 set
set 标签可以用于动态包含需要更新的列,忽略其它不更新的列。
<update id="update">
update tb_brand
<set>
<if test="brandName != null and brandName != ''">
brand_name = #{brandName},
</if>
<if test="companyName != null and companyName != ''">
company_name = #{companyName},
</if>
<if test="ordered != null">
ordered = #{ordered},
</if>
<if test="description != null and description != ''">
description = #{description},
</if>
<if test="status != null">
status = #{status}
</if>
</set>
where id = #{id};
</update>
5.5.5 foreach
删除多行数据:参数是一个数组,数组中存储的是多条数据的id
编写SQL时需要遍历数组来拼接SQL语句
foreach 标签
用来迭代任何可迭代的对象(如数组,集合)。
- collection 属性:
- mybatis会将数组参数,封装为一个Map集合。
- 默认:array = 数组
- 使用@Param注解改变map集合的默认key的名称
- mybatis会将数组参数,封装为一个Map集合。
- item 属性:本次迭代获取到的元素。
- separator 属性:集合项迭代之间的分隔符。
foreach
标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。 - open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
- close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
<delete id="deleteByIds">
delete from tb_brand where id
in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
;
</delete>
假如数组中的id数据是{1,2,3},那么拼接后的sql语句就是:
delete from tb_brand where id in (1,2,3);
5.6 添加-主键返回
在数据添加成功后,有时候需要获取插入数据库数据的主键(主键是自增长)
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>
insert 标签上添加如下属性:
- useGeneratedKeys:是够获取自动增长的主键值。true表示获取
- keyProperty :指定将获取到的主键值封装到哪儿个属性里
5.7 单个和多个参数
接收多个参数需要使用 @Param
注解,码的可读性更高
-
POJO 类型
直接使用。要求
属性名
和参数占位符名称
一致 -
Map 集合类型
直接使用。要求
map集合的键名
和参数占位符名称
一致 -
Collection 集合类型
Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,collection集合);
map.put(“collection”,collection集合;
可以使用
@Param
注解替换map集合中默认的 arg 键名。 -
List 集合类型
Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,list集合);
map.put(“collection”,list集合);
map.put(“list”,list集合);
可以使用
@Param
注解替换map集合中默认的 arg 键名。 -
Array 类型
Mybatis 会将集合封装到 map 集合中,如下:
map.put(“arg0”,数组);
map.put(“array”,数组);
可以使用
@Param
注解替换map集合中默认的 arg 键名。 -
其他类型
比如int类型,
参数占位符名称
叫什么都可以。尽量做到见名知意
5.8 注解实现CRUD
Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:
- 查询 :@Select
- 添加 :@Insert
- 修改 :@Update
- 删除 :@Delete
@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);
注意:
- 注解是用来替换映射配置文件方式配置的,使用了注解,就不需要再映射配置文件中书写对应的
statement
注解完成简单功能,配置文件完成复杂功能