Mybatis(二)

Mybatis框架学习

一、关联查询
二、查询缓存
三、与Spring整合
四、Mybatis 逆向工程


关联查询

商品订单数据模型

数据模型分析思路

  1. 每张表记录的数据内容
    分模块对每张表记录的内容进行熟悉,相当于学习系统需求(功能)的过程

  2. 每张表数据库字段设置
    非空字段,外键字段

  3. 数据库级别表与表之间的关系
    外键关系

  4. 表与表之间的业务关系
    在分析表与表之间的业务关系时一定要建立在某个业务意义基础上去分析

数据模型的分析

用户表User:
记录了购买商品的用户信息

订单表orders:
记录了用户所创建的订单

订单明细表odersdetail:
记录了订单的详细信息,即购买的商品信息

商品表:items
记录了商品信息

一对一查询

查询订单信息,管理查询创建订单的与用户信息

注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信 息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用 户可以下多个订单。

方法一:使用 resultType

定义订单信息 po 类,此 po 类中包括了订单信息和用户信息

sql语句

1
2
3
SELECT orders.*, user.username, userss.address 
FROM orders, user
WHERE orders.user_id=user.id

定义 po 类

1
2
3
4
public class OrdersCustom extends Orders {
private String username;// 用户名称
private String address;// 用户地址
get/set。。。

OrdersCustom 类继承 Orders 类后 OrdersCustom 类包括了 Orders 类的所有字段,只需要定义 用户的信息字段即可

Mapper.xml

1
2
3
4
5
6
<!-- 查询所有订单信息 --> 
<select id="findOrdersList" resultType="cn.itcast.mybatis.po.OrdersCustom">
SELECT orders.*, user.username, user.address
FROM orders,user
WHERE orders.user_id = user.id
</select>

Mapper 接口
public List findOrdersList() throws Exception;

测试:

1
2
3
4
5
6
7
8
9
10
Public void testfindOrdersList()throws Exception{ 
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<OrdersCustom> list = userMapper.findOrdersList();
System.out.println(list);
//关闭session s
ession.close();

方法二:使用 resultMap

定义专门的 resultMap 用于映射一对一查询结果。

SQL语句

1
2
3
SELECT orders.*, user.username, user.address 
FROM orders, user
WHERE orders.user_id=user.id

定义 po 类

在 Orders 类中加入 User 属性,user 属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个 User 对象存储关联查询的用户信息。

Mapper.xml

1
2
3
4
5
<select id="findOrdersListResultMap" resultMap="userordermap"> 
SELECT orders.*, user.username, user.address
FROM orders,user
WHERE orders.user_id = user.id
</select>

这里 resultMap 指定 userordermap 。

定义 resultMap:
需要关联查询映射的是用户信息,使用 association 将用户信息映射到订单对象的用户属性中。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 订单信息resultmap --> 
<resultMap type="cn.itcast.mybatis.po.Orders" id="userordermap">
<!-- 这里的id,是mybatis在进行一对一查询时将user字段映射为user对象时要使用, 必须写 -->
<id property="id" column="id"/>
<result property="user_id" column="user_id"/>
<result property="number" column="number"/>
<association property="user" javaType="cn.itcast.mybatis.po.User">
<!-- 这里的id为user的id,如果写上表示给user的id属性赋值 -->
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
</association>
</resultMap>

association:表示进行关联查询单条记录
property:表示关联查询的结果存储在 cn.itcast.mybatis.po.Orders 的 user 属性中
javaType:表示关联查询的结果类型

:查询结果的 user_id 列对应关联对象的 id 属性,这里是表示 user_id 是关联查询对象的唯一标识。

:查询结果的 username 列对应 关联对象的 username 属性。

resultType 和resultMap小结

实现一对一查询
resultType:使用resultType简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。如果没有查询结果的特殊要求,建议使用

resultMap:需要单独定义resultMap,实现有点麻烦,如果有对查询结果的特殊要求,使用resultMap可以完成将关联查询映射pojo的属性中

resultMap可以实现延迟加载,resultType不行。

一对多查询

案例:查询所有订单信息及订单下的订单明细信息。
订单信息与订单明细为一对多关系。
使用 resultMap 实现如下:

Sql 语句:

1
2
3
4
5
6
7
8
9
SELECT orders.*, 
user.username,
user.address,
orderdetail.idorderdetail_id,
orderdetail.items_id,
orderdetail.items_num
FROM orders,user,orderdetail
WHEREorders.user_id=user.id
AND orders.id=orderdetail.orders_id

定义 po 类

在 Orders 类中加入 User 属性。 在 Orders 类中加入 Listorderdetails 属性

Mapper.xml

1
2
3
4
5
6
7
8
9
10
<select id="findOrdersDetailList" resultMap="userorderdetailmap"> 
SELECT orders.*,
user.username,
user.address,
orderdetail.id orderdetail_id,
orderdetail.items_id,
orderdetail.items_num
FROM orders,user,orderdetail
WHERE orders.user_id = user.id
AND orders.id = orderdetail.orders_id </select>

定义 resultMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 订单信息resultmap --> 
<resultMap type="cn.itcast.mybatis.po.Orders" id="userorderdetailmap">
<id property="id"column="id"/>
<result property="user_id" column="user_id"/>
<result property="number" column="number"/>
<association property="user" javaType="cn.itcast.mybatis.po.User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
</association>


<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id property="id" column="orderdetail_id"/>
<result property="items_id" column="items_id"/>
<result property="items_num" column="items_num"/>
</collection>
</resultMap>

前半部分和上边一对一查询订单及用户信息定义的 resultMap 相同, collection 部分定义了查询订单明细信息。
collection:表示关联查询结果集
property=”orderdetails” : 关联查询的结果集存储在 cn.itcast.mybatis.po.Orders 上哪个属性。
ofType=”cn.itcast.mybatis.po.Orderdetail” : 指定关联查询的结果集中的对象类型 即 List 中的对象类型。

的意义同一对一查询。

resultMap 使用继承

上边定义的 resultMap 中上半部分和一对一查询订单信息的 resultMap 相同,这里使用继承 可以不再填写重复的内容,如下:

1
2
3
4
5
6
7
<resultMap type="cn.itcast.mybatis.po.Orders" id="userorderdetailmap" extends="userordermap"> 
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id property="id" column="orderdetail_id"/>
<result property="items_id" column="items_id"/>
<result property="items_num" column="items_num"/>
</collection>
</resultMap>

使用 extends 继承订单信息 userordermap 。

Mapper 接口:

public ListfindOrdersDetailList () throws Exception;

测试:

1
2
3
4
5
6
7
8
9
10
11
Public void testfindOrdersDetailList()throws Exception{ 
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<Orders> list = userMapper.findOrdersDetailList();
System.out.println(list);
//关闭session
session.close();
}

小结

使用 collection 完成关联查询,将关联查询信息映射到集合对象。

多对多查询

查询用户和商品之间的信息

需求

查询用户购买的商品信息。

SQL

需要查询所有用户信息,关联查询订单及订单明细信息,订单明细信息中关联查询商品信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT orders.*, 
USER.username,
USER.address,
orderdetail.idorderdetail_id,
orderdetail.items_id,
orderdetail.items_num,
items.nameitems_name,
items.detailitems_detail
FROM orders,
USER,
orderdetail,
items
WHERE orders.user_id=USER.id
AND orders.id=orderdetail.orders_id
AND orderdetail.items_id=items.id

po 定义

在 User 中 添 加 List orders 属 性 , 在 Orders 类 中 加 入 List orderdetails 属性

resultMap

需要关联查询映射的信息是:订单、订单明细、商品信息
订单:一个用户对应多个订单,使用 collection 映射到用户对象的订单列表属性中
订单明细:一个订单对应多个明细,使用 collection 映射到订单对象中的明细属性中
商品信息:一个订单明细对应一个商品,使用 association 映射到订单明细对象的商品属性中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 一对多查询 查询用户信息、关联查询订单、订单明细信息、商品信息 --> 
<resultMap type="cn.itcast.mybatis.po.User" id="userOrderListResultMap">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<collection property="orders" ofType="cn.itcast.mybatis.po.Orders">
<id column="id" property="id"/>
<result property="number" column="number"/>
<collection property="orderdetails" ofType="cn.itcast.mybatis.po.Orderdetail">
<id column="orderdetail_id" property="id"/>
<result property="ordersId" column="id"/>
<result property="itemsId" column="items_id"/>
<result property="itemsNum" column="items_num"/>
<association property="items" javaType="cn.itcast.mybatis.po.Items">
<id column="items_id" property="id"/>
<result column="items_name" property="name"/>
<result column="items_detail" property="detail"/>
</association>
</collection>
</collection>

小结

一对多是多对多的特例,如下需求: 查询用户购买的商品信息,用户和商品的关系是多对多关系。
需求 1: 查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见) 企业开发中常见明细列表,用户购买商品明细列表, 使用 resultType 将上边查询列映射到 pojo 输出。

需求 2: 查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细) 使用 resultMap 将用户购买的商品明细列表映射到 user 对象中。

