Array.prototype.filter() - JavaScript | MDN

mikel阅读(711)

来源: Array.prototype.filter() – JavaScript | MDN

Array.prototype.filter()

filter() 方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。

 

尝试一下

 

语法

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

参数

callback
用来测试数组的每个元素的函数。返回 true 表示该元素通过测试,保留该元素,false 则不保留。它接受以下三个参数:

element
数组中当前正在处理的元素。
index可选
正在处理的元素在数组中的索引。
array可选
调用了 filter 的数组本身。
thisArg可选
执行 callback 时,用于 this 的值。

返回值

一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

描述

filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或等价于 true 的值的元素创建一个新数组。callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。

callback 被调用时传入三个参数:

  1. 元素的值
  2. 元素的索引
  3. 被遍历的数组本身

如果为 filter 提供一个 thisArg 参数,则它会被作为 callback 被调用时的 this 值。否则,callback 的 this 值在非严格模式下将是全局对象,严格模式下为 undefinedcallback 函数最终观察到的 this 值是根据通常函数所看到的 “this”的规则确定的。

filter 不会改变原数组,它返回过滤后的新数组。

filter 遍历的元素范围在第一次调用 callback 之前就已经确定了。在调用 filter 之后被添加到数组中的元素不会被 filter 遍历到。如果已经存在的元素被改变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值。被删除或从来未被赋值的元素不会被遍历到。

示例

筛选排除所有较小的值

下例使用 filter 创建了一个新数组,该数组的元素由原数组中值大于 10 的元素组成。

function isBigEnough(element) {
  return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44]

过滤 JSON 中的无效条目

以下示例使用 filter() 创建具有非零 id 的元素的 json。

var arr = [
  { id: 15 },
  { id: -1 },
  { id: 0 },
  { id: 3 },
  { id: 12.2 },
  { },
  { id: null },
  { id: NaN },
  { id: 'undefined' }
];

var invalidEntries = 0;

function isNumber(obj) {
  return obj !== undefined && typeof(obj) === 'number' && !isNaN(obj);
}

function filterByID(item) {
  if (isNumber(item.id) && item.id !== 0) {
    return true;
  }
  invalidEntries++;
  return false;
}

var arrByID = arr.filter(filterByID);

console.log('Filtered Array\n', arrByID);
// Filtered Array
// [{ id: 15 }, { id: -1 }, { id: 3 }, { id: 12.2 }]

console.log('Number of Invalid Entries = ', invalidEntries);
// Number of Invalid Entries = 5

在数组中搜索

下例使用 filter() 根据搜索条件来过滤数组内容。

var fruits = ['apple', 'banana', 'grapes', 'mango', 'orange'];

/**
 * Array filters items based on search criteria (query)
 */
function filterItems(query) {
  return fruits.filter(function(el) {
      return el.toLowerCase().indexOf(query.toLowerCase()) > -1;
  })
}

console.log(filterItems('ap')); // ['apple', 'grapes']
console.log(filterItems('an')); // ['banana', 'mango', 'orange']

ES2015 实现

const fruits = ['apple', 'banana', 'grapes', 'mango', 'orange'];

/**
 * Array filters items based on search criteria (query)
 */
const filterItems = (query) => {
  return fruits.filter((el) =>
    el.toLowerCase().indexOf(query.toLowerCase()) > -1
  );
}

console.log(filterItems('ap')); // ['apple', 'grapes']
console.log(filterItems('an')); // ['banana', 'mango', 'orange']

Polyfill

filter 被添加到 ECMA-262 标准第 5 版中,因此在某些实现环境中不被支持。可以把下面的代码插入到脚本的开头来解决此问题,该代码允许在那些没有原生支持 filter 的实现环境中使用它。该算法是 ECMA-262 第 5 版中指定的算法,假定 fn.call 等价于 Function.prototype.call 的初始值,且 Array.prototype.push 拥有它的初始值。

if (!Array.prototype.filter){
  Array.prototype.filter = function(func, thisArg) {
    'use strict';
    if ( ! ((typeof func === 'Function' || typeof func === 'function') && this) )
        throw new TypeError();

    var len = this.length >>> 0,
        res = new Array(len), // preallocate array
        t = this, c = 0, i = -1;
    if (thisArg === undefined){
      while (++i !== len){
        // checks to see if the key was set
        if (i in this){
          if (func(t[i], i, t)){
            res[c++] = t[i];
          }
        }
      }
    }
    else{
      while (++i !== len){
        // checks to see if the key was set
        if (i in this){
          if (func.call(thisArg, t[i], i, t)){
            res[c++] = t[i];
          }
        }
      }
    }

    res.length = c; // shrink down array to proper size
    return res;
  };
}

规范

浏览器兼容性

Report problems with this compatibility data on GitHub

JavaScript数组方法的兼容性写法 汇总:indexOf()、forEach()、map()、filter()、some()、every() - 凳子_joinery - 博客园

mikel阅读(711)

来源: JavaScript数组方法的兼容性写法 汇总:indexOf()、forEach()、map()、filter()、some()、every() – 凳子_joinery – 博客园

ECMA Script5中数组方法如indexOf()、forEach()、map()、filter()、some()并不支持IE6~8,但是国内依然有一大部分用户使用IE6~8,而以上数组方法又确实非常好用。在过去,我会为了兼容性尽量不用这些方法。但是,总不能为了旧的丢了新的吧?!虽然说JQuery已经集成好了不少语法糖,但JQuery体积太庞大,作为一名志于体面的前端儿得知道原生的兼容性写法要怎么写。于是这几天,我开始在琢磨这些方法的兼容性写法。其实并不难,就是以前不够自信不敢写。写完以后,对数组认识更深一些了。总结见下文。

备注:以下兼容性写法均可兼容至IE6;

一. indexOf()

indexOf()方法 返回根据给定元素找到的第一个索引值,否则返回-1。

语法:

array.indexOf(searchElement[, fromIndex = 0])

参数:

searchElement  // 位于数组中的元素;

fromIndex // 开始查找指定元素的索引值,默认值为 0 (即在整个数组中查找指定元素);

fromIndex大于或等于数组长度,则停止查找并返回-1。如果参数中提供的索引值是一个负值,则将其作为数组末尾的一个抵消,即-1表示从最后一个元素开始查找,-2表示从倒数第二个元素开始查找 ,以此类推。

兼容性:不兼容IE6~8。

根据indexOf()的语法写出兼容IE6~8的写法如下:

兼容性写法:

if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(ele) {
        // 获取数组长度
        var len = this.length;
        // 检查值为数字的第二个参数是否存在,默认值为0
        var fromIndex = Number(arguments[1]) || 0;
        // 当第二个参数小于0时,为倒序查找,相当于查找索引值为该索引加上数组长度后的值
        if(fromIndex < 0) {
            fromIndex += len;
        }
        // 从fromIndex起循环数组
        while(fromIndex < len) {
            // 检查fromIndex是否存在且对应的数组元素是否等于ele
            if(fromIndex in this && this[fromIndex] === ele) {
                return fromIndex;
            }
            fromIndex++;
        }
        // 当数组长度为0时返回不存在的信号:-1
        if (len === 0) {
            return -1;
        }
    }
}

调用示例:

var arr = ["a", "b", "c"];
alert(arr.indexOf("b")); // 1

二. forEach()

forEach() 方法让数组的每一项都执行一次给定的函数。forEach()方法会修改原数组。

语法:

array.forEach(callback[, thisArg])

参数:

1 . callback :在数组每一项上执行的函数,接收三个参数: currentValue(当前项的值)、index(当前项的索引)和array(数组本身)。

2 . thisArg :可选参数。用来当作callback 函数内this的值的对象,即callback 函数的执行上下文;

forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除(使用delete方法等情况)或者从未赋值的项将被跳过(但不包括哪些值为 undefined 的项)。

兼容性:不兼容IE6~8。

根据forEach()的语法写出兼容IE6~8的写法如下:

兼容性写法:

if ( !Array.prototype.forEach) {
  Array.prototype.forEach = function forEach(callback) {
      // 获取数组长度
    var len = this.length;
    if(typeof callback != "function") {
        throw new TypeError();
    }
    // thisArg为callback 函数的执行上下文环境
    var thisArg = arguments[1];
    for(var i = 0; i < len; i++) {
        if(i in this) {
            // callback函数接收三个参数:当前项的值、当前项的索引和数组本身
            callback.call(thisArg, this[i], i, this);
        }
    }
  }
}

调用示例:

var arr = ["a", "b", "c", "a", "d", "a"];
arr.forEach(function(ele, index, array){
    if(ele == "a") {
        array[index] = "**";
    }
});
alert(newArr); // ["**", "b", "c", "**", "d", "**"]

三. map()

map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。

语法:

array.map(callback[, thisArg])

参数:

1 . callback : 在数组每一项上执行的函数,接收三个参数: currentValue(当前项的值)、index(当前项的索引)和array(数组本身);

2 . thisArg :可选参数。用来当作callback 函数内this的值的对象,即callback 函数的执行上下文;

