1. 概念
Mybatis 基于java的持久层框架,它的内部封装了JDBC,让开发人员只需要关注SQL语句本身,不需要花费精力在驱动的加载、连接的创建、Statement的创建等复杂的过程。
Mybatis通过XML或注解的方式将要执行的各种的statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执行的SQL语句,最后由mybatis框架执行SQL,并将结果直接映射为java对象。
采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBCAPI底层的访问细节,避免我们与jdbc的api打交道,就能完成对数据的持久化操作。
解决的问题:
- 数据库连接的创建、释放连接的频繁操作造成资源的浪费从而影响系统的性能。
- SQL语句编写在代码中,硬编码造成代码不容易维护,实际应用中SQL语句变化的可能性比较大,一旦变动就需要改变java类。
- 使用preparedStatement的时候传递参数使用占位符,也存在硬编码,因为SQL语句变化,必须修改源码。
-
2. 入门案例
创建数据可和表
- 创建 Maven 项目,添加相关依赖
- 编写 mybatis 配置文件
- 编写实体类
- 编写 ORM 映射文件
- 映射文件注册到配置文件中
- 配置映射文件的扫描位置
- 测试
@Testpublic void test01(){SqlSession sqlSession = null;try {// 1. 读取 mybatis 配置文件Reader reader = Resources.getResourceAsReader(resource);// 2. 根据图纸创建工厂SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);// 3. 创建可以执行 sql 语句的 SqlSession 产品sqlSession = sqlSessionFactory.openSession();// 4. 执行 sql 语句List<Team> list = sqlSession.selectList("com.zmh.pojo.Team.queryAll");list.forEach(team -> System.out.println(team));} catch (IOException e) {e.printStackTrace();} finally {sqlSession.close();}}
增删改需要手动提交事务
3. Mybatis 对象分析
3.1 Resources
加载资源,读取资源文件
3.2 SqlSessionFactoryBuilder
为了创建 SqlSessionFactory ,使用 SqlSessionFactoryBuilder 的 build 方法
使用到了建造者设计模式。
建造者模式: 又称生成器模式,是一种对象的创建模式。 可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有不同的内部表象的产品(将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示). 这样用户只需指定需要建造的类型就可以得到具体产品,而不需要了解具体的建造过程和细节。
在建造者模式中,角色分指导者(Director)与建造者(Builder): 用户联系指导者, 指导者指挥建造者, 最后得到产品. 建造者模式可以强制实行一种分步骤进行的建造过程.
3.3 SqlSessionFactory
SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法。
3.4 SqlSession
SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。
SqlSession 特性:
- 会开启一个事务(默认不自动提交)
- 将从由当前环境配置的 DataSource 实例中获取 Connection 对象。事务隔离级别将会使用驱动或数据源的默认设置。
- 预处理语句不会被复用,也不会批量处理更新。
SqlSession 有超过 20 个方法
3.5 Mybatis 架构
框架图:
4. 原有的 Dao 方式开发
我们知道 SqlSession 是线程不安全的,采用原始的方式进行开发可能会产生线程冲突,所以我们引入 ThreadLocal的概念
4.1 ThreadLocal
ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
线程之间不会冲突,因为有不同的副本。
5. 使用 Mapper 接口编写 Mybatis 项目
5.1 为什么使用 Mapper 接口
使用原始的 dao 层方式开发时我们发现, dao 的实现类并没有做什么实质性的工作,仅仅是通过 SqlSession 的相关 API 定位到映射文件 mapper 中相应 id 的 SQL 语句,真正对 DB 进行操作的工作其实是由框架通过 mapper 中的 SQL 完成的。
所以,MyBatis 框架就抛开了 Dao 的实现类,直接定位到映射文件 mapper 中的相应 SQL 语句,对DB 进行操作。这种对 Dao 的实现方式称为 Mapper接口 的动态代理方式。
5.2 实现
- 编写 TeamMapper 接口
- 创建 TeamMapper.xml 文件
- 配置映射文件
- 使用 getMapper 方法动态获取代理对象
5.3 原理
6. 增删改相关细节
6.1 获取自增的 ID
在 sql 语句执行前(相当于赋随机值)
- 编写接口
- 映射文件中加入
selectKey
在 sql 语句执行后(相当于查询)
- 编写接口
- 映射文件中加入
selectKey<insert id="addGame" parameterType="com.zmh.pojo.GameRecord"><selectKey order="BEFORE" keyProperty="recordId" resultType="java.lang.String">SELECT UUID();</selectKey>INSERT INTO `mybatis01`.`gamerecord`(`recordId`, `homeTeamId`, `score`, `visitingTeamId`)VALUES (#{recordId},#{homeTeamId},#{score},#{visitingTeamId})</insert>
6.2 输入映射
不使用 parameterType 的几种方法
6.2.1 arg 或 param
两个细节:
- mybatis3.3版本之前:可以直接写#{0} #{1}
从mybatis3.4开始:#{arg0} #{arg1}… 或者是 #{param1} #{param2}… - sql语句中不能使用小于号,使用转移符号替换;大于号没有限制,也可以使用转义符号替换 >
<select id="queryByRange" resultType="com.zmh.pojo.Team">select * from team where teamId>=#{param1} and teamId <=#{param2};</select>
6.2.2 使用 @Param 注解
List<Team> queryByRange(@Param("min") Integer min, @Param("max") Integer max);
<select id="queryByRange" resultType="com.zmh.pojo.Team">select * from team where teamId>=#{min} and teamId <=#{max};</select>
6.2.3 通过 map 传递多个参数
@Testpublic void testParam02(){Map<String, Object> map = new HashMap<>();map.put("min", 1005);map.put("max", 1007);List<Team> teams = dao.queryByRange1(map);teams.forEach(team -> System.out.println(team));}
6.2.4 通过 pojo 类传递多个参数
也就相当于 resultType 省略
6.3 #{} 和 ${} 的区别
#{}
表示占位符,相当于 sql 语句中的 ?
${}
表示字符串的原样替换, 使用 Statement 或者 PreparedStatement 把 sql 语句和 ${} 的内容连接起来。一般用在替换表名,列名,不同列排序等操作。
List<Team> queryByField(@Param("column") String column, @Param("columnValue") String columnValue);
<select id="queryByField" resultType="com.zmh.pojo.Team">SELECT * FROM TEAM WHERE ${column}=#{columnValue};</select>
6.4 输出映射
6.4.1 resultType
- 简单类型
- pojo 类型
- Map 类型
- 大于一条数据会抛出异常,应该使用 List
6.4.2 resultMap
就是要自己去定义这个映射的关系,输出映射写为 resultMap ,值为其映射的 ID 值。
<select id="queryAll2" resultMap="baseResultMap">select * from team;</select><resultMap id="baseResultMap" type="com.kkb.pojo.Team"><!--一般主键列用id,其余列用resultcolumn:表示数据库表中的列名,不区分大小写property:表示实体类中的对应的属性名,区分大小写javaType:实体类中的对应的属性的类型,可以省略,mybatis会自己推断jdbcType="数据库中的类型column的类型" 一般省略--><id column="teamId" property="teamId" javaType="java.lang.Integer" ></id><result column="teamName" property="teamName" javaType="java.lang.String"></result><result column="location" property="location" javaType="java.lang.String"></result><result column="createTime" property="createTime" javaType="java.util.Date"></result></resultMap>
6.4.3 数据库表中列与实体类属性不一致的处理方式
- 在查询语句中通过别名的方式,将数据库中列名与实体类中的属性进行对应。
- 输出映射使用 ResultMap,然后自己去定义 ResultMap 去将数据库中列名与实体类中的属性进行对应。
7. Mybatis 的全局配置文件
首先在头部使用约束文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
7.1 配置的内容
configuration(配置)properties--属性:加载外部的配置文件,例如加载数据库的连接信息Settings--全局配置参数:例如日志配置typeAliases--类型别名typeHandlers----类型处理器objectFactory-----对象工厂Plugins------插件:例如分页插件Environments----环境集合属性对象environment(环境变量)transactionManager(事务管理器)dataSource(数据源)Mappers---映射器:注册映射文件用
7.2 属性
属性可以在外部进行配置,并可以进行动态替换。我们既可以在 properties 元素的子元素中设置(例如DataSource 节点中的 properties 节点),也可以在 Java 属性文件中配置这些属性。<br />
7.3 设置 settings
7.4 类型别名 typeAliases
- 默认的
自定义别名
<!--自定义类型别名--><typeAliases><!--对单个的实体类定义别名--><typeAlias type="com.kkb.pojo.Team" alias="Team"/><!--推荐写法:批量定义别名:扫描指定包下的所有类,同时别名定义为类名,别名的首字母大小写都可以--><package name="com.kkb.pojo"/></typeAliases>
7.5 映射器 Mappers
使用相对于类路径的资源引用
<mapper resource="com/zmh/mapper/GameRecordMapper.xml"/>
使用映射器接口实现类的完全限定类名
<mapper resource="com.zmh.mapper.GameRecordMapper"/>
将包内的映射器接口实现全部注册为映射器
<mapper resource="com.zmh.mapper"/>
7.6 dataSource 标签
7.7 事务
7.7.1 默认需要手动提交事务
MyBatis 支持两种事务管理器类型:JDBC 与 MANAGED。
- JDBC:使用JDBC的事务管理机制,通过Connection对象的 commit()方法提交,通过rollback()方法 回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
- MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。
7.7.2 自动提交事务
如果sqlSession = SqlSessionFactory.openSession(true);参数设置为true,再次执行增删改的时候就不需要执行session.commit()方法,事务会自动提交。
8. Mybatis 中的关系映射
8.1 对一关系的映射
8.1.1 关联对象打点调用属性方式
连接查询
一般单独定义 resultMap
extends=”表示继承的其他的 resultMap 的 id”
8.1.2 直接引用关联对象的 Mapper 映射
连接查询
引用 TeamMapper 中的 resultMap
8.1.3 直接引用关联对象单独查询的方法
方式3:直接引用关联对象的单独查询的方法:要求:关联对象的Maper中必须要求有单独的查询方法
property=”关联对象的属性名”
javaType=”关联对象的类型”
select=”关联对象的单独查询的语句”
column=”外键列”
<select id="queryById1" resultMap="joinTeamResult1">SELECT * FROM PLAYER P INNER JOIN TEAM T ON P.teamId=T.teamId WHERE PLAYERID=#{id};</select><select id="queryById2" resultMap="joinTeamResult2">SELECT * FROM PLAYER P INNER JOIN TEAM T ON P.teamId=T.teamId WHERE PLAYERID=#{id};</select><select id="queryById3" resultMap="joinTeamResult3">SELECT * FROM PLAYER WHERE PLAYERID=#{ID};</select><resultMap id="baseResultMap" type="Team"><id column="playerId" property="playerId"/><result column="playerName" property="playerName"/><result column="playerNum" property="playerNum"/><result column="teamId" property="teamId"/></resultMap><!-- 方法一 --><resultMap id="joinTeamResult1" type="Player" extends="baseResultMap"><result column="teamId" property="team1.teamId"/><result column="teamName" property="team1.teamName"/><result column="location" property="team1.location"/><result column="createTime" property="team1.createTime"/></resultMap><!-- 方法二 --><resultMap id="joinTeamResult2" type="Player" extends="baseResultMap"><association property="team2" javaType="Team" resultMap="com.zmh.mapper.TeamMapper.baseResultMapper"/></resultMap><!-- 方法三 --><resultMap id="joinTeamResult3" type="Player" extends="baseResultMap"><association property="team3" javaType="Team" select="com.zmh.mapper.TeamMapper.queryById" column="teamId"/></resultMap>
8.2 对多关系的映射
8.2.1 连接查询+引用关联对象的结果映射
<select id="queryById02" resultMap="joinResult1">SELECT * FROM TEAM T INNER JOIN PLAYER P ON P.TEAMID = T.TEAMID WHERE T.TEAMID=#{teamId};</select><resultMap id="joinResult1" type="Team" extends="baseResultMapper"><collection property="playerList" javaType="java.util.ArrayList" ofType="Player"resultMap="com.zmh.mapper.PlayerMapper.baseResultMap"/></resultMap>
8.2.2 引用关联对象的单独查询的方法
<select id="queryById03" resultMap="joinResult2">select * from team where teamId=#{id};</select><resultMap id="joinResult2" type="Team" extends="baseResultMapper"><collection property="playerList" javaType="java.util.ArrayList"select="com.zmh.mapper.PlayerMapper.queryByTeamId" column="teamId"/></resultMap>
9. 动态 SQL
9.1 where 标签在 select 中的使用
- 原有的写法
字符串的条件拼接
/*原有的多条件分析:都是通过java中的字符串拼接实现String sql="select * from team where 1 = 1 ";// 如果用户输入了名称,就模糊查询and teamName like '%?%'// 如果用户输入了日期,按照日期区间查询and createTime> ? and createTime< ?//如果输入了地区,按照地区查询and location =?";*/if(vo.getName()!=null && !"".equals(vo.getName().trim())){sql+=" and teamName like '%"+vo.getName().trim()+"%'";}if(vo.getBeginTime()!=null ){sql+=" and getEndTime>"+vo.getBeginTime();}if(vo.getBeginTime()!=null ){sql+=" and createTime<="+vo.getEndTime();}if(vo.getLocation()!=null && !"".equals(vo.getLocation().trim())){sql+=" and location ="+vo.getLocation().trim();}
- 改进后
<select id="queryByVo" parameterType="com.zmh.vo.QueryTeamVO" resultMap="baseResultMapper">select * from team<where><if test="name != null">and teamName like concat(concat('%', #{name}), '%')</if><if test="location != null">and location = #{location}</if><if test="begin != null">and createTime >= #{begin}</if><if test="end != null">and createTime <= #{end}</if></where></select>
9.2 set 标签在 update 中的使用
<!-- 动态 SQL set--><update id="updateDynamic" parameterType="Team">update team<set><if test="teamName != null">teamName = #{teamName},</if><if test="location != null">location = #{location},</if><if test="createTime != null">createTime = #{createTime},</if></set>where teamId = #{teamId}
9.3 forEach 标签
添加和删除
<insert id="addList" parameterType="arraylist">INSERT INTO TEAM (TEAMNAME, LOCATION) VALUES<foreach collection="list" item="l" separator=",">(#{l.teamName},#{l.location})</foreach></insert><delete id="deleteList">DELETE FROM TEAM WHERE TEAMID IN<foreach collection="list" item="teamId" separator="," open="(" close=")">#{teamId}</foreach></delete>