resultMap 小结

resultType: 作用: 将查询结果按照 sql 列名 pojo 属性名一致性映射到 pojo 中。
场合: 常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时, 此时可直接使用 resultType 将每一条记录映射到 pojo 中,在前端页面遍历 list(list 中是 pojo) 即可。

resultMap: 使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
association:
作用: 将关联查询信息映射到一个 pojo 对象中。
场合: 为了方便查询关联信息可以使用 association 将关联订单信息映射为用户对象的 pojo 属 性中,
比如:查询订单及关联用户信息。
使用 resultType 无法将查询结果映射到 pojo 对象的 pojo 属性中,根据对结果集查询遍 历的需要选择使用 resultType 还是 resultMap。
collection:
作用: 将关联查询信息映射到一个 list 集合中。
场合:为了方便查询遍历关联信息可以使用 collection 将关联信息映射到 list 集合中,
比如: 查询用户权限范围模块及模块下的菜单,可使用 collection 将模块映射到模块 list 中,将菜 单列表映射到模块对象的菜单 list 属性中,这样的作的目的也是方便对查询结果集进行遍历 查询。
如果使用 resultType 无法将查询结果映射到 list 集合中。

延迟加载

需要查询关联信息时,使用 mybatis 延迟加载特性可有效的减少数据库压力,首次查询 只查询主要信息,关联信息等用户获取时再加载。

