Mybatis如何实现关联属性懒加载
作者:code_mo
Mybatis 关联属性懒加载
延迟加载配置
mybatis默认没有开启延迟加载,需要在config.xml中setting配置。
lazyLoadingEnabled:true使用延迟加载,false禁用延迟加载,默认为false。
aggressiveLazyLoading:true启用时,当延迟加载开启时访问对象中一个懒对象属性时,将完全加载这个对象的所有懒对象属性。false,当延迟加载时,按需加载对象属性(即访问对象中一个对象属性时,不会加载对象中的引用属性)。默认为true。
修改延迟加载需要的select语句
延迟加载场景分析
查询订单时,需要关联查询订单明细表
假设有10 个订单,每个订单有5个明细信息。
如果用户仅仅需要查看订单信息,不需要订单明细信息,但是查询了10+50个对象到内存(只有10个是用户想要的)。显然是增加内存的负担。
如果用户的需求仅仅是查询订单信息,我仅仅订单信息用户,没有查询订单的详情,那么,内存中只有10对象(10个对象全是客户想的),这样做显然 节省了内存。
业务需求特点:需要的时候在进行查询,不需要的时候不尽兴查询,这种情况就是延迟加载,也叫作懒加载。
配置过程
去掉sql语句中对user表的查询语句
对resultMap orderAndUser 进行修改
注意:查询订单时要同时查询userid,避免查询user时没有凭据
修改orderMapper.xml文件select语句
<!-- 关联属性延迟加载 --> <select id="queryOrderAndUser" resultMap="orderAndUser"> select o.id oids,o.orderid,o.createtime,o.note,o.userid from orders o,t_user u where o.userid=u.id </select> <resultMap type="orders" id="orderAndUser"> <id property="id" column="oids"></id> <result property="orderId" column="orderid"></result> <result property="createtime" column="createtime" /> <result property="note" column="note" /> <association property="user" javaType="users" select="net.neuedu.mybatis.mapper.UserMapper.queryUserById" column="userid"> <!-- 这里不查询用户的信息,而是将用的信息放在另外一个查询中 column是order表中的userid字段 --> </association> </resultMap>
因为在assocication多了一个select元素,就要在UserMapper中多加一个方法。
//根据ID查询用户 public Users queryUserById(Integer id);
在UserMapper.xml中添加queryUserById的select
<!-- public Users queryUserById(Integer id); --> <select id="queryUserById" parameterType="int" resultType="users"> select * from t_user where id=#{id} </select>
Mybatis注解方式懒加载失效分析
本人在使用spring mvc + mybatis的后台结构的项目的时候,在使用mybatis的懒加载出现了一些问题:
明明懒加载的配置都正确了,但就是用debug断点调试的时候懒加载的属性总是提前加载,在经过几天的不断努力之后,终于发现了原因:
mybatis懒加载配置:
<!-- 查询时,关闭关联对象即时加载以提高性能 --> <setting name="lazyLoadingEnabled" value="true" /> <!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能 --> <setting name="aggressiveLazyLoading" value="false" />
懒加载查询语句:
@Select("select * from user_main where username=#{username}") @Results({ @Result(property = "roleNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getRoleNamesByUserId")), @Result(property = "permissionNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getPermissionsByUserId")) }) public UserPO getUserByUsername(@Param("username")String username);
测试用例:
@Test public void testGetUserByUsername() throws Exception { SqlSession session = sqlSessionFactory.openSession(); try { UserMapper userMapper = session.getMapper(UserMapper.class); UserPO userPo = userMapper.getUserByUsername("zhangsan"); System.out.println(userPo); System.out.println(userPo.getPermissionNames()); // List<String> map = userMapper.getRoleMain(); // System.out.println(map); session.close(); } finally { session.close(); } }
结果调试测试用例的时候,使用debug却发现懒加载并没有生效,后来看了mybatis懒加载的源码,依然没有发现原因,感觉就是roleNames和permissionNames就是突然的加载了,断点也捕捉不到,也没有博捉到触发的事件,这时楼主我就很奇怪了,最后经过几天的努力终于发现了原因:
mybatis源码:configuration
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
默认这四个方法或触发懒加载,当然,触发懒加载的方法还有getRoleNames 和 getPermissions ,这两个(只针对当前实例)
然后通过sysout输出的时候,发现hashcode和equals方法被莫名的触发了,而且debug断点还捕捉不到,然后roleNames和permissionNames就被莫名的加载了
最后发现,这个问题和debug的原理有一定的关系,
原来,在显示debug的黄色框框时,debug会另起一个线程,然后重新调用一遍代码,然后显示userpo信息的时候,又会调用userpo的hashcode方法和tostring方法,
这样的话,就会导致这两个方法会在debug线程内触发懒加载,造成的效果就是懒加载失效,但是实际上懒加载是生效了的,只是在debug模式上被触发了,而且用断点还捕捉不到,就会形成一个奇怪的问题,如果想用debug来查看效果,也是很简单:
通过将触发默认的四个方法屏蔽来查看效果,但是不支持,因为这样的话,可能会影响实体的hash排序等问题,即使是用了这个方法,也是建议看完效果以后改成默认的
这种事代码方式的,还有一种是配置文件方式的(自己从网上找)
configuration.setLazyLoadTriggerMethods(new HashSet<String>());
还有一种更好的方式来查看懒加载的效果,那就是mybatis的默认日志,建议将日志级别调成debug(这个debug级别和debug模式不一样),然后执行的时候就可以通过sql语句的打印来判断懒加载是否生效。
其实这个问题的分析最后,还是在说debug断点调试的问题,它的这种原理需要注意,特别是会在另一个线程调用hashcode和tostring方法,相信在很多方面会影响到我们,希望大家能够注意吧~
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。