一 、什么是Mybatis?
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。——Mybatis官网
好书分享:《深入浅出Mybatis技术原理与实战》
二、 小试牛刀
个人感觉学习一个新的框架,可以直接从最简单的demo入手,拿去先用,把一个简单的实例跑起来,有了效果,再去讨论其实现的原理、架构和方法就变得简单了。这样,可能会有不同的效果。
话不多说,让我们先小试牛刀,看一个Mybatis的实例!这个实例是基IDEA+Mysql+Maven创建的。
2-1 准备工作
在数据库mybatis中,创建表tbl_user,并且插入两条数据。
//创建表tbl_user
create table tbl_user (
id INT PRIMARY KEY AUTO_INCREMENT,,
name VARCHAR(225) NOT NULL,
sex VARCHAR(225) NOT NULL,
address VARCHAR(225)
)
//添加数据
INSERT INTO `mybatis`.`tbl_user` (`id`, `name`, `sex`, `address`) VALUES (1, '宋先生', '男', '北京市朝阳区');
INSERT INTO `mybatis`.`tbl_user` (`id`, `name`, `sex`, `address`) VALUES (2, '李女士', '女', '北京市海淀区');
2-2 创建实例
① 新建一个maven工程,在pom文件中添加以下依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>、
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
③ 新建与数据库表tbl_user相对应的类:User
package com.entity;
public class User {
private int id;
private String name;
private String sex;
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
② 添加Mybatis的配置文件mybatis.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>
<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://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
</configuration>
④ 定义操作tbl_user表的sql映射文件—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="com.Mapper.UserMapper">
<select id="getUserById" parameterType="int" resultType="User">
select * from `tbl_user` where id = #{id}
</select>
</mapper>
⑤ 定义一个接口,注意接口的方法和XML映射文件的id保持一致
package com.Mapper;
import com.entity.User;
public interface UserMapper {
public User getUserById(int id);
}
⑥ 在mybatis.xml文件中,注册userMapper.xml文件
<configuration>
<typeAliases>
<typeAlias alias="User" type="MybatisDemo.com.entity.User" />
</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://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="./mapper/userMapper.xml" />
</mappers>
</configuration>
⑦ 新建测试类UserController
package com.controller;
import com.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
public class UserController {
public static void main(String[] args) {
SqlSession sqlSession=null;
try {
Reader reader = Resources.getResourceAsReader("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println("用户的名字是:"+user.getName()+",性别:"+user.getSex()+",住址:"+user.getAddress());
} catch (IOException e) {
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
运行结果:
这样一个简单的Mybatis的实例,就运行起来了,你可能对其中的一些配置、注解感到不解,不要着急,接下来,让我们慢慢揭开Mybatis的神秘面纱。
三、 深入研究
认识往往是从表象到本质的一个过程,我们由简单的demo,带着疑问慢慢开始深入Mybatis,我们从Mybatis的基本构件、配置、映射器、插件等开始学习Mybatis,先来认识一下Mybatis的基本构件。
3-1、 基本构件及生命周期
组成构件:
SqlSessionFactoryBuilder(构造器):根据配置信息或者代码生成SqlSessionFactory。
SqlSessionFactory(工厂接口):生成SqlSession。
SqlSession: 是一个既可以发送sql执行并获取结果,也可以获取Mapper的接口。
SQL Mapper:由一个java接口和XML文件组成,需要给出相对应的Sql和映射规则。
它们之间的关系如图所示:
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。
生命周期
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder的作用就是一个构建器,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的生命周期只存在方法作用域(也就是局部方法变量)。
SqlSessionFactory
SqlSessionFactory的作用是创建SqlSession,一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。它的责任是唯一的。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。最简单的就是使用单例模式或者静态单例模式。
SqlSession
SqlSession是一个会话,它的最佳的作用域是请求或方法作用域。每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:
SqlSession session = sqlSessionFactory.openSession();
try {
// some code
} finally {
session.close();
}
在你的所有的代码中一致性地使用这种模式来保证所有数据库资源都能被正确地关闭。
映射器(Mapper)
映射器是创建用来绑定映射语句的接口,没有任何实现类。映射器接口的实例是从 SqlSession 中获得的。因此映射器实例的最大作用域是和 SqlSession 相同的,因为它们都是从 SqlSession 里被请求的。尽管如此,映射器实例的最佳作用域是方法作用域。也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可废弃。并不需要显式地关闭映射器实例。
3-2 、 配置
在MybatisDemo项目中的mybatis.xml配置文件中出现的标签有<typeAliases>
、<environments
、<mappers>
等配置项。我们详细介绍一下:
typeAliases : 别名
别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类全限定名的长度,别名分为系统定义别名和自定义别名,如下是一个自定义别名:
// 两种方式自定义别名
// ① 自定义别名
<typeAliases>
<typeAlias alias="User" type="com.entity.User" />
</typeAliases>
//② 在包中被扫描的Java Bean,可使用@Alias注解.
//在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
<typeAliases>
<package name="com.entity" />
</typeAliases>
environments : 配置环境
配置环境可以注册多个数据源,每一个数据源分为两大部分:一个是数据库源的位置,另外一个是数据库事务的配置。
//默认情况下,将启用development这个数据源配置
<environments default="development">
<environment id="development">
// 配置数据库事务 type取值:JDBC、MANAGED、自定义
<transactionManager type="JDBC" />
// 配置数据源 type取值:UPPOOLED(非连接池)、POOLED(连接池)、JNDI、自定义
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="111111" />
</dataSource>
</environment>
</environments>
- mappers:引入映射器
<mappers>
<mapper resource="./mapper/userMapper.xml" />
</mappers>
typeHandlers
MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,或者从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis为我们提供了大量的typehHandler,可参考官网。我们需要注意一下几点:
数值类型的精度,数据库int、double、decimal和java的精度、长度都不一样。、
时间精度,以日为单位:
DateOnlyTypeHandler
,以秒为单位:SqlTimestampTypehandler
除以上配置外,还有properties
、settings
、objectFactory
、plugins
、databaseIdProvider
等。
3-3 映射器
映射器是Mybatis最强大的工具。下图是映射器的主要元素:点击查看具体用法。
元素名称 | 描述 |
---|---|
select | 查询 |
insert | 插入 |
update | 更新 |
delete | 删除 |
sql | 定义部分sql,被其他语句引用 |
result | 用来描述如何从数据库结果集中来加载对象 |
cache | 缓存配置 |
cache-ref | 引用其他命名空间缓存配置 |
基本操作示例:
<select id="getEmployeesById" parameterType="String" resultType="Employees">
select * from `employees` where id = #{id}
</select>
<select id="getEmployeesList" resultType="Employees">
select * from employees
</select>
<insert id="insertEmployees" parameterType="Employees">
insert into employees (name,dept,salary,sex)values(#{name},#{dept},#{salary},#{sex})
</insert>
<update id="updateEmployees">
update employees set name=#{name},dept=#{dept},salary=#{salary},sex=#{sex} where id=#{id}
</update>
<delete id="deleteEmployees">
DELETE from employees where id =#{id}
</delete>
resultMap
resultMap 元素是 MyBatis 中最复杂的元素。它的作用是定义映射规则、级联关系、定制类型转化器等。
我们主要了解一下映射器中的resultMap的级联。数据库中存在一对一、一对多的关系。在Mybatis中级联分为以下三种:
元素名称 | 描述 |
---|---|
association | 代表一对一关系 |
collection | 代表一对多关系 |
discriminator | 鉴别器,根据特定条件关联不同的结果集 |
association:一对一关系,代码所示:一个员工只有一张员工卡
<resultMap type="Dept" id="CartResultMap">
<constructor>
<idArg column="id" javaType="String" />
</constructor>
<result property="id" column="id"/>
<result property="number" column="number"/>
//fetchType 有效值为 lazy(延迟加载)和eager(即时加载)。
<association property="cardInfo" fetchType="eager" column="id">
<id property="id" column="id" javaType="String" jdbcType="VARCHAR"/>
<result property="name" column="e_name" javaType="string" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" javaType="date" jdbcType="TIME"/>
</association>
</resultMap>
<select id="getEmployeesCart" resultMap="CartResultMap" parameterType="String" >
select e.*
from employees e
where e.id=#{id}
</select>
collection:一对多关系。代码所示:一个部门有很多位员工。
<resultMap type="Dept" id="resultEmpMap">
<constructor>
<idArg column="id" javaType="String" />
</constructor>
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="empList" fetchType="lazy" ofType="MybatisDemoA.entity.Employees" column="id">
<id property="id" column="id" javaType="String" jdbcType="VARCHAR"/>
<result property="name" column="e_name" javaType="string" jdbcType="VARCHAR"/>
<result property="sex" column="sex" javaType="string" jdbcType="VARCHAR"/>
<result property="salary" column="salary" javaType="string" jdbcType="VARCHAR"/>
</collection>
</resultMap>
<select id="getDept" resultMap="resultEmpMap" parameterType="String" >
select d.*,e.*44
from dept d,employees e
where e.deptId = d.id and d.id=#{id}
</select>
缓存
MyBatis 包含一个非常强大的查询缓存特性,包括一级缓存和二级缓存。
默认情况下是没有开启二级缓存的,一级缓存只是相对于同一个session而言的。要开启二级缓存,你需要在你的 SQL 映射文件中添加:
<cache/>
这个简单语句的效果如下:
映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
所有的这些属性都可以通过缓存元素的属性来修改。比如:
<cache eviction="FIFO" flushInterval="60000" size="1024" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 1024个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。
可用的收回策略有:默认的是 LRU。
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
3-4 动态SQL
MyBatis 的强大特性之一便是它的动态 SQL。
元素名称 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose(when、otherwise) | 相对于java中的case when | 多条件分支判断 |
trim(where set) | 辅助元素 | 用于处理sql拼装问题 |
trim(where set) | 循环语句 | 在in语句等列举条件常用 |