打开延迟加载开关

在 mybatis 核心配置文件中配置: lazyLoadingEnabled、aggressiveLazyLoading

设置项  描述  允许值 默认值
lazyLoadingEnabled 全局性设置懒加载,如果设为‘FALSE’,则所有相关联的都会被初始化加载 TRUE或FALSE FALSE
aggressiveLazyLoading 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载,否则,每个属性都按需加载 true 或false true
1
2
3
4
<settings> 
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

一对一查询延迟加载

查询订单信息,关联查询用户信息。

  1. 默认只查询订单信息
    当需要查询用户信息时再去查询用户信息。在查询订单的statement中使用association去延迟加载下边的statement关联查询用户信息

  2. 关联查询用户信息
    通过上边查询到的订单信息中的User_id去关联查询用户信息

Sql 语句:
SELECT orders.* FROM orders

定义 po 类
在 Orders 类中加入 User 属性。

Mapper.xml

1
2
3
4
<select id="findOrdersList3" resultMap="userordermap2"> 
SELECT orders.*
FROM orders
</select>

定义 resultMap

1
2
3
4
5
6
7
<!-- 订单信息resultmap --> 
<resultMap type="cn.itcast.mybatis.po.Orders" id="userordermap2">
<id property="id" column="id"/>
<result property="user_id" column="user_id"/>
<result property="number" column="number"/>
<association property="user" javaType="cn.itcast.mybatis.po.User" select="findUserById" column="user_id"/>
</resultMap>

association:
select=”findUserById”:指定关联查询 sql 为 findUserById
column=”user_id”:关联查询时将 users_id 列的值传入 findUserById
最后将关联查询结果映射至 cn.itcast.mybatis.po.User 。

Mapper 接口:
public List findOrdersList3() throws Exception;

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Public void testfindOrdersList3()throws Exception{ 
//获取session
SqlSession session = sqlSessionFactory.openSession();
//获限mapper接口实例
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询订单信息
List<Orders> list = userMapper.findOrdersList3();
System.out.println(list);
//开始加载,通过orders.getUser方法进行加载
for(Orders orders:list){
System.out.println(orders.getUser());
}
//关闭session
session.close();
}

延迟加载的思考

不使用 mybatis 提供的延迟加载功能是否可以实现延迟加载?
实现方法: 针对订单和用户两个表定义两个 mapper 方法。
1、订单查询 mapper 方法
2、根据用户 id 查询用户信息 mapper 方法
默认使用订单查询 mapper 方法只查询订单信息。
当需要关联查询用户信息时再调用根据用户 id 查询用户信息 mapper 方法查询用户信息。

查询缓存

mybatis 缓存介绍

Mybatis 一级缓存的作用域是同一个 SqlSession,在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取 数据将不再从数据库查询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的 一级缓存也就不存在了。Mybatis 默认开启一级缓存。

