SegmentFault 好享家技术团队最新的文章
2020-03-28T13:30:22+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
Dart基础语法
https://segmentfault.com/a/1190000022180376
2020-03-28T13:30:22+08:00
2020-03-28T13:30:22+08:00
1029
https://segmentfault.com/u/1029
0
<h2>Dart基础语法</h2>
<h5>一、选择开发工具</h5>
<p>首先需要安装Dart SDK(<a href="https://link.segmentfault.com/?enc=%2FdNeobPVC61zjZH8AXL%2FDw%3D%3D.a7AqD6YITKJ02r4RNPGBGstNtgKLTrSRHdRTGc0iQ6eKqIdQFD2KFY7D3OYTvQX3AvLgYV2PDbj%2BTCc%2B5vxVIQ%3D%3D" rel="nofollow">https://www.dart-china.org/t/...</a>),SDK安装完成呢后,选择一款编辑工具。这里就先只推荐两款工具 VS Code ,Android Studio。工具下载完成后,安装dart插件,这个时候就可以编写dart代码了。我们先新建一个hello.dart文件,然后在文件中输入下方的代码,点击运行按钮就可以执行代码。</p>
<pre><code class="dart">// 在dart中,main方法是代码运行的入口方法,一切都是从main方法开始的。。
void main (){
// 点击IDE运行按钮 在打印台中会打印出 hello Dart。 接下来的例子都是在该方法下执行的(除特殊说明)。
print('hello Dart');
}</code></pre>
<h5>二、常用的数据类型</h5>
<ol>
<li>
<strong>num</strong> num是数字类型的父类,包含了两个子类 int和double。</li>
<li>
<p><strong>String</strong> String可以用 '' 或 "" 来创建,也可以使用''' 或者"""来创建可变行的字符串。</p>
<p>eg.num与String的一些常用操作</p>
<pre><code class="dart">// String -> num
int one = int.parse('1');
print(one + 2); // 输出 3 double类型同理
int one = int.parse('hello');
print(one + 2); // 报错
// int -> String
String oneStr = 1.toString();
print(oneStr); // 输出 1
// double -> String
String oneStr = 3.1455.toStringAsFixed(2);
print(oneStr); // 截取两位小数, 输出3.15 四舍五入
String twoStr = 3.toStringAsFixed(2);
print(twoStr); // 输出3.00
// 可以使用${表达式}将表达式的值放入字符串中。如果表达式是标识符,{}可省略
String oneStr = 'hello';
String twoStr = "dart";
print('$oneStr $twoStr'); //输出 hello dart
print('$oneStr ${twoStr.toLowerCase()}'); //输出 hello dart
print('$oneStr $twoStr.toLowerCase()'); //输出 hello dart.toLowerCase()</code></pre>
</li>
<li>
<p><strong>bool</strong> Dart是强bool类型检查。</p>
<ul>
<li>只有两个对象具有bool类型:true和false,它们都是编译时常量</li>
<li>
<p>if(非bool类型) 报错,assert( 非bool类型)报错,也就是说需要bool类型的,如果我们用int 等非bool类型的值都会报错。</p>
<pre><code class="dart">if (1){} // 报错</code></pre>
</li>
</ul>
</li>
<li>
<p><strong>List</strong> 列表</p>
<pre><code class="dart">var list_1 = new List(); // 非固定长度 new可以省略
// 添加元素
list_1.add('1');
// 添加多个元素
list_1.addAll(['1', '2']);
// 获取list长度
print(list_1.length); // 输出2
// 获取list最后一个元素
print(list_1.last); //输出 2
// 获取list第一个元素
print(list_1.first); // 输出 1
// 查询某位置的元素,index从0开始
print(list_1[0]); // 输出 1
// 根据某个元素获取对应的index
print(list_1.indexOf("1")); // 输出 0
// 删除元素
list_1.removeAt(0); // 通过索引删除
list_1.remove("1"); // 通过对象删除 如果有两个 "1" 那么会删除第一个
list_1.removeLast(); // 删除最后一个元素
list_1.removeRange(start,end); // 删除范围内的数据
list_1.removeWhere((item) => item.length > 6);// 删除元素长度大于6的元素 这种写法可见下面箭头函数的详解
list_1.clear(); // 清除所有元素
/*注意*/
List list_2 = new List(2); // 固定长度
list_2.add('1'); // 报错 报错信息 Cannot add to a fixed-length list
// 另外一种定义方式
List list_3 = ['1','2','3'];
print(list_3); // 输出 [1,2,3]</code></pre>
</li>
<li>
<p><strong>Set</strong> 集合 Set是没有顺序且不能重复的集合,所以不能通过索引去获取值</p>
<pre><code class="dart">Set set_1 = new Set();
Set set_2 = new Set(2); // 报错 set没有固定长度的定义
set_1.add('1');
set_1.add('2');
set_1.add('1');
print(set_1); // 输出 {1, 2}
// 可以和list一样通过contains来判断是否包含某个元素
print(set_1.contains("1")); // 输出 true
set_1.addAll(['b','c']);
print(set_1); // 输出 {1, 2, b, c}</code></pre>
</li>
<li>
<p><strong>Map</strong> 映射是无序的键值对。键和值都可以是任何类型的对象。</p>
<pre><code class="dart">// 常用的两种定义方式
Map map_1 = Map();
Map map_2 = {"key_1":"value_1","key_2":"value_2"};
print(map_1); // 输出 {}
print(map_2); // 输出 {key_1: value_1, key_2: value_2}
// 赋值
map_1["1"] = "one";
map_1["2"] = "two";
print(map_1); // 输出 {1: one, 2: two}
map_1["1"] = "first";
print(map_1); // 输出 {1: first, 2: two}
map_1[1] = "one"; // 在map中key必须要保持唯一 value可以相同 key类型可以为任意其他类型
print(map_1); // 输出 {1: one, 2: two, 1: one}
// 常用api
map_1.remove(1); // 删除key为1的元素
map_1.containsKey(1); // 判断是否存在key为1的元素</code></pre>
</li>
</ol>
<h5>三、函数/方法</h5>
<ol>
<li>
<p>内置函数/方法</p>
<pre><code class="dart">print("hello Dart");</code></pre>
<p>这个是常用的将数据打印到控制台的方法,这是系统提供给我们的内置函数,可以直接调用。</p>
</li>
<li>
<p>自定义方法</p>
<p>自定义方法的基本格式:</p>
<p>返回类型 方法名称 (参数1,参数2,...){</p>
<p> 方法体</p>
<p> return 返回值;</p>
<p>}</p>
<pre><code class="dart">int getMaxCount(int a, int b){
if (a > b){
return a;
}
return b;
}
print(getMaxCount(2, 1)); // 输出 2
// 在dart中 返回值和参数类型是可以省略的
getMinCount(a,b){
if (a < b){
return a;
}
return b;
}
print(getMinCount(2, 1)); // 输出 1</code></pre>
</li>
<li>
<p>方法的传参</p>
<pre><code class="dart">// 可选参数
String printUserInfo(String username,[int age]){ // username和age表示是形参 其中age表示可选参数
return "姓名:$username 年龄:$age";
}
print(printUserInfo("张三",30)); // 张三 和 30 表示实参
print(printUserInfo("张三")); // 这里可以不用传入age的参数,如果age不是可选参数的话,那么age是必须要传的
// 默认参数
String printUserInfoDefalut(String username,[int age,String sex = "男"]){
return "姓名:$username 年龄:$age 性别:$sex";
}
print(printUserInfoDefalut("李四",30)); // 输出 姓名:李四 年龄:30 性别:男
/*注意
对于可选参数的传递是按照参数的顺序进行的 print(printUserInfoDefalut("李四","女")); 这里会报错。虽然sex和age都是可选参数,但是当对一个形参赋值时,会默认选择第一个。这里可选参数的第一个形参是age int类型的,这里传递一个"女"是string类型的,所以会报错。
*/
// 命名参数
String printUserInfoName(String username,{int age,String sex = "男"}){
return "姓名:$username 年龄:$age 性别:$sex";
}
print(printUserInfoDefalut("李四",age:30,sex:"男")); // 输出 姓名:李四 年龄:30 性别:男
// 这样我们通过指定参数名称实现我们想要传递的参数。这个比较常用。
// 将方法作为参数
fun_1(){
print("方法1");
}
fun_2 (Function fun){ // fun方法其实就是一个Function对象
fun();
}
fun_2(fun_1); // 输出 方法1</code></pre>
</li>
<li>
<p>箭头函数</p>
<pre><code class="dart">// 遍历数组
List list = ["h","e","l","l","0"];
// 正常写法
list.forEach((element){
print(element);
});
// 箭头函数的方式 箭头后跟的方法体只能是一行 这只是一种简写的方式
list.forEach((element)=>print(element));
list.forEach((element) => {
print(element) // 这里不能写分号,也是只能执行一行代码
});
List list_1 = [1,3,5,1,3,7];
// 这也是数组的一种遍历并处理的方式
List list_2 = list_1.map((element){
if(element >3){
return element*3;
}
return element;
}).toList();
print(list_2);
List list_3 = list_1.map((element)=>element>3?element*3:element).toList();
print(list_3);</code></pre>
</li>
<li>
<p>自执行与自调用</p>
<pre><code class="dart">//实际上自执行方法我们可以理解为一个匿名函数的自调用
((){})();// 格式 将一个匿名函数用()包起来 然后执行
((int n){
print(n);
})(12); //输出12
// 方法可以自己调用自己
int sum = 1;
fn(n){
sum*=n;
if (n == 1){
return;
}
fn(n-1);
}
fn(5); // 这样通过方法的自调用可以实现5的阶乘</code></pre>
</li>
<li>
<p>闭包</p>
<ul>
<li>全局变量特点:全局变量常驻内存,会污染全局。</li>
<li>局部变量特点:不常驻内存会被垃圾回收机制回收,不会污染全局。</li>
<li>
<p>闭包:常驻内存,不会污染全局。写法:函数嵌套函数,并return内层函数。</p>
<pre><code class="dart">//闭包
fun(){
// 虽然这里定义的是一个局部变量,但是通过闭包的写法就不会被垃圾回收机制回收,且不会污染全局
int a = 1;
return (){
a++;
print(a);
};
}
Function b = fun(); // 因为fun方法返回的就是一个方法,所以下面直接调用b方法。
b();
b();
b();
// 输出 2 3 4</code></pre>
</li>
</ul>
</li>
</ol>
<h5>四、语法规则</h5>
<ol>
<li>
<p>Dart中一切皆对象,都是继承自Object类。所以在Dart中不存在基本数据类型,我们定义的任何一个对象,如果不将其赋值的话,那么这个对象就是一个null对象。</p>
<pre><code class="dart">int a;
int b = 0;
print(a); // 输出 null
print(b); // 输出 0</code></pre>
</li>
<li>
<p>Dart是动态型语言,如果没有指定其类型时,则默认时dynamic类型。在运行时会自动推导出具体的类型。</p>
<pre><code class="dart">var str = 'shsh';
str = 1; // 报错</code></pre>
</li>
<li>
<p>运算符及修饰符</p>
<ul>
<li>static:用于修饰类成员变量,这个变量是属于类的,通过类名直接调用,而不是通过对象调用。这个与java类似。非静态方法可以访问静态成员以及非静态成员。静态方法无法访问非静态成员,也无法调用非静态方法。</li>
<li>final:用于修饰变量,表示单赋值(single-assignment),使用final修饰的变量必须进行初始化,一旦被赋值之后,不能够再次被赋值,否则编译会报错。</li>
<li>
<p>const:与final有一点类似,即只能被赋值一次。但是其修饰的对象有一定的限制。const修饰的对象的状态是完全可以在编译期间就能确定的,并且是不可变的。</p>
<pre><code class="dart">const n = 1+2;// 可行 因为这个是在编译期间我们就知道n = 3的
const list = new List(); // 不可行
const list_1 = [1,2,3]; // 可行
list_1 = [3,4]; // 不可行</code></pre>
</li>
<li>
<p>常用的运算符和表达式</p>
<pre><code class="dart">//加减乘除
int a = 10;
int b = 3;
print(a/b); // 3.3333333333333335 不用判断除数和被除数的类型,默认返回double类型
print(a~/b); // 3 整除取整
print(a*b); // 30
print(a+b); // 13
print(a-b); // 7
print(a%b); // 1 取余
//as 定型
Person per = new Person(); // 我们定义一个Person类,关于类的定义下文会介绍
// 如果per是Person类型的话 那么就给Person的name属性赋值,如果不是Person类型则不赋值 并
(per as Person).name = "张三";
// is 如果对象是指定的类型 那么就返回true 否则就返回false
if (per is Person){
per.name = "张三";
}
/// 注意:以上代码不相等。如果per为空或不为Person,第二个示例(带is)什么也不做;第一个(带有as)抛出异常。
// ??与三目运算符
String str_1;
String str_2 = "张三";
print(str_1??str_2); // 输出 张三
print((str_1 is! Null)?str_1:str_2); // 输出张三 在dart中 关于条件的判断必须是bool类型
// 级联符号 可以避免创建临时变量的繁琐步骤
Person() ..name ="张三";
// 等价于
Person per = new Person();
per.name = "张三";
// ?. 有条件的成员属性访问
Person per; // 这里per就是null
// 如果直接调用成员会报错
per.name = "张三"; // 报错
per?.name = "张三"; // 不会报错</code></pre>
</li>
</ul>
</li>
</ol>
<h5>五、类与对象</h5>
<ol>
<li>
<p>类的创建</p>
<pre><code class="dart">// 与上面的例子不同,类的定义需要与mian方法同级
class Person{
String name; // 属性
void study (){
print("学习");
} // 方法
}
void main (){
// 实例化,也就是创建对象
Person per = new Person(); // per就是一个对象
per.name = "张三";
print("${per.name}"); // 输出 张三 调用对象的属性
per.study(); // 输出 学习 调用方法
}</code></pre>
</li>
<li>
<p>类的构造函数</p>
<ul>
<li>
<p>默认构造函数。每个类都有一个默认的构造函数,默认是不用写的,如果需要在构造函数做操作的话,则重写一下默认构造函数。</p>
<pre><code class="dart">class Person{
String name;
//构造参数名与类名要相同
Person(){ // 无参
print("我需要在这里做一些操作");
}
// 初始化列表
person():name = "张三"{
// 在执行构造函数运行之前 初始化一些实例变量
}
}
class Student{
String name;
Student(String name){ // 有参数 参数必传
this.name = name;
}
void printStudentInfo (){
print("学生的姓名:${this.name}");
}
}
class Coder{
String name;
Coder(this.name); // 简写的有参构造函数 参数必传
void printCoderInfo (){
print("程序员的姓名:${this.name}");
}
}
class Engineer{
String name;
Engineer({this.name}); // 简写的有参构造函数 参数非必传
void printEngineerInfo (){
print("工程师的姓名:${this.name}");
}
}
void main (){
// 实例化,也就是创建对象
Person per = new Person(); // 输出 我需要在这里做一些操作。
// 报错 因为我们修改了默认的构造函数,所以之前的无参构造函数就不能使用了。
// Student stu = new Student();
Student stu_1 = new Student('张三');
stu_1.printStudentInfo();
Coder coder = Coder("张三");
coder.printCoderInfo();
Engineer eng = new Engineer();
eng.printEngineerInfo(); // 输出 工程师的姓名:null
Engineer eng_1 = new Engineer(name: "张三");
eng_1.printEngineerInfo(); // 输出 工程师的姓名:张三
}</code></pre>
</li>
<li>
<p>命名构造函数。类是可以有多个构造函数的,通过不同的命名来实现不同功能的构造函数</p>
<pre><code class="dart">class Engineer{
String name;
String hairStyle; //发型
Engineer({this.name,this.hairStyle = "茂密的头发"}); // 简写的有参构造函数 参数非必传
Engineer.seniorEngineer(String name){ //命名构造函数
this.name = name;
this.hairStyle = "秃了";
}
void printEngineerInfo (){
print("工程师的姓名:${this.name} 发型:${this.hairStyle}");
}
}
void main (){
Engineer eng_1 = Engineer(name:"张三");
eng_1.printEngineerInfo();
Engineer eng_2 = Engineer.seniorEngineer("李四");
eng_2.printEngineerInfo();
}</code></pre>
</li>
<li>
<p>类中的setter和getter方法</p>
<pre><code class="dart">class Rect{
num height;
num width;
Rect(this.height,this.width);
// getter方法
get area{
return this.height*this.width;
}
// setter方法
set areaHeight(num height){
this.height = height;
}
}
void main(){
Rect re = Rect(10,10);
print("面积:${re.area}"); // 输出 面积:100
re.areaHeight = 5;
print("面积:${re.area}"); // 输出 面积:50
}</code></pre>
</li>
</ul>
</li>
<li>
<p>类的继承</p>
<ul>
<li>子类使用 extends 关键字来继承父类</li>
<li>子类会继承父类中可见你的属性和方法,但是不会继承构造函数</li>
<li>
<p>子类能复写父类的方法 getter和setter</p>
<pre><code class="dart">class Person{
String name;
num age;
Person(this.name,this.age);
void printPersonInfo(){
print("姓名:${this.name} 年龄:${this.age}");
}
void work(){
print("我正在工作");
}
}
class Coder extends Person{
// 因为Person是重写了构造函数,所以这里也需要写一下与父类关联的构造函数
// 如果父类有多个构造函数 则需要选一个自己需要的
Coder(String name,num age,String grade):super(name,age){
// 通过代码可以看出,这么写的目的是当我们在实例化Coder时也会将值传递给父类
// grade 是子类的属性,所以不用将参数传递给父类
this.grade = grade;
}
String grade;
void printCoderInfo(){
super.work(); // 调用父类的方法
print("姓名:${this.name} 年龄:${this.age} 等级:${this.grade}");
}
@override // 重写父类方法
void work() {
print("我正在写代码");
}
}
void main(){
Coder code = Coder("zhangsan",20,"初级码农");
code.printCoderInfo(); // 输出 姓名:zhangsan 年龄:20 等级:初级码农
code.work(); // 输出 我正在写代码
}</code></pre>
</li>
</ul>
</li>
<li>
<p>抽象类。Dart中的抽象类主要是用于定义标准,子类可以继承抽象类,然后实现抽象类的接口。</p>
<ul>
<li>抽象类通过abstract关键字来定义。</li>
<li>抽象方法不能用abstract声明,我们将没有方法体的方法称为抽象方法。</li>
<li>如果子类继承抽象类必须实现里面的抽象方法。</li>
<li>如果把抽象类当作接口实现的话必须得实现抽象类里定义的所有属性和方法。</li>
<li>抽象类不能被实例化,子类可以。</li>
<li>如果要实现抽象方法约束子类的话我们需要用extends继承抽象类。</li>
<li>
<p>如果只是把抽象类作为一个标准的话我们需要用implements实现抽象类。</p>
<pre><code class="dart">abstract class Person{
String name;
int age;
Person(this.name,this.age);
work(); // 没有实现方法体 这是一个抽象方法 抽象方法是必须要在子类中实现的
eat(){
print('吃饭');
}
}
class Coder extends Person{
Coder(String name, int age) : super(name, age);
@override
work(){
print("${this.name}在搬运代码,他今年已经${this.age}了");
}
}
/*
在dart中普通类和抽象类都可以作为接口被实现 使用关键字 implements
dart的接口会将普通类或者是抽象类中的属性或者方法都重写一遍,所以我们一般采用抽象类实现接口。
因为只有抽象类才可以创建抽象方法。
*/
class Engineer implements Person{
// 这里就不用像继承一样需要实现父类的构造方法。
@override
int age;
@override
String name;
@override
eat() {
print("工程师在吃饭");
}
@override
work() {
print("${this.name}在搞算法,他今年才${this.age}");
}
}
void main (){
Coder code = Coder("张三", 30);
code.work(); // 输出 张三在搬运代码,他今年已经30了
Engineer eng = Engineer()
..name = "李四"
..age = 20
..work(); // 输出 李四在搞算法,他今年才20
}</code></pre>
</li>
</ul>
</li>
<li>
<p>多接口和mixins</p>
<ul>
<li>
<p>通过implements的方式,可以实现多接口。</p>
<pre><code class="dart">abstract class A{
String a;
printA();
}
abstract class B{
String b;
printB();
}
class C implements A,B{
@override
String a;
@override
String b;
@override
printA() {
// TODO: implement printA
return null;
}
@override
printB() {
// TODO: implement printB
return null;
}
}
// 这样C类的对象就可以同时实现 A B的的抽象方法</code></pre>
</li>
<li>
<p>通过mixins实现多继承,Dart是不支持多继承,是无法实现类似C++的多继承的功能。</p>
<pre><code class="dart">class A{
// mixins 不能有显示的构造方法 其实这个也好理解 因为使用mixins继承多个类的话,它也不知道用什么构造方法 只能使用默认的了
// A.withName(){
//
// }
a(){
print('A.a()');
}
}
class B{
a(){
print('B.a()');
}
b(){
print('B.b()');
}
}
class C{
a(){
print('C.a()');
}
b(){
print('C.b()');
}
c(){
print('C.c()');
}
}
class D extends A with B,C{
int count; // 可以定义自己的属性
}
void main (){
var d = new D();
d.a(); // 输出 C.a() 默认调用最后一个方法
}
/*mixins的方式组合的类 是必须要继承object的 A B C 就是不能继承与任何的类 除了默认的object 如果想实现类似继承的效果 我们可以使用接口的方式*/</code></pre>
</li>
</ul>
</li>
</ol>
<h5>六、泛型</h5>
<ol>
<li>
<p>泛型方法</p>
<pre><code class="dart">T getData<T>(value){
return value;
}
print(getData("xingming")); // 输出 xingming
print(getData(20)); // 输出 20
print(getData<String>(20)); // 报错 因为指定的是string类型 但是传入的是int类型
// 通过泛型方法我们就可以不用区分传入的参数类型和输出的参数类型。
var names = List<String>();
names.addAll(['1', '2', '3']);
names.add(42); // 报错</code></pre>
</li>
<li>
<p>泛型类</p>
<p>通过指定类的泛型类型,来确定输入的数据类型是否正确</p>
<pre><code class="dart">void main (){
LogList loglist = LogList<int>(); // 确定泛型类型为int类型,后续类中的类型判断就是int类型
loglist.addData(1);
loglist.addData("1"); // 报错
loglist.printList();
LogList loglist_1 = LogList(); // 没有确定泛型类型
loglist_1.addData(1);
loglist_1.addData("1"); // 没有报错
loglist_1.printList();
}
class LogList<T>{
List list = List<T>();
void addData(T value){
list.add(value);
}
void printList(){
for (int i = 0;i<list.length;i++){
print(list[i]);
}
}
}</code></pre>
</li>
<li>
<p>泛型接口</p>
<pre><code class="dart">void main (){
B b = B<int>();
b.pirntA(1);
//b.pirntA("12"); // 报错
}
abstract class A<T>{
pirntA(T value);
}
class B<T> implements A<T>{
@override
pirntA(T value) {
print(value);
}
}</code></pre>
</li>
</ol>
<h5>七、结尾</h5>
<p> 上面简单的介绍了一些Dart语言的基础语法知识,关于Dart中更深层次的内容,我会在以后的文章中再慢慢的剖析。关注公众号,阅读更多精彩技术文章。</p>
Mybatis流式查询避免OOM
https://segmentfault.com/a/1190000022167975
2020-03-27T12:01:31+08:00
2020-03-27T12:01:31+08:00
1029
https://segmentfault.com/u/1029
0
<h2>前言</h2>
<p>当指定查询数据过大时,我们一般使用分页查询的方式,一页一页的将数据放到内存处理。但有些情况不需要分页的方式查询数据,如果一下子将数据全部加载出来到内存中,很可能会发生OOM。这时我们可以使用流式查询解决问题。</p>
<h2>非流式查询</h2>
<p>为了更好的复现问题,将jvm参数,最大堆设置成212M。使用mysql数据库,表大小为730MB。</p>
<p><img src="/img/remote/1460000022167978" alt="img" title="img"></p>
<p>非流式查询表里所有数据代码</p>
<pre><code class="java"> List<InfoPO> infoPOs = infoMapper.selectList(new EntityWrapper<>());</code></pre>
<p>通过查看idea控制台,很快出现了内存溢出。</p>
<p><img src="/img/remote/1460000022167979" alt="img" title="img"></p>
<p>通过jconsole工具,查看内存使用情况</p>
<p><img src="/img/remote/1460000022167982" alt="img" title="img"></p>
<p>在14.26,发现内存直接被释放了。</p>
<h2>流式查询</h2>
<p>流式查询表里所有数据代码</p>
<pre><code class="java">@Select("select * from t_iot")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(InfoPO.class)
void selectAutoList(ResultHandler<InfoPO> handler);</code></pre>
<pre><code class="java">infoMapper.selectAutoList(resultContext -> {
resultContext.getResultObject();
});</code></pre>
<p>通过查看idea控制台,程序运行正常</p>
<p><img src="/img/remote/1460000022167980" alt="img" title="img"></p>
<p>在通过jconsole工具,查看内存使用情况</p>
<p><img src="/img/remote/1460000022167983" alt="img" title="img"></p>
<p>发现内存消耗处于平稳状态。</p>
<h2>流式查询原理</h2>
<p>查看源码可知,我们使用流式查询时,必须要满足以下3个条件</p>
<pre><code class="java">/**
* We only stream result sets when they are forward-only, read-only, and the
* fetch size has been set to Integer.MIN_VALUE
*
* @return true if this result set should be streamed row at-a-time, rather
* than read all at once.
*/
protected boolean createStreamingResultSet() {
return ((this.query.getResultType() == Type.FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY)
&& (this.query.getResultFetchSize() == Integer.MIN_VALUE));
}</code></pre>
<ol>
<li>resultSetConcurrency=ResultSet.CONCUR_READ_ONLY 设置只读结果集</li>
<li>resultSetType = ResultSetType.FORWARD_ONLY 设置结果集的游标只能向下滚动</li>
<li>fetchSize = Integer.MIN_VALUE 设置fetch size为int的最小值,这里和oracle/db2有区别.<p>Oracle/db2是从服务器一次取出fetch size 条记录放在客户端,客户端处理完成一个批次后再向服务器取下一个批次,直到所有数据处理完成。</p>
<p>mysql在执行ResultSet.next()方法时,会通过数据库连接一条一条的返回。MySQL按照自己的节奏不断的把buffer写回网络中。flush buffer的过程是阻塞式的,也就是说如果网络中发生了拥塞,send buffer被填满,会导致buffer一直flush不出去,那MySQL的处理线程会阻塞,从而避免数据把客户端内存撑爆。</p>
</li>
</ol>
<p>设置三个参数之后,断点进入到了流式返回结果集ResultsetRowsStreaming。</p>
<p><img src="/img/remote/1460000022167984" alt="img" title="img"></p>
<p>ResultSet数据返回的结果,对象有3种实现方式</p>
<p><img src="/img/remote/1460000022167981" alt="img" title="img"></p>
<p>ResultsetRowsStatic 静态结果集,默认的查询方式,普通查询</p>
<p>ResultsetRowsCursor 游标结果集,服务器端基于游标查询</p>
<p>ResultsetRowsStreaming 动态结果集,流式查询</p>
<p>查看ResultsetRowsStatic类注释</p>
<pre><code class="java">/**
* Represents an in-memory result set
*/
public class ResultsetRowsStatic extends AbstractResultsetRows implements ResultsetRows {</code></pre>
<p>表示放在内存中的结果集。</p>
<p>查看ResultsetRowsStreaming类注释</p>
<pre><code class="java">/**
* Provides streaming of Resultset rows. Each next row is consumed from the
* input stream only on {@link #next()} call. Consumed rows are not cached thus
* we only stream result sets when they are forward-only, read-only, and the
* fetch size has been set to Integer.MIN_VALUE (rows are read one by one).
*
* @param <T>
* ProtocolEntity type
*/
public class ResultsetRowsStreaming<T extends ProtocolEntity> extends AbstractResultsetRows implements ResultsetRows {</code></pre>
<p>提供了Resultset行的流。获取下一行都是从仅在{@link #next()}调用时输入流。因此不会缓存已使用的行。我们只在结果集只有前进、只读和时才流结果集获取大小已设置为整数。MIN_VALUE(逐个读取行)。</p>
<h2>总结</h2>
<p>之前使用过db2处理流式查询,设置的fetch size为100,没有问题。这次使用mysql刚开始时也设置的100,发现内存溢出了,后来在网上看到mysql流式获取数据的坑,debug进去果然没走到ResultsetRowsStreaming类,设置fetch size 参数为Integer.MIN_VALUE后,才进了ResultsetRowsStreaming类。</p>
Taro请求域名配置
https://segmentfault.com/a/1190000022167925
2020-03-27T11:57:35+08:00
2020-03-27T11:57:35+08:00
1029
https://segmentfault.com/u/1029
0
<h2>背景</h2>
<p>使用Taro进行小程序的开发,调用后端api获取信息,开发,测试,正式环境的域名是不同的。最初始的版本中,我们在不同环境中不断修改域名信息。这样造成一个问题,提交审核的时候我们一定要记得将域名改成正式版,不然会出现问题。这种人为控制,显然不能保证百分百没有问题。我们就考虑是不是可以通过process.env来区分不同的环境</p>
<h2>尝试使用process.env解决问题</h2>
<h4>修改配置文件</h4>
<p>原配置文件</p>
<pre><code>export const baseUrl = 'https://release.com/';</code></pre>
<p>修改后配置文件</p>
<pre><code>let baseUrlPrefix = ''
const env = process.env.NODE_ENV === 'development' ? 'development' : 'production'
console.log(process.env.NODE_ENV)
switch (env) {
case 'development':
baseUrlPrefix = 'http://dev.com/'
break
case 'production':
baseUrlPrefix = 'https://release.com/'
break
}
export const baseUrl = baseUrlPrefix</code></pre>
<h4>执行命令查看</h4>
<p>执行命令</p>
<pre><code>npm run dev:weapp</code></pre>
<p>发现控制台输出的process.env.NODE_ENV=development</p>
<p>执行命令</p>
<pre><code>npm run build:weapp</code></pre>
<p>发现控制台输出的process.env.NODE_ENV=production</p>
<p>执行结果说明一个问题,我们是可以通过process.env进行判断的开发环境和正式环境</p>
<p>那么我们遗留下来一个问题:测试环境怎么办?</p>
<h2>测试环境怎么设置</h2>
<h4>执行npm run dev和build的区别</h4>
<p>查看package.json文件</p>
<pre><code>"scripts": {
"build:weapp": "taro build --type weapp",
"dev:weapp": "npm run build:weapp -- --watch"
}</code></pre>
<p>可以看出,dev和build的差别就是--watch,那么是不是--watch设置的process.env?</p>
<h4>--watch为什么使process.env=development</h4>
<p>查看源码 taro-build</p>
<pre><code>program
.option('--type [typeName]', 'Build type, weapp/swan/alipay/tt/h5/quickapp/rn/qq/jd')
.option('--watch', 'Watch mode')
.option('--page [pagePath]', 'Build one page')
.option('--component [pagePath]', 'Build one component')
.option('--env [env]', 'Env type')
.option('--ui', 'Build Taro UI library')
.option('--ui-index [uiIndexPath]', 'Index file for build Taro UI library')
.option('--plugin [typeName]', 'Build Taro plugin project, weapp')
.option('--port [port]', 'Specified port')
.option('--release', 'Release quickapp')
.parse(process.argv)
const { type, watch, ui, port, release, page, component, uiIndex } = program
let { env, plugin } = program
env = process.env.NODE_ENV || env
if (env) {
process.env.NODE_ENV = env
} else {
if (watch) {
process.env.NODE_ENV = 'development'
} else {
process.env.NODE_ENV = 'production'
}
}</code></pre>
<p>这里可以看到当env不存在的时候,如果执行参数里面有watch就设置process.env.NODE_ENV = 'development',否则就是'production'。</p>
<p>到这里你是不是发现了什么端倪?</p>
<h4>端倪</h4>
<p>除了--watch之外,taro执行build命令的时候还接受一个参数--env,设置当前所在环境的,那么我们的测试环境是不是可以通过这个参数设置来实现呢?</p>
<p>我们来修改一下package.json</p>
<pre><code>"scripts": {
"build:weapp": "taro build --type weapp",
"dev:weapp": "npm run build:weapp -- --watch",
"test:weapp": "npm run test:weapp -- --watch --env test"
}</code></pre>
<p>修改配置文件</p>
<pre><code>let baseUrlPrefix = ''
const env = process.env.NODE_ENV === 'development' ? 'development' : (process.env.NODE_ENV === 'test' ? 'test' : 'production')
console.log(process.env.NODE_ENV)
switch (env) {
case 'development':
baseUrlPrefix = 'http://dev.com/'
break
case 'test':
baseUrlPrefix = 'https://test.com/'
break
case 'production':
baseUrlPrefix = 'https://release.com/'
break
}
export const baseUrl = baseUrlPrefix</code></pre>
<p>执行命令</p>
<pre><code>npm run test:weapp</code></pre>
<p>想象一下打印出来的process.env.NODE_ENV是什么?</p>
<h4>发生了什么?</h4>
<p>process.env.NODE_ENV = 'production',为什么?</p>
<p>我们来看看我们的应用的config文件夹里面的index.js做了什么</p>
<pre><code>module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
}
return merge({}, config, require('./prod'))
}</code></pre>
<p>我们设置的process.env.NODE_ENV应该是test,走到这里webpack的配置文件使用的是prod的配置,我们看看prod.js里面写了什么</p>
<pre><code>module.exports = {
env: {
NODE_ENV: '"production"'
},
defineConstants: {
},
mini: {},
h5: {
/**
* 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。
* 参考代码如下:
* webpackChain (chain) {
* chain.plugin('analyzer')
* .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
* }
*/
}
}</code></pre>
<p>看到了吗,这里把NODE_ENV又重置成了production,我们来尝试调整</p>
<h4>增加test配置</h4>
<p>增加test.js配置在config文件夹中</p>
<pre><code>module.exports = {
env: {
NODE_ENV: '"test"'
},
defineConstants: {
},
mini: {},
h5: {}
}</code></pre>
<p>修改index.js</p>
<pre><code>module.exports = function (merge) {
if (process.env.NODE_ENV === 'development') {
return merge({}, config, require('./dev'))
} else if (process.env.NODE_ENV === 'test') {
return merge({}, config, require('./test'))
}
return merge({}, config, require('./prod'))
}</code></pre>