(1)一个人只要自己不放弃自己,整个世界也不会放弃你. (2)天生我才必有大用 (3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟. (4)做难事必有所得 (5)精神乃真正的刀锋 (6)战胜对手有两次,第一次在内心中. (7)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~
(1)mybatis是一人持久层框架,mybatis是一个不完全的ORM框架。sql语句需要程序员自己去编写,但是mybatis也有映射(输入参数映射、输出结果映射)。
(2)mybatis入门门槛不高,学习成本低,让程序员把精力放在sql语句上,对sql语句优化非常方便,适用与需求变化较多项目,比如互联网项目。
(1)配置mybatis的配置文件,SqlMapConfig.xml(名称不固定) (2)通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂 SqlSessionFactory在实际使用时按单例方式。 (3)通过SqlSessionFactory创建SqlSession SqlSession是一个面向用户接口(提供操作数据库方法),实现对象是线程不安全的,建议sqlSession应用场合在方法体内。 (4)调用sqlSession的方法去操作数据。 如果需要提交事务,需要执行SqlSession的commit()方法。 (5)释放资源,关闭SqlSession
(1)原始dao 的方法 需要程序员编写dao接口和实现类 需要在dao实现类中注入一个SqlSessionFactory工厂。
(2)mapper代理开发方法(建议使用) 只需要程序员编写mapper接口(就是dao接口) 程序员在编写mapper.xml(映射文件)和mapper.java需要遵循一个开发规范:
mapper.xml中namespace就是mapper.java的类全路径。mapper.xml中statement的id和mapper.java中方法名一致。mapper.xml中statement的parameterType指定输入参数的类型和mapper.java的方法输入参数类型一致。mapper.xml中statement的resultType指定输出结果的类型和mapper.java的方法返回值类型一致。可以配置properties属性、别名、mapper加载。。。
resultType: 查询到的列名和resultType指定的pojo的属性名一致,才能映射成功。 reusltMap:
可以通过resultMap 完成一些高级映射。如果查询到的列名和映射的pojo的属性名不一致时,通过resultMap设置列名和属性名之间的对应关系(映射关系)。可以完成映射。(1)对订单商品数据模型进行分析。
(2)高级映射:(了解)
实现一对一查询、一对多、多对多查询。延迟加载(3)查询缓存
一级缓存
二级缓存(了解mybatis二级缓存使用场景)
(4)mybatis和spirng整合(掌握)
(5)逆向工程(会用)
(1)每张表记录的数据内容
分模块对每张表记录的内容进行熟悉,相当 于你学习系统 需求(功能)的过程。
(2)每张表重要的字段设置
非空字段、外键字段
(3)数据库级别表与表之间的关系
外键关系
(4)表与表之间的业务关系
在分析表与表之间的业务关系时一定要建立在某个业务意义基础上去分析。
(1)用户表user:
记录了购买商品的用户信息
(2)订单表:orders
记录了用户所创建的订单(购买商品的订单)
(3)订单明细表:orderdetail:
记录了订单的详细信息即购买商品的信息
(4)商品表:items
记录了商品信息
(1)在分析表与表之间的业务关系时需要建立在某个业务意义基础上去分析。
(2)先分析数据级别之间有关系的表之间的业务关系:
(1)user---->orders:一个用户可以创建多个订单,一对多
(2)orders—>user:一个订单只由一个用户创建,一对一
(1)orders—> orderdetail:一个订单可以包括 多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系
(2)orderdetail–> orders:一个订单明细只能包括在一个订单中,一对一
(1)orderdetail—> items:一个订单明细只对应一个商品信息,一对一
(2)items–> orderdetail:一个商品可以包括在多个订单明细 ,一对多
(1)orders和items:
orders和items之间可以通过orderdetail表建立关系。
查询订单信息,关联查询创建订单的用户信息
将上边sql查询的结果映射到pojo中,pojo中必须包括所有查询列名。
原始的Orders.java不能映射全部字段,需要新创建的pojo。
创建一个pojo继承包括查询字段较多的po类。
同resultType实现的sql一样
使用resultMap将查询结果中的订单信息映射到Orders对象中,在orders类中添加User属性,将关联查询出来的用户信息映射到orders对象中的user属性中。
实现一对一查询:
(1)resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
(2)如果没有查询结果的特殊要求建议使用resultType。
(3)resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的属性中。
(4)resultMap可以实现延迟加载,resultType无法实现延迟加载。
查询订单及订单明细的信息。
使用resultType将上边的 查询结果映射到pojo中,订单信息的pojo就是重复。
要求: (1)对orders映射不能出现重复记录。 (2)在orders.java类中添加List<orderDetail> orderDetails属性。 (3)最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中。(1)mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
(2)使用resultType实现: 将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。
查询用户及用户购买商品信息。
查询主表是:用户表 关联表:由于用户和商品没有直接关联,通过订单和订单明细进行关联,所以关联表: orders、orderdetail、items
SELECT orders.*, user.username, user.sex, user.address, orderdetail.id orderdetail_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(1)将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)
(2)针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。
(3)一对多是多对多的特例,如下需求:
(4)查询用户购买的商品信息,用户和商品的关系是多对多关系。 需求1:
查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。
(5)需求2: 查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细) 使用resultMap将用户购买的商品明细列表映射到user对象中。
(6)总结:
使用resultMap是针对那些对查询结果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多个list。
(1)作用:
将查询结果按照sql列名pojo属性名一致性映射到pojo中。
(2)场合:
常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。
使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
(1)作用:
将关联查询信息映射到一个pojo对象中。
(2)场合:
为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
(1)作用:
将关联查询信息映射到一个list集合中。
(2)场合:
为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。
(1)resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
(2)需求:
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。
(3)延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高 数据库性能,因为查询单表要比关联查询多张表速度要快。
查询订单并且关联查询用户信息
需要定义两个mapper的方法对应的statement。
(1)只查询订单信息
SELECT * FROM orders在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
<!-- 8.查询订单,用户信息需要延迟加载 select * from orders --> <select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoading"> select * from orders </select>(2)关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
使用UserMapper.xml中的findUserById
<select id="findUserById" parameterType="int" resultType="User"> SELECT * FROM user WHERE id = #{value} </select>上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,中间的关联通过resultMap的定义将延迟加载执行配置起来。
使用association中的select指定延迟加载去执行的statement的id
<!-- 9.延迟加载的resultMap --> <resultMap type="com.gdc.virtualstall.po.Orders" id="OrdersUserLazyLoading"> <!-- 9.1对订单信息进行映射配置--> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!-- 9.2实现对用户信息进行延迟加载 9.3指定延迟加载需要执行的statement的id,(是根据user_id查询用户信息的statement) 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; 9.4要使用IUserMapper.xml中findUserById完成根据用户id(user_id)查询用户信息,如果findUserById不在本mapper文件中, 则需要加namespace --> <association property="user" javaType="com.gdc.virtualstall.po.User" select="com.gdc.virtualstall.mapper.IUserMapper.findUserById" column="user_id"> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="address" property="address"/> </association> </resultMap>(1)mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。
(2)在mybatis核心配置文件中配置: lazyLoadingEnabled、aggressiveLazyLoading
设置项描述允许值默认值lazyLoadingEnabled全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。true与falsefalseaggressiveLazyLoading当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。true和falsetrue <!-- s2.全局配置参数 需要时再设置--> <settings> <!-- s2.1 打开延迟加载的开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- s2.2将积级加载改为消极加载,即按需加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>(1)不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??
(2)实现方法如下:
(3)定义两个mapper方法:
查询订单列表根据用户id查询用户信息(4)实现思路:
先去查询第一个mapper方法,获取订单信息列表在程序中(service),按需去调用第二个mapper方法去查询用户信息。(5)总之: 使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
(1)mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
(2)mybaits提供一级缓存,和二级缓存。
(3)一级缓存是SqlSession级别的缓存。 在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是互相不影响的。
(4)二级缓存是mapper级别的缓存。 多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
(5)为什么要用缓存? 如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
(1)第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。 得到用户信息,将用户信息存储到一级缓存中。
(2)如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
(3)第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
(1)mybatis默认支持一级缓存,不需要在配置文件去配置。
(2)按照上边一级缓存原理步骤去测试。
/** * 一级缓存测试 * @throws Exception */ @Test public void testCache1() throws Exception{ // s1.通过会话工厂得到会话 SqlSession sqlSession = sqlSessionFactory.openSession(); // s2.创建IUserMapper对象,mybatis自动生成mapper代理对象 IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class); /* * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第一次发请求,查询id为1的用户 */ User user1 = userMapper.findUserById(1); System.out.println(user1); /* * s3.1如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存, * 这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 * * s3.2更新user1的信息,去清空缓存 */ user1.setUsername("测试用户22"); userMapper.updateUser(user1); // s3.3执行commit操作去清空缓存 sqlSession.commit(); //s4.第二次发请求,查询id为1的用户 User user2 = userMapper.findUserById(1); System.out.println(user2); sqlSession.close(); }(1)正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
(2)一个service方法中包括 很多mapper方法调用。
service{ //开始执行时,开启事务,创建SqlSession对象 //第一次调用mapper的方法findUserById(1) //第二次调用mapper的方法findUserById(1),从一级缓存中取数据 //方法结束,sqlSession关闭 }(3)如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为sqlSession方法结束,sqlSession就关闭,一级缓存就清空。
(1)前提:首先开启mybatis的二级缓存。
(2)sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
(3)如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。
(4)sqlSession2去查询用户id为1的用户信息, 去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
(5)二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
(6)UserMapper有一个二级缓存区域(缓存区域按namespace分) ,其它mapper也有自己的二级缓存区域(缓存区域按namespace分)。
(7)每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。
(1)mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
(2)在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/> 属性名称描述允许值默认值cacheEnabled对在此配置文件下的所有cache 进行全局性开/关设置。true falsetrue(3)在UserMapper.xml中开启二缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)。
<mapper namespace="com.gdc.virtualstall.mapper.IUserMapper"> <!-- s0:开启本mapper的namespace下的二级缓存 --> <cache/>为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。
(1)二级缓存测试代码1
/** * 二级缓存测试 * @throws Exception */ @Test public void testCache2() throws Exception{ // s1.通过会话工厂得到会话 SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); // s2.创建IUserMapper对象,mybatis自动生成mapper代理对象 IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class); /* * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第一次发请求,查询id为1的用户 */ User user1 = userMapper1.findUserById(1); System.out.println(user1); //s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域 sqlSession1.close(); //s3.2使用sqlSession3执行commit()操作 IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class); /* * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第二次发请求,查询id为1的用户 */ User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }(2)二级缓存测试代码2(存在清空二级缓存的操作)
/** * 二级缓存测试 * @throws Exception */ @Test public void testCache2() throws Exception{ // s1.通过会话工厂得到会话 SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); // s2.创建IUserMapper对象,mybatis自动生成mapper代理对象 IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class); /* * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第一次发请求,查询id为1的用户 */ User user1 = userMapper1.findUserById(1); System.out.println(user1); //s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域 sqlSession1.close(); //s3.2使用sqlSession3执行commit()操作 IUserMapper userMapper3 = sqlSession3.getMapper(IUserMapper.class); User user3 = userMapper3.findUserById(1); user3.setUsername("李丽华"); userMapper3.updateUser(user3); //s3.3执行提交,清空IUserMapper下的二级缓存 sqlSession3.commit(); sqlSession3.close(); IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class); /* * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第二次发请求,查询id为1的用户 */ User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }(1)在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findUserById" parameterType="int" resultType="User" useCache="false"> SELECT * FROM user WHERE id = #{value} </select>(2)总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
(3)测试代码
/** * 二级缓存测试 * @throws Exception */ @Test public void testCache2() throws Exception{ // s1.通过会话工厂得到会话 SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); // s2.创建IUserMapper对象,mybatis自动生成mapper代理对象 IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class); /* * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第一次发请求,查询id为1的用户 */ User user1 = userMapper1.findUserById(1); System.out.println(user1); //s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域 sqlSession1.close(); //s3.2使用sqlSession3执行commit()操作 /*IUserMapper userMapper3 = sqlSession3.getMapper(IUserMapper.class); User user3 = userMapper3.findUserById(1); user3.setUsername("李丽华"); userMapper3.updateUser(user3); //s3.3执行提交,清空IUserMapper下的二级缓存 sqlSession3.commit(); sqlSession3.close();*/ IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class); /* * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第二次发请求,查询id为1的用户 */ User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }(1)在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。
(2)设置statement配置中的flushCache=“true” 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。 如下:
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">(3)总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
(1)flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
(2)size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
(3)readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:
LRU – 最近最少使用的:移除最长时间不被使用的对象。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。(1)ehcache是一个分布式缓存框架。
(2)为什么要使用ehcache缓存,而不使用mybatis自己的缓存? 原因见7.4.1
(1)我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)
(2)不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理。
(3)mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。
(1)mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。
(2)mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类。
(3)mybatis jar包位置
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); /** * Optional. It is not called by the core. * * @param key The key * @return The object that was removed */ Object removeObject(Object key); /** * Clears this cache instance */ void clear();(4)mybatis默认实现cache类是:
可以ctrl + t Cache接口看实现类,查看mybatis实现了哪些缓存接口,即支持哪些缓存接口。
public class PerpetualCache implements Cache { private String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); public PerpetualCache(String id) { this.id = id; } public String getId() { return id; } public int getSize() { return cache.size(); } public void putObject(Object key, Object value) { cache.put(key, value); } public Object getObject(Object key) { return cache.get(key); }(1)配置mapper中cache中的type为ehcache对cache接口的实现类型。
<!-- s0:开启本mapper的namespace下的二级缓存 s0.1指定Cache(缓存)接口的实现类的类型,mybtis默认使用org.apache.ibatis.cache.impl.PerpetualCache s0.2要和ehcache整合,需要配置type为ehcache实现Cache接口的类型 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>(1)在classpath下配置ehcache.xml
(2)内容如下:
<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>(3)属性说明:
diskStore:指定数据在磁盘中的存储位置。defaultCache:当借助CacheManager.add(“demoCache”)创建Cache时,EhCache便会采用 <defalutCache/>指定的的管理策略以下属性是必须的:
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(先进先出)(4)测试
(1)直接执行测试程序即可,查看输出日志缓存命中率是否有变化 Cache Hit Ratio [com.gdc.virtualstall.mapper.IUserMapper]: 0.5
(2)测试代码
/** * 二级缓存测试 * @throws Exception */ @Test public void testCache2() throws Exception{ // s1.通过会话工厂得到会话 SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); // s2.创建IUserMapper对象,mybatis自动生成mapper代理对象 IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class); /* * s3.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第一次发请求,查询id为1的用户 */ User user1 = userMapper1.findUserById(1); System.out.println(user1); //s3.1这里执行关闭操作,将sqlSession中的数据写到二级缓存区域 sqlSession1.close(); //s3.2使用sqlSession3执行commit()操作 /*IUserMapper userMapper3 = sqlSession3.getMapper(IUserMapper.class); User user3 = userMapper3.findUserById(1); user3.setUsername("李丽华"); userMapper3.updateUser(user3); //s3.3执行提交,清空IUserMapper下的二级缓存 sqlSession3.commit(); sqlSession3.close();*/ IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class); /* * s4.下边查询使用一个SqlSession(sqlSession生成了一个动态代理对象,相当于就只有一个SqlSession) * 第二次发请求,查询id为1的用户 */ User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }(1)对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
(2)实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
(1)mybatis二级缓存对细粒度的数据级别的缓存实现不好
(2)比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
(1)需要spring通过单例方式管理SqlSessionFactory。
(2)spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(spring和mybatis整合自动完成)
(3)持久层的mapper都需要由spring进行管理。
(1)创建一个新的java工程(接近实际开发的工程结构)
(2)jar包:
mybatis3.2.7的jar包spring3.2.0的jar包mybatis和spring的整合包:早期ibatis和spring整合是由spring官方提供,mybatis和spring整合由mybatis提供。全部jar包
见资料压缩包:mybatis与spring整合全部jar包(包括springmvc).zip文件
(1)在applicationContext.xml配置sqlSessionFactory和数据源
(2)sqlSessionFactory在mybatis和spring的整合包下。
<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/context" 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-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.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>(1)在SqlMapconfig.xml中去加载User.xml
<!-- s5.加载映射文件 --> <mappers> <mapper resource="sqlmap/User.xml"/>(2)SqlMapconfig.xml的配置整理
<?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> <!-- s1.statement语句别名定义 --> <typeAliases> <!-- s1.批量别名的定义 指定包名,mybatis会自动的扫描包中的po类,自动定义别名,别名是什么呢?别名就是类名,(首字母大小或小写都可以) --> <package name="com.gdc.ssm.po"/> </typeAliases> <!-- s2.加载映射文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> <!-- s1.通过mapper接口来加载映射文件 s1.1需要遵循一些规范: (1)需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录。 (2)前提是使用的是mapper代理的方法 --> <!-- <mapper class="com.gdc.virtualstall.mapper.IUserMapper"/> --> <!-- s2.批量加载mapper映射文件 s2.1指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载 s2.2需要遵循一些规范: (1)需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录。 (2)前提是使用的是mapper代理的方法 --> <package name="com.gdc.ssm.mapper"/> </mappers> </configuration>(1)dao接口实现类需要注入SqlSessoinFactory,通过spring进行注入。
(2)这里spring声明配置方式,配置dao的bean:
(3)让UserDaoImpl实现类继承SqlSessionDaoSupport
package com.gdc.ssm.dao.daoimpl; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.support.SqlSessionDaoSupport; import com.gdc.ssm.dao.IUserDao; import com.gdc.ssm.po.User; /** * s1.需要在实现类中注入SqlSessionFactory s2 .此处通过构造参数注入 */ public class UserDaoImpl extends SqlSessionDaoSupport implements IUserDao { @Override public User findUserById(int id) throws Exception { //s0.继承SqlSessionDaoSupport,通过this.getSqlSession()得到SqlSession // s1.为了保证线程安全,将SqlSession的创建放在方法中 SqlSession sqlSession = this.getSqlSession(); User user = sqlSession.selectOne("test.findUserById", id); return user; } @Override public List<User> findUserByName(String name) throws Exception { // s1.为了保证线程安全,将SqlSession的创建放在方法中 SqlSession sqlSession = this.getSqlSession(); List<User> userList = sqlSession.selectList("test.findUserByName", name); return userList; } @Override public void insertUser(User user) throws Exception { // s1.为了保证线程安全,将SqlSession的创建放在方法中 SqlSession sqlSession = this.getSqlSession(); // s2.插入用户对象 int ret = sqlSession.insert("test.insertUser", user); System.out.println(ret); // s3.提交事务 sqlSession.commit(); } @Override public void deleteUser(int id) throws Exception { // s1.为了保证线程安全,将SqlSession的创建放在方法中 SqlSession sqlSession = this.getSqlSession(); // s2.删除用户对象 int ret = sqlSession.delete("test.deleteUser", 27); System.out.println(ret); // s3.提交事务 sqlSession.commit(); } }(1)在applicationContext.xml中配置dao。
<!-- s4.原始dao接口 --> <bean id="userDao" class="com.gdc.ssm.dao.daoimpl.UserDaoImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> </bean>此方法问题: 需要针对每个mapper进行配置,麻烦。
(1)mybaits需要程序员自己编写sql语句,mybatis官方提供逆向工程 可以针对单表自动生成mybatis执行所需要的代码(mapper.java,mapper.xml、po…)
(2)企业实际开发中,常用的逆向工程方式:
由数据库的表生成java代码。
建议使用java程序方式,不依赖开发工具。
(1)需要将生成工程中所生成的代码拷贝到自己的工程中。
(2)测试ItemsMapper中的方法
package com.gdc.ssm.mapper; import java.util.Date; import java.util.List; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.gdc.ssm.po.Items; import com.gdc.ssm.po.ItemsExample; 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 testDeleteByPrimaryKey() { } @Test public void testInsert() { //s1.构造items对象 Items items = new Items(); items.setName("手机"); items.setPrice(2300F); items.setCreatetime(new Date()); int ret = itemsMapper.insert(items); System.out.println(ret); } /** * 自定义条件查询 */ @Test public void testSelectByExample() { ItemsExample itemsExample = new ItemsExample(); //通过criteria构造查询条件 ItemsExample.Criteria criteria = itemsExample.createCriteria(); criteria.andNameEqualTo("手机"); //可能返回多条记录 List<Items> list = itemsMapper.selectByExample(itemsExample); System.out.println(list); } /** * 根据主键查询 */ @Test public void testSelectByPrimaryKey() { Items items = itemsMapper.selectByPrimaryKey(1); System.out.println(items); } @Test public void testUpdateByPrimaryKey() { //s1.对所有字段进行更新,需要先查询出来再更新 Items items = itemsMapper.selectByPrimaryKey(1); items.setName("水杯"); int ret = itemsMapper.updateByPrimaryKey(items); System.out.println(ret); //s2.如果传入的字段不为空才更新,在批量更新中使用此方法,不需要先查询再更新 //itemsMapper.updateByPrimaryKeySelective(record); } }