兼容性:不兼容IE6~8。

根据map()的语法写出兼容IE6~8的写法如下:

兼容性写法:

if (!Array.prototype.map) {
  Array.prototype.map = function(callback) {
      // 获取数组长度
      var len = this.length;
      if(typeof callback != "function") {
          throw new TypeError();
      }
      // 创建跟原数组相同长度的新数组,用于承载经回调函数修改后的数组元素
      var newArr = new Array(len);
      // thisArg为callback 函数的执行上下文环境
      var thisArg = arguments[1];
      for(var i = 0; i < len; i++) {
          if(i in this) {
              newArr[i] = callback.call(thisArg, this[i], i, this);
          }
      }
      return newArr;
  }    
}

调用示例:

var arr = ["a", "b", "c"];
var newArr = arr.map(function(ele, index, array){
    ele += "12";
    return ele;
});
alert(newArr); // ["a12", "b12", "c12"]

四. filter()

filter() 方法利用所有通过指定函数测试的元素创建一个新的数组,并返回。

语法:

arr.filter(callback[, thisArg])

参数:

1 . callback : 在数组每一项上执行的函数,接收三个参数: currentValue(当前项的值)、index(当前项的索引)和array(数组本身);

2 . thisArg :可选参数。用来当作callback 函数内this的值的对象,即callback 函数的执行上下文;

filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 “true” 的元素创建一个新数组。callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素只会被跳过,不会被包含在新数组中。filter 不会改变原数组。

兼容性:不兼容IE6~8。

根据filter()的语法写出兼容IE6~8的写法如下:

兼容性写法:

if (!Array.prototype.filter) {
    Array.prototype.filter = function(callback) {
      // 获取数组长度
      var len = this.length;
      if(typeof callback != "function") {
          throw new TypeError();
      }
      // 创建新数组,用于承载经回调函数修改后的数组元素
      var newArr = new Array();
      // thisArg为callback 函数的执行上下文环境
      var thisArg = arguments[1];
      for(var i = 0; i < len; i++) {
          if(i in this) {
              if(callback.call(thisArg, this[i], i, this)) {
                  newArr.push(val);
              }
          }
      }
      return newArr;
  }
}

调用示例:

var arr = [1, 2, 3, 4, 3, 2, 5];
var newArr = arr.filter(function(ele, index, array){
    if(ele < 3) {
        return true;
    }else {
        return false;
    }
});
alert(newArr); // [1, 2, 2]

五. some()

some() 方法测试数组中的某些元素是否通过了指定函数的测试。返回布尔值。some() 被调用时不会改变数组。

语法:

arr.some(callback[, thisArg])

参数:

1 . callback : 在数组每一项上执行的函数,接收三个参数: currentValue(当前项的值)、index(当前项的索引)和array(数组本身);

2 . thisArg :可选参数。用来当作callback 函数内this的值的对象,即callback 函数的执行上下文;

some() 为数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个true 的值。如果找到了这样一个值,some() 将返回 true。否则返回 false。

兼容性:不兼容IE6~8。

根据some()的语法写出兼容IE6~8的写法如下:

兼容性写法:

if (!Array.prototype.some) {
  Array.prototype.some = function(callback) {
      // 获取数组长度
      var len = this.length;
      if(typeof callback != "function") {
          throw new TypeError();
      }
      // thisArg为callback 函数的执行上下文环境
      var thisArg = arguments[1];
      for(var i = 0; i < len; i++) {
          if(i in this && callback.call(thisArg, this[i], i, this)) {
              return true;
          }
      }
      return false;
  }
}

调用示例:

var arr = [1, 2, 3, 4, 3, 2, 5];
var newArr = arr.some(function(ele, index, array){
    if(ele < 2) {
        return true;
    }else {
        return false;
    }
});
alert(newArr); // true

六. every()

every() 方法测试数组的所有元素是否都通过了指定函数的测试。every() 不会改变原数组。

语法:

arr.every(callback[, thisArg])

参数:

1 . callback : 在数组每一项上执行的函数,接收三个参数: currentValue(当前项的值)、index(当前项的索引)和array(数组本身);

2 . thisArg :可选参数。用来当作callback 函数内this的值的对象,即callback 函数的执行上下文;

every() 方法为数组中的每个元素执行一次 callback 函数。只有所有元素都在callback函数中返回true才返回true,否则返回false。

兼容性:不兼容IE6~8。

根据some()的语法写出兼容IE6~8的写法如下:

兼容性写法:

if (!Array.prototype.every) {
  Array.prototype.every = function(callback) {
      // 获取数组长度
      var len = this.length;
      if(typeof callback != "function") {
          throw new TypeError();
      }
      // thisArg为callback 函数的执行上下文环境
      var thisArg = arguments[1];
      for(var i = 0; i < len; i++) {
          if(i in this && !callback.call(thisArg, this[i], i, this)) {
              return false;
          }
      }
      return true;
  }
}

调用示例:

var arr = [1, 2, 3, 4, 3, 2, 5];
var newArr = arr.every(function(ele, index, array){
    if(ele < 3) {
        return true;
    }else {
        return false;
    }
});
alert(newArr); // false

Lodop谷歌浏览器无法打印,解决谷歌浏览器最新chrome94版本CORS跨域问题 - 知乎

mikel阅读(1181)

来源: 解决谷歌浏览器最新chrome94版本CORS跨域问题 – 知乎

CORS跨域问题:

升级谷歌浏览器最新chrome94版本后,提示Access to XMLHttpRequest at ‘http://localhost:xxxx/api‘ from origin ‘http://xxx.xxx.com:xxxx’ has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`.

解决办法:

打开浏览器,进入chrome://flags/页面

搜索Block insecure private network requests

设置为Disabled,Relaunch就好了。

新问题:升级新版本提示提示Access to XMLHttpRequest at ‘http://localhost:xxxx/api‘ from origin ‘http://xxx.xxx.com:xxxx’ has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`.的问题

需要设置Allow invalid certificates for resources loaded from localhost. 为enabled

以上解决不了只能在服务器端解决了:

http://aszhi.com/jishu/80.html

三种方法:IIS、Apache、Nginx可以直接配置Access-Control-Allow-Origin 跨域,具体如下:

IIS配置 我用这个配置解决了问题

只需要在IIS添加HTTP响应标头即可!

  1. Access-Control-Allow-Headers:Content-Type, api_key, Authorization
  2. Access-Control-Allow-Origin:*

Apache配置

主要修改http.conf 或者,修改Apache伪静态规则文件.htaccess

  1. <Directory “/Users/cindy/dev”>
  2. AllowOverride ALL
  3. Header set Access-Control-Allow-Origin *

Nginx配置

主要是修改nginx.conf

  1. location ~* .(eot|ttf|woff|svg|otf)$ {
  2. add_header Access-Control-Allow-Origin *;
  3. }

上面的eot|ttf|woff|svg|otf,表示请求后缀类型,或者也可以直接写如下代码:

  1. location / {
  2. add_header Access-Control-Allow-Origin *;
  3. }

Only variable references should be returned by reference_diligentyang的博客-CSDN博客

mikel阅读(639)

来源: (1条消息) Only variable references should be returned by reference_diligentyang的博客-CSDN博客

在新版本的PHP中,打开的旧版的CI框架,报错如下

A PHP Error was encountered
Severity: Notice

Message: Only variable references should be returned by reference

Filename: core/Common.php

Line Number: 257

打开System\core\Common.php

修改如下内容

return $_config[0] =& $config;

$_config[0] =& $config;
return $_config[0];

即可
————————————————
版权声明:本文为CSDN博主「diligentyang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_28602957/article/details/51956757

比MyBatis快100倍,天生支持联表!

mikel阅读(742)

来源: 比MyBatis快100倍,天生支持联表!

来自:gitee

链接:https://gitee.com/troyzhxu/bean-searcher

目录

  • 简介
  • 开源协议
  • 界面展示
  • 功能概述
  • 技术选型
  • 源码地址

简介

对于 Java 程序员来说,MyBatis、Hibernate 等都是常见的 ORM 框架,对于一些简单的 CRUD,事务支持非常不错。

但是有时候用起来可能比较繁琐,最近看到一个新的类似 ORM 的框架,具备了 ORM 框架的功能,同时也还有一点 GraphQL 语法的味道。

比 MyBatis 效率快 100 倍的条件检索引擎,天生支持联表,使一行代码实现复杂列表检索成为可能,绝无夸张之语!

图片

开源协议

使用 Apache-2.0 开源协议。

界面展示

如下图:
图片

你的产品给你画了以上一张图,还附带了一些要求:

  • 检索结果分页展示
  • 可以按任意字段排序
  • 按检索条件统计某些字段值

这时候,后台接口该怎么写???使用 Mybatis 或 Hibernate 写 100 行代码是不是还打不住?而使用 Bean Searcher,只需一行代码便可实现上述要求!!!

功能概述