Mybatis 二级缓存是多个 SqlSession 共享的,其作用域是 mapper 的同一个 namespace,不同 的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行 相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从 缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存 需要在 setting 全局参数中配置开启二级缓存。

一级缓存

原理

下图是根据 id 查询用户的一级缓存图解:

一级缓存区域是根据 SqlSession 为单位划分的。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。

Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为 从查询出来映射生成的 java 对象

sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域。

测试 1

1
2
3
4
5
6
7
8
9
10
11
12
//获取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();

测试 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取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);

二级缓存

原理

下图是多个 sqlSession 请求 UserMapper 的二级缓存图解。

二级缓存区域是根据 mapper 的 namespace 划分的,相同 namespace 的 mapper 查询数据放 在同一个区域,如果使用 mapper 代理方法每个 mapper 的 namespace 都不同,此时可以理 解为二级缓存区域是根据 mapper 划分。

每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。

Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为 从查询出来映射生成的 java 对象

sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域。

开启二级缓存:

在核心配置文件 SqlMapConfig.xml 中加入

描述 允许值 默认值
cacheEnabled 对在此配置文件下的所有cache进行全局性开/关设置 true/false true

要在你Mapper 映射文件中添加一行: ,表示此 mapper 开启二级缓存。

实现序列化

二级缓存需要查询结果映射的 pojo 对象实现 java.io.Serializable 接口实现序列化和反序 列化操作,注意如果存在父类、成员 pojo 都需要实现序列化接口。

1
2
3
publicclassOrdersimplementsSerializable
publicclassUserimplementsSerializable
...

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取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();

禁用二级缓存

在 statement 中设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会 发出 sql 去查询,默认情况是 true,即该 sql 使用二级缓存。

刷新缓存

在 mapper 的同一个 namespace 中,如果有其它 insert、update、delete 操作数据后需要刷新 缓存,如果不执行刷新缓存会出现脏读。

设置 statement 配置中的 flushCache=”true” 属性,默认情况下为 true 即刷新缓存,如果改成 false 则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。 如下:

MybatisCache 参数

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式 的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用 内存资源数目。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象 的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回 缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

如下例子:

这个更高级的配置创建了一个FIFO 缓存,并每隔60 秒刷新,存数结果对象或列表的 512 个引 用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。 可用的收回策略有,默认的是 LRU:

  1. LRU– 最近最少使用的:移除最长时间不被使用的对象。
  2. FIFO– 先进先出:按对象进入缓存的顺序来移除它们。
  3. SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  4. WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

mybatis 整合 ehcache

EhCache 是一个纯 Java 的进程内缓存框架,是一种广泛使用的开源 Java 分布式缓存, 具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。
原理:

通过实现 Cache 接口可以实现 mybatis 缓存数据通过其它缓存数据库整合,mybatis 的特长 是 sql 操作,缓存数据的管理不是 mybatis 的特长,为了提高缓存的性能将 mybatis 和第三方 的缓存数据库整合,比如 ehcache、memcache、redis 等。

第一步:引入缓存的依赖包

ehcache-core-2.6.5.jar
mybatis-ehcache-1.0.2.jar

Maven坐标:

1
2
3
4
5
<dependency> 
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.2</version>
</dependency>

第二步:引入缓存配置文件

classpath 下添加:ehcache.xml 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> 
<diskStore path="F:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

属性说明:
diskStore :指定数据在磁盘中的存储位置。
defaultCache :当借助 CacheManager.add(“demoCache”)创建 Cache 时,EhCache 便 会采用指定的的管理策略

以下属性是必须的:

  • maxElementsInMemory - 在内存中缓存的 element 的最大数目
  • maxElementsOnDisk - 在磁盘上缓存的 element 的最大数目,若是 0 表示无穷大
  • eternal- 设定缓存的 elements 是否永远不过期。如果为 true,则缓存的数据始终有效, 如果为 false 那么还要根据 timeToIdleSeconds,timeToLiveSeconds 判断
    overflowToDisk - 设定当内存缓存溢出的时候是否将过期的 element 缓存到磁盘上

