build()
方法,它会将上述的配置文件字节流进行xml解析封装成Configuration
对象,再将Configuration
对象设置到new出来的SqlSessionFactory
并返回;openSession()
方法,用来根据Configuration
创建SqlSession
对象;Executor
的引用,封装了数据库的查询方法;上面自定义的框架会发现和工作中使用的Mybatis框架用法有点出入,这是因为考虑到:
- 自定义框架使用中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方 法,
关闭 sqlsession);- 自定义框架使用中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码;
因此可以针对xml文件定义对应的mapper接口,然后使用动态代理来生成具体的mapper实现类来帮我们自动完成上述操作。
Mybatis是一个半自动化的ORM(对象/关系映射)框架,它支持定制化SQL、存储过程以及高级映
射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的
XML或注解来配置和映射原生类型、接口和Java的POJO (Plain Old Java Objects,普通老式Java对 象)
为数据库中的记录。
原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了
google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11
月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框
架包括SQL Maps和Data Access Objects(DAO)。
Mybatis是一个半自动化的持久层框架,对开发人员开说,核心sql还是需要自己进行优化,sql和java编
码进行分离,功能边界清晰,一个专注业务,一个专注数据。
分析图示如下:
相比于Hibernate这种全自动化的ORM框架它更灵活、更轻量。
基本开发步骤:
①添加MyBatis的pom依赖
②创建user数据表
③编写User实体类
④编写映射文件UserMapper.xml
⑤编写核心文件SqlMapConfig.xml
⑥编写测试类
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口
定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper 接口开发需要遵循以下规范:
SqlMapConfig.xml
1)environments标签
数据库环境的配置,支持多环境配置
其中,事务管理器(transactionManager)类型有两种:
•JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作
用域。
•MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生
命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因
此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
其中,数据源(dataSource)类型有三种:
•UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
•POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
•JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置
数据源,然后放置一个 JNDI 上下文的引用。
2)mapper标签
该标签的作用是加载映射的,加载方式有如下几种:
•使用相对于类路径的资源引用,例如:
•使用完全限定资源定位符(URL),例如:
•使用映射器接口实现类的完全限定类名,例如:
•将包内的映射器接口实现全部注册为映射器,例如:
3)Properties标签
实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的
properties文件,该标签必须放在配置文件顶部!
4)typeAliases标签
类型别名,是为Java 类型设置一个短的名字。配置方式有:
;
,这种方式的别名为类名,不区分大小写;xxxMapper.xml
<select id="findByCondition" parameterType="user" resultType="user">
select * from User
<where>
<if test="id!=0">
and id=#{id}
if>
<if test="username!=null">
and username=#{username}
if>
where>
select>
<select id="findByIds" parameterType="list" resultType="user">
select * from User
<where>
<foreach collection="list" open="id in(" close=")" item="id"
separator=",">
#{id}
foreach>
where>
select>
sql>
<select id="findById" parameterType="int" resultType="user">
<include refid="selectUser">include> where id=#{id}
select>
<select id="findByIds" parameterType="list" resultType="user">
<include refid="selectUser">include>
<where>
<foreach collection="array" open="id in(" close=")" item="id"
separator=",">
#{id}
foreach>
where>
select>
public class Order {
private int id;
private Date ordertime;
private double total;
//代表当前订单从属于哪一个客户
private User user;
} p
ublic class User {
private int id;
private String username;
private String password;
private Date birthday;
}
想要从数据库中查询出Order对象,而Oder对象持有一个User对象。
对应的mapper.xml如下:
<mapper namespace="com.jarry.mapper.OrderMapper">
<resultMap id="orderMap" type="com.jarry.domain.Order">
<result column="uid" property="user.id">result>
<result column="username" property="user.username">result>
<result column="password" property="user.password">result>
<result column="birthday" property="user.birthday">result>
resultMap>
<select id="findAll" resultMap="orderMap">
select * from orders o,user u where o.uid=u.id
select>
mapper>
或者
<resultMap id="orderMap" type="com.jarry.domain.Order">
<result property="id" column="id">result>
<result property="ordertime" column="ordertime">result>
<result property="total" column="total">result>
<association property="user" javaType="com.jarry.domain.User">
<result column="uid" property="id">result>
<result column="username" property="username">result>
<result column="password" property="password">result>
<result column="birthday" property="birthday">result>
association>
resultMap>
public class Order {
private int id;
private Date ordertime;
private double total;
//代表当前订单从属于哪一个客户
private User user;
} p
ublic class User {
private int id;
private String username;
private String password;
private Date birthday;
//代表当前用户具备哪些订单
private List<Order> orderList;
}
从数据库中查询User对象,该User对象持有一个Oder对象的list。
对应的mapper.xml如下:
<mapper namespace="com.jarry.mapper.UserMapper">
<resultMap id="userMap" type="com.jarry.domain.User">
<result column="id" property="id">result>
<result column="username" property="username">result>
<result column="password" property="password">result>
<result column="birthday" property="birthday">result>
<collection property="orderList" ofType="com.jarry.domain.Order">
<result column="oid" property="id">result>
<result column="ordertime" property="ordertime">result>
<result column="total" property="total">result>
collection>
resultMap>
<select id="findAll" resultMap="userMap">
select *,o.id oid from user u left join orders o on u.id=o.uid
select>
mapper>
双向一对多,参考5.2。
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
Mapper接口方法写法如下:
@Select("select * from orders")
@Results({
@Result(id=true,property = "id",column = "id"),
@Result(property = "ordertime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid",
javaType = User.class,
one = @One(select = "com.lagou.mapper.UserMapper.findById"))
})
List<Order> findAll();
Mapper接口方法写法如下:
public interface UserMapper {
@Select("select * from user")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "orderList",column = "id",
javaType = List.class,
many = @Many(select =
"com.lagou.mapper.OrderMapper.findByUid"))
})
List<User> findAllUserAndOrder();
} p
ublic interface OrderMapper {
@Select("select * from orders where uid=#{uid}")
List<Order> findByUid(int uid);
}
双向一对多,参考6.3。
为了加快查询效率,Mybatis引入了缓存机制。
①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数
据结构(HashMap)用于存储缓存数据。不同的sqlSession
之间的缓存数据区域(HashMap)是互相不影响的。
②、二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession
可以共用二级缓存,二级缓存是跨SqlSession的。
缓存查询顺序:二级缓存------>一级缓存------->数据库
Executor
类中执行。Mybatis提供了一个Cache接口,可以实现自己的缓存策略。
基于HashMap实现换粗无法适用集群或分布式环境,因为他是JVM进程级别的。因此需要整合分布式缓存框架,如Redis。
mybatis提供了一个针对cache接口的redis实现类。
使用步骤:
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-redisartifactId>
<version>1.0.0-beta2version>
redis.properties
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
Intercepts ({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这
个拦截器
@Signature (type = StatementHandler .class , //这是指拦截哪个接口
method = "prepare",//这个接口内的哪个方法名,不要拼错了
args = { Connection.class, Integer .class}), 这是拦截的方法的入参,按顺序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
})
public class MyPlugin implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// //这里是每次执行操作的时候,都会进行这个拦截器的方法内
Override
public Object intercept(Invocation invocation) throws Throwable {
//增强逻辑
System.out.println("对方法进行了增强....");
return invocation.proceed(); //执行原方法
}
/**获取配置文件的属性**/
//插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
Override
public void setProperties(Properties properties) {
System.out.println("插件配置的初始化参数:"+properties );
}
}
sqlMapConfig.xml
<plugins>
<plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
<property name="name" value="Bob"/>
plugin>
plugins>
Mybatis的功能架构分为三层:
**(1) API接口层:**提供给外部使用的接口 API,开发人员通过这些本地API来操纵数据库。接口层一接收到
调用请求就会调用数据处理层来完成具体的数据处理。
MyBatis和数据库的交互有两种方式:
a. 使用传统的MyBati s提供的API ;
b. 使用Mapper代理的方式
**(2) 数据处理层:**负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根
据调用的请求完成一次数据库操作。
**(3) 基础支撑层:**负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是
共 用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑
(1) 加载配置并初始化
触发条件:加载配置文件
配置来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml),—个是java代码中的 注
解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement 对
象,存储在内存之中
(2) 接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
(3) 处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A) 根据SQL的ID查找对应的MappedStatement对象。
(B) 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
© 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D) 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理
结果。
(E) 释放连接资源。
(4) 返回处理结果
将最终的处理结果返回。
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策
略。
<resultMap id="userMap" type="user">
<id column="id" property="id">id>
<result column="username" property="username">result>
<result column="password" property="password">result>
<result column="birthday" property="birthday">result>
<collection property="orderList" ofType="order" column="id"
select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
collection>
resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
select>
在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
settings>
局部配置优先于全局配置。
Builder模式的定义是"将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表
示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范
围,就可以使用工厂模式和Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加
复杂的对象的构建,甚至只会构建产品的一个部分,直白来说,就是使用多个简单的对象一步一步构建
成一个复杂的对象
Mybatis中的体现:
SqlSessionFactory 的构建过程:
Mybatis的初始化工作非常复杂,不是只用一个构造函数就能搞定的。所以使用了建造者模式,使用了
大 量的Builder,进行分层构造,核心对象Configuration使用了 XmlConfigBuilder来进行构造
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的
MybatisMapConfig.xml 和所有的 *Mapper.xml 文件,构建 Mybatis 运行的核心对象 Configuration
对 象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。
在Mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂
模式。
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创
建型模式。
在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建
其他类的实例,被创建的实例通常都具有共同的父类
Mybatis 体现:
Mybatis中执行Sql语句、获取Mappers、管理事务的核心接口SqlSession的创建过程使用到了工厂模
式。
有一个 SqlSessionFactory 来负责 SqlSession 的创建
可以看到,该Factory的openSession ()方法重载了很多个,分别支
持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。
代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式 的
英文叫做Proxy,它是一种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代 理。
Mybatis中实现:
代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接
口,不需要实现,由Mybati s后台帮我们完成具体SQL的执行。
当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又
会调用 mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new
ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} p
ublic Class<T> getMapperInterface() {
return mapperInterface;
} p
ublic Map<Method, MapperMethod> getMethodCache() {
return methodCache;
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new
Class[] { mapperInterface },
mapperProxy);
} p
ublic T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,
mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用
newInstance(MapperProxy mapperProxy)生成代理对象然后返回,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。通
过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给
MapperProxy.invoke方法,而该方法则会调用后续的
sqlSession.cud>executor.execute>prepareStatement 等一系列方法,完成 SQL 的执行和返回。