特性如下:

  • 支持实体多表映射
  • 支持动态字段运算符
  • 支持分组聚合、查询
  • 支持Select | Where | From 子查询
  • 支持实体类嵌入参数
  • 支持字段转换器
  • 支持 SQL 拦截器
  • 支持数据库 Dialect 扩展
  • 支持多数据源与动态数据源
  • 支持注解缺省与自定义
  • 支持字段运算符扩展
  • 等等

| 快速开发

使用 Bean Searcher 可以极大节省后端的复杂列表检索接口的开发时间。

| 集成简单

可以和任意 Java Web 框架集成,如:SpringBoot、Grails、Jfinal 等。

| 扩展性强

面向接口设计,用户可自定义扩展 Bean Searcher 中的任何组件。

| 支持注解缺省

约定优于配置,可省略注解,可复用原有域类,同时支持自定义注解。

| 支持多数据源

分库分表?在这里特别简单,告别分库分表带来的代码熵值增高问题。

| 支持 Select 指定字段

同一个实体类,可指定只 Select 其中的某些字段,或排除某些字段。

| 支持参数过滤器

支持添加多个参数过滤器,可自定义参数过滤规则。

| 支持字段转换器

支持添加多个字段转换器,可自定义数据库字段到实体类字段的转换规则。

| 支持 SQL 拦截器

支持添加多个 SQL 拦截器,可自定义 SQL 生成规则。

技术选型

框架目的:只一行代码实现多表联查分页搜索任意字段组合过滤任意字段排序多字段统计。

架构图如下:
图片

为什么用?这绝不是一个重复的轮子。

虽然增删改是 hibernate 和 mybatis、data-jdbc 等等 ORM 的强项,但查询,特别是有 多条件、联表、分页、排序的复杂的列表查询,却一直是它们的弱项。

传统的 ORM 很难用较少的代码实现一个复杂的列表检索,但 Bean Searcher 却在这方面下足了功夫,这些复杂的查询,几乎只用一行代码便可以解决。

例如,这样的一个典型的需求:
图片

后端需要写一个检索接口,而如果用传统的 ORM 来写,代码之复杂是可以想象的,而 Bean Searcher 却可以只一行代码实现以上功能。

首先,你有一个实体类:
@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u")

public class User {
  private long id;
  private String username;
  private int status;
  private int age;
  private String gender;
  private Date joinDate;
  private int roleId;
  @DbField("r.name")
  private String roleName;
  // Getters and setters...
}
然后你就可以用一行代码实现这个用户检索接口:
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private BeanSearcher beanSearcher; // 注入 BeanSearcher 的检索器

    @GetMapping("/index")
    public SearchResult<User> index(HttpServletRequest request) {
        // 这里只写一行代码
        return beanSearcher.search(User.class, MapUtils.flat(request.getParameterMap()), new String[]{ "age" });
    }

}

这一行代码实现了以下功能:

  • 多表联查
  • 分页搜索
  • 组合过滤
  • 任意字段排序
  • 字段统计

例如,该接口支持如下请求:

GET: /user/index:无参请求(默认分页)。

代码如下:

{
  "dataList": [
    {
      "id": 1,
      "username": "Jack",
      "status": 1,
      "level": 1,
      "age": 25,
      "gender": "Male",
      "joinDate": "2021-10-01"
    },
    ...     // 默认返回 15 条数据
  ],
  "totalCount": 100,
  "summaries": [
    2500    // age 字段统计
  ]
}
GET: /user/index? page=1 & size=10:指定分页参数。
GET: /user/index? status=1:返回 status = 1 的用户。
GET: /user/index? name=Jac & name-op=sw:返回 name 已 Jac 开头的用户。
GET: /user/index? name=Jack & name-ic=true:返回 name = Jack(忽略大小写)的用户。
GET: /user/index? sort=age & order=desc:按字段 age 降序查询。

GET: /user/index? onlySelect=username,age:只检索 username 与 age 两个字段。

代码如下:
{
  "dataList": [
    {
      "username": "Jack",
      "age": 25
    },
    ...
  ],
  "totalCount": 100,
  "summaries": [
    2500
  ]
}

GET: /user/index? selectExclude=joinDate:检索时排除 joinDate 字段。

| 参数构建器

Map<String, Object> params = MapUtils.builder()
        .selectExclude(User::getJoinDate)                 // 排除 joinDate 字段
        .field(User::getStatus, 1)                        // 过滤:status = 1
        .field(User::getName, "Jack").ic()                // 过滤:name = 'Jack' (case ignored)
        .field(User::getAge, 20, 30).op(Opetator.Between) // 过滤:age between 20 and 30
        .orderBy(User::getAge, "asc")                     // 排序:年龄,从小到大
        .page(0, 15)                                      // 分页:第 0 页, 每页 15 条
        .build();
List<User> users = beanSearcher.searchList(User.class, params);

| 快速开发

使用 Bean Searcher 可以极大地节省后端的复杂列表检索接口的开发时间:

  • 普通的复杂列表查询只需一行代码
  • 单表检索可复用原有 Domain,无需定义 SearchBean

| 集成简单

可以和任意 Java Web 框架集成,如:SpringBoot、Spring MVC、Grails、Jfinal 等等。

SpringBoot 项目,添加依赖即集成完毕:

implementation 'com.ejlchina:bean-searcher-boot-stater:3.6.0'
接着便可在 Controller 或 Service 里注入检索器:
/**
 * 注入 Map 检索器,它检索出来的数据以 Map 对象呈现
 */
@Autowired
private MapSearcher mapSearcher;

/**
 * 注入 Bean 检索器,它检索出来的数据以 泛型 对象呈现
 */
@Autowired
private BeanSearcher beanSearcher;

其他框架,使用如下依赖:

implementation 'com.ejlchina:bean-searcher:3.6.0'
然后可以使用 SearcherBuilder 构建一个检索器:
DataSource dataSource = ...     // 拿到应用的数据源

// DefaultSqlExecutor 也支持多数据源
SqlExecutor sqlExecutor = new DefaultSqlExecutor(dataSource);

// 构建 Map 检索器
MapSearcher mapSearcher = SearcherBuilder.mapSearcher()
        .sqlExecutor(sqlExecutor)
        .build();

// 构建 Bean 检索器
BeanSearcher beanSearcher = SearcherBuilder.beanSearcher()
        .sqlExecutor(sqlExecutor)
        .build();

| 扩展性强

面向接口设计,用户可自定义扩展 Bean Searcher 中的任何组件!

比如你可以:

  • 自定义 FieldOp 来支持更多的字段运算符
  • 自定义 FieldConvertor 来支持任意的 特殊字段类型
  • 自定义 DbMapping 来实现自定义注解,或让 Bean Searcher 识别其它 ORM 的注解
  • 自定义 ParamResolver 来支持其它形式的检索参数
  • 自定义 Dialect 来支持更多的数据库
  • 等等..

源码地址

Gitee:https://gitee.com/ejlchina-zhxu/bean-searcher
GitHub:https://github.com/ejlchina/bean-searcher
— EOF —

SQLServer获取最后插入的ID值SCOPE_IDENTITY、IDENT_CURRENT 和 @@IDENTITY的比较_zhang0788的博客-CSDN博客

mikel阅读(601)

来源: SQLServer获取最后插入的ID值SCOPE_IDENTITY、IDENT_CURRENT 和 @@IDENTITY的比较_zhang0788的博客-CSDN博客

IDENT_CURRENT 返回为任何会话和任何作用域中的特定表最后生成的标识值。IDENT_CURRENT 不受作用域和会话的限制,而受限于指定的表。
@@IDENTITY 返回为当前会话的所有作用域中的任何表最后生成的标识值。
SCOPE_IDENTITY 返回为当前会话和当前作用域中的任何表最后生成的标识值

也许大家对SQL Server中的 @@IDENTITY 都不陌生,都知道它是获取数据表中最后一条插入数据的IDENTITY值。
比如,表 A 中有个 ID 为自增1的字段,假设此时 ID 的值为100,现在如果我往表A插入一条数据,并在插入后
SELECT @@IDENTITY,则其返回 101,最后一条IDENTITY域(即ID域)的值。

现在问题来了,为什么说要慎用@@IDENTITY呢?原因是 @@IDENTITY 它总是获取最后一条变更数据的自增字段的值,
而忽略了进行变更操作所在的范围约束。比如,我有表 A 和表 B 两个表,现在我在表 A 上定义了一个Insert触发器,
当在表 A 中插入一条数据时,自动在表 B 也插入一条数据。此时,大家注意,有两个原子操作:在A中插入一条数据, 接着在B中随后插入一条数据。

现在我们想下,假设上面表 A 和表 B 都有IDENTITY自增域,那么我们在表 A 插入一条数据后,使用了
SELECT @@IDENTITY 输出时,输出的到底是 A 还是 B 的自增域的值呢? 答案很明显,是谁最后插入就输出谁,
那么就是 B 了。于是,我本意是想得到 A 的自增域值,结果得到了 B 的自增域值,一只 BUG 随之诞生,搞不好还
会影响到整个系统数据的混乱。

