MyBatis
概述
原是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了 Google Code,随着开发团队转投Google Code 旗下, iBatis3.x正式更名为MyBatis。是 一个基于Java的持久层框架。 iBatis提供的 持久层框架包括SQL Maps和Data Access Objects (DAO)。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映 射
优点:MyBatis 避免了几乎所有的 JDBC 代码手动设置参数以及获取结果集。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录,是 一种 ORM(ORM Object Relational Mapping 对象关系映射)实现.
传统JDBC与MyBatis的对比
传统 JDBC 编程
- 加载数据库驱动
- 创建并获取数据库链接
- .创建 statement 对象
- .拼写 sql 语句
- 设置 sql 语句中的占位符的值
- .执行 sql 语句并获取结果
- 对 sql 执行结果进行解析处理
- 释放资源
JDBC 编程问题
- 数据库连接的创建、释放频繁造成系统资源浪费从而影响系统性能,如果使 用数据库连接池可解决该问题。
- SQL 语句编写在 Java 代码中,这种硬编码造成代码不易维护,当 SQL 变动 时需要修改 java 源代码。
- 使用 preparedStatement 向占位符传参数存在硬编码,因为 SQL 语句的 where 条件中占位符的个数可能会变化,修改 SQL 还要修改 Java 源代码, 系统不易维护。
- 对结果集解析存在硬编码,SQL 语句变化导致解析代码变化,系统不易维护。
MyBatis框架
MyBatis 是优秀的持久层框架,它能够解决 JDBC 编程中存在的问题。接下 来先了解 MyBatis 的架构。
Mybatis环境搭建与应用
导入 MyBatis jar 包数据库驱动包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
创建 MyBatis 全局配置文件
在resources包的mybatis里面配置属性
<?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>
添加属性
</configuration>
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
创建 sql 映射文件
<?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.ffyc.mybatispro.mapper.AdminMapper">
添加sql语句
<!--
parameterType="参数类型"
#{属性名} = values(?,?,?) 赋值方式是预编译,安全的,可以防止sql注入
${属性}values('admin2','111','男') 等于字符串拼接 不安全
${} 多用来传列名而不是数据
useGeneratedKeys="true" keyColumn="id" keyProperty="id
useGeneratedKeys="true 可以返回插入数据的主键
keyColumn="id" 告知主键列
keyProperty="id" 告知主键列对应的属性
insert into admin(account,password,sex)values('${account}','${password}','${sex}')
-->
比如;
<insert id="saveAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">/*id与接口的方法名一致 后面的parameterType是model的对象地址 (使用mybatis.html定义的别名)*/
insert into admin(account,password,sex)values(#{account},#{password},#{sex})
</insert>
<update id="updateAdmin" parameterType="Admin">
update admin set account=#{account},
password=#{password},
sex=#{sex}
where id = #{id}
</update>
<delete id="deleteAdmin">
delete from admin where id = #{id}
</delete>
<!--查找的时候可能会出现列名与set属性名不一致的现象 用resultMap来解决-->
<resultMap id="AdminMap" type="Admin">
<id column="id" property="id"></id><!--id是主键列-->
<result column="account" property="account"></result><!--column是列名 右边是set属性名-->
<result column="password" property="password"></result>
<result column="sex" property="sex"></result>
</resultMap>
<select id="findAdminById" resultMap="AdminMap">/*已经设置了别名 所以可以不用地址,直接用别名*/
select * from admin where id = #{id}
</select><!--将数据库数据自动映射到java对象中,有前提表中的列明与类中的属性名相同-->
<select id="getAdmin" resultMap="AdminMap">
select * from admin where account=#{acc} and sex=#{sex}
</select>
<select id="getAdminList" resultMap="AdminMap">
select * from admin
</select>
<select id="getLogin" resultType="Admin">
select id,account from admin where account=#{account} and password=#{password}
</select>
</mapper>
加载mysql驱动
<!--
environments 配置与数据库连接的相关信息
-->
<environments default="development">
<!--配置本地开发信息-->
<environment id="development">
<!--配置事务管理类型,使用JDBC事务管理
事务:一次对数据库操作的若干流程管理
-->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!--数据库驱动-->
<property name="driver" value="${Driver}"/>
<!--数据库地址-->
<property name="url" value="${Url}"/>
<property name="username" value="${UserName}"/>
<property name="password" value="${PassWord}"/>
</dataSource>
</environment>
</environments>
这里需要在resources里面创建config.properites来定义驱动mysql的别名
Driver=com.mysql.cj.jdbc.Driver
Url=jdbc:mysql://127.0.0.1:3306/mybatis_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
UserName=(数据库账号)
PassWord=(数据库密码)
mybatis日志
Mybatis 内置的日志工厂提供日志功能,具体的日志实现有以下几种方式: SLF4J | LOG4J | LOG4J2 JDK_LOGGING COMMONS_LOGGING STDOUT_LOGGING NO_LOGGING 具体选择哪个日志实现由 MyBatis 的内置日志工厂确定。它会使用最先找到的。
配置日志:
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
参数传递
简单的参数形式不需要使用 parameterType 参数定义,
例如:User selectUsers(int id);
多个参数使用@Param(“id”)绑定
User selectUsers(@Param(“id”)int id,@Param(“name”)String name);
<select id="selectUsers" resultType="User">
select id, username, password from users where id = #{id}
</select>
如果传入一个复杂的对象,就需要使用 parameterType 参数进行类型定义,例如: void insertUser(User user);
<insert id="insertUser" parameterType="User">
insert into users (id, username, password) values (#{id}, #{username}, #{password})
</insert>
也可以使用 Map 对象传递 void insertUser(Map<String,Object> map); 在 sql 中使用表达式获取 map 的键即可.
多表关联处理结果集
resultMap 元素中 association , collection 元素 association – 复杂类型联合;
许多查询结果合成这个类型 一对一结果映射– association 能引用自身, 或者从其它地方引用 collection – 复杂类型集合 嵌套结果映射– collection 能引用自身, 或者从其它地方引用 多对一与一对多
association 关联元素处理“有一个”类型的关系,即一对一关联。它有两种关联 方式
- 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型。
- 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。
多表查询关系到别名 所以先定义类型
Collection 关联元素处理一对多关联。
员工多方,在多方配置一方
使用 resultMap 组装查询结果
<resultMap id="empmap" type="Employee">
<id column="id" property="id"></id>
<result column="ename" property="name"></result>
<result column="age" property="age"></result>
<association property="dept" javaType="Dept">
<result column="dname" property="name"></result>
</association>
<association property="admin" javaType="Admin">
<result column="account" property="account"></result>
</association>
</resultMap>
然后用别名来查询
<select id="getEmployeeById" resultMap="empmap">
SELECT
emp.id,
emp.name ename,
emp.age,
d.name dname,
a.account
FROM employee emp LEFT JOIN dept d ON emp.deptId = d.id
LEFT JOIN admin a ON emp.adminId = a.id
WHERE emp.id = #{id}
</select>
部门一方,配置多方集合
<resultMap id="deptmap" type="Dept">
<id column="id" property="id"></id>
<result column="dname" property="name"></result>
<association property="admin" javaType="Admin">
<result column="account" property="account"></result>
</association>
<collection property="employeeList" javaType="list" ofType="Employee"> <!--声名他是集合 在声名他是Employee 第一个名字直接跟变量名对应就行-->
<result column="ename" property="name"></result>
</collection>
</resultMap>
查询
<select id="getDeptById" resultMap="deptmap">
SELECT
d.id,
d.name dname,
a.account,
emp.name ename
FROM dept d LEFT JOIN admin a ON d.adminId = a.id
LEFT JOIN employee emp ON d.id = emp.deptId
WHERE d.id = #{id}
</select>
懒加载
需要查询关联信息时,使用 Mybatis 懒加载特性可有效的减少数据库压力, 首次查询只查询主表信息,关联表的信息在用户获取时再加载。
Mybatis 一对一关联的 association 和一对多的 collection 可以实现懒加 载。懒加载时要使用 resultMap,不能使用 resultType。
启动懒加载:
Mybatis 默认没有打开懒加载配置,需要在 SqlMapperConfig.xml 中通过 settings 配置 lazyLoadingEnabled 来开启懒加载。
<setting name="lazyLoadTriggerMethods" value="true"/>
代码:
<resultMap id="empmap1" type="Employee">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<association property="admin" javaType="Admin" fetchType="lazy"
select="findAdminById" column="adminId">
<result property="account" column="account"></result>
</association>
<association property="dept" javaType="Dept" fetchType="lazy"
select="findDeptById" column="deptId">
<result property="name" column="name"></result>
</association>
</resultMap>
- Select:指定关联查询懒加载对象的 Mapper Statement ID 是findDeptByID
- column=“dept_id”:关联查询时将 dept_id 列的值传入 findDeptByID, 并将 findDeptByID 查询的结果映射到 Emp 的 dept 属性中
- collection 和 association 都需要配置 select 和 column 属性,两者配置方法 相同
注解
常用注解标签
- @Insert : 插入 sql , 和 xml insert sql 语法完全一样
- @Select : 查询 sql, 和 xml select sql 语法完全一样
- @Update : 更新 sql, 和 xml update sql 语法完全一样
- @Delete : 删除 sql, 和 xml delete sql 语法完全一样
- @Param : 入参 @Results : 设置结果集合
- @Result : 结果
使用案例
查询所有信息:
@Select("select * from t_emp")
@Results(id = "empMap",value = {
@Result(column = "emp_id",property = "empId",id = true),
@Result(column = "emp_name",property = "empName"),
@Result(column = "emp_tel",property = "empTel"),
@Result(column = "emp_education",property = "empEducation"), @Result(column = "emp_birthday",property = "empBirthday")
})
List<Employee> getAll();
查询单个信息
@Select("select * from t_emp where emp_id=#{empId}")
@ResultMap(value="empMap")
Employee getById(@Param("empId") Integer empId);
插入信息
@Insert("insert into t_emp (emp_id, emp_name, emp_tel, " + " emp_education, emp_birthday, fk_dept_id" + " )"
values (#{empId}, #{empName}, #{empTel}, "
+ " #{empEducation}, #{empBirthday}, #{fkDeptId}" + " )")
int insert(Employee record);
删除信息
@Delete("delete from t_emp where emp_id=#{empId}")
int deleteByPrimaryKey(@Param("empId") Integer empId);
Mybatis 动态 SQL
**MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。**MyBatis 中用于实现动态 SQL 的元素主要有: Ifwhere trimset choose (when, otherwise) foreach
If 元素
if 标签可以对传入的条件进行判断
<select id="getEmployee" parameterType="Employee" resultMap="empmap" useCache="true">
SELECT
emp.id,
emp.name ename,
emp.age,
d.name dname,
a.account
FROM employee emp LEFT JOIN dept d ON emp.deptId = d.id
LEFT JOIN admin a ON emp.adminId = a.id
<where>
<if test=" name!=null & name !='' ">
emp.name = #{name}
</if>
<if test="age != 0">
and emp.age = #{age}
</if>
</where>
</select>
对于查询条件个数不确定的情况,可使用元素。
元素会进行判断,如果它包含的标签中有返回值的话,它就插入一个 ‘where’。此外,如果标签返回的内容是以 AND 或 OR 开头,它会剔除掉 AND 或 OR。
trim 元素
<!--trim来实现 prefix 关键字 prefixOverrides覆盖首部指定内容
Choose 元素
-->
SELECT
emp.id,
emp.name ename,
emp.age,
d.name dname,
a.account
FROM employee emp LEFT JOIN dept d ON emp.deptId = d.id
LEFT JOIN admin a ON emp.adminId = a.id
<trim prefix="where" prefixOverrides="and">
<choose>
<when test="name!=null & name!=''">
and emp.name = #{name}
</when>
<otherwise>
and emp.name = '张三'
</otherwise>
</choose>
</trim>
</select>
Choose 元素
SELECT
emp.id,
emp.name ename,
emp.age,
d.name dname,
a.account
FROM employee emp LEFT JOIN dept d ON emp.deptId = d.id
LEFT JOIN admin a ON emp.adminId = a.id
<trim prefix="where" prefixOverrides="and">
<choose>
<when test="name!=null & name!=''">
and emp.name = #{name}
</when>
<otherwise>
and emp.name = '张三'
</otherwise>
</choose>
</trim>
</select>
Set 元素
Set 元素可以把最后一个逗号去掉
<update id="updateEmployee" parameterType="Employee">
update employee
<set>
<if test="name!=null">
name = #{name},
</if>
<if test="age!=null">
age = #{age},
</if>
<if test="dept.id!=0">
deptId = #{dept.id},
</if>
</set>
where id = #{id}
</update>
多用于和if搭配来修改数据库
也可以使用 trim 实现
Foreach 元素
主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。foreach 元素的属性主要有== item,index,collection,open,separator,close。 item ==表示集合中每一个元素进行迭代时的别名,index 指定一个名字,用于 表示在迭代过程中,每次迭代到的位置,open 表示该语句以什么开始, separator 表示在每次进行迭代之间以什么符号作为分隔符,close 表示以什 么结束,在使用 foreach 的时候最关键的也是最容易出错的就是 collection 属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的。
- 如果传入的是单参数且参数类型是一个 List 的时候,collection 属 性值为 list
- 如果传入的是单参数且参数类型是一个 array 数组的时候, collection 的属性值为 array
特殊符号处理
在 mybatis 中的 xml 文件中,存在一些特殊的符号,比如:<、>、"、&、<> 等,正常书写 mybatis 会报错,需要对这些符号进行转义。具体转义如下所示:
特殊字符 转义字符
< <
> >
" "
’ '
& &
除了可以使用上述转义字符外,还可以使用<![CDATA[]]>来包裹特殊字符。如 下所示:
<if test="id != null"> AND <![CDATA[ id <> #{id} ]]> </if> <![CDATA[ ]]>是 XML 语法。在 CDATA 内部的所有内容都会被解析器忽略
但是有个问题那就是
<if> </if> <where> </where> <choose> </choose> <trim> </trim>
等这些标签都不会被解析,所以 我们只把有特殊字符的语句放在 <![CDATA[ ]]> 尽量缩小<![CDATA[ ]]> 的范围。
Mybatis的一级缓存和二级缓存
缓存(cache)的作用是为了减去数据库的压力(查询对数据库的压力是最大的),提高数据库的性能。缓存实现的 原理是从数据库中查询出来的对象在使用完后不要销毁,而是存储在内存(缓存) 中,当再次需要获取该对象时,直接从内存(缓存)中直接获取,不再向数据库 执行 select 语句,从而减少了对数据库的查询次数,因此提高了数据库的性能。缓存是使用 Map 集合缓存数据的。
Mybatis 有一级缓存和二级缓存。一级缓存的作用域是同一个 SqlSession, 在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库 中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查 询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存 也就不存在了。Mybatis 默认开启一级缓存。
二 级 缓 存 是 多 个 SqlSession 共 享 的 , 其 作 用 域 是 mapper 的 同 一 个 namespace,不同的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行相同的 sql 语句,第一次执行完毕会将数据库 中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存需要在 setting 全局参 数中配置开启二级缓存
一级缓存
Mybatis 对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓 存,一级缓存只是相对于同一个 SqlSession 而言。所以在参数和 SQL 完全一样 的情况下,我们使用同一个 SqlSession 对象调用一个 Mapper 方法,往往只执 行一次 SQL,因为使用 SelSession 第一次查询后,MyBatis 会将其放在缓存中, 以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下, SqlSession 都会取出当前缓存的数据,而不会再次发送 SQL 到数据库。
一级缓存的生命周期
- MyBatis 在开启一个数据库会话时,会 创建一个新的 SqlSession 对象, SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新 的 PerpetualCache 对象,如果 SqlSession 调用了 close()方法,会释放掉一级 缓存 PerpetualCache 对象,一级缓存将不可用。
- 如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象 中的数据,但是该对象仍可使用
- SqlSession 中执行了任何一个 update 操作(update()、delete()、 insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用
二级缓存
二级缓存是 SqlSessionFactory 级别的,根据 mapper 的 namespace 划分区域 的,相同 namespace 的 mapper 查询的数据缓存在同一个区域,如果使用 mapper 代理方法每个 mapper 的 namespace 都不同,此时可以理解为二级缓 存区域是根据 mapper 划分。
每次查询会先从缓存区域查找,如果找不到则从数据库查询,并将查询到数据写 入缓存。Mybatis 内部存储缓存使用一个 HashMap,key 为hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。 sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止脏读。
MyBatis 的缓存机制整体设计以及二级缓存的工作模式
配置二级缓存配置
第一步:启用二级缓存
mybatis的配置如下:
<settings>
<!--二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
第二步:POJO 序列化
将所有的 model 类实现序列化接口 Java.io. Serializable。
第三步:配置映射文件
在 Mapper 映射文件中添加,表示此 mapper 开启二级缓存。 当 SqlSeesion 关闭时,会将数据存入到二级缓存。
<!--
flushCache="true" 是否刷新缓存区数据 true-刷新 false-不刷新
-->
<cache flushInterval="3000"></cache>
useCache=“true” 开启二级缓存
增加、修改、删除的标签后面可以加flusrCache 值为true或false 表示每次操作是否清空缓存区 true为清空 放置脏读