BitMap概述
public enum EmTags {
HAS_1(0,"存在标签1"),
HAS_2(1,"存在标签2"),
HAS_3(2,"存在标签3")
;
private int key;
private String desc;
EmTags(int key, String desc){
this.key = key;
this.desc = desc;
}
public int getKey() {
return key;
}
public String getDesc() {
return desc;
}
public static EmTags getEmByKey(int key){
List keys = new ArrayList();
keys.add(key);
List ems = getEmListByKeys(keys);
return CollectionUtil.isEmpty(ems) ? null : ems.get(0);
}
public static List getEmListByKeys(List keys){
List result = new ArrayList();
if(CollectionUtil.isEmpty(keys)){
return result;
}
for(Integer key : keys){
for(EmTags tag : EmTags.values()){
if(tag.getKey() == key){
result.add(tag);
break;
}
}
}
return result;
}
}
// 根据给定的标签枚举列表返回对应的bit值
public static long getBitByTags(Set tagsList){
long result = 0;
if(CollectionUtils.isEmpty(tagsList)){
return result;
}
long base = 1;
for(EmTags emTags : tagsList){
result = result | base << emTags.getKey();
}
return result;
}
// 给出查询满足所有标签的记录所需的bit值列表
public static List getQueryBitsByTags(List tagsList){
List result = new ArrayList<>();
if(CollectionUtils.isEmpty(tagsList)){
return result;
}
long base = 1;
for(EmTags emTags : tagsList){
result.add(base << emTags.getKey());
}
return result;
}
// 根据给定的bit值返回所需的标签枚举列表
public static List getTagsByBit(long bitValue){
List result = new ArrayList<>();
if(bitValue == 0){
return result;
}
for(EmTags emTags : EmTags.values()){
if((bitValue & 1 << emTags.getKey()) != 0){
result.add(emTags);
}
}
return result;
}
<update id="addTag" parameterType="map">
UPDATE pg_page_tags SET
page_tags = page_tags | #{addTags, jdbcType = BIGINT}
WHERE page_id = #{pageId, jdbcType = BIGINT}
</update>
<update id="deleteTag" parameterType="map">
UPDATE pg_page_tags SET
page_tags = page_tags & ~#{addTags, jdbcType = BIGINT}
WHERE page_id = #{pageId, jdbcType = BIGINT}
</update>
<select id="queryPagesByTagsBit" parameterType="map">
SELECT <include refid="Base_Column_List"/> FROM pg_page_tags
WHERE page_tags & #{tagsBit, jdbcType = BIGINT}
</select>
<select id="queryPagesByTagsBit" parameterType="map">
SELECT <include refid="Base_Column_List"/> FROM pg_page_tags
WHERE page_status = 1
<if test="list != null">
AND
<foreach collection="list" item="item" separator=" AND ">
page_tags & #{item, jdbcType = BIGINT}
</foreach>
</if>
</select>
MySQL是对位运算的结果进行了支持的,在where语句中,位运算结果为0被认为是false,位运算结果不为0的值被认为是true。(&是按位与,| 是按位或,~是按位取反,其他更多命令可以百度 OR google,目前业务中这几个已经足够了~当然,完全可以将什么位移运算"<<"、">>"移到SQL里,但那样可能就影响代码的可阅读性和可维护性……毕竟写过位运算代码的人都知道,这种代码过了一周之后可能连自己都看不懂了)
看到这里……应该大家对为什么要取所有标签的bit值的List作为查询条件去查询数据有答案了把,因为一次按位与的操作,只能查询到满足任一标签的查询结果,如果要查询同时满足所有标签的结果,就只能一次次的按位与,并且用and取交集了~
基于BitMap思想的打标框架
现在,我们已经有了标签与bitMap转换的工具类,有了针对bit标签专门设计的SQL,剩下的就是怎样将这些工具组装成一个高效、易扩展的标签框架。
一、定义注解
首先,框架要考虑到区分不同标签的打标逻辑,以及方便的扩展性。因此,框架提供了一个带属性的注解@TagCheck(属性值为标签枚举)用于标识该类是属于哪个标签的打标实现类。
@Target({ElementType.TYPE})
// 表示可以在运行时获取到该注解
@Retention(RetentionPolicy.RUNTIME)
public @interface TagCheck {
// 注解有一个标签枚举类型的值
EmTags value();
}
二、定义接口
其次,需要定义一个接口,所有实现该接口的类将被视为实现了标签统计的服务。
public interface TagCheckFacade {
Boolean checkTag(Map args);
}
三、注解及接口实现
然后,所有用于提供标签判断的类都需要添加上@TagCheck注解以及实现TagCheckFacade中的checkTag方法。用于表示该类用于检查哪个标签,以及具体的实现逻辑。
@Component
// 标签检查注解,标识该类用于判断页面是否满足EmTags.XXX标签
@TagCheck(EmTags.XXX)
public class XXXTagService implements TagCheckFacade {
@Override
// 实现checkTag方法,用于判断是否满足标签
public Boolean checkTag(Map<String, Object> args) {
// TODO 具体判断逻辑
return true;
}
}
这样,一个扩展实现的标签判断逻辑就扩展完成了。
@Component
public class TagServiceCenter {
private Map checkFacadeMap = new HashMap<>();
public void registTagCheckService(EmTags emTags, TagCheckFacade tagCheckFacade){
checkFacadeMap.put(emTags, tagCheckFacade);
}
public TagCheckFacade getTagCheckService(EmTags emTags){
return checkFacadeMap.get(emTags);
}
}
@Component
public class TagCheckServiceRegister implements ApplicationContextAware, BeanPostProcessor {
private ApplicationContext applicationContext;
private final Log logger = LogFactory.getLog(getClass());
@Override
// ApplicationContextAware, 管理ApplicationContext
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 判断该bean是否实现该接口
if(TagCheckFacade.class.isAssignableFrom(bean.getClass())){
// 获取注解
TagCheck tagCheck = bean.getClass().getAnnotation(TagCheck.class);
if(tagCheck != null){
TagServiceCenter tagServiceCenter = applicationContext.getBean(TagServiceCenter.class);
// 获取注解中标识的标签枚举
EmTags emTags = tagCheck.value();
tagServiceCenter.registTagCheckService(emTags, (TagCheckFacade) bean);
}
}
return bean;
}
}
Map args;
// 获取标签绑定的判断实现逻辑
TagCheckFacade tagCheckFacade = tagServiceCenter.getTagCheckService(EmTags.XXX);
// 调用判断标签的逻辑
Boolean result = tagCheckFacade.checkTag(args);
HashSet tagsResult = new HashSet<>();
Map args = new HashMap<>();
for(EmTags emTags : EmTags.values()){
TagCheckFacade tagCheckFacade = tagServiceCenter.getTagCheckService(emTags);
if(tagCheckFacade != null && tagCheckFacade.checkTag(args)){
tagsResult.add(emTags);
}
}
标签系统的移植性
经过上文可以发现,整个标签框架实现很简单,通过上述的核心代码进行扩展和二次开发并非难事。
如要需要以jar包形式提供移植方案的话。因为枚举类型不支持Spring配置枚举值,因此需要转换标签的记录形式,但此外整体思想和框架不变。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者郭蕴霆授权发布。