因此,对于这种情况,建议大家慎用 @@IDENTITY,而尽量采用 SCOPE_IDENTITY() 函数替换之。SCOPE_IDENTITY()
也是得到最后一条自增域的值,但是它是仅限在一个操作范围之内,而不像 @@IDENTITY 是取全局操作的最后一步操作
所产生的自增域的值的。
————————————————
版权声明:本文为CSDN博主「zhang0788」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhang0788/article/details/5172923

SQL Server 获取刚插入数据的ID号_宇宙最强皮皮虾的博客-CSDN博客

mikel阅读(733)

来源: SQL Server 获取刚插入数据的ID号_宇宙最强皮皮虾的博客-CSDN博客

第一种方法:
insert into table1 values(colvalue1,colvalue2)

select ident_current(‘table1’)

第二种方法:
insert into table1 values(colvalue1,colvalue2)
select @@identity

主要应用Transact-SQL中的函数

1、IDENT_CURRENT()      

2、SQL Server 2000 标识函数 SCOPE_IDENTITY ()

3、SQL Server 2000 标识函数 @@IDENTITY

IDENT_CURRENT() 

返回为某个会话和作用域中指定的表或视图生成的最新的标识值。

语法

IDENT_CURRENT( 'table_name' )

备注

IDENT_CURRENT 类似于 SQL Server 2000 标识函数 SCOPE_IDENTITY 和 @@IDENTITY。这三个函数都返回最后生成的标识值。但是,上述每个函数中定义的“最后”的作用域和会话有所不同。

  • IDENT_CURRENT 返回为某个会话和用域中的指定表生成的最新标识值。
  • @@IDENTITY 返回为跨所有作用域的当前会话中的某个表生成的最新标识值。
  • SCOPE_IDENTITY 返回为当前会话和当前作用域中的某个表生成的最新标识值。

在空表中调用 IDENT_CURRENT 函数时,此函数将返回 NULL。

如果语句和事务失败,它们会更改表的当前标识,从而使标识列中的值出现不连贯现象。即使未提交试图向表中插入值的事务,也永远无法回滚标识值。例如,如果因 IGNORE_DUP_KEY 冲突而导致 INSERT 语句失败,表的当前标识值仍然会增加。

参数

table_name
其标识值被返回的表的名称。table_name 的数据类型为 varchar,无默认值。

返回类型

sql_variant

示例

以下示例将显示由 IDENT_CURRENT@@IDENTITY 和 SCOPE_IDENTITY 返回的不同标识值。

USE AdventureWorks; GO DROP TABLE t6; DROP TABLE t7; GO CREATE TABLE t6(id int IDENTITY); CREATE TABLE t7(id int IDENTITY(100,1)); GO CREATE TRIGGER t6ins ON t6 FOR INSERT AS BEGIN INSERT t7 DEFAULT VALUES END; GO --End of trigger definition SELECT * FROM t6; --id is empty. SELECT * FROM t7; --ID is empty. --Do the following in Session 1 INSERT t6 DEFAULT VALUES; SELECT @@IDENTITY; SELECT SCOPE_IDENTITY(); SELECT IDENT_CURRENT('t7'); SELECT IDENT_CURRENT('t6'); -- Do the following in Session 2. SELECT @@IDENTITY; SELECT SCOPE_IDENTITY(); SELECT IDENT_CURRENT('t7');

SCOPE_IDENTITY ()

返回插入到同一作用域中的标识列内的最后一个标识值。一个范围是一个模块:存储过程、触发器、函数或批处理。因此,如果两个语句处于同一个存储过程、函数或批处理中,则它们位于相同的作用域中。

语法

SCOPE_IDENTITY()

备注

SCOPE_IDENTITY、IDENT_CURRENT 和 @@IDENTITY 是相似的函数,因为它们都返回插入到标识列中的值。

IDENT_CURRENT 不受作用域和会话的限制,而受限于指定的表。IDENT_CURRENT 返回为任何会话和作用域中的特定表所生成的值。有关详细信息,请参阅 IDENT_CURRENT (Transact-SQL)

SCOPE_IDENTITY 和 @@IDENTITY 返回在当前会话中的任何表内所生成的最后一个标识值。但是,SCOPE_IDENTITY 只返回插入到当前作用域中的值;@@IDENTITY 不受限于特定的作用域。

例如,有两个表 T1 和 T2,并且在 T1 上定义了 INSERT 触发器。当将某行插入 T1 时,触发器被激发,并在 T2 中插入一行。 该方案演示了两个作用域:在 T1 上的插入,以及在 T2 通过触发器的插入。

假设 T1 和 T2 都有标识列,@@IDENTITY 和 SCOPE_IDENTITY 将在 T1 上的 INSERT 语句的最后返回不同的值。@@IDENTITY 将返回在当前会话中的任何作用域内插入的最后一个标识列的值。这是在 T2 中插入的值。SCOPE_IDENTITY() 将返回在 T1 中插入的 IDENTITY 值。这是在同一个作用域内发生的最后的插入。如果在任何 INSERT 语句作用于作用域中的标识列之前调用 SCOPE_IDENTITY() 函数,则该函数将返回空值。

如果语句和事务失败,它们会更改表的当前标识,从而使标识列中的值出现不连贯现象。即使未提交试图向表中插入值的事务,也永远无法回滚标识值。例如,如果因 IGNORE_DUP_KEY 冲突而导致 INSERT 语句失败,表的当前标识值仍然会增加。

返回类型

numeric

示例

以下示列创建两个表,TZ 和 TY,并在 TZ 中创建一个 INSERT 触发器。当将某行插入表 TZ 中时,触发器 (Ztrig) 将激发并在 TY 中插入一行。

USE tempdb GO CREATE TABLE TZ ( Z_id int IDENTITY(1,1)PRIMARY KEY, Z_name varchar(20) NOT NULL) INSERT TZ VALUES ('Lisa') INSERT TZ VALUES ('Mike') INSERT TZ VALUES ('Carla') SELECT * FROM TZ --Result set: This is how table TZ looks. Z_id Z_name ------------- 1 Lisa 2 Mike 3 Carla CREATE TABLE TY ( Y_id int IDENTITY(100,5)PRIMARY KEY, Y_name varchar(20) NULL) INSERT TY (Y_name) VALUES ('boathouse') INSERT TY (Y_name) VALUES ('rocks') INSERT TY (Y_name) VALUES ('elevator') SELECT * FROM TY --Result set: This is how TY looks: Y_id Y_name --------------- 100 boathouse 105 rocks 110 elevator CREATE TRIGGER Ztrig ON TZ FOR INSERT AS BEGIN INSERT TY VALUES ('') END INSERT TZ VALUES ('Rosalie') SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY] GO SELECT @@IDENTITY AS [@@IDENTITY] GO

下面是结果集: 

SCOPE_IDENTITY 4 @@IDENTITY 115

@@IDENTITY

返回最后插入的标识值的系统函数。

语法

@@IDENTITY

备注

在一条 INSERT、SELECT INTO 或大容量复制语句完成后,@@IDENTITY 中包含语句生成的最后一个标识值。如果语句未影响任何包含标识列的表,则 @@IDENTITY 返回 NULL。如果插入了多个行,生成了多个标识值,则 @@IDENTITY 将返回最后生成的标识值。如果语句触发了一个或多个触发器,该触发器又执行了生成标识值的插入操作,那么,在语句执行后立即调用 @@IDENTITY 将返回触发器生成的最后一个标识值。如果对包含标识列的表执行插入操作后触发了触发器,并且触发器对另一个没有标识列的表执行了插入操作,则 @@IDENTITY 将返回第一次插入的标识值。出现 INSERT 或 SELECT INTO 语句失败或大容量复制失败,或者事务被回滚的情况时,@@IDENTITY 值不会恢复为以前的设置。

如果语句和事务失败,它们会更改表的当前标识,从而使标识列中的值出现不连贯现象。即使未提交试图向表中插入值的事务,也永远无法回滚标识值。例如,如果因 IGNORE_DUP_KEY 冲突而导致 INSERT 语句失败,表的当前标识值仍然会增加。

@@IDENTITY、SCOPE_IDENTITY 和 IDENT_CURRENT 是相似的函数,因为他们都返回插入到表的 IDENTITY 列的最后一个值。

@@IDENTITY 和 SCOPE_IDENTITY 可以返回当前会话中的所有表中生成的最后一个标识值。但是,SCOPE_IDENTITY 只在当前作用域内返回值,而 @@IDENTITY 不限于特定的作用域。

IDENT_CURRENT 不受作用域和会话的限制,而受限于指定的表。IDENT_CURRENT 可以返回任何会话和任何作用域中为特定表生成的标识值。有关详细信息,请参阅 IDENT_CURRENT (Transact-SQL)

