一、MyBatis介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
二、为什么要用mybatis?
JDBC编程存在的问题
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能, 如果使用数据库链接池可解决此问题。
2、Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
3、使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
三、框架搭建
下载 https://github.com/mybatis/mybatis-3/releases
mybatis-3.2.7.jar----mybatis的核心包
lib----mybatis的依赖包
mybatis-3.2.7.pdf----mybatis使用手册
1.创建java工程,jdk1.7
2.加入jar包 mybatis核心包、依赖包、数据驱动包。
3.在classpath下创建log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
4.在classpath(config)下创建SqlMapConfig.xml,约束文档路径:mybatis-3.2.7.jar/org/apache/ibatis/builder/xml/mybatis-3-config.dtd
5.创建实体类、数据库表
privateintid;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
6.在classpath(config)下的sqlmap目录下创建sql映射文件User.xml
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEmapper
PUBLIC"-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
<selectid="findUserById"parameterType="int"resultType="com.oaec.mybatis.pojo.User">
select * from s_user where id = #{id}
</select>
</mapper>
parameterType:定义输入到sql中的映射类型,
#{id}表示使用preparedstatement设置占位符号并将输入变量id传到sql。
resultType:定义结果映射类型。
7.mybatis框架需要加载映射文件,将Users.xml添加在SqlMapConfig.xml
<!--加载映射文件-->
<mappers>
<mapper resource="sqlmap/User.xml"/>
</mappers>
8.测试程序
9. 模糊查询
<select id="findUserByUsername" parameterType="java.lang.String"
resultType="com.oaec.mybatis.pojo.User">
select * from user where username like '%${value}%'
</select>
10.#{}和${}
#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。#{}括号中可以是value或其它名称。
${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,如果parameterType传输单个简单类型值,${}括号中只能是value。
11.parameterType和resultType
parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。
12.selectOne和selectList
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常
selectList可以查询一条或多条记录。
13.添加数据
<insert id="insertUser" parameterType="com.oaec.mybatis.po.User">
insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})
</insert>
User user = new User();
user.setUsername("张旭");
user.setAddress("卢龙");
user.setSex("1");
sqlSession.insert("test.insertUser", user);
14.删除数据
<delete id="deleteUserById" parameterType="int">
delete from user where id=#{id}
</delete>
sqlSession.delete("test.deleteUserById",1);
15修改数据
<update id="updateUser" parameterType="com.oaec.mybatis.pojo.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
where id=#{id}
</update>
User user = new User();
user.setId(2);
user.setUsername("冯");
user.setAddress("秦皇岛");
user.setSex("1");
sqlSession.update("test.updateUser", user);
16.总结 :Mybatis解决jdbc编程的问题
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
17.与hibernate比较
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
四、Mapper动态代理方式
1. 代理方式要遵循的规范
1、 Mapper.xml文件中的namespace与mapper接口的类路径相同。
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
4、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
2. 接口文件
public interface UserMapper {
//根据用户id查询用户信息
public User findUserById(int id) throws Exception;
//查询用户列表
public List<User> findUserByUsername(String username) throws Exception;
//添加用户信息
publicvoid insertUser(User user)throws Exception;
}
3.映射文件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">
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口地址
-->
<mapper namespace="com.oaec.mybatis.mapper.UserMapper">
<select id="findUserById" parameterType="int" resultType="user">
SELECT * FROM USER WHERE id=#{value}
</select>
<select id="findUserByName" parameterType="java.lang.String" resultType="com.oaec.mybatis.po.User">
SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>
<insert id="insertUser" parameterType="com.oaec.mybatis.po.User">
insert into user(username,birthday,sex,address) value(#{username},#{birthday},#{sex},#{address})
</insert>
</mapper>
4.加载映射文件
<!-- 加载映射文件 -->
<mappers>
<mapperresource="mapper/UserMapper.xml"/>
</mappers>
5.测试
package com.oaec.mybatis.mapper;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
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 org.junit.Before;
import org.junit.Test;
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
// 此方法是在执行testFindUserById之前执行
@Before
public void setUp() throws Exception {
// 创建sqlSessionFactory
// mybatis配置文件
String resource = "SqlMapConfig.xml";
// 得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建会话工厂,传入mybatis的配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
}
@Test
public void testFindUserById() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper的方法
User user = userMapper.findUserById(1);
System.out.println(user);
}
@Test
public void testFindUserByName() throws Exception {
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper对象,mybatis自动生成mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper的方法
List<User> list = userMapper.findUserByName("小明");
sqlSession.close();
System.out.println(list);
}
}
一、SqlMapConfig.xml配置文件
1. typeAliases(类型别名)
mybatis支持的别名(默认)
别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
自定义别名
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="com.oaec.mybatis.pojo.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.oaec.mybatis.pojo"/>
<package name="其它包"/>
</typeAliases>
2.mappers(映射器)
使用相对于类路径的资源 : <mapper resource="sqlmap/User.xml" />
使用完全限定路径:
如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
使用mapper接口类路径:
如:<mapper class="com.oaec.mybatis.mapper.UserMapper"/>
注册指定包下的所有mapper接口
如:<package name="com.oaec.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
二、mapper.xml映射文件
1. parameterType(输入类型)
1.1 ${}和#{} (已讲)
1.2 传递简单类型(已讲 int)
1.3 传递pojo对象(已讲 User)
1.4 传递pojo包装对象
public class QueryVo {
private User user;
//自定义用户扩展类,继承自User类
private UserCustom userCustom;
}
2. resultType(输出类型)
2.1 输出简单类型(已讲 int)
2.2 输出pojo对象(已讲 User)
2.3 输出pojo包装对象
public class QueryVo {
private User user;
//自定义用户扩展类,继承自User类
private UserCustom userCustom;
}
2.4 输出pojo对象列表(已讲)
3. resultMap
3.1 resultType可以指定pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
3.2 定义resultMap
第一种:多对一或者一对一
<resultMap type="user" id="userDepartmentResultMap">
<id column="id" property="id"/>
<result column="dep_id" property="depid"/>
<result column="username" property="username"/>
<association property="department"
javaType="com.oaec.mybatis.pojo.Department">
<id column="dep_id" property="depid"/>
<result column="depname" property="depname"/>
</association>
</resultMap>
第二种 一对多
<resultMap id="departmentUsersResultMap" type="department" >
<id column="id" property="id"/>
<result column="depname" property="depname"/>
<association property="users"
javaType="com.oaec.mybatis.pojo.User">
<id column="user_id" property="userid"/>
<result column="username" property="username"/>
</association>
</resultMap>
4. 动态SQL调用
4.1 if
where 1=1
<if test="id != null and id !=''">
and id=#{id}
</if>
<if test="username != null and username !=''">
and username like '%${username}%'
</if>
4.2 where
<where>
<if test="id !=null and id !=''">
and id=#{id}
</if>
<if test="username !=null and username !=''">
and username like '%${username}%'
</if>
</where>
<where> 可以自动处理第一个and。
4.3 foreach
需求: 传入多个id查询用户信息,用下边两个sql实现
SELECT * FROM s_USER WHERE username LIKE '%张%' AND (id =1 OR id =2 OR id=4)
SELECT * FROM s_USER WHERE username LIKE '%张%' id IN (1,2,4)
4.3.1
在pojo(QueryVO)中定义list属性ids存储多个用户id,并添加getter/setter方法
<if test="ids !=null and ids.size>0">
<foreach collection="ids" open=" and id in(" close=")" item="id" separator="," >
#{id}
</foreach>
</if>
4.3.2 传递单个List
传递List类型在编写mapper.xml没有区别,唯一不同的是只有一个List参数时它的参数名为list
<if test="list!=null">
<foreach collection="list" item="item" open="and id in("separator=","close=")">
#{item.id}
</foreach>
</if>
4.4 sql片段
Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的
<sql id="query_user_where">
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</sql>
使用include引用
<select id="findUserList" parameterType="user" resultType="user">
select * from user
<where>
<include refid="query_user_where"/>
</where>
</select>
三、缓存 (见图)
Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
1.一级缓存
一级缓存区域是根据SqlSession为单位划分的。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
1.1 原理(见图)
1.2 测试
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//第二次查询,由于是同一个session则不再向数据发出语句直接从缓存取出
User user2 = userMapper.findUserById(1);
System.out.println(user2);
//关闭session
session.close();
1.3 测试2
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//在同一个session执行更新
User user_update = new User();
user_update.setId(1);
user_update.setUsername("宋江");
userMapper.updateUser(user_update);
session.commit();
//第二次查询,虽然是同一个session但是由于执行了更新操作session的缓存被清空,这里重新发出sql操作
User user2 = userMapper.findUserById(1);
System.out.println(user2);
2. 二级缓存
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域
2.1 原理 (见图)
2.2 开启二级缓存
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
要在你的Mapper映射文件中添加一行: <cache /> ,表示此mapper开启二级缓存
2.3 实现序列化
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口
2.4 测试
//获取session1
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper userMapper = session1.getMapper(UserMapper.class);
//使用session1执行第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//关闭session1
session1.close();
//获取session2
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
//关闭session2
session2.close();
2.5 mybatis整合ehcache
EhCache 是一个纯Java的进程内缓存框架,是一种广泛使用的开源Java分布式缓存,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
操作步骤:
1. 引入缓存jar包
2. 开启二级缓存
<setting name="cacheEnabled" value="true"/>
3. 在mapper.xml文件中加入
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
4.引入缓存配置文件 classpath下添加:ehcache.xml
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候,移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
2.6 局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如以下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。