项目开发的过程中,碰到一个需求,要求是:用户完成任务后,会获得积分,需要展示跟任务积分差不多的积分商品列表.
中间用到了两个之前技术点:
1.跨库查询
2.排序
①跨库查询
先说跨库查询,因为项目使用的是springcloud框架,分为了3个部分,一是核心业务(designer),二是核心业务的总控后台(mbg),三是商城项目(mall),需要展示的积分商品是存在mall对应的数据库中,但该业务是属于designer的,所以使用了跨库查询,数据库为mysql,跨库查询的核心点:
因为是mybatis和springmvc,所以代码编写过程是按照service->dao->xml的顺序来进行的
首先要先在config文件中配置自己所要跨的库名:
#商城库名
myConfig.mallDb=mall_test
接下来要在java中引用配置好的库名:
@Component
@ConfigurationProperties(prefix = "myConfig")
public class UserSignPoint {
/**
* 商城库名
*/
private String mallDb;
public String getMallDb() {
return mallDb;
}
public void setMallDb(String mallDb) {
this.mallDb = mallDb;
}
}
@Component注解:
把普通pojo实例化到spring容器中,当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类
@ConfigurationProperties(prefix = “myConfig”)注解:
是Spring Boot中的标签,简单点说它可以让开发者将整个配置文件,映射到对象中.
这样spring容器初始化UserSignPoint类时,就会把配置文件中的mallDb对应的库名映射到实体类的属性中.
接下来就是在service层写具体的业务逻辑,这里跳过不说,
直接进入到dao层,这里需要注意的点是@Param注解.
这个注解作用主要是传递参数,当只有一个参数,且在sql中使用的是
#{param}
,可以省略不写,但如果使用的是
${param}
时,是必须要写的.#{param}和${param}的区别:#号是相当于传递参数,简单点来说是在sql中给要传递过去的参数自动添加"",而?则是拼接sql用的,所以在xml文件中就使用
${mallDb}.goods_master
这样来实现跨库查询
②排序
在完成项目的过程中,出现了一个问题,如下图:
有一个分享的消息,点开后展示的是跟获得积分最接近的4个商品数据,本来以为挺简单的一个功能,后来发现存在一些问题,在db中的商品数据,积分和价格并不是固定的,部分商品是通过设定的比例来确定的,所以只能先查询出所有的数据,然后再java中去进行计算,接下来就是重点了.
我需要拿到4个跟完成任务获得积分相近的4条数据,于是我在VO的实体类中新增了一个字段,用来存积分商品-任务积分的绝对值,然后通过这个绝对值进行升序排列,从头取4个即可完成需求,因为是对象排序,太久没写有些忘了,所以去网上找了相关的方法,发现有两种方法来完成:
1.Collections.sort(List list)
2.Collections.sort(List list,Comparator c)
第一种,概括来说称为自然排序,参与排序的对象需实现comparable接口,重写其compareTo()方法,方法体中实现对象的比较大小规则,也就是在实体类里重写compareTo的方法.
但第一种耦合高,并且不够灵活,所以通常采用第二种方法
第二种,是创建一个comparator的比较器(匿名内部类),然后重写compareTo的方法,就可以实现排序了.
去网上找了一下相关的源码资料,大概总结了一下整个过程(第二种方法):
首先
Collections.sort(strings);
//跟踪进入源码
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);//发现了这个方法,c就是一个比较器
}
//进一步跟进代码
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);//把之前的集合转成一个obj数组,c是比较器
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
//再追踪代码
public static <T> void sort(T[] a, Comparator<? super T> c) {
//c==null,就是不采用比较器,直接自然排序
if (c == null) {
sort(a);
} else {
//老的归并排序,不用管了现在默认是false
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
//接下来先看c==null的情况
public static void sort(Object[] a) {
//依旧不用管
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
//走else,看的出来走的是Timsort的sort方法
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
//再进入
//a数组,lo数组最小长度0,hi最大长度,剩余的不重要
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
//assert验证相当于if(true)
assert a != null && lo >= 0 && lo <= hi && hi <= a.length;
//数组长度
int nRemaining = hi - lo;
if (nRemaining < 2)
return; // Arrays of size 0 and 1 are always sorted
//当数组大小小于MIN_MERGE(32)的时候,就用一个"mini-TimSort"的方法排序,jdk1.7新加
if (nRemaining < MIN_MERGE) {
//将我们最长的递减序列,找出来,然后倒过来(这个方法看不懂)
int initRunLen = countRunAndMakeAscending(a, lo, hi);
//二进制排序
binarySort(a, lo, hi, lo + initRunLen);
return;
}
//先扫描一次array,找到已经排好的序列,然后再用刚才的mini-TimSort,然后合并,这就是TimSort的核心思想
ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen);
int minRun = minRunLength(nRemaining);
do {
// Identify next run
//该值最终范围为 16 <= k <= 32之间
int runLen = countRunAndMakeAscending(a, lo, hi);
// If run is short, extend to min(minRun, nRemaining)
//该处为什么要判断?该判断主要为了确认,在该排序的子数组中,是否包含了无序排列
// 如果runLen == mainRun的时候,就说明数组中的所有元素都处于有序状态
if (runLen < minRun) {
int force = nRemaining <= minRun ? nRemaining : minRun;
// 排序, 在该处中,lo是开始索引,force为有序数组的长度,所以lo+runLen为实际开始排序的元素的索引
// 而 lo + force 确认了当次排序的范围
binarySort(a, lo, lo + force, lo + runLen);
runLen = force;
}
// Push run onto pending-run stack, and maybe merge
ts.pushRun(lo, runLen);
ts.mergeCollapse();
// Advance to find next run
lo += runLen;
nRemaining -= runLen;
} while (nRemaining != 0);
// Merge all remaining runs to complete sort
assert lo == hi;
ts.mergeForceCollapse();
assert ts.stackSize == 1;
}
//回到c!=null时的情况
//源码走的是
TimSort.sort(a, 0, a.length, c, null, 0, 0);
//进入代码
static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
T[] work, int workBase, int workLen) {
assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;
int nRemaining = hi - lo;
if (nRemaining < 2)
return; // Arrays of size 0 and 1 are always sorted
// If array is small, do a "mini-TimSort" with no merges
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
binarySort(a, lo, hi, lo + initRunLen, c);
return;
}
//有比较器的时候主要是这个地方的源码不一样,
//没有比较器的时候用的是ComparableTimSort这个来进行下面的比较操作的,而用了比较器之后则是直接通过TimSort这个来进行比较的,其余地方的代码都是一样的.
TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
int minRun = minRunLength(nRemaining);
do {
// Identify next run
int runLen = countRunAndMakeAscending(a, lo, hi, c);
// If run is short, extend to min(minRun, nRemaining)
if (runLen < minRun) {
int force = nRemaining <= minRun ? nRemaining : minRun;
binarySort(a, lo, lo + force, lo + runLen, c);
runLen = force;
}
// Push run onto pending-run stack, and maybe merge
ts.pushRun(lo, runLen);
ts.mergeCollapse();
// Advance to find next run
lo += runLen;
nRemaining -= runLen;
} while (nRemaining != 0);
// Merge all remaining runs to complete sort
assert lo == hi;
ts.mergeForceCollapse();
assert ts.stackSize == 1;
}
通过看源码可以看出,Collections.sort方法有没有比较器,底层实现都是TimSort实现的,TimSort算法就是找到已经排好序数据的子序列,然后对剩余部分排序,然后合并起来.
在写排序的过程中,发现了jdk1.8可以简化代码
Collections.sort(hotGoodsList, new Comparator<HotGoods>() {
@Override
public int compare(HotGoods o1, HotGoods o2) {
return o1.getGoodsScore().compareTo(o2.getGoodsScore());
}
});
//可以直接替换成
hotGoodsList.sort(Comparator.comparing(HotGoods::getGoodsScore));