@@IDENTITY 函数的作用域是执行该函数的本地服务器上的当前会话。此函数不能应用于远程或链接服务器。若要获得其他服务器上的标识值,请在远程服务器或链接服务器上执 行存储过程,并使(在远程或链接服务器的环境中执行的)该存储过程收集标识值,并将其返回本地服务器上的发出调用的连接。

返回类型

numeric

示例

以下示例向包含标识列 (LocationID) 的表中插入一行,并使用 @@IDENTITY 显示新行中使用的标识值。

USE AdventureWorks; GO --Display the value of LocationID in the last row in the table. SELECT MAX(LocationID) FROM Production.Location; GO INSERT INTO Production.Location (Name, CostRate, Availability, ModifiedDate) VALUES ('Damaged Goods', 5, 2.5, GETDATE()); GO SELECT @@IDENTITY AS 'Identity'; GO --Display the value of LocationID of the newly inserted row. SELECT MAX(LocationID) FROM Production.Location; GO

细谈八种架构设计模式及其优缺点概述 - 掘金

mikel阅读(702)

来源: 细谈八种架构设计模式及其优缺点概述 – 掘金

一、什么是架构

我想这个问题,十个人回答得有十一个答案,因为另外的那一个是大家妥协的结果。哈哈,我理解,架构就是骨架,如下图所示:

 

 

人类的身体的支撑是主要由骨架来承担的,然后是其上的肌肉、神经、皮肤。架构对于软件的重要性不亚于骨架对人类身体的重要性。

二、. 什么是设计模式

这个问题我问过的面试者不下于数十次,回答五花八门,在我看来,模式就是经验,设计模式就是设计经验,有了这些经验,我们就能在特定情况下使用特定的设计、组合设计,这样可以大大节省我们的设计时间,提高工作效率。 作为一个工作10年以上的老码农,经历的系统架构设计也算不少,接下来,我会把工作中用到的一些架构方面的设计模式分享给大家,望大家少走弯路。总体而言,共有八种,分别是:

  1. 单库单应用模式:最简单的,可能大家都见过
  2. 内容分发模式:目前用的比较多
  3. 查询分离模式:对于大并发的查询、业务
  4. 微服务模式:适用于复杂的业务模式的拆解
  5. 多级缓存模式:可以把缓存玩的很好
  6. 分库分表模式:解决单机数据库瓶颈
  7. 弹性伸缩模式:解决波峰波谷业务流量不均匀的方法之一
  8. 多机房模式:解决高可用、高性能的一种方法

三、单库单应用模式

这是最简单的一种设计模式,我们的大部分本科毕业设计、一些小的应用,基本上都是这种模式,这种模式的一般设计见下图:

 

 

如上图所示,这种模式一般只有一个数据库,一个业务应用层,一个后台管理系统,所有的业务都是用过业务层完成的,所有的数据也都是存储在一个数据库中的,好一点会有数据库的同步。虽然简单,但是也并不是一无是处

  • 优点:结构简单、开发速度快、实现简单,可用于产品的第一版等有原型验证需求、用户少的设计。
  • 缺点:性能差、基本没有高可用、扩展性差,不适用于大规模部署、应用等生产环境。

四、内容分发模式

基本上所有的大型的网站都有或多或少的采用这一种设计模式,常见的应用场景是使用CDN技术把网页、图片、CSS、JS等这些静态资源分发到离用户最近的服务器。这种模式的一般设计见下图:

 

 

如上图所示,这种模式较单库单应用模式多了一个CDN、一个云存储OSS(七牛、又拍等雷同)。一个典型的应用流程(以用户上传、查看图片需求为例)如下:

  1. 上传的时候,用户选择本地机器上的一个图片进行上传
  2. 程序会把这个图片上传到云存储OSS上,并返回该图片的一个URL
  3. 程序把这个URL字符串存储在业务数据库中,上传完成。
  4. 查看的时候,程序从业务数据库得到该图片的URL
  5. 程序通过DNS查询这个URL的图片服务器
  6. 智能DNS会解析这个URL,得到与用户最近的服务器(或集群)的地址A
  7. 然后把服务器A上的图片返回给程序
  8. 程序显示该图片,查看完成。

由上可知,这个模式的关键是智能DNS,它能够解析出离用户最近的服务器。运行原理大致是:根据请求者的IP得到请求地点B,然后通过计算或者配置得到与B最近或通讯时间最短的服务器C,然后把C的IP地址返回给请求者。这种模式的优缺点如下:

  • 优点:资源下载快、无需过多的开发与配置,同时也减轻了后端服务器对资源的存储压力,减少带宽的使用。
  • 缺点:目前来说OSS,CDN的价格还是稍微有些贵(虽然已经降价好几次了),只适用于中小规模的应用,另外由于网络传输的延迟、CDN的同步策略等,会有一些一致性、更新慢方面的问题。

五、查询分离模式

这种模式主要解决单机数据库压力过大,从而导致业务缓慢甚至超时,查询响应时间变长的问题,也包括需要大量数据库服务器计算资源的查询请求。这个可以说是单库单应用模式的升级版本,也是技术架构迭代演进过程中的必经之路。 这种模式的一般设计见下图:

 

 

如上图所示,这种模式较单库单应用模式与内容分发模式多了几个部分,一个是业务数据库的主从分离,一个是引入了ES,为什么要这样?都解决了哪些痛点,下面具体结合业务需求场景进行叙述。

场景一:全文关键词检索

我想这个需求,绝大多数应用都会有,如果使用传统的数据库技术,大部分可能都会使用like这种SQL语句,高级一点可能是先分词,然后通过分词index相关的记录。SQL语句的性能问题与全表扫描机制导致了非常严重的性能问题,现在基本上很少见到。 这里的ES是ElasticSearch的缩写,是一种查询引擎,类似的还有Solr等,都差不多的技术,ES较Solr配置简单、使用方便,所以这里选用了它。另外,ES支持横向扩展,理论上没有性能的瓶颈。同时,还支持各种插件、自定义分词器等,可扩展性较强。在这里,使用ES不仅可以替代数据库完成全文检索功能,还可以实现诸如分页、排序、分组、分面等功能。具体的,请同学们自行学习之。那怎么使用呢?一个一般的流程是这样的:

  1. 服务端把一条业务数据落库
  2. 服务端异步把该条数据发送到ES
  3. ES把该条记录按照规则、配置放入自己的索引库
  4. 客户端查询的时候,由服务端把这个请求发送到ES,得到数据后,根据需求拼装、组合数据,返回给客户端

实际中怎么用,还请同学们根据实际情况做组合、取舍。

场景二:大量的普通查询

这个场景是指我们的业务中的大部分辅助性的查询,如:取钱的时候先查询一下余额,根据用户的ID查询用户的记录,取得该用户最新的一条取钱记录等。我们肯定是要天天要用的,而且用的还非常多。同时呢,我们的写入请求也是非常多的,导致大量的写入、查询操作压向同一数据库,然后,数据库挂了,系统挂了,领导生气了,被开除了,还不起房贷了,露宿街头了,老婆跟别人跑了,……

不敢想,所以要求我们必须分散数据库的压力,一个业界较成熟的方案就是数据库的读写分离,写的时候入主库,读的时候读从库。这样就把压力分散到不同的数据库了,如果一个读库性能不行,扛不住的话,可以一主多从,横向扩展。可谓是一剂良药啊!那怎么使用呢?一个一般的流程是这样的:

  1. 服务端把一条业务数据落库
  2. 数据库同步或异步或半同步把该条数据复制到从库
  3. 服务端读数据的时候直接去从库读相应的数据

比较简单吧,一些聪明的、爱思考的、上进的同学可能发现问题了,也包括上面介绍的场景一,就是延迟问题,如:数据还没有到从库,我就马上读,那么是读不到的,会发生问题的。 对于这个问题,各家公司解决的思路不一样,方法不尽相同。一个普遍的解决方案是:读不到就读主库,当然这么说也是有前提条件的,但具体的方案这里就不一一展开了,我可能会在接下来的分享中详解各种方案。 另外,关于数据库的复制模式,还请同学们自行学习,太多了,这里说不清。该总结一下这种模式的优缺点的了,如下:

  • 优点:减少数据库的压力,理论上提供无限高的读性能,间接提高业务(写)的性能,专用的查询、索引、全文(分词)解决方案。
  • 缺点:数据延迟,数据一致性的保证。

六、微服务模式

上面的模式看似不错,解决了性能问题,我可以不用露宿街头了、老婆还是我的,哈哈。但是

软件系统天生的复杂性决定了,除了性能,还有其他诸如高可用、健壮性等大量问题等待我们解决,再加上各个部门间的撕逼、扯皮,更让我们码农雪上加霜,所以

继续吧……

