一、关于mybatis的association和collection聚合查询问题。
resultMap可以实现高级映射,即使用association和collection实现一对一,一对多的映射情况下,association和collection具备懒加载的功能。懒加载可以提高数据库性能,MyBatis延迟加载的策略是先从单表查询然后再从关联表查询,这样可以大大提高数据库性能,单表查询要比关联查询多张表速度要快。
但是针对于多对多或者多对一的情况下,此种查询方式会存在“N+1”的弊端,即N+1次数据库查询。数据库查询通常是应用程序性能的瓶颈,一般应尽量减少数据库查询的次数,那么这种方式就会大大降低系统的性能
当查询一个列表的时候,采用聚合查询,会造成数据库的多次访问,此种情况下,association和collection数据的获取,都是先获取主数据,然后根据主数据的某些字段获取关联数据,是一条条查询的,并不是多表关联查询的(具体可参考http://blog.51cto.com/legend2011/1131629
)。
案例测试:
测试日志:
2018-04-10 16:22:07.938 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.U.selectOneUserByUserName : ==> Preparing: SELECT id,username,password,email,realName,nickName,sex, photo,birthDay,accountNonLocked,creater, createTime,updater,updateTime,flag FROM user WHERE username = ? and flag = '1'
2018-04-10 16:22:08.343 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.U.selectOneUserByUserName : ==> Parameters: caicaicai(String)
2018-04-10 16:22:08.873 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.U.selectOneUserByUserName : <== Total: 1
2018-04-10 16:22:21.347 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.R.selectRoleListByUserId : ==> Preparing: SELECT r.roleName, r.roleLabel, r.id, r.creater, r.createTime, r.updater, r.updateTime, r.flag FROM role r LEFT JOIN user_role ur ON r.id = ur.roleId AND ur.flag='1' WHERE r.flag='1' AND userId = ?
2018-04-10 16:22:21.349 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.R.selectRoleListByUserId : ==> Parameters: 1(BigDecimal)
2018-04-10 16:22:21.357 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.R.selectRoleListByUserId : <== Total: 1
2018-04-10 16:22:21.359 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.P.selectPermissionList : ==> Preparing: SELECT p.permission, p.permissionName, p.id, p.creater, p.createTime, p.updater, p.updateTime, p.flag FROM permission p LEFT JOIN role_permission rp ON p.id = rp.permissionId AND rp.flag='1' LEFT JOIN user_role ur ON rp.roleId = ur.roleId AND ur.flag='1' WHERE p.flag='1' AND userId = ?
2018-04-10 16:22:21.360 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.P.selectPermissionList : ==> Parameters: 1(BigDecimal)
2018-04-10 16:22:21.367 DEBUG 4110 --- [nio-8080-exec-1] c.s.d.m.P.selectPermissionList : <== Total: 0
通过测试日志可以体现出这种情况下也是对数据库进行多次查询。所以这种情况下其实对数据的访问次数并没有减少等同于分开3次查询。
对于association和collection的使用,对于N+1的问题:
(1)我门可以采用,把信息一次性地查询出来。MyBatis association的两种形式就是采用这种方式。
(2)采用懒加载的方式,当真正需要数据的时候再查询。
所以对于这两个的使用还是需要辩证的看待,在什么情况下使用,怎么使用。
====================================================
备至:关于collection 的使用的一个坑供学习借鉴:https://blog.csdn.net/yangjiehuan/article/details/78523080
二、软件设计的几个模型
● 失血模型:
模型仅仅包含数据的定义和getter/setter方法,业务逻辑和应用逻辑都放到服务层中。这种类在Java中叫POJO,在.NET中叫POCO。
● 贫血模型:
贫血模型中包含了一些业务逻辑,但不包含依赖持久层的业务逻辑。这部分依赖于持久层的业务逻辑将会放到服务层中。可以看出,贫血模型中的领域对象是不依赖于持久层的。
是指领域对象里只有get和set方法,或者包含少量的CRUD方法,所有的业务逻辑都不包含在内而是放在Business Logic层。
优点是系统的层次结构清楚,各层之间单向依赖,Client->(Business Facade)->Business Logic->Data Access(ADO.NET)。当然Business Logic是依赖Domain Object的。似乎现在流行的架构就是这样,当然层次还可以细分。
该模型的缺点是不够面向对象,领域对象只是作为保存状态或者传递状态使用,所以就说只有数据没有行为的对象不是真正的对象。在Business Logic里面处理所有的业务逻辑,在POEAA(企业应用架构模式)一书中被称为Transaction Script模式。
● 充血模型:
充血模型中包含了所有的业务逻辑,包括依赖于持久层的业务逻辑。所以,使用充血模型的领域层是依赖于持久层,简单表示就是 UI层->服务层->领域层<->持久层。
层次结构和贫血模型差不多,不过大多业务逻辑和持久化放在Domain Object里面,Business Logic只是简单封装部分业务逻辑以及控制事务、权限等,这样层次结构就变成Client->(Business Facade)->Business Logic->Domain Object->Data Access。
优点是面向对象,Business Logic符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重。
缺 点是如何划分业务逻辑,什么样的逻辑应该放在Domain Object中,什么样的业务逻辑应该放在Business Logic中,这是很含糊的。即使划分好了业务逻辑,由于分散在Business Logic和Domain Object层中,不能更好的分模块开发。熟悉业务逻辑的开发人员需要渗透到Domain Logic中去,而在Domian Logic又包含了持久化,对于开发者来说这十分混乱。 其次,因为Business Logic要控制事务并且为上层提供一个统一的服务调用入口点,它就必须把在Domain Logic里实现的业务逻辑全部重新包装一遍,完全属于重复劳动。
如果技术能够支持充血模型,那当然是最完美的解决方案。不过现在 的.NET框架并没有ORM工具(不算上开源的NHibernate,Castle之类),没有ORM就没有透明的持久化支持,在Domain Object层会对Data Access层构成依赖,如果脱离了Data Access层,Domain Object的业务逻辑就无法进行单元测试,这也是很致命的。如果有像Spring的动态注入和Hibernate的透明持久化支持,那么充血模型还是能 够实现的。
● 胀血模型:
胀血模型就是把和业务逻辑不想关的其他应用逻辑(如授权、事务等)都放到领域模型中。我感觉胀血模型反而是另外一种的失血模型,因为服务层消失了,领域层干了服务层的事,到头来还是什么都没变。
四、关于单表操作时数据库查询:
可以采用List
public static void main(String[] args) {
List users = new ArrayList<>();
users.add(User.builder().realName("caicai").build());
users.add(User.builder().realName("taotao").build());
users.add(User.builder().realName("yangyang").build());
//需要关连查询的信息-此处分开查出-然后组装
List nickNames = new ArrayList<>();
nickNames.add(User.builder().realName("caicai").nickName("123").build());
nickNames.add(User.builder().realName("taotao").nickName("456").build());
nickNames.add(User.builder().realName("yangyang").nickName("789").build());
// 将查询的list封装map
Map nickUsers = nickNames.stream().collect(
Collectors.toMap(nickUser -> nickUser.getRealName(),
nickUser -> nickUser));
//利用唯一识别realName匹配拼装生成需要的完整信息的List
List dreamUser = users.stream().map(h -> {
h.setNickName(nickUsers.get(h.getRealName()).getNickName());
return h;
}).collect(Collectors.toList());
System.out.println("===="+ dreamUser);
}
附录:
推荐一款开源前后端协调开发的神器
YApi 是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台
地址:https://github.com/YMFE/yapi
YApi 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理