MyBatis
一、MyBatis简介
MyBatis 是apache的一个开源项目,是一个基于Java的持久层框架。
MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。
MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
MyBatis流行的主要原因在于它的简单性和易使用性。在Java应用程序中,数据持久化层涉及到的工作有:将从数据库查询到的数据生成所需要的Java对象;将Java对象中的数据通SQL持久化到数据库中。
二、当前有很多Java实现的持久化框架,而MyBatis流行起来有以下原因:
1.它消除了大量的JDBC冗余代码
Java通过JDBC的API来操作关系型数据库,但是JDBC是一个非常底层的API,我们需要书写大量的代码来完成对数据库的操作。例如一个插入操作:
第一步:在SQLMapper映射配置文件中配置SQL语句,假定为StudentMapper.xml
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(ID,NAME,EMAIL)
VALUES(#{id},#{name},#{email})
</insert>
第二步:创建一个StudentMapper接口
public interface StudentMapper{
void insertStudent(Student student);
}
第三步:编写java代码完成插入操作:
SqlSession session = null;
//读取mybatis配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//创建工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//使用工厂创建session对象
session = sqlSessionFactory.openSession();
//使用session获得映射接口的实现类对象
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
Student s = new Student(1,"tom","123@briup.com");
studentMapper.insertStudent(s);
session.commit();
特别之处的是,MyBatis还提供了其他的一些特性来简化持久化逻辑的实现
它支持复杂的SQL结果集数据映射到嵌套的对象结构
它支持一对一和一对多的结果集和Java对象的映射
它支持根据输入的数据构建动态的SQL语句
2.它有低的学习曲线
MyBatis能够流行的首要原因之一在于它学习和使用起来非常简单,它取决于你Java和 SQL方面的知识。如果开发人员很熟悉Java和SQL,他们会发现MyBatis入门非常简单。
3.它能很好地与传统数据库协同工作
有时我们可能需要用不正规形式与传统数据库协同工作,使用成熟的ORM框架(例如大家熟知的Hibernate)有可能、但是很难跟传统数据库很好地协同工作,因为他们尝试将Java对象静态地映射到数据库的表上.而MyBatis是将查询的结果与Java对象映射起来,这使得MyBatis可以很好地与传统数据库协同工作。你可以根据面向对象的模型创建Java域对象,执行传统数据库的查询,然后将结果映射到对应的Java对象上。
4.它可以接受SQL语句
成熟的ORM框架鼓励使用实体对象和在其底层自动产生SQL语句。由于这种SQL语句的生成方式,我们有可能不能够利用到数据库的一些特有的特性。Hibernate允许执行本地SQL,但是这样会打破持久层和数据库独立的原则。MyBatis框架接受SQL语句,而不是将其对开发人员隐藏起来。由于MyBatis不会产生任何的SQL语句,所以开发人员就要准备SQL语句,这样就可以充分利用数据库特有的特性并且可以准备自定义的查询。另外,MyBatis 对存储过程也提供了支持。
5.它提供了与Spring框架的集成支持
MyBatis提供了与流行的依赖注入框架Spring的开包即用的集成支持,这将进一步简化MyBatis的使用
6.它提供了与第三方缓存类库的集成支持
MyBatis有内建的SqlSession级别的缓存机制,用于缓存Select语句查询出来的结果。除此之外,MyBatis提供了与多种第三方缓存类库的集成支持,如EHCache,OSCache,Hazelcast等。
7.它引入了更好的性能
性能问题是关乎软件应用成功与否的关键因素之一。为了达到更好的性能,需要考虑很多事情,而对很多应用而言,数据持久化层是整个系统性能的关键。MyBatis支持数据库连接池,消除了为每一个请求创建一个数据库连接的开销,MyBatis提供了内建的缓存机制,在SqlSession级别提供了对SQL查询结果的缓存。即:如果你调用了相同的select查询,MyBatis 会将放在缓存的结果返回,而不会去再查询数据库。MyBatis框架并没有大量地使用代理机制,因此对于其他的过度地使用代理的ORM框架而言,MyBatis可以获得更好的性能.
三、Mybatis基本结构
Mybatis的功能架构分为三层:
1.API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
2.数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
3.础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑
四、mybatis面向用户的对象
1.SqlSessionFactory(用于创建SqlSession,加载配置文件等(全局级))
每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心。SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象来获得。SqlSessionFactoryBuilder对象可以从XML配置文件,或从Configuration类的习惯准备的实例中构建SqlSessionFactory对象。
2.SqlSession (用于执行SQL语句,获取Mapper映射、事务处理(线程级))
正如其名,Sqlsession对应着一次数据库会话。由于数据库回话不是永久的,因此Sqlsession的生命周期也不应该是永久的,相反,在你每次访问数据库时都需要创建它(当然并不是说在Sqlsession里只能执行一次sql,你可以执行多次,当一旦关闭了Sqlsession就需要重新创建它)。创建Sqlsession的地方只有一个,那就是SqlsessionFactory的openSession方法。
创建sqlsession经过了以下几个主要步骤:
1) 从配置中获取Environment;
2) 从Environment中取得DataSource;
3) 从Environment中取得TransactionFactory;
4) 从DataSource里获取数据库连接对象Connection;
5) 在取得的数据库连接上创建事务对象Transaction;
6) 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);
7) 创建sqlsession对象。
3.Mapper(封装SQL行为,映射为Java方法操作)
表面上看mapper是在sqlsession里创建的,但实际创建它的地方是MapperRegistry:
public <T>T getMapper(Class<T> type, SqlSession sqlSession) {
if (!knownMappers.contains(type))
thrownewBindingException("Type " + type + " isnot known to the MapperRegistry.");
try {
returnMapperProxy.newMapperProxy(type, sqlSession);
} catch (Exceptione) {
thrownewBindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以看到,mapper是一个代理对象,它实现的接口就是传入的type,这就是为什么mapper对象可以通过接口直接访问。同时还可以看到,创建mapper代理对象时传入了sqlsession对象,这样就把sqlsession也关联起来了。
五、MyBatis映射配置
SQL映射文件有很少的几个顶级元素(按照他们应该被定义的顺序如下)。
1.cache - 配置给定命名空间的缓存
2.cache-ref – 从其他命名空间引用缓存配置。
3.resultMap – 最复杂,也是最有力量的元素,用来描述如何从数据库结果集中来加载你的对象。
4.parameterMap – 已经被废弃了!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除。
5.sql – 可以重用的SQL块,也可以被其他语句引用。
6.insert – 映射插入语句
7.update – 映射更新语句
8.delete -映射删除语句
9.select - 映射查询语句
(详细的代码讲解可以参考:)
六、动态语句
1.if
<select id=" getStudentListLikeName " parameterType="StudentEntity"
resultMap="studentResultMap">
SELECT * from STUDENT_TBL ST
<if test="studentName!=null and studentName!='' ">
WHERE ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName}),'%')
</if>
</select>
2.choose(when,otherwise)
有时候并不想应用所有的条件,而只是想从多个选项中选择一个。MyBatis提供了choose 元素,按顺序判断when中的条件出否成立,如果有一个成立,则choose结束。当choose中所有when的条件都不满则时,则执行otherwise中的sql。类似于Java 的switch语句,choose为switch,when为case,otherwise则为default。
<select id="getStudentListChooseEntity" parameterType="StudentEntity" resultMap="studentResultMap">
SELECT * from STUDENT_TBL ST
<where>
<choose>
<when test="studentName!=null and studentName!='' ">
ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName}),'%')
</when>
<when test="studentSex!= null and studentSex!= '' ">
AND ST.STUDENT_SEX = #{studentSex}
</when>
<when test="studentBirthday!=null">
AND ST.STUDENT_BIRTHDAY = #{studentBirthday}
</when>
<when test="classEntity!=null and classEntity.classID !=null and classEntity.classID!='' ">
AND ST.CLASS_ID = #{classEntity.classID}
</when>
<otherwise></otherwise>
</choose>
</where>
</select>
3.trim(where,set)
trim是更灵活的去处多余关键字的标签,他可以实践where和set的效果。
where例子的等效trim语句
<select id="getStudentListWhere" parameterType="StudentEntity" resultMap="studentResultMap">
SELECT * from STUDENT_TBL ST
<trim prefix="WHERE" prefixOverrides="AND|OR">
<if test="studentName!=null and studentName!='' ">
ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName}),'%')
</if>
<if test="studentSex!= null and studentSex!= '' ">
AND ST.STUDENT_SEX = #{studentSex}
</if>
</trim>
</select>
set例子的等效trim语句
<update id="updateStudent" parameterType="StudentEntity">
UPDATE STUDENT_TBL
<trim prefix="SET" suffixOverrides=",">
<if test="studentName!=null and studentName!='' ">
STUDENT_TBL.STUDENT_NAME = #{studentName},
</if>
<if test="studentSex!=null and studentSex!='' ">
STUDENT_TBL.STUDENT_SEX = #{studentSex},
</if>
<if test="studentBirthday!=null ">
STUDENT_TBL.STUDENT_BIRTHDAY = #{studentBirthday},
</if>
<if test="classEntity!=null and classEntity.classID!=null and classEntity.classID!='' ">
STUDENT_TBL.CLASS_ID = #{classEntity.classID}
</if>
</trim>
WHERE STUDENT_TBL.STUDENT_ID = #{studentID};
</update>
4.foreach
对于动态SQL 非常必须的,主是要迭代一个集合,通常是用于IN 条件。List实例将使用“list”做为键,数组实例以“array”做为键。
1 )参数为list实例的写法
SqlMapper.xml
<select id="getStudentListByClassIDs" resultMap="studentResultMap">
SELECT * FROM STUDENT_TBL ST
WHERE ST.CLASS_ID IN
<foreach collection="list" item="classList" open="(" separator="," close=")">
#{classList}
</foreach>
</select>
Java
List<String> classList = new ArrayList<String>();
classList.add("20000002");
classList.add("20000003");
List<StudentEntity> studentList = studentMapper.getStudentListByClassIDs(classList);
for(StudentEntity entityTemp : studentList){
System.out.println(entityTemp.toString());
}
2)参数为Array实例的写法
SqlMapper.xml
<select id="getStudentListByClassIDs" resultMap="studentResultMap">
SELECT * FROM STUDENT_TBL ST
WHERE ST.CLASS_ID IN
<foreach collection="array" item="ids" open="(" separator="," close=")">
#{ids}
</foreach>
</select>
Java
String[] ids = new String[2];
ids[0] = "20000002";
ids[1] = "20000003";
List<StudentEntity> studentList = studentMapper.getStudentListByClassIDs(ids);
for(StudentEntity entityTemp : studentList){
System.out.println(entityTemp.toString());
}
七、缓存
映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有 insert, update 和 delete 语句会刷新缓存。
缓存会使用 Least Recently Used( LRU,最近最少使用的)算法来收回。
根据时间表(比如 no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的
八、Spring 事务配置
1.Spring Xml 配置
2.Spring aop 配置
3.Spring 注解配置
详细学习(http://blog.sina.com.cn/s/blog_692d0a650100xs7p.html)
九、事务传播属性
Propagation (事务的传播属性)
Propagation :key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:??
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
十、事务隔离级别
Isolation Level(事务隔离等级):
1、Serializable:最严格的级别,事务串行执行,资源消耗最大;
2、REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
4、Read Uncommitted:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。
十一、批量操作
1.insert多次提交
批量增加操作步骤
1) 在接口UserMapper中添加批量增加方法。
/**
* 批量增加操作
* @param users
*/
public void batchInsertUsers(List<User> users);
2)在User.xml中添加批量增加操作的配置。
<!-- 批量增加操作 -->
<insert id="batchInsertUsers" parameterType="java.util.List">
insert into mhc_user(userName,password) values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.userName},#{item.password})
</foreach>
</insert>
由于批量增加的方法中参数为List,所以parameterType的值为Java.util.List。
3)创建批量操作的工具类BatchDataUtils,编写批量增加方法。
/**
* 批量增加操作
* @param users
*/
public static void batchInsertUsers(List<User> users){
SqlSessionFactory ssf = MyBatisUtil.getSqlSessionFactory();
SqlSession session = ssf.openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.batchInsertUsers(users);
session.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtil.closeSession(session);
}
}
2.在接口UserMapper中添加批量删除方法。
批量删除操作步骤
1)在接口UserMapper中添加删除增加方法。
/**
* 批量删除操作
* @param ids
*/
public void batchDeleteUsers(List ids);
2)在User.xml中添加批量增加操作的配置。
<!-- 批量删除操作 -->
<delete id="batchDeleteUsers" parameterType="java.util.List">
delete from mhc_user where id in
<foreach collection="list" index="index" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
由于批量删除的方法中参数为List,所以parameterType的值为java.util.List。
3)在批量操作的工具类BatchDataUtils中编写批量删除方法。
/**
* 批量删除操作
* @param ids
*/
public static void batchDeleteUsers(List ids){
SqlSessionFactory ssf = MyBatisUtil.getSqlSessionFactory();
SqlSession session = ssf.openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.batchDeleteUsers(ids);
session.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtil.closeSession(session);
}
}
3.批量查询操作步骤
1)在接口UserMapper中添加批量查询方法。
/**
* 批量查询操作
* @param ids
* @return
*/
public List<User> batchSelectUsers(List ids);
2)在User.xml中添加批量查询操作的配置。
<!-- 批量查询操作 -->
<select id="batchSelectUsers" resultType="User">
select *
from mhc_user where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
由于批量查询的方法的返回为List<User>,所以resultType的值为User,即com.mahaochen.mybatis.domain.User。详见configuration.xml中。
<typeAliases>
<!-- 注册实体Bean -->
<typeAlias type="com.mahaochen.mybatis.domain.User" alias="User"/>
</typeAliases>
3)创建批量操作的工具类BatchDataUtils,编写批量查询方法。
/**
* 批量查询操作
* @param ids
* @return
*/
public static List<User> batchSelectUsers(List ids){
SqlSessionFactory ssf = MyBatisUtil.getSqlSessionFactory();
SqlSession session = ssf.openSession();
List<User> users = null;
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
users = userMapper.batchSelectUsers(ids);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtil.closeSession(session);
}
return users;
}
}
4.批量更细操作步骤
1)在接口UserMapper中添加批量增加方法。
/**
* 批量更新操作
* @param ids
*/
public void batchUpdateUsers(List users);
2)在User.xml中添加批量更新操作的配置。
<!-- 批量更新操作 -->
<!-- FOR MySQL mysql需要数据库连接配置&allowMultiQueries=true
例如:jdbc:mysql://127.0.0.1:3306/mhc?allowMultiQueries=true -->
<update id="batchUpdateUsers" parameterType="java.util.List">
<foreach collection="list" item="item" index="index" open="" close="" separator=";">
update mhc_user
<set>
userName = #{item.userName}, password = #{item.password}
</set>
where id = #{item.id}
</foreach>
</update>
<!-- 【扩展知识】 FOR Oracle 有以下三种方式-->
<!-- 方式一 -->
<update id="batchUpdateUsers01" parameterType="java.util.List">
<foreach collection="list" item="item" index="index" open="begin" close=";end;" separator=";" >
update mhc_user
<set>
userName = #{item.userName}, password = #{item.password}
</set>
where id = #{item.id}
</foreach>
</update>
<!-- 方式二 -->
<update id="batchUpdateUsers02" parameterType="java.util.List">
<foreach collection="list" item="item" index="index" open="begin" close="end;" separator="" >
update mhc_user
<set>
userName = #{item.userName}, password = #{item.password}
</set>
where id = #{item.id};
</foreach>
</update>
<!-- 方式三 -->
<update id="batchUpdateUsers03" parameterType="java.util.List">
begin
<foreach collection="list" item="item" index="index" separator="" >
update mhc_user
<set>
userName = #{item.userName}, password = #{item.password}
</set>
where id = #{item.id};
</foreach>
end;
</update>
由于批量更新的方法中参数为List,所以parameterType的值为java.util.List。
3)创建批量操作的工具类BatchDataUtils,编写批量更新方法。
/**
* 批量更新操作
* @param users
*/
public static void batchUpdateUsers(List users){
SqlSessionFactory ssf = MyBatisUtil.getSqlSessionFactory();
SqlSession session = ssf.openSession();
try {
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.batchUpdateUsers(users);
session.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtil.closeSession(session);
}
}
十二、单元测试
Spring 事务控制
数据准备
真实调用
数据回滚
Being away from home, we have nothing but a desire to make a figure