微服务模式可以说是最近的热点,花花绿绿、大大小小、国内国外的公司都在鼓吹,实践这个模式,可是大部分都没有弄清楚为什么要这么做,也并不知道这么做有什么好处、坏处,在这里,我将以我自己的亲身实践说一下我对这个模式的看法,不喜勿喷!随着业务与人员的增加,遇到了如下的问题:

  1. 单机数据库写请求量大量增加,导致数据库压力变大
  2. 数据库一旦挂了,那么整个业务都挂了
  3. 业务代码越来越多,都在一个GIT里,越来越难以维护
  4. 代码腐化严重、臭味越来越浓
  5. 上线越来越频繁,经常是一个小功能的修改,就要整个大项目要重新编译
  6. 部门越来越多,该哪个部门改动大项目中的哪个东西,撕逼的厉害
  7. 其他一些外围系统直接连接数据库,导致一旦数据库结构发生变化,所有的相关系统都要通知,甚至对修改不敏感的系统也要通知
  8. 每个应用服务器需要开通所有的权限、网络、FTP、各种各样的,因为每个服务器部署的应用都是一样的
  9. 作为架构师,我已经失去了对这个系统的把控……

为了解决上述问题,我司使用了微服务模式,这种模式的一般设计见下图:

 

 

如上图所示,我把业务分块,做了垂直切分,切成一个个独立的系统,每个系统各自衍化,有自己的库、缓存、ES等辅助系统,系统之间的实时交互通过RPC,异步交互通过MQ,通过这种组合,共同完成整个系统功能。 那么,这么做是否真的解决上述问题了呢?不玩虚的,一个个来说。对于问题一,由于拆分成了多个子系统,系统的压力被分散了,而各个子系统都有自己的数据库实例,所以数据库的压力变小。

对于问题二,一个子系统A的数据库挂了,只是影响到系统A和使用系统A的那些功能,不会所有的功能不可用,从而解决一个数据库挂了,导致所有功能不可用的问题。

问题三、四,也因为拆分得到了解决,各个子系统有自己独立的GIT代码库,不会相互影响。通用的模块可通过库、服务、平台的形式解决。

问题五,子系统A发生改变,需要上线,那么我只需要编译A,然后上线就可以了,不需要其他系统做同样的事情。

问题六,顺应了康威定律,我部门该干什么事、输出什么,也通过服务的形式暴露出来,我部只管把我部的职责、软件功能做好就可以。

问题七,所有需要我部数据的需求,都通过接口的形式发布出去,客户通过接口获取数据,从而屏蔽了底层数据库结构,甚至数据来源,我部只需保证我部的接口契约没有发生变化即可,新的需求增加新的接口,不会影响老的接口。

问题八,不同的子系统需要不同的权限,这个问题也优雅的解决了。

问题九,暂时控制住了复杂性,我只需控制好大的方面,定义好系统边界、接口、大的流程,然后再分而治之、逐个击破、合纵连横。

目前来说,所有问题得到解决!bingo! 但是,还有许多其他的副作用会随之产生,如RPC、MQ的超高稳定性、超高性能,网络延迟,数据一致性等问题,这里就不展开来讲了,太多了,一本书都讲不完。

另外,对于这个模式来说,最难把握的是,切记不要切分过细,我见过一个功能一个子系统,上百个方法分成上百个子系统的,真的是太过度了。实践中,一个较为可行的方法是:能不分就不分,除非有非常必要的理由!。

  • 优点:相对高性能,可扩展性强,高可用,适合于中等以上规模公司架构。
  • 缺点:复杂、度不好把握。指不仅需要一个能在高层把控大方向、大流程、总体技术的人,还需要能够针对各个子系统有针对性的开发。把握不好度或者滥用的话,这个模式适得其反!

七、多级缓存模式

这个模式可以说是应对超高查询压力的一种普遍采用的策略,基本的思想就是在所有链路的地方,能加缓存就加缓存,如下图所示:

 

 

如上图所示,一般在三个地方加入缓存,一个是客户端处,一个是API网关处,一个是具体的后端业务处,下面分别介绍。

客户端处缓存:这个地方加缓存可以说是效果最好的—无延迟。因为不用经过长长的网络链条去后端业务处获取数据,从而导致加载时间过长,客户流失等损失。虽然有CDN的支持,但是从客户端到CDN还是有网络延迟的,虽然不大。具体的技术依据不同的客户端而定,对于WEB来讲,有浏览器本地缓存、Cookie、Storage、缓存策略等技术;对于APP来讲,有本地数据库、本地文件、本地内存、进程内缓存支持。以上提到的各种技术有兴趣的同学可以继续展开来学习。如果客户端缓存没有命中,那么就会去后端业务拿数据,一般来讲,都会有个API网关,在这里加缓存也是非常有必要的。

API网关处缓存:这个地方加缓存的好处是不用把请求发送到后方,直接在这里就处理了,然后返回给请求者。常见的技术,如http请求,API网关用的基本都是nginx,可以使用nginx本身的缓存模块,也可以使用Lua+Redis技术定制化。其他的也都大同小异。

后端业务处:这个我想就不用多说了,大家应该差不多都知道,什么Redis,Memcache,Jvm内等等,不熬述了。

实践中,要结合具体的实际情况,综合利用各级缓存技术,使得各种请求最大程度的在到达后端业务之前就被解决掉,从而减少后端服务压力、减少占用带宽、增强用户体验。至于是否只有这三个地方加缓存,我觉得要活学活用,**心法比剑法重要!**总结一下这个模式的优缺点:

  • 优点:抗住大量读请求,减少后端压力。
  • 缺点:数据一致性问题较突出,容易发生雪崩,即:如果客户端缓存失效、API网关缓存失效,那么所有的大量请求瞬间压向后端业务系统,后果可想而知。

八、分库分表模式

这种模式主要解决单表写入、读取、存储压力过大,从而导致业务缓慢甚至超时,交易失败,容量不够的问题。一般有水平切分和垂直切分两种,这里主要介绍水平切分。这个模式也是技术架构迭代演进过程中的必经之路。 这种模式的一般设计见下图:

 

 

如上图所示红色部分,把一张表分到了几个不同的库中,从而分担压力。是不是很笼统?哈哈,那我们接下来就详细的讲解一下。首先澄清几个概念,如下: 主机:硬件,指一台物理机,或者虚拟机,有自己的CPU,内存,硬盘等。 实例:数据库实例,如一个MySQL服务进程。一个主机可以有多个实例,不同的实例有不同的进程,监听不同的端口。 :指表的集合,如学校库,可能包含教师表、学生表、食堂表等等,这些表在一个库中。一个实例中可以有多个库。库与库之间用库名来区分。 :库中的表,不必多说,不懂的就不用往下看了,不解释。

那么怎么把单表分散呢?到底怎么个分发呢?分发到哪里呢?以下是几个工作中的实践,分享一下: 主机:这是最主要的也是最重要的点,本质上分库分表是因为计算与存储资源不够导致的,而这种资源主要是由物理机,主机提供的,所以在这里分是最基本的,毕竟没有可用的计算资源,怎么分效果都不是太好的。 实例:实例控制着连接数,同时受OS限制,CPU、内存、硬盘、网络IO也会受间接影响。会出现热实例的现象,即:有些实例特别忙,有些实例非常的空闲。一个典型的现象是:由于单表反应慢,导致连接池被打满,所有其他的业务都受影响了。这时候,把表分到不同的实例是有一些效果的。 库:一般是由于单库中最大单表数量的限制,才采取分库。 表:单表压力过大,索引量大,容量大,单表的锁。据以上,把单表水平切分成不同的表。

大型应用中,都是一台主机上只有一个实例,一个实例中只有一个库,库==实例==主机,所以才有了分库分表这个简称。

既然知道了基本理论,那么具体是怎么做的呢?逻辑是怎么跑的呢?接下来以一个例子来讲解一下。 这个需求很简单,用户表(user),单表数据量1亿,查询、插入、存储都出现了问题,怎么办呢?

首先,分析问题,这个明显是由于数据量太大了而导致的问题。 其次,设计方案,可以分为10个库,这样每个库的数据量就降到了1KW,单表1KW数据量还是有些大,而且不利于以后量的增长,所以每个库再分100个表,这个每个单表数据量就为10W了,对于查询、索引更新、单表文件大小、打开速度,都有一些益处。接下来,给IT部门打电话,要10台物理机,扩展数据库…… 最后,逻辑实现,这里应该是最有学问的地方。首先是写入数据,需要知道写到哪个分库分表中,读也是一样的,所以,需要有个请求路由层,负责把请求分发、转换到不同的库表中,一般有路由规则的概念。

怎么样,简单吧?哈哈,too 那义务。说说这个模式的问题,主要是带来了事务上的问题,因为分库分表,事务完成不了,而分布式事务又太笨重,所以这里需要有一定的策略,保证在这种情况下事务能够完成。采取的策略如:最终一致性、复制、特殊设计等。再有就是业务代码的改造,一些关联查询要改造,一些单表orderBy的问题需要特殊处理,也包括groupBy语句,如何解决这些副作用不是一句两句能说清楚的,以后有时间,我单独讲讲这些。

该总结一下这种模式的优缺点的了,如下:

  • 优点:减少数据库单表的压力。
  • 缺点:事务保证困难、业务逻辑需要做大量改造。

九、弹性伸缩模式

