前段时间在完成一个列表功能的时候发现我for循环里嵌套了2个查询语句,然后就感觉太影响性能了,而且也体现一个人的水平, 这里我举下例子(方便回忆)。

实体

//项目用户实体首先这是和用户实体一对多的关系,既一个项目下有多个用户这样我们查询这个数据库时候就会出现list列表传个projectId进来
public class ProjectUser {

    private  String userId;

    private String projectId;
}
//用户实体
public class User {

    private  String id;

    private String imageId;

	private String userName;

}
//头像实体类
public class Image {

    private  String id;

    private String imageUrl;
}
//出参
public class UserInfoVO{

	private String projectId;
	//这里返回给前端一个列表数组
	private List<UserInfo> userInfoList;

}
public class UserInfo{
	private Sring userName;
	
	private Sring userId;
	
	private Sring imageUrl
}
    

实际碰到的问题(优化前)

//需求:我们需要返回一个列表给前端,这个列表包含的数据有用户id,用户头像,用户姓名,前端传个项目id进来
//菜鸟的我是这样写的
//1.我们需要通过项目id去获取这个项目下有多少个用户
//2.然后循环这个列表,获取这个集合下的userId
//3.拿到userId需要去查user表拿到用户名,还有imageid
//4.这时再拿imageId去查image表拿imageUrl获取头像
UserInfoVO userInfoVO=new UserInfoVO();
List<UserInfo> userInfoList=new ArraryList<>();
List<ProjectUser > projectList=projectUserDao.selectByProjectId(projectId);
//这里列表可能为空的如果一些坑的项目没有会报错所以可以做非空判断
if(CollectionUtils.isNotEmpty(projectList)){
for(ProjectUser projectuser:projectList){
	UserInfo userinfo =new UserInfo();
	User user=userDao.selectByUserId(projectuser.getUserId());
	Image image=imageDao.selectByImageId(user.getImageId());
	userinfo.setUserName(user.getUserName());
	userinfo.setUserId(user.getId());
	userinfo.setImageUrl(image.getImageUrl());
	userInfoList.add(userinfo);
}
userInfoVO.setProjectId(projectId);
userInfoVO.setUserInfoList(userInfoList);
//从这里可以看出我这个for循环里嵌套了查询语句而且还是两条,这样如果数据量过大的话,每次循环都会去连接数据库,这样是容易影响性能和导致数据库崩溃的。
}

实际碰到的问题(优化后)

//这里我的优化思路是利用stream流结合sql语句进行优化
//1.首先还是查询出list集合
List<ProjectUser > projectList=projectUserDao.selectByProjectId(projectId);
//2.然后用stream流对这个集合的关键字段进行转换筛选处理,这里收集所有的userId,待会让sqlfor循环。
List<String> userIdList=projectList.stream().map(ProjectUser::getUserId).collect(Collectors.toList());
//3.写一条sql语句让sql做循环(效果比你for循环里查询数据库好)
List<UserInfoDTO> userInfoDTOList=projectUserDao.selectByUserIds(userIdList);
//4.拿到list集合利用stream流把他转换成map类型
Map<String,UserInfoDTO> userInfoMap=userInfoDTOList.stream().collect(Collectors.toMap(UserInfoDTO::getUserId,Function.identity()))
//5.接下来就是填充数据了,
for(String userId:userIdList){
	UserInfo userinfo =new UserInfo();
	UserInfoDTO userInfoDTO=userInfoMap.get(userId);
	userinfo.setUserName(userInfoDTO.getUserName());
	userinfo.setUserId(userInfoDTO.getId());
	userinfo.setImageUrl(userInfoDTO.getImageUrl());
	userInfoList.add(userinfo);
}
userInfoVO.setProjectId(projectId);
userInfoVO.setUserInfoList(userInfoList);
//从这里可以看出我优化成功,for循环里没有查询sql,所以stream流结合sql语句YYDS,只能说stream流真的太强了。
}

SQL语句

//这个类可以用来接受我们需要接受的数据
public class UserInfoDTO{
	private Sring userName;
	
	private Sring userId;
	
	private Sring imageUrl

}

	//这里是dao层
	List<UserInfoDTO> selectByUserIds(@Param("userIdList")List<String> userIdList);

//sql
	<select id="selectByUserIds" resultType="com.chenguangzhao.entities.UserInfoDTO" parameterType="java.util.List">
		select
		a.id AS userId,
		a.user_name AS userName,
		b.image_url AS imageUrl
		from t_user a
		LEFT JOIN t_image b on a.image_id=b.id
		<where>
			<if test="userIdList.size()>0">
			//这里需要标明那个表的id不然会分不清楚
				a.id in
				<foreach collection="userIdList" item="id" index="index" open="(" close=")" separator=",">
					#{id}
				</foreach>
			</if>
		</where>
	</select>

性能提高总结

如果性能还是没有得到解决的话,建议结合定时器以及检查sql是否失效,或者走索引。
1.sql优化
sql优化
2.数据库做主从复制,主库写数据,从库读数据(这一步我感觉大部分项目都是做了的)

3.定时器+reids(当然要实时查询的话还是得websocket(实时传输数据给前端)结合消息队列(观察者模式、redis发布订阅模式都可以替代消息队列)
大数据下,如果数据量比较大的话,可以尝试结合redis缓存和定时器来进行提高性能
定时器加redis优化

4.如果业务比较多的话,需要查很多表的话,建议一次性把所有的数据查出来,然后对数组进行处理以及组装过滤等操作(可以采用stream流,这个很好用这样可以减少很多for循环)

5.如果性能还得不到提高建议结合多线程对数据进行处理(能尽量用sql把所有数据查出来就查出来)
多线程提高性能

6.上面的几种情况下还不能解决问题的话考虑使用设计模式比如单例模式,享元模式(目前我也没学),策略模式+工厂模式
设计模式优化

7.通过消息队列提前把数据推到队列中,然后消费者消费队列中的数据

8.关于LEFT JOIN 语句的优化,当你数据量比较大的时候关联表比较多可能会影响到性能
LEFT JOIN 优化

9.mysql联合查询

联合查询-减少查询

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