以下属性是可选的:

  • timeToIdleSeconds - 当 缓 存 在 EhCache 中 的 数 据 前 后 两 次 访 问 的 时 间 超 过 timeToIdleSeconds 的属性取值时,这些数据便会删除,默认值是 0,也就是可闲置时间无穷 大
  • timeToLiveSeconds - 缓存 element 的有效生命期,默认是 0.,也就是 element 存活时间 无穷大
  • diskSpoolBufferSizeMB 这个参数设置 DiskStore(磁盘缓存)的缓存区大小.默认是 30MB.每个 Cache 都应该有自己的一个缓冲区.
  • diskPersistent- 在 VM 重启的时候是否启用磁盘保存 EhCache 中的数据,默认是 false。
  • diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是 120 秒。每 个 120s,相应的线程会进行一次 EhCache 中数据的清理工作
  • memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的 element 加入的时候, 移 除缓存中 element 的策略。默认是 LRU(最近最少使用),可选的有 LFU(最不常使用)和 FIFO(先进先出)

第三步:开启 ehcache 缓存

EhcacheCache是ehcache对Cache接口的实现

修改mapper.xml文件,在cache中指定EhcacheCache

1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

根据需求调整缓存参数:

1
2
3
4
5
6
7
8
9
<cache type="org.mybatis.caches.ehcache.EhcacheCache" > 
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="3600"/>
<!-- 同ehcache参数maxElementsInMemory -->
<property name="maxEntriesLocalHeap" value="1000"/>
<!-- 同ehcache参数maxElementsOnDisk -->
<property name="maxEntriesLocalDisk" value="10000000"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>

应用场景

对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用 mybatis 二级缓 存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析 sql、电话 账单查询 sql 等。

实现方法如下:通过设置刷新间隔时间,由 mybatis 每隔一段时间自动清空缓存,根据 数据变化频率设置缓存刷新间隔 flushInterval,比如设置为 30 分钟、60 分钟、24 小时等, 根据需求而定。

局限性

mybatis 二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进 行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如 果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新 其它商品的信息,因为 mybaits 的二级缓存区域以 mapper 为单位划分,当一个商品信息变 化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针 对性缓存。

spring与Mybatis整合

实现 mybatis 与 spring 进行整合,通过 spring 管理 SqlSessionFactory、mapper 接口。

mybatis 与 spring 整合 jar

mybatis 官方提供与 mybatis 与 spring 整合 jar 包:
mybatis_spring-1.2.2.jar
以及:
spring3.2.0
mybatis3.2.7
dbcp连接池
数据库驱动

Mybatis配置文件

在classpath下创建mybatis/SqlMapConfig.xml

1
2
3
4
5
6
7
8
<?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>
<!-- 使用自动扫描器时,mapper.xml文件如果和mapper.java接口在一个目录则此处不用 定义mappers -->
<mappers>
<package name="cn.itcast.mybatis.mapper" />
</mappers>
</configuration>

Spring 配置文件:

在 classpath 下创建 applicationContext.xml,定义数据库链接池、SqlSessionFactory。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/contex t" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3. 2.xsd

http://www.springframework.org/schema/mvc

http://www.springframework.org/schema/mvc/spring-mvc-3.2.xs
d
http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-contex t-3.2.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.2.xs
d

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
">

<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="10"/>
<property name="maxIdle" value="5"/>
</bean>
<!-- mapper配置 -->
<!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包 中的 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 加载mybatis的全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
</bean>
</beans>

注意:在定义 sqlSessionFactory 时指定数据源 dataSource 和 mybatis 的配置文件。

Mapper 编写的三种方法

Dao 接口实现类继承 SqlSessionDaoSupport

使用此种方法即原始 dao 开发方法,需要编写 dao 接口,dao 接口实现类、映射文件。

1、 在 sqlMapConfig.xml 中配置映射文件的位置

1
2
3
4
<mappers> 
<mapperresource="mapper.xml 文 件 的 地 址 "/>
<mapperresource="mapper.xml 文 件 的 地 址 "/>
</mappers>

2、 定义 dao 接口
3、dao 接口实现类集成 SqlSessionDaoSupport
dao 接口实现类方法中可以 this.getSqlSession()进行数据增删改查。
4、spring 配置

1
2
3
4
<bean id=" "class="mapper 接 口 的 实 现 ">
<property name="sqlSessionFactory" ref="sqlSessionFactory">
</property>
</bean>

使用 org.mybatis.spring.mapper.MapperFactoryBean

此方法即 mapper 接口开发方法,只需定义 mapper 接口,不用编写 mapper 接口实现类。 每个 mapper 接口都需要在 spring 配置文件中定义。

1、 在 sqlMapConfig.xml 中配置 mapper.xml 的位置 如果 mapper.xml 和 mappre 接口的名称相同且在同一个目录,这里可以不用配置