这种模式主要解决突发流量的到来,导致无法横向扩展或者横向扩展太慢,进而影响业务,全站崩溃的问题。这个模式是一种相对来说比较高级的技术,也是各个大公司目前都在研究、试用的技术。截至今日,有这种思想的架构师就已经是很不错了,能够拿到较高薪资,更别提那些已经实践过的,甚至实现了底层系统的那些,所以,你懂得…… 这种模式的一般设计见下图:

 

 

如上图所示,多了一个弹性伸缩服务,用来动态的增加、减少实例。原理上非常简单,但是这个模式到底解决什么问题呢?先说说由来和意义。

每年的双11、六一八或者一些大促到来之前,我们都会为大流量的到来做以下几个方面的工作: 提前准备10倍甚至更多的机器,即使用不上也要放在那里备着,以防万一。这样浪费了大量的资源。 每台机器配置、调试、引流,以便让所有的机器都可用。这样浪费了大量的人力、物力,更容易出错。 如果机器准备不充分,那么还要加班加点的重复上面的工作。这样做特别容易出错,引来领导的不满,没时间回家陪老婆,然后你的老婆就……(自己想)

在双十一之后,我们还要人工做缩容,非常的辛苦。一般一年中会有多次促销,那么我们就会一直这样,实在是烦!

最严重的,突然间的大流量爆发,会让我们触不及防,半夜起来扩容是在正常不过的事情,为此,我们偷懒起来,要更多的机器备着,也就出现了大量的cpu利用率为1%的机器。

我相信,如果你是老板一定很震惊吧!!! 哈哈,那么如何改变这种情况呢?请接着看

为此,首先把所有的计算资源整合成资源池的概念,然后通过一些策略、监控、服务,动态的从资源池中获取资源,用完后在放回到池子中,供其他系统使用。 具体实现上比较成熟的两种资源池方案是VM、docker,每个都有着自己强大的生态。监控的点有CPU、内存、硬盘、网络IO、服务质量等,根据这些,在配合一些预留、扩张、收缩策略,就可以简单的实现自动伸缩。怎么样?是不是很神奇?深入的内容我们会在的码农原创的公众号文章中详细介绍。

该总结一下这种模式的优缺点的了,如下:

  • 优点:弹性、随需计算,充分优化企业计算资源。
  • 缺点:应用要从架构层做到可横向扩展化改造、依赖的底层配套比较多,对技术水平、实力、应用规模要求较高。

十、多机房模式

这种模式主要解决不同地区高性能、高可用的问题。

随着应用用户不断的增加,用户群体分布在全球各地,如果把服务器部署在一个地方,一个机房,比如北京,那么美国的用户使用应用的时候就会特别慢,因为每一个请求都需要通过海底光缆走上个那么一秒钟(预估)左右,这样对用户体验及其不好。怎么办?使用多机房部署。

这种模式的一般设计见下图:

 

 

如上图所示,一个典型的用户请求流程如下:

用户请求一个链接A 通过DNS智能解析到离用户最近的机房B 使用B机房服务链接A

是不是觉得很简单,没啥?其实这里面的问题没有表面这么简单,下面一一道来。 首先是数据同步问题,在中国产生的数据要同步到美国,美国的也一样,数据同步就会涉及数据版本、一致性、更新丢弃、删除等问题。 其次是一地多机房的请求路由问题,典型的是如上图,中国的北京机房和杭州机房,如果北京机房挂了,那么要能够通过路由把所有发往北京机房的请求转发到杭州机房。异地也存在这个问题。

所以,多机房模式,也就是异地多活并不是那么的简单,这里只是起了个头,具体的有哪些坑,会在另一篇文章中介绍。

该总结一下这种模式的优缺点的了,如下:

  • 优点:高可用、高性能、异地多活。
  • 缺点:数据同步、数据一致性、请求路由。

至此,整个关于八种架构设计模式及其优缺点概述就介绍完了,大约1W字左右。最后,我想说的是没有银弹、灵活运用,共勉!

作者:风平浪静如码
链接:https://juejin.cn/post/6844904007438172167
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

sql server登录失败。该登录名来自不受信任的域,不能与 Windows 身份验证一起使用.报错:18452_水亦流人必上的博客-CSDN博客_该登录名来自不受信任的域

mikel阅读(803)

来源: sql server登录失败。该登录名来自不受信任的域,不能与 Windows 身份验证一起使用.报错:18452_水亦流人必上的博客-CSDN博客_该登录名来自不受信任的域

原因是:服务器没有加入域、而登录服务器的名称用的又是IP(图1)

解决方法:在服务器名称选择(计算机名称)、如果没有就按计算机名称输入进去

 

 

COLA 4.0:应用架构的最佳实践_张建飞(Frank)的博客-CSDN博客_cola4.0

mikel阅读(645)

来源: COLA 4.0:应用架构的最佳实践_张建飞(Frank)的博客-CSDN博客_cola4.0

前几天和几个饿了么的同学聊天,一听说他们还在用COLA 1.0,我二话没说,90度鞠躬,赔礼道歉,虚心聆听他们的吐槽。COLA的初衷旨在控制复杂度,救码农于水火,惭愧的是,早期的思想不成熟,设计也多有缺陷,不仅没帮到他们,反而坑了他们,实在抱歉。

实际上,我在COLA 3.0迭代的时候,已经举起奥卡姆剃刀,砍掉了很多东西。

然而还不够,主要体现在对架构的思考还不够透彻。再三考量,我觉得有必要对COLA进行一次重新梳理,回归初心,让COLA真正成为应用架构的最佳实践,帮助广大的业务技术同学,脱离酱缸代码的泥潭!

应用架构的本质
什么是架构?十个人可能有十个回答,架构在技术的语境下,就和架构师一样魔幻。我曾经看过一本技术书,用了一章的篇幅讨论架构的定义,最终也没有说明白。

实际上,定义架构也没那么难,如下图所示,架构的本质,简单来说,就是要素结构。所谓的要素(Components)是指架构中的主要元素,结构是指要素之间的相互关系(Relationship)。

例如组织架构,其要素是什么?组成组织的要素当然是人,结构呢?结构是人与人之间的关系。因此,组织架构就是关于定义人的职责划分,以及人与人之间协作关系的一种设计方法。

同样,对于应用架构而言,代码是其核心组成要素,结构就是这些代码该如何被组织,也就是要如何处理模块(Module)、组件(Component)、包(Package)和类(Class)之间的关系。简而言之,应用架构就是要解决代码要如何被组织的问题。

一个没有架构的应用系统,就像一堆随意堆放、杂乱无章的玩具,只有熵值,没有熵减。而一个有良好架构的应用系统,有章法、有结构,一切都显得紧紧有条。

好的组织架构会遵循一定的架构模式,大部分的组织都会按职能和业务来设计自己的架构。如果你反其道而行之,硬要把销售、财务和技术人员放在一个部门,就会显得很奇怪。

同样,好的应用架构,也遵循一些共同模式,不管是六边形架构、洋葱圈架构、整洁架构、还是COLA架构,都提倡以业务为核心,解耦外部依赖,分离业务复杂度和技术复杂度。

应用架构的本质,就是要从繁杂的业务系统中提炼出共性,找到解决业务问题的最佳共同模式,为开发人员提供统一的认知,治理混乱。帮助应用系统“从混乱到有序”,COLA架构就是为此而生,其核心职责就是定义良好的应用结构,提供最佳实践。

COLA 架构
自从COLA诞生以来,已经被使用在很多的业务系统里面,有CRM的业务,有电商的业务,有物流的业务,有外卖业务,有排课系统… COLA作为应用架构,有一定的普适性,是因为业务问题都有一定的共性。例如,典型的业务系统都需要:

接收request,响应response;
做业务逻辑处理,像校验参数,状态流转,业务计算等等;
和外部系统有联动,像数据库,微服务,搜索引擎等;
正是有这样的共性存在,才会有很多普适的架构思想出现,比如分层架构、六边形架构、洋葱圈架构、整洁架构(Clean Architecture)、DDD架构等等。

这些应用架构思想虽然很好,但我们很多同学还是“不讲Co德,明白了很多道理,可还是过不好这一生”。问题就在于缺乏实践和指导。COLA的意义就在于,他不仅是思想,还提供了可落地的实践。应该是为数不多的应用架构层面的开源软件。

分层结构
假如你是一个公司的CTO要管100号人,你怎么管?按照管理学的定义,一个人的管理幅度如果超过10个,管理就会变得很困难。因此,管100号人,你可以把他们分成10个小组,这样你管理10个小组长就好了。

所有的复杂系统都会呈现出层级结构,管理如此,软件设计也不例外,你能想象如果网络协议不是四层,而是一层,意味着,你要在应用层去处理链路层的bit数据流会是怎样的情景吗?同样,应用系统处理复杂业务逻辑也应该是分层的,下层对上层屏蔽处理细节,每一层各司其职,分离关注点,而不是一个ServiceImpl解决所有问题。

