一、返回select结果集
1、创建存储过程
DELIMITER // DROP PROCEDURE IF EXISTS proc_queryUser; CREATE PROCEDURE proc_queryUser( IN user_name VARCHAR(50) CHARACTER SET utf8 ) BEGIN SET @exeSql = CONCAT('SELECT id,username,sex,birthday,address ', 'from t_user where username like \'',user_name,'%\' order by id'); -- 打印sql -- SELECT @exeSql; -- 预定义一个语句,并将它赋给stmtsql PREPARE stmtsql FROM @exeSql; EXECUTE stmtsql; -- 释放一个预定义语句的资源 DEALLOCATE PREPARE stmtsql; END // DELIMITER ;
2、存储过程调用
SET @user_name='小'; CALL proc_queryUser(@user_name);
3、在UserMapper.java中添加接口方法
/**调用存储过程查询用户**/ public List findUserByProc(String user_name); /**调用存储过程查询用户**/ public List findUserByProc1(Map map);
4、在UserMapper.xml中添加如下配置项:
<!-- 调用存储过程 --> <!-- 第一种方式,参数使用parameterType --> <select id="findUserByProc" parameterType="java.lang.String" statementType="CALLABLE" resultType="com.mybatis.entity.User"> {call proc_queryUser(#{user_name,jdbcType=VARCHAR,mode=IN})} </select> <parameterMap type="java.util.Map" id="userMap"> <parameter property="user_name" mode="IN" jdbcType="VARCHAR"/> </parameterMap> <!-- 调用存储过程 --> <!-- 第二种方式,参数使用parameterMap --> <select id="findUserByProc1" parameterMap="userMap" statementType="CALLABLE" resultType="com.mybatis.entity.User"> {call proc_queryUser(?)} </select>
说明:这里使用两种方式调用存储过程,两种方式的区别主要在于参数的使用方式上,第一种方式使用parameterType,第二种方式使用parameterMap。
5、测试代码:
package com.mybatis.test; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import com.mybatis.entity.User; import com.mybatis.mapper.UserMapper; public class TestMybatisProceduce { private SqlSessionFactory sqlSessionFactory; // 此方法是在执行@Test方法之前执行 @Before public void setUp() throws Exception { String resource = "SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 创建SqlSessionFcatory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void TestProceduce(){ SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建Usermapper对象,mybatis自动生成mapper代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List userList = mapper.findUserByProc("小"); System.out.println(userList); sqlSession.close(); } @Test public void TestProceduce1(){ SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建Usermapper对象,mybatis自动生成mapper代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map map = new HashMap(); map.put("user_name", "小"); List userList = mapper.findUserByProc1(map); System.out.println(userList); sqlSession.close(); } }
6、运行结果:
[User [id=8, username=小A, sex=2, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015], User [id=9, username=小B, sex=2, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015], User [id=10, username=小C, sex=1, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015], User [id=11, username=小D, sex=2, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015]] [User [id=8, username=小A, sex=2, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015], User [id=9, username=小B, sex=2, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015], User [id=10, username=小C, sex=1, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015], User [id=11, username=小D, sex=2, address=北京, birthday=Sat Jun 27 00:00:00 CST 2015]]
二、带输入输出参数的存储过程
1、创建存储过程
DELIMITER // DROP PROCEDURE IF EXISTS proc_queryUserCount; CREATE PROCEDURE proc_queryUserCount( IN user_name VARCHAR(50) CHARACTER SET utf8, OUT count INT ) BEGIN SET @exeSql = CONCAT('SELECT count(*) into @rowsCount from t_user where username like \'',user_name,'%\''); -- 打印sql -- SELECT @exeSql; -- 预定义一个语句,并将它赋给stmtsql PREPARE stmtsql FROM @exeSql; EXECUTE stmtsql; -- 释放一个预定义语句的资源 DEALLOCATE PREPARE stmtsql; SET count = @rowsCount; END // DELIMITER ;
2、存储过程调用:
SET @user_name='小'; CALL proc_queryUserCount(@user_name,@count); SELECT @count;
3、在UserMapper.java中添加接口方法
/**调用存储过程(带输入输出参数的存储过程)**/ public void findUserCountByProc(Map map);
4、在UserMapper.xml中添加如下配置项:
<parameterMap type="java.util.HashMap" id="userMap1"> <parameter property="user_name" jdbcType="VARCHAR" mode="IN"/> <parameter property="count" jdbcType="INTEGER" mode="OUT"/> </parameterMap> <!-- 调用存储过程 (带输入输出参数的存储过程)--> <select id="findUserCountByProc" parameterMap="userMap1" statementType="CALLABLE"> {call proc_queryUserCount(?,?)} </select>
5、测试代码:
@Test public void TestProceduce2(){ SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建Usermapper对象,mybatis自动生成mapper代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map map = new HashMap(); map.put("user_name", "小"); mapper.findUserCountByProc(map); System.out.println("userCount="+map.get("count")); sqlSession.close(); }
6、运行结果:
userCount=4
三、返回多个结果集
1、创建存储过程
DELIMITER // DROP PROCEDURE IF EXISTS proc_query; CREATE PROCEDURE proc_query( IN user_id INT, OUT order_count INT ) BEGIN SET @exeSql = CONCAT('SELECT id,username,sex,date_format(birthday,\'%Y-%m-%d\')birthday,address ', 'from t_user where id=\'',user_id,'\''); PREPARE stmtsql FROM @exeSql; EXECUTE stmtsql; DEALLOCATE PREPARE stmtsql; SET @exeSql1 = CONCAT('SELECT id,user_id,number,date_format(createtime,\'%Y-%m-%d %H:%i:%s\')createtime ', 'from orders where user_id=\'',user_id,'\''); PREPARE stmtsql1 FROM @exeSql1; EXECUTE stmtsql1; DEALLOCATE PREPARE stmtsql1; SET @exeSql2 = CONCAT('SELECT count(*) into @rowsCount from orders ', 'where user_id=\'',user_id,'\''); PREPARE stmtsql2 FROM @exeSql2; EXECUTE stmtsql2; DEALLOCATE PREPARE stmtsql2; SET order_count = @rowsCount; END // DELIMITER ;
2、存储过程调用:
SET @user_id=2; CALL proc_query(@user_id,@order_count); SELECT @order_count;
3、在UserMapper.java中添加接口方法
/**调用存储过程(返回多个结果集)**/ public List<List<?>> findUserOrdersByProc(Map map);
4、在UserMapper.xml中添加如下配置项:
<resultMap type="java.util.HashMap" id="userInfoMap"> <result column="id" property="id" javaType="java.lang.Integer" jdbcType="INTEGER"/> <result column="username" property="username" javaType="java.lang.String" jdbcType="VARCHAR"/> <result column="birthday" property="birthday" javaType="java.lang.String" jdbcType="DATE"/> <result column="sex" property="sex" javaType="java.lang.String" jdbcType="CHAR"/> <result column="address" property="address" javaType="java.lang.String" jdbcType="VARCHAR"/> </resultMap> <resultMap type="java.util.HashMap" id="ordersMap"> <result column="id" property="id" javaType="java.lang.Integer" jdbcType="INTEGER"/> <result column="user_id" property="user_id" javaType="java.lang.Integer" jdbcType="INTEGER"/> <result column="number" property="number" javaType="java.lang.String" jdbcType="VARCHAR"/> <result column="createtime" property="createtime" javaType="java.lang.String" jdbcType="TIMESTAMP"/> </resultMap> <!-- 调用存储过程 (返回多个结果集)--> <select id="findUserOrdersByProc" parameterType="java.util.Map" resultMap="userInfoMap,ordersMap" statementType="CALLABLE"> {call proc_query(#{user_id,jdbcType=INTEGER,mode=IN}, #{order_count,jdbcType=INTEGER,mode=OUT})} </select>
5、测试代码:
@Test public void TestProceduce3(){ SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建Usermapper对象,mybatis自动生成mapper代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map map = new HashMap(); map.put("user_id", "2"); List<List<?>> resultList = mapper.findUserOrdersByProc(map); List<Map> list1 = (List<Map>)resultList.get(0); List<Map> list2 = (List<Map>)resultList.get(1); System.out.println(list1); System.out.println(list2); System.out.println("orderCount="+map.get("order_count")); sqlSession.close(); }
6、运行结果:
[{id=2, birthday=2014-07-10, sex=1, username=张三, address=北京市}] [{createtime=2015-07-17 14:13:23, id=3, number=1000012, user_id=2}] orderCount=1
四、总结:
如果sql中用的是select出结果,不需要配置out参数。多个结果集/结果集可以配置resultMap 来返回LIST,主要是调用selectList方法会自动把结果集加入到list中去的。
一、什么是延迟加载
resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。
需求:如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。
延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
二、使用association实现延迟加载
需要定义两个mapper的方法对应的statement。
1、只查询订单信息
SELECT * FROM orders
在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
2、关联查询用户信息
通过上边查询到的订单信息中user_id去关联查询用户信息
OrdersCustomMapper.xml的延迟加载的核心代码:
使用association中的select指定延迟加载去执行的statement的id。
<!-- 查询订单关联查询用户,用户信息按需延迟加载 的 resultMap定义 --> <resultMap type="com.mybatis.entity.Orders" id="ordersUserLazyLoading"> <!--对订单信息进行映射配置 --> <id column="id" property="id"/> <result column="number" property="number"/> <result column="createtime" property="createTime"/> <result column="note" property="note"/> <!-- 实现对用户信息进行延迟加载 select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement) 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="com.mybatis.entity.User" select="findUserById" column="user_id"/> </resultMap> <!-- 根据Id查询用户,用于测试延迟加载 --> <select id="findUserById" parameterType="int" resultType="com.mybatis.entity.User" > select * from t_user where id=#{id} </select> <!-- 查询订单关联用户,用户信息延迟加载 --> <select id="findOrdersUserLazyLoading" resultMap="ordersUserLazyLoading"> select * from orders </select>
OrdersCustomMapper.java的代码:
/**查询订单,关联查询用户,用户按需延迟加载*/ public List<Orders>findOrdersUserLazyLoading(); /**根据Id查询用户(这个方法本应该放在UserMapper类的,测试方便先放在这)*/ public User findUserById(int id);
测试思路及代码:
1、执行上边mapper方法(findOrdersUserLazyLoading),内部去调用OrdersMapperCustom中的findOrdersUserLazyLoading只查询orders信息(单表)。
2、在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载。
3、延迟加载,去调用findUserbyId这个方法获取用户信息。
// 查询用户关联查询用户,用户信息延迟加载 @Test public void TestFindOrdersUserLazyLoading() { SqlSession sqlSession = sqlSessionFactory.openSession(); // 创建代理对象 OrdersCustomMapper oc = sqlSession.getMapper(OrdersCustomMapper.class); // 调用mapper的方法 List<Orders> list = oc.findOrdersUserLazyLoading(); for(Orders order: list){ //执行getUser()去查询用户信息,这里实现延迟加载 User user = order.getUser(); System.out.println(user); } sqlSession.close(); }
注:项目中要引入cglib.jar和asm.jar。
三、延迟加载在mybatis核心配置文件sqlMapConfig.xml中的配置
mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。
在mybatis核心配置文件中配置:lazyLoadingEnabled、aggressiveLazyLoading
设置项 | 描述 | 允许值 | 默认值 |
lazyLoadingEnabled | 全局性设置懒加载。如果设为'false',则所有相关联的都会被初始化加载。 | true | false | false |
aggressiveLazyLoading | 当设置为'true'的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 | true | false | true |
<!-- 全局参数的配置 --> <settings> <!--打开延迟加载的开关 --> <setting name="lazyLoadingEnabled" value="true"/> <!--将积极加载改为消极加载及按需加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
四、延迟加载的原理
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
小结:使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。
一、什么是查询缓存
mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。mybaits提供一级缓存和二级缓存。
1、一级缓存是sqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap),用于存储缓存数据。不同的sqlSession之间的缓存区域(HashMap)是互不影响的。
2、二级缓存是mapper级别的缓存,多个sqlSession去操作同一个Mapper的sql语句,多个SqlSession可以公用二级缓存,二级缓存是跨sqlSession的。
3、为什么要用缓存?
如果缓存中有数据就不用从数据库中获取,减少了和数据之间的交互次数,大大提高系统的性能。
二、一级缓存
1、一级缓存的工作原理
(1). 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
(2). 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
(3). 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。mybatis默认支持一级缓存的。测试很简单,这里就不贴代码了。
2、一级缓存的应用
正式开发,假如项目是将mybatis和spring进行整合开发....,事务控制在service中。一个service方法中包括很多mapper方法调用。
service{ //开始执行时,开启事务,创建SqlSession对象 //第一次调用mapper的方法findUserById(1) //第二次调用mapper的方法findUserById(1),从一级缓存中取数据 //方法结束,sqlSession关闭 }
如果是执行两次service调用查询相同的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
三、二级缓存
1、二级缓存的工作原理
(1). 首先开启mybatis的二级缓存。
(2). sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
(3). 如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。
(4). sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按namespace分) ,其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
2、开启二级缓存
mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。在核心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
描述 | 允许值 | 默认值 | |
cacheEnabled | 对在此配置文件下的所有cache 进行全局性开/关设置。 | true | false | true |
a.在mybatis的核心配置文件sqlMapConfig.xml中开启二级缓存,其实默认值就为true,写上方便代码维护。
<!-- 全局参数的配置 --> <settings> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings>
b.实体类User实现序列化接口
public class User implements Serializable{ //属性...... //getter and setter...... }
为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存。
c.Junit测试:
/** * 二级缓存测试 * @throws Exception */ @Test public void testCache() throws Exception{ SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); // 创建代理对象 UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); // 第一次发起请求,查询id为1的用户 User user1 = userMapper1.findUserById(1); System.out.println(user1); //这里执行关闭操作,将sqlsession中的数据写到二级缓存区域 sqlSession1.close(); //使用sqlSession3执行commit()操作 UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class); User user = userMapper3.findUserById(1); user.setUsername("张明明"); userMapper3.updateUser(user); //执行提交,清空UserMapper下边的二级缓存 sqlSession3.commit(); sqlSession3.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); // 第二次发起请求,查询id为1的用户 User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }
3、禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
4、刷新缓存
在mapper的同一个namespace中,如果有其它insert、update、delete操作,操作数据完成后需要刷新缓存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache="true" 属性,默认情况下为true即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
如下:
<insert id="insertUser" parameterType="com.mybaits.entity.User" flushCache="true">
5、Mybatis Cache参数
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。可用的收回策略有, 默认的是 LRU:
(1).LRU – 最近最少使用的:移除最长时间不被使用的对象。
(2).FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
(3).SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
(4).WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
6、二级缓存应用场景
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
7、二级缓存的局限性
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。