mybatis 高级映射 查询缓存 spring集合
mybatis是什么?
mbatis是一个持久层框架,mybatis是一个不完全的ORM框架,sql语句需要程序员自己去编写,但是mybatis有映射(输入参数映射、输出结果映射)
mybatis入门门槛不高,学习成本低,让程序员把精力放在sql语句上,对sql语句优化非常方便,
适用于需求变化较多的项目,比如互联网项目。
mybatis框架执行过程:
1、配置mybatis的配置文件,SqlMapConfig.xml(名称不固定)
2、通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂
SqlSessionFactory在实际使用时按单例方式
3、通过SqlSessionFactory创建SqlSession
SqlSession是一个面向用户接口(提供操作数据库方法),实现对象是线程不安全的,
建议sqlSession应用场合在方法体内。
4、调用sqlSession的方法操作数据
如果需要提交事务,需要执行SqlSession的commit()方法。
5、释放资源,关闭SqlSession
mybatis开发dao的方法
1、原始dao的方法
需要程序员编写dao接口和实现类
需要在dao实现类中注入一个SqlSessionFactory工厂
2、mapper代理开发方法
只需要程序员编写mapper接口(就是dao接口)
程序员编写mapper.xml(映射文件)和mapper.java需要遵循一个开发规范:
1、mapper.xml中namespace就是mapper.java的类全路径
2、mapper.xml中statement的id和mapper.java中方法名一致
3、mapper.xml中statement的parameterType指定输入参数的类型和mapper.java的方法输入参数类型一致。
4、mapper.xml中statement的resultType指定输出结果的类型和mapper.java的方法返回值类型一致
SqlMapConfig.xml配置文件:可以配置properties属性、别名、mapper加载
输入映射:
parameterType:指定输入参数类型可以简单类型、pojo、hashmap。。
对于综合查询,建议parameterType使用包装的pojo,有利于系统的扩展
输出映射:
resultType:
查询到的列名和resultType指定的pojo的属性名一致,才能映射成功
resultMap:
可以通过resultMap完成一些高级映射
如果查询到的列名和映射的pojo的属性名不一致时,通过resultMap设置列名和属性名之间的对应关系(映射关系)。
可以完成映射
高级映射:
将关联查询的列映射到一个pojo属性中。(一对一)
将关联查询的列映射到一个List<pojo>中。(一对多)
动态sql:(重点)
if判断
where
foreach
sql片段
对订单商品数据模型进行分析
高级映射:
实现一对一查询、一对多、多对多查询
延迟加载
查询缓存:
一级缓存
二级缓存
mybatis和spring整合
逆向工程
1、订单商品数据模型
1.1、数据模型分析思路
1、每张表记录的数据内容型
分模块对每张表记录的内容进行熟悉,相当于学习系统需求(功能)的过程
2、每张表重要的字段设置
非空字段、外键字段
3、数据库级别表与表之间的关系
外键关系
4、表与表之间的业务关系
在分析表与表之间的业务关系时一定要建立在某个业务意义基础上去分析
表与表之间的业务关系:
在分析表与表之间的业务关系时需要建立在某个业务基础上去分析。
先分析数据级别之间有关系的表之间的业务关系:
user和orders:
user-》orders:一个用户可以创建多个订单,一对多
orders-》user:一个订单只由一个用户创建,一对一
orders和orderdetail:
orders-》orderdetail:一个订单可以包括多个订单明细,因为一个订单可以购买多个商品,
每个商品的购买信息在orderdetail记录,一对多关系
orderdetail-》orders:一个订单明细只能包括在一个订单中,一对一
orderdetail和items:
orderdetail-》items:一个订单明细只对应一个商品信息,一对一
items-》orderdetail:一个商品可以包括在多个订单明细,一对多
再分析数据库级别没有关系的表之间是否有业务关系:
orders和items:
orders和items之间可以通过orderdetail表建立关系
由业务关系决定一对多还是一对一
2、一对一查询
2.1、需求
查询订单信息,关联查询
2.2、resultType
2.2.1、sql语句
确定查询的主表:订单表
确定查询的关联表:用户表
关联查询使用内连接?还是外连接?
由于orders表中有一个外键(user_id)通过外键关联查询用户表只能查询出一条记录,可以使用内连接
SELECT
orders.*,user.`username`,user.`sex`,user.`address`
FROM
orders,USER
WHERE
orders.`user_id` = user.`id`
2.2.2、创建pojo
将上边sql查询的结果映射到pojo中,pojo中必须包括所有查询列名
原始的Orders.java不能映射全部字段,需要新创建的pojo
创建一个pojo继承包括查询字段较多的pojo
//订单的扩展类
//通过此类映射订单和用户查询的结果,让此类继承包括字段较多的pojo类(然后扩展少的类
public class OrderCustom extends Order{
/**
* 添加用户属性
* User.username
* User.sex
* User/address
*/
private String username;
private String sex;
private String address;
2.2.3、mapper.xml
<mapper namespace="my.mybatis.mapper.OrdersMapperCustom">
<select id="findOrderUser" resultType="my.mybatis.po.OrderCustom">
SELECT
orders.*,user.`username`,user.`sex`,user.`address`
FROM
orders,USER
WHERE
orders.`user_id` = user.`id`
</select>
2.2.4、mapper.java
public interface OrdersMapperCustom {
//用户信息综合查询
public List<OrderCustom> findOrderUser() throws Exception;
}
2.2.5、测试
public class OrdersMapperCustomTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws IOException {
String resource = "SqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void testFindOrderUser() throws Exception {
SqlSession openSession = sqlSessionFactory.openSession();
//创建代理对象
OrdersMapperCustom mapper = openSession.getMapper(OrdersMapperCustom.class);
//调用mapper方法
List<OrderCustom> findOrderUser = mapper.findOrderUser();
System.out.println(findOrderUser);
openSession.close();
}
}
2.3、resultMap
2.3.1、sql语句
同resultType实现的sql
2.3.2、使用resultMap映射的思路
使用resultMap将查询结果中的订单信息映射到Orders对象()中,在orders类中添加User属性
2.3.3、需要Orders类中添加user属性
public class Orders {
private Integer id;
private Integer user_id;
private Integer number;
private Date createtime;
private String note;
*** private User user;
2.3.4、mapper.xml
2.3.4.1、定义resultMap
<!-- 定义resultMap
将SELECT id id_,username username_ FROM USER 和User类中的属性作一个映射关系
type:resultMap最终映射的java对象类型,可以使用别名
id:对resultMap的唯一标识
-->
<resultMap type="orders" id="ordersUserResultMap">
<!-- 配置映射的订单信息
id表示查询结果集中唯一标识,订单信息中的唯一标识,如果有多个列组成唯一标识,配置多个id
cloumn:订单信息的唯一标识列
property:订单信息的唯一标识 列锁映射到Orders中那个属性
最终resultMap对column和property作一个映射关系(对应关系)
-->
<id column="id" property="id"/>
<result column="user_id" property="user_id"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 配置映射的关联的用户信息 -->
<!-- association:用于映射关联查询单个对象的信息
property:要将关联查询的用户信息映射到Orders哪个属性
-->
<association property="user" javaType="user">
<!-- id:关联查询用户的唯一标识
column:指定唯一标识用户信息的列
javaType:映射到user的那个属性
-->
<id column="user_id" property="id"/>
<id column="username" property="username"/>
<id column="sex" property="sex"/>
<id column="address" property="address"/>
</association>
</resultMap>
2.3.4.2、statement定义
<select id="findOrdersByResultMap" resultMap="ordersUserResultMap">
SELECT
orders.*,user.`username`,user.`sex`,user.`address`
FROM
orders,USER
WHERE
orders.`user_id` = user.`id`
</select>
2.3.5、mapper.java
//查询订单关联查询用户使用resultMap
public List<OrderCustom> findOrdersByResultMap() throws Exception;
2.4、resultType和resultMap实现一对一查询小结
实现一对一查询
resultType:使用resultType较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
如果没有查询结果的特殊要求建议使用resultType
resultMap:需要单独定义resultMap,实现有点麻烦,如果有对查询有特殊的要求,
使用resultMap可以完成将关联查询映射pojo的属性中
resultMap可以实现延迟加载,resultType无法实现延迟加载
3、一对多查询
3.1、需求
查询订单及订单明细的信息
3.2、sql语句
确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可
SELECT
orders.*,user.`username`,user.`sex`,user.`address`,orderdetail.`items_id`,orderdetail.items_num,orderdetail.`orders_id`
FROM
orders,USER,orderdetail
WHERE
orders.`user_id` = user.`id` AND orderdetail.`orders_id` = orders.`id`
3.3、分析
使用resultType将上边的查询结果映射到pojo中,订单信息有重复
要求:
对orders映射不能出现重复记录
在orders.java类中添加List<orderDetail> orderDetails属性
最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetail属性
映射成的orders记录数为两条(orders信息不重复)
每个orders中的orderDetails属性存储了该订单所对应的订单明细
3.4、在orders中添加list订单明细属性
public class Orders {
private Integer id;
private Integer user_id;
private Integer number;
private Date createtime;
private String note;
//用户信息
private User user;
//订单明细
private List<Orderdetail> orderdetails;
3.5、mapper.xml
<!-- 查询订单关联查询用户及订单明细,使用resultmap -->
<select id="findOrdersAndOrderdetailResultMap" resultMap="OrdersAndOrderdetailResultMap">
SELECT
orders.*,
user.`username`,
user.`sex`,
user.`address`,
orderdetail.`items_id` orederdetail_id,
orderdetail.items_num,
orderdetail.`orders_id`
FROM
orders,USER,orderdetail
WHERE
orders.`user_id` = user.`id` AND orderdetail.`orders_id` = orders.`id`
</select>
3.6、resultMap定义
<!-- 订单及订单明细的resultMap
使用extends继承,不用在此配置订单信息和用户信息的映射
-->
<resultMap type="orders" id="OrdersAndOrderdetailResultMap" extends="ordersUserResultMap">
<!-- 订单信息 -->
<!-- 用户信息 -->
<!-- 使用extends继承,不用再其中配置订单信息和用户信息的映射 -->
<!-- 订单明细信息
一个订单查了出了多条明细,要使用collection进行映射
collection:对关联查询到多条记录映射到集合对象中
property:将关联查询到的多条记录映射到orders的orderdetails属性
ofType:指定映射到集合属性中pojo的类型
-->
<collection property="orderdetails" ofType="my.mybatis.po.Orderdetail" >
<!-- id:订单明细的唯一标识
property:要将订单明细的唯一标识映射到my.mybatis.po.Orderdetail的哪个属性
-->
<id column="orederdetail_id" property="id"></id>
<result column="items_id" property="items_id"></result>
<result column="items_num" property="items_num"></result>
<result column="orders_id" property="orders_id"></result>
</collection>
</resultMap>
3.7、mapper.java
//查询订单关联查询用户使用resultMap
public List<OrderCustom> findOrdersAndOrderdetailResultMap() throws Exception;
3.8、小结
mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中
使用resultType实现
将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,将订单明细放在orderdetails中。
resultType再写个pojo类 或者在继承了一个类中写上其他所需的字段
去重
for (int i = 0; i < findOrderUser.size()-1; i++) {
for (int j = findOrderUser.size()-1; j >i; j--) {
if(findOrderUser.get(i).getOrders_id().equals(findOrderUser.get(j).getOrders_id()))
findOrderUser.remove(j);
}
}
4、多对多查询
4.1、需求
查询用户及用户购买商品信息
4.2、sql语句
查询主表是:用户表
关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表:
orders、orderdetail、items
SELECT
orders.*,
user.`username`,
user.`sex`,
user.`address`,
orderdetail.`id` orederdetail_id,
orderdetail.`items_id`,
orderdetail.items_num,
orderdetail.`orders_id`,
items.`name` items_name,
items.`detail` items_detail,
items.`price` items_price
FROM
orders,USER,orderdetail,items
WHERE
orders.`user_id` = user.`id` AND orderdetail.`orders_id` = orders.`id` AND orderdetail.`items_id` =items.`id`
4.3、映射思路
将用户信息映射到user中
在user类中添加订单列表属性List<Orders> orderslist.将用户创建的订单映射到orderslist
在Orders中添加订单明细列表属性 List<Orderdetail> orderdetails.将订单明细映射到orderdetails
在Orderdetail中添加items属性,将订单明细锁对应的商品映射到items
4.4、mapper.xml
<!-- 查询订单关联查询用户及订单明细,使用resultmap -->
<select id="findOrdersAndOrderdetailAndItemsResultMap" resultMap="OrdersAndOrderdetailAndItemsResultMap">
SELECT
orders.*,
user.`username`,
user.`sex`,
user.`address`,
orderdetail.`id` orederdetail_id,
orderdetail.`items_id`,
orderdetail.items_num,
orderdetail.`orders_id`,
items.`name` items_name,
items.`detail` items_detail,
items.`price` items_price
FROM
orders,USER,orderdetail,items
WHERE
orders.`user_id` = user.`id` AND orderdetail.`orders_id` = orders.`id` AND orderdetail.`items_id` =items.`id`
</select>
4.5、resultMap定义
<!-- 查询用户及购买的产品 -->
<resultMap type="user" id="UserAndItemsResultMap">
<!-- 用户信息 -->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<!-- 订单信息
一个用户对应多个订单,使用collection映射
-->
<collection property="ordersList" ofType="my.mybatis.po.Orders">
<id column="id" property="id"/>
<result column="user_id" property="user_id"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 订单明细
一个订单包括多个明细
-->
<collection property="orderdetails" ofType="my.mybatis.po.Orderdetail">
<id column="orederdetail_id" property="id"></id>
<result column="items_id" property="items_id"></result>
<result column="items_num" property="items_num"></result>
<result column="orders_id" property="orders_id"></result>
<!-- 商品信息
一个订单明细对应一个商品
-->
<association property="items" javaType="my.mybatis.po.Items">
<id column="items_id" property="id"></id>
<result column="items_name" property="name"/>
<result column="items_detail" property="detail"/>
<result column="items_price" property="price"/>
</association>
</collection>
</collection>
</resultMap>
4.6、mapper.java
//查询用户购买商品信息
public List<User> findOrdersAndOrderdetailAndItemsResultMap() throws Exception;
4.7、多对多查询总结
将查询用户购买的商品信息明细清单(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)
针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能
一对多是多对多的特例,如下需求
查询用户购买的商品信息,用户和商品的关系是多对多
需求一:
查询字段:用户帐号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表
使用resultType将上边查询列表映射到pojo输出
需求2:
查询字段:用户帐号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中
总结:
使用resultMap是针对那些对查询结果映射有特殊要求的功能,比如特殊要求映射成list中包括多个list
5、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集合中。
6、延迟加载
6.1、什么是延迟加载
resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
需求:
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息,把对用户信息的按需去查询就是延迟加载
延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
6.2、使用association实现延迟加载
6.2.1、需求
查询订单并且关联查询用户信息
6.2.2、mapper.xml
需要定义两个mapper的方法对应的statement
1、只查询订单信息
SELECT * FROM orders;
在查询订单的statement中使用association去延迟加载(执行)下边的statement;
2、关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
使用UserMapper.xml中的findUset
6.2.3、延迟加载resultMap
使用association中的select指定延迟加载去执行的statement的id
<!-- 延迟加载的resultMap -->
<resultMap type="orders" id="OrderUserLazyLoadingResultMap">
<!-- 对订单信息进行映射配置 -->
<id column="id" property="id"></id>
<result column="user_id" property="user_id"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 实现对用户信息进行延迟加载
select:指定延迟加载需要执行的statement的id(根据user_id查询用户信息的statement)
要使用userMapper.xml中findUserById完成根据用户id(user_id)用户信息的查询,如果findUserById不在本mapper中,需要前边加namespace
column:订单信息中关联用户信息查询的列,是user_id
关联查询的sql理解为:
SELECT orders.*,
(SELECT username FROM USER WHERE orders.`user_id` = user.id) username,
(SELECT sex FROM USER WHERE orders.`user_id` = user.id) sex
FROM
orders;
-->
<association property="user" javaType="user"
select="my.mybatis.mapper.UserMapper.findUserList" column="user_id">
<!-- 实现对用户信息进行延迟加载 -->
</association>
</resultMap>
6.2.4、mapper.java
//查询订单关联查询用户,
public List<Orders> findOrderUserLazyLoading() throws Exception;
6.2.5、测试
6.2.5.1、测试思路:
1、执行上边mapper方法(findOrdersUserLazyLoading),内部去调用my.mybatis.mapper.OrdersMapperCustom中的indOrdersUserLazyLoading值查询orders信息(单表)
2、在程序中去变量上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载
3、延迟加载,去调用UserMapper.xml中findUserById这个方法获取用户信息
6.2.5.2、延迟加载配置
在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
设置项 描述 允许值 默认值
lazyLoadingEnabled 全局性设置懒加载,如果设置为false,则所有相关联的都会被初始化记载 true|false false
aggressiveLazyLoading 当设置为true的时候,懒加载的对象可能被任何属性全部加载。否则,每个属性都按需加载 true|false true
在SqlMapConfig.xml中配置
<!-- 全局配置参数 -->
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为小计加载即按需要加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
6.2.5.3、测试代码
//查询订单关联查询用户,用户信息使用延迟加载
@Test
public void testFindOrderUserLazyLoading() throws Exception {
SqlSession openSession = sqlSessionFactory.openSession();
//创建代理对象
OrdersMapperCustom mapper = openSession.getMapper(OrdersMapperCustom.class);
//调用mapper方法 查询订单信息(单表)
List<Orders> list = mapper.findOrderUserLazyLoading();
//遍历上边的订单列表
for(Orders orders:list) {
//执行getUser()去查询用户信息,这里实现按需加载
User user = orders.getUser();
System.out.println(user);
}
openSession.close();
}
6.2.6、延迟加载思考
不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载?
实现方法如下:
定义两个mapper方法:
1、查询订单列表
2、根据用户id查询用户信息
实现思路:
先查询第一个mapper方法,获取订单信息列表
在程序中(service)按需去调用第二个mapper方法去查询用户信息
总之:
使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其他信息
7、查询缓存
7.1、什么是查询缓存
mybatis提供查询缓存,用户减轻数据压力,提高数据库性能
mybatis提供一级缓存,和二级缓存
一级缓存是SqlSession级别的缓存,在操作数据库时需要构造sqlsession对象,
在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,
多个sqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
为什么要用缓存?
如果缓存中有数据就不用从数据库中获取,大大提高系统性能
7.2、一级缓存
7.2.1、一级缓存工作原理
根据id查询用户的一级缓存图解
一级缓存区域是根据sqllSession为单位划分的
每次查询会先从缓存区域找,如果找不到从数据库察徐某。查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+sql语句。value为从查询出来映射生成的java对象
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息
得到用户信息,将用户信息存储到一级缓存中
如果SqlSession去执行commit操作(执行插入、更新、删除)清空SqlSession中的一级缓存,
这样做的目的是为了让缓存中存储的是最新的信息,避免脏读
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息
7.2.2、一级缓存测试
mybatis默认支持一级缓存,不需要在配置文件去配置
7.3、二级缓存
7.3.1、原理
首先开启mybatis的二级缓存
sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中
sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper二级缓存区域
UserMapper有一个二级缓存区域(按namespace分)其他mapper也有自己的二级缓存区域(按namespace分)
每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据库将存在相同的二级缓存区域中
7.3.2、开启二级缓存
mybatis的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存
在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
描述 允许值 默认值
cacheEnabled 对在此配置文件下的所有cache进行全局性开/关设置 true false true
在UserMapper.xml中开启二级缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)
<mapper namespace="my.mybatis.mapper.UserMapper">
<!-- 开启本mapper的namespace下的二级缓存 -->
<cache/>
7.3.3、调用pojo类实现序列化接口
public class User implements Serializable {
//属性名和数据库表的字段相对应
private int id ;
private String username;//用户姓名
private String sex;//性别
private Date birthday;//生日
private String address;//地址
为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一样在内存
7.3.4、测试方法
// 二级缓存测试
@Test
public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
//查询使用一个SqlSession
//第一次发起请求,查询id为1的用户
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//这里执行关闭操作,将sqlSession1中的数据写到二级缓存区域
sqlSession1.close();
//使用sqlSession3执行commit()操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user3 = userMapper3.findUserById(1);
//更新user3的信息
user3.setUsername("小光");
userMapper3.updateUser(user3);
//执行提交,清空USerMapper下边的二级缓存
sqlSession3.commit();
sqlSession3.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
//第二次发起请求,查询id为1的用户
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
7.3.5、useCache配置
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,
默认情况是true,即该sql使用二级缓存
<select id="findUserById" parameterType="java.lang.Integer" resultType="my.mybatis.po.User" useCache="false">
总结:针对每次查询都需要最新的数据sql,要设置成useCache=false;
7.3.6、刷新缓存(就是清空缓存)
在mapper的同一个namespace中,如果有其他insert、update、delete操作数据后要刷新缓存,
如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache="true"属性,默认情况下为true即刷新缓存,如果改成false则不会刷新,
使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<select id="findUserById" parameterType="java.lang.Integer" resultType="my.mybatis.po.User" flushCache="true">
总结:一般情况下执行完commit操作需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读
7.3.7、Mybatis Cache参数
flushInterval(刷新间隔)可以被设置成任意的正整数,而且它们代表一个合理的毫秒形式的时间段,
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,缓存的对象数目和运行环境的可用内存资源数目。默认值为1024
readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。
因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。
这回慢一些,但是安全,因此默认是false
<cache flushInterval="6000" eviction="FIFO" size="512" readOnly="true"/>
这个更高级的配置创建了一个FIFO缓存,并每隔60秒刷新,存数结果对象或列表的512个引用,而且返回的对象被认为是只读的,
因此在不同线程中的调用者之间修改他们会导致冲突。可用的收回策略 默认的是LRU
1、LRU:最近最少使用的,移除最长时间不被使用的对象
2、FIFO:先进先出:按对象进入缓存的顺序来移除他们
7.4、mybatis整合ehcache
ehcache是一个分布式缓存框架
7.4.1、分布缓存
系统为了提高系统并发,性能,一般对系统进行分布式部署(集群部署方式)
不使用分布缓存,缓存的数据在各各服务器单独存储,不方便系统开发,所以要使用分布式缓存对缓存数据进行集中管理
mybatis无法实现分布式缓存,需要和其他分布式缓存框架进行整合
7.4.2、整合方法
mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类
cache接口
public interface Cache {
/**
* @return The identifier of this cache
*/
String getId();
/**
* @param key Can be any object but usually it is a {@link CacheKey}
* @param value The result of a select.
*/
void putObject(Object key, Object value);
/**
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
mybatis默认实现cache类:
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
7.4.3、加入ehcache包
7.4.4、整合ehcache
配置mapper中cache中的type为ehcache对cache接口的实现类型
<mapper namespace="my.mybatis.mapper.UserMapper">
<!-- 开启本mapper的namespace下的二级缓存
type:指定cache接口的实现类的类型,mybatis默认使用PerpertualCache
要和ehcache整合,需要配置type为ehcache实现cache接口的类型
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
type的类在mybatis-ehcachexxx.jar中找
7.4.5、加入ehcache的配置文件
在classpath下配置ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
</ehcache>
在ehcache-core-2.7.0.jar中有个ehcache-failsafe.xml 里面的默认配置
属性说明:
diskStore:指定数据在磁盘中的存储位置
defaultCache:当借助CacheManager.add("demoCache")创建cache时,Ehcache便会采用<defaultCache/>指定的管理策略
以下属性是必须的:
maxElementsMemory:在内存中缓存的element的最大数目
maxElementsOnDisk:在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal:设定缓存的elements是否永远不过期,如果为true,则缓存的数据始终有效,
如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。
overflowToDisk:设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上,以下属性是可选的
timeToIdleSeconds:当缓存在EhCache中的数据前后两次访问的时间超过TimeToIdleSeconds的属性取值时,
这些数据便会删除,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:缓存elements的有效生命期,默认是0,也就是element存活时间无穷大,
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小默认是30Mb,每个cache都应该有自己的一个缓冲区
7.5、二级应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,
提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,
比如设置为30分钟、60分钟、24小时等,根据需求而定
7.6、二级缓存局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,
由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存
就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其他商品的信息。
因为mybatis的二级缓冲区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空,
解决此类问题需要在业务层根据需求对数据有针对性缓存。
8、spring和mybatis整合
8.1、整合思路
需要spring通过单例方式关联SqlSessionFactory。
spring和mybatis整合生成对立对象,使用SqlSessionFactory创建SqlSession(spring和mybatis整合自动完成)
持久层的mapper都需要由spring进行管理
8.2、整合环境
创建一个新的java工程(接近实际开发的工程结构)
jar包:
mybatis-3.4.6的jar包
spring-3.2.5的jar包
mybatis和spring的整合包:早期ibatis和spring整合由spring官方提供,mybatis和spring整合由mybatis提供
工程结构
8.3sqlSessionFactory
在applicationContext.xml皮质sqlSessionFactory
sqlSessionFactory在mybatis和spring整合包下
<!-- 加载配置文件 -->
<context:property-placeholder
location="classpath:db.properties"
/>
<!-- 数据源,使用dbcp -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxIdle" value="5"></property>
<property name="maxActive" value="10"></property>
</bean>
<!-- sqlSessionFactory在mybatis-spring.jar中找-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 加载mybatis的配置文件 -->
<property name="configLocation" value="mybatis/SqlMapConfig.xml"></property>
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
8.4、原始dao开发(和spring整合后)
8.4.1、mapper.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">
<mapper namespace="test">
<!-- 在映射文件中配置很多sql语句
需求:通过id查询用户表的记录
通过select执行数据库查询
id:标识映射文件中的sql
将sql语句封装到mappedStatement对象中,所以将id称为statement的id
#{}表示一个占位符号
#{}:其中的id表示接入输入的参数,参数名称就是id,如果输入参数是简单类型,#{}中的参数名可以任意,可以value或者其他名称
resultType:指定sql输出结果所映射的java对象类型,select指定resultType表示将单条记录映射成的java对象
-->
<select id="findUserById" parameterType="int" resultType="my.mybatis.po.User">
SELECT * FROM USER WHERE id=#{value}
</select>
</mapper>
在sqlMapConfig.xml中加载User.xml
<!-- 加载映射文件 -->
<mappers>
<mapper resource="sqlmap/User.xml"/>
8.4.2、dao(实现类继承SqlSessionDaoSupport)
public interface UserDao {
//根据id查询用户信息
public User findUserById(int id) throws Exception;
}
dao接口实现类需要注入sqlSessionFactory,通过spring进行注入
这里spring声明配置方式,配置dao的bean
让UserDaoImpl实现类继承SqlSessionDaoSupport
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao{
public User findUserById(int id) throws Exception {
SqlSession sqlSession = this.getSqlSession();
User user = sqlSession.selectOne("test.findUserById", id);
//释放资源
sqlSession.close();
return user;
}
}
8.4.2.1、Dao接口实现类继承SqlSessionDaoSupport
使用此种方法即原始dao开发方法,需要编写dao接口,dao接口实现类、映射文件
1、在sqlMapConfig.xml中配置映射文件的位置
<mappers>
<mapper resource="mapper.xml文件的地址"/>
</mappers>
2、定义dao接口
3、定义dao接口
dao接口实现类方法中可以this.getSqlSession()进行数据增删改查
4、spring配置
<bean id="userDao" class="my.mybatis.dao.UserDaoImpl"></bean>
8.4.3、配置dao
在applicationContext.xml中配置dao
<!-- 原始dao接口 -->
<bean id="userDao" class="my.mybatis.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
8.4.4、测试程序
public class UserDaoImplTest {
private ApplicationContext applicationContext;
//在setUp这个方法得到spring容器
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
}
@Test
public void testFindUserById() throws Exception {
UserDao userDao= (UserDao) applicationContext.getBean("userDao");
//调用userDao的方法
User user= userDao.findUserById(1);
System.out.println(user);
}
}
8.5、mapper代理开发
8.5.1、mapper.xml和mapper.java
8.5.2、通过MapperFactoryBean创建代理对象
<!-- mapper配置
MapperFactoryBean:根据mapper接口生成代理对象
-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- mapperInterface指定mapper接口 -->
<property name="mapperInterface" value="my.ssm.mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
此方法问题:
需要针对每个mapper进行配置,麻烦
8.5.3、通过MapperScannerConfigurer进行mapper扫描(建议使用)
<!-- mapper批量扫描,从mapper包中扫描出mapper接口,自动创建代理对象并且在spring容器注册
遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称名保持一致,且在一个目录中
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的包名
如果扫描多个包,每个包中间使用半角逗号分隔
-->
<property name="basePackage" value="my.ssm.mapper"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
如果仍使用之前的SqlSessionFactory,properties不会被加载进来,eclipse也会报错
SqlMapConfig.xml
和spring 整合后,使用mapper扫描器,这里不需要配置了
<!-- <package name="my.ssm.mapper"/> -->
8.5.4、测试代码
public class UserMapperTest {
private ApplicationContext applicationContext;
//在setUp这个方法得到spring容器
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
}
@Test
public void testFindUserById() throws Exception {
UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");
User findUserById = userMapper.findUserById(1);
System.out.println(findUserById);
}
}
9、逆向工程
9.1、什么是逆向工程
mybatis需要程序员自己编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java和mapper、po)
企业开发中,常用的逆向工程方式:
由数据库的表生成java代码
9.2、下载逆向工程
带有docs其中的index.html可以查看配置文件和运行程序
xml文件编写
9.3、使用方法
9.3.1、运行逆向工程
还可以通过eclipse的插件生成代码
建议使用java程序方式,不依赖开发工具
9.3.2、生成代码配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<classPathEntry location="./src" />
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否取出自动生成的注释 true:是 false:否 -->
<property name="suppressAllcomments" value="true" />
</commentGenerator>
<!-- 数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root"
password="123">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:root"
userId="root" password="123"> </jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL和NUMERIC类型解析为Integer,为true时把 JDBC DECIMAL 和NUMERIC解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="my.ssm.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值是否被清理前后代的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="my.ssm.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetProject:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="my.ssm.mapper" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
<!-- 有些表的字段需要指定java类型 -->
<!-- <table schema="" tableName=""> <columnOverride column="" javaType=""></columnOverride>
</table> -->
</context>
</generatorConfiguration>
9.3.3、执行生成程序
public class GeneratorSqlmap {
public static void main(String[] args) {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (XMLParserException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public void generator() throws Exception, XMLParserException {
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);
}
}
生成后的代码
9.3.4、使用生成的代码
需要将生成工程中所生成的打码拷贝到自己的工程中
测试itemsMapper中的方法
public class ItemsMapperTest {
private ApplicationContext applicationContext;
private ItemsMapper itemsMapper;
@Before
public void setUp() throws Exception{
applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
itemsMapper = (ItemsMapper) applicationContext.getBean("itemsMapper");
}
//插入
@Test
public void testInsert() {
//构造items对象
Items items = new Items();
items.setName("手机");
items.setPrice(2133f);
itemsMapper.insert(items);
}
//根据自定义条件查询
@Test
public void testSelectByExample() {
ItemsExample itemsExample = new ItemsExample();
//通过criteria构造查询条件
Criteria createCriteria = itemsExample.createCriteria();
createCriteria.andPicLike("%pic%");
//可能返回多条记录
List<Items> list = itemsMapper.selectByExampleWithBLOBs(itemsExample);
System.out.println(list);
}
//根据主键查询
@Test
public void testSelectByPrimaryKey() {
Items items = itemsMapper.selectByPrimaryKey(1);
System.out.println(items);
}
//更新数据
@Test
public void testUpdateByPrimaryKey() {
//对所有的字段更新,先查询出来再更新
Items items = itemsMapper.selectByPrimaryKey(1);
items.setName("杯具");
itemsMapper.updateByPrimaryKey(items);
//如果传入的字段不为空才更新,一般在批量更新中使用,不需要先查询再更新
// itemsMapper.updateByPrimaryKeySelective(record);
}
}