1
2
3
<mappers> <mapperresource="mapper.xml 文 件 的 地 址 "/>
<mapperresource="mapper.xml 文 件 的 地 址 "/>
</mappers>

2、 定义 mapper 接口

3、Spring 中定义

1
2
3
4
<bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean"> 
<property name="mapperInterface" value="mapper 接 口 地 址 "/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

使用 mapper 扫描器

此方法即 mapper 接口开发方法,只需定义 mapper 接口,不用编写 mapper 接口实现类。 只需要在 spring 配置文件中定义一个 mapper 扫描器,自动扫描包中的 mapper 接口生成代 代理对象。

1、mapper.xml 文件编写,
2、 定义 mapper 接口
注意 mapper.xml 的文件名和 mapper 的接口名称保持一致,且放在同一个目录

3、 配置 mapper 扫描器

1
2
3
4
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
<property name="basePackage" value="mapper 接 口 包 地 址 ">
</property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>

basePackage : 扫 描 包 路 径 , 中 间 可 以 用 逗 号 或 分 号 分 隔 定 义 多 个 包

4、 使用扫描器后从 spring 容器中获取 mapper 的实现对象

如果将 mapper.xml 和 mapper 接口的名称保持一致且放在一个目录 则不用在 sqlMapConfig.xml 中进行配置

Mybatis逆向工程

Mybatis需要程序员自己编写SQL语句,Mybatis官方提供逆向工程,可以针对表单自动生成Mybatis执行所需要的代码(mappe.java ,mapper.xml ,po)

使用官方网站的 mapper 自动生成工具 mybatis-generator-core-1.3.2 来生成 po 类和 mapper 映射文件。

第一步:mapper 生成配置文件:

在 generatorConfig.xml 中配置 mapper 生成的详细信息,注意改下几点:

1、 添加要生成的数据库表
2、po 文件所在包路径
3、mapper 文件所在包路径

配置文件如下: 详见 generatorSqlmapCustom 工程

第二步:使用 java 类生成 mapper 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Public void generator() throws Exception{ 
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator (config, callback, warnings);
myBatisGenerator.generate(null);
}
Public static void main(String[] args) throws Exception {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}

第三步:拷贝生成的 mapper 文件到工程中指定的目录中

Mapper.xml 的文件拷贝至 mapper 目录内

Mapper.java 的文件拷贝至 mapper 目录内

注意:mapperxml 文件和 mapper.java文件在一个目录内且文件名相同。

第四步 Mapper 接口测试

学会使用 mapper 自动生成的增、删、改、查方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//删除符合条件的记录 
int deleteByExample(UserExample example);
//根据主键删除
int deleteByPrimaryKey(String id);
//插入对象所有字段
int insert(User record);
//插入对象不为空的字段
int insertSelective(User record);
//自定义查询条件查询结果集
List<User> selectByExample(UserExample example);
//根据主键查询
UserselectByPrimaryKey(String id);
//根据主键将对象中不为空的值更新至数据库
int updateByPrimaryKeySelective(User record);
//根据主键将对象中所有字段的值更新至数据库
int updateByPrimaryKey(User record);

逆向工程注意事项

Mapper 文件内容不覆盖而是追加

XXXMapper.xml 文件已经存在时,如果进行重新生成则 mapper.xml 文件内容不被覆盖而是进 行内容追加,结果导致 mybatis 解析失败。 解决方法:删除原来已经生成的 mapperxml 文件再进行生成。 Mybatis 自动生成的 po 及 mapper.java 文件不是内容而是直接覆盖没有此问题。

Tableschema 问题

下边是关于针对 oracle 数据库表生成代码的 schema 问题:

Schma 即数据库模式,oracle 中一个用户对应一个 schema,可以理解为用户就是 schema。 当 Oralce 数据库存在多个 schema 可以访问相同的表名时,使用 mybatis 生成该表的 mapper.xml 将会出现 mapper.xml 内容重复的问题,结果导致 mybatis 解析错误。

解决方法:在 table 中填写 schema,如下:

XXXX 即为一个 schema 的名称,生成后将 mapper.xml 的 schema 前缀批量去掉,如果不去掉 当 oracle 用户变更了 sql 语句将查询失败。

快捷操作方式:mapper.xml 文件中批量替换:“fromXXXX.”为空

Oracle 查询对象的 schema 可从 dba_objects 中查询,如下:

select * from dba_objects

Just for fun!
------------- 文章已经到尾 -------------