对于一个典型的业务应用系统来说,COLA会做如下层次定义,每一层都有明确的职责定义:

 

1)适配层(Adapter Layer):负责对前端展示(web,wireless,wap)的路由和适配,对于传统B/S系统而言,adapter就相当于MVC中的controller;

2)应用层(Application Layer):主要负责获取输入,组装上下文,参数校验,调用领域层做业务处理,如果需要的话,发送消息通知等。层次是开放的,应用层也可以绕过领域层,直接访问基础实施层;

3)领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对App层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次;

4)基础实施层(Infrastructure Layer):主要负责技术细节问题的处理,比如数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外,领域防腐的重任也落在这里,外部依赖需要通过gateway的转义处理,才能被上面的App层和Domain层使用。

包结构
分层是属于大粒度的职责划分,太粗,我们有必要往下再down一层,细化到包结构的粒度,才能更好的指导我们的工作。

还是拿一堆玩具举例子,分层类似于拿来了一个架子,分包类似于在每一层架子上又放置了多个收纳盒。所谓的内聚,就是把功能类似的玩具放在一个盒子里,这样可以让应用结构清晰,极大的降低系统的认知成本和维护成本。

那么,对于一个后端应用来说,应该需要哪些收纳盒呢?这一块的设计真可谓是费了老鼻子劲了,基本上每一次COLA的迭代都会涉及到包结构的调整,迭代到现在,才算基本稳定下来。

各个包结构的简要功能描述,如下表所示:

层次 包名 功能 必选
Adapter层 web 处理页面请求的Controller 否
Adapter层 wireless 处理无线端的适配 否
Adapter层 wap 处理wap端的适配 否
App层 executor 处理request,包括command和query 是
App层 consumer 处理外部message 否
App层 scheduler 处理定时任务 否
Domain层 model 领域模型 否
Domain层 ability 领域能力,包括DomainService 否
Domain层 gateway 领域网关,解耦利器 是
Infra层 gatewayimpl 网关实现 是
Infra层 mapper ibatis数据库映射 否
Infra层 config 配置信息 否
Client SDK api 服务对外透出的API 是
Client SDK dto 服务对外的DTO 是
你可能会有疑问,为什么Domain的model是可选的?因为COLA是应用架构,不是DDD架构。在工作中,很多同学问我领域模型要怎么设计,我的回答通常是:无有必要勿增实体。领域模型对设计能力要求很高,没把握用好,一个错误的抽象还不如不抽象,宁可不要用,也不要滥用,不要为了DDD而DDD。

问题的关键是要看,新增的模型没有给你带来收益。比如有没有帮助系统解耦,有没有提升业务语义表达能力的提升,有没有提升系统的可维护性和可测性等等。

模型虽然可选,但DDD的思想是一定要去学习和贯彻的,特别是统一语言、边界上下文、防腐层的思想,值得深入学习,仔细体会。实际上,COLA里面的很多设计思想都来自于DDD。其中就包括领域包的设计。

前面的包定义,都是功能维度的定义。为了兼顾领域维度的内聚性,我们有必要对包结构进行一下微调,即顶层包结构应该是按照领域划分,让领域内聚。

也就是说,我们要综合考虑功能和领域两个维度包结构定义。按照领域和功能两个维度分包策略,最后呈现出来的,是如下图所示的顶层包节点是领域名称,领域之下,再按功能划分包结构。

例如,在我们刚刚上线的一个云店铺(cloudstore)项目中,按照COLA的分包策略,我们在每一个module下面首先按照领域做一个顶层划分,然后在领域内,再按照功能进行分包。

解耦
“高内聚,低耦合”这句话,你工作的越久,就越会觉得其有道理。

所谓耦合就是联系的紧密程度,只要有依赖就会有耦合,不管是进程内的依赖,还是跨进程的RPC依赖,都会产生耦合。依赖不可消除,同样,耦合也不可避免。我们所能做的不是消除耦合,而是把耦合降低到可以接受的程度。在软件设计中,有大量的设计模式,设计原则都是为了解耦这一目的。

在DDD中有一个很棒的解耦设计思想——防腐层(Anti-Corruption),简单说,就是应用不要直接依赖外域的信息,要把外域的信息转换成自己领域上下文(Context)的实体再去使用,从而实现本域和外部依赖的解耦。

在COLA中,我们把AC这个概念进行了泛化,将数据库、搜索引擎等数据存储都列为外部依赖的范畴。利用依赖倒置,统一使用gateway来实现业务领域和外部依赖的解耦。

其实现方式如下图所示,主要是在Domain层定义Gateway接口,然后在Infrastructure提供Gateway接口的实现。

举个例子,假如有一个电商系统,对于下单这个操作,它需要联动订单服务、商品服务、库存服务、营销服务等多个系统才能完成。

那么在订单域,该如何获取商品和库存信息呢?最直接的方式,无外乎就是RPC调用商品和库存服务,拿到DTO直接使用就完了。

然而,商品域吐出的是一个大而全的DTO(可能包含几十个字段),而在下单这个阶段,订单所需要的可能只是其中几个字段而已。更合适的做法,应该是在订单域中,使用gateway对商品域和库存域的依赖进行解耦。

这样做有两个好处,一个是降低了对外域信息依赖的耦合;另一个是通过上下文映射(Context mapping),确保本领域边界上下文(Bounded context)下领域知识的完整性,实现了统一语言(Ubiquitous language)。

COLA Archetype
以上就是COLA架构的核心内容了。然而这么多module,这么多package,如果要手动去创建的话,是非常繁琐和费时的。为了能够快速创建满足COLA架构的应用,我创建了两个Maven Archetype。

一个是用来创建纯后端服务的archetype:cola-archetype-service。
一个是用来创建adapter和后端服务一体的web应用archetype:cola-archetype-web。
另外,你也可以使用阿里云的应用生成器去生成一个COLA应用,只是那边的版本没有同步更新,可能会老旧一点。

COLA组件
使用过老版本COLA的同学,应该知道,COLA除了架构之外,还提供了一些框架级别的功能,比如拦截器功能,扩展点功能等。

之前,这种框架功能和架构混淆在一起,会让人以为使用COLA,就必须要使用这些功能。实际上二者是可以分开使用的,也就是说,你可以单纯的使用COLA架构,而不使用任何COLA组件提供的功能也是完全没问题的。

当然,我还是强烈推荐你可以有选择的使用这些COLA组件,毕竟这些组件都是我们在实际工作中的总结沉淀,其复用性和价值是被反复验证过的。

为了方便管理,以及更清晰的把架构和框架区分开来。在此次COLA 4.0的升级中,我把这些功能组件全部收拢到了cola-components下面。到目前为止,我们已经沉淀了以下组件:

组件名称 功能 版本 依赖
cola-component-dto 定义了DTO格式,包括分页 1.0.0 无
cola-component-exception 定义了异常格式,
主要有BizException和SysException 1.0.0 无
cola-component-statemachine 状态机组件 1.0.0 无
cola-component-domain-starter Spring托管的领域实体组件 1.0.0 无
cola-component-catchlog-starter 异常处理和日志组件 1.0.0 exception
,dto组件
cola-component-extension-starter 扩展点组件 1.0.0 无
cola-component-test-container 测试容器组件 1.0.0 无
这些组件是一个良好的开端,我相信,在未来会有更多有用的组件加入。当然,作为一个开源项目,如果你有好的组件idea,欢迎你随时为这个组件库添砖加瓦。

COLA 4.0
总结一下,在本次COLA升级中,我们进一步明确了架构和框架功能的定义。升级之后,如下图所示,COLA会被分成COLA架构和COLA组件两个部分:

COLA架构:关注应用架构的定义和构建,提升应用质量。
COLA组件:提供应用开发所需要的可复用组件,提升研发效率。

COLA 开源地址: https://github.com/alibaba/COLA

你可以按照以下步骤去使用COLA:

** 第一步:安装 cola archetype **
下载cola-archetypes下的源码到本地,然后本地运行mvn install安装。

** 第二步:安装 cola components **
下载cola-components下的源码到本地,然后本地运行mvn install安装。

** 第三步:创建应用 **
执行以下命令:

mvn archetype:generate -DgroupId=com.alibaba.demo -DartifactId=demoWeb -Dversion=1.0.0-SNAPSHOT -Dpackage=com.alibaba.demo -DarchetypeArtifactId=cola-framework-archetype-web -DarchetypeGroupId=com.alibaba.cola -DarchetypeVersion=4.0.0
1
命令执行成功的话,会看到如下的应用代码结构:

** 第四步:运行应用 **
首先在demoWeb目录下运行mvn install(如果不想运行测试,可以加上-DskipTests参数)。然后进入start目录,执行mvn spring-boot:run。
运行成功的话,可以看到SpringBoot启动成功的界面。

生成的应用中,已经实现了一个简单的Rest请求,可以在浏览器中输入 http://localhost:8080/helloworld 进行测试。
————————————————
版权声明:本文为CSDN博主「张建飞(Frank)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/significantfrank/article/details/110934799