jtLiBrain

任何伟大的事都不会一蹴而就,三分智慧,七分韧性

0%

方案一

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private static Singleton uniqueInstance;

private Singleton() {}

public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

缺点:无法应对多线程的场景,即使将getInstance()加上synchronized关键字也会带来性能方面的问题。

方案二:饿汉模式

1
2
3
4
5
6
7
8
9
public class Singleton {
private static Singleton uniqueInstance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {
return uniqueInstance;
}
}

方案三:双重检查加锁+饿汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

场景

披萨店

简单工厂

场景描述

披萨店提供预定功能,可以提供多种类型(参数type)披萨的预定;已知的是:a)披萨店会时常调整所提供的披萨种类,b)披萨制作过程(包含:准备、烘焙、切片、包装)不会经常变化;代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PizzaStore {
Pizza orderPizza(String type) {
Pizza pizza;

if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}
}

采用这样的设计,每当披萨店有新品披萨上架、或下架某类披萨时就需要对该代码进行修改,如下图所示:

解决方案

使用简单工厂,对orderPizza(String type)方法中变化的部分进行封装,使orderPizza(String type)方法对“修改关闭”,于是我们将创建对象的代码从orderPizza(String type)方法中抽象出来,如下;这样orderPizza(String type)方法只需要从工厂中得到比萨对象,然后对它进行一系列的制作过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;

if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PizzaStore {
SimplePizzaFactory factory;

public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}

public Pizza orderPizza(String type) {
Pizza pizza;

pizza = factory.createPizza(type);

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}

}

简单工厂的定义

工厂方法

工厂方法的定义

场景描述

现在考虑开展披萨加盟店,经营者为保障加盟店营运的质量,希望这些加盟店尽量使用之前披萨店的代码(即,PizzaStore);又已知的是,每个地区的加盟店可能想要提供不同风味的比萨。按照简单工厂的方式,一个可能的设计可能会是:每家加盟店都有自己的工厂来创建具体的披萨对象,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class NYPizzaFactory {
public Pizza createPizza(String type) {
//
}
}

public class ChicagoPizzaFactory {
public Pizza createPizza(String type) {
//
}
}

public class CaliforniaPizzaFactory {
public Pizza createPizza(String type) {
//
}
}

但是调研中发现,并不是所有的加盟者都希望完全按照PizzaStore的流程来制作披萨,往往他们都是一些有着丰富经验的厨师,他们想要加入自己的一些过程设计。这样的话,PizzaStore就不能满足需要了,因为它把制作披萨的过程牢牢地绑定到了其内部。

解决方案

我们a)把PizzaStore类声明为抽象类,b)把简单工厂中的方法拿回到PizzaStore类中,并将其声明为抽象方法,c)为每个区域的店创建一个PizzaStore类的子类。这样,可以让比萨的制作活动仍然局限在PizzaStore类,而同时又让这些加盟店能够自由地制作该区域风味的披萨。我们称createPizza()为工厂方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;

pizza = createPizza(type);

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}

abstract Pizza createPizza(String type);
}

纽约州的披萨店:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}

如此一来,每个地区的加盟店类型都继承PizzaStore类,并各自决定如何制作自己的披萨(即,定义各自的createPizza()方法);并且每个PizzaStore的具体子类都有自己的比萨变体。这样就做到了所有加盟店仍然使用PizzaStore框架,并可以使用已经非常不错的订单系统(即,orderPizza()方法)。

抽象工厂

抽象工厂模式的定义

抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

每个具体工厂都可以生产一整组产品。

场景描述

为了确保加盟店使用高质量的原料,我们打算建造原料工厂,并将原料运送到各家加盟店;并且,针对不同区域的加盟店,原料工厂能提供只适合该区域加盟店的原料。

已知所有披萨所需要的原料种类都是相同的(面团、酱料、芝士、海鲜佐料),这些原料的制作方式根据区域的不同而有差异。

解决方案

工厂接口PizzaIngredientFactory中定义所有原料创建的方法:

1
2
3
4
5
6
7
8
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}

为每个区域建造一个工厂,使用该区域所特有的一组原料供工厂使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}

public Sauce createSauce() {
return new MarinaraSauce();
}

public Cheese createCheese() {
return new ReggianoCheese();
}

public Veggies[] createVeggies() {
Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
return veggies;
}

public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}

public Clams createClam() {
return new FreshClams();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;

abstract void prepare();

void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
void setName(String name) {
this.name = name;
}
String getName() {
return name;
}
public String toString() {
// code to print pizza here
}
}

奶酪披萨使用工厂接口PizzaIngredientFactory来准备原料:

1
2
3
4
5
6
7
8
9
10
11
12
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}

具体的披萨店知道从哪个工厂进行原料采购:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory =
new NYPizzaIngredientFactory();
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if (item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if (item.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}

工厂方法VS抽象工厂

工厂方法 抽象工厂
1.不需要另外定义接口,只需要定义抽象方法;
2.使用继承,把对象的创建委托给子类
1.抽象工厂的任务是定义一个负责创建一组产品的接口,接口中的每个方法都负责创建一个具体产品;
2.使用对象组合,对象的创建被实现在工厂接口所暴露出来的方法中;

参考

  1. https://wickedlysmart.com/head-first-design-patterns/

装饰器模式的定义

装饰器模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

如下:

  1. ConcreteComponent是我们将要动态地加上新行为的对象,它扩展自Component
  2. 每个装饰者Decorator都有一个(包装一个)组件对象,也就是说,装饰者有一个实例变量保存某个Component是的引用;
  3. 每个具体的装饰者可以加上新的方法。新行为是通过在旧行为前面或后面做一些计算来添加的;

场景

星巴兹咖啡店的订单系统

场景介绍

星巴兹咖啡店扩展迅速,他们准备更新订单系统,以合乎他们的饮料供应要求。类设计原来是这样的:a)饮料类Beverage是一个抽象类,店内所有提供的饮料都要继承此类;b)cost()方法是抽象的,子类必须定义自己的实现,以实现对每种饮料计算价格;c)购买咖啡时,也可以加入各种调料,例如:蒸奶、豆浆、摩卡或覆盖奶泡;星巴兹会根据所加入的调料收取不同的费用。

采用这样的设计,他们的类将会爆炸:

解决方案

这里采用的做法是:以饮料为主体,在运行时以调料来“装饰”饮料。比如,如果顾客想要摩卡和奶泡深焙咖啡,那么需要这样做:

  1. 拿一个深焙咖啡DarkRoast对象;
  2. 以摩卡Mocha对象装饰它;
  3. 以奶泡Whip对象再进行装饰它;
  4. 调用cost()方法,并依赖委托将调料的价钱加上去;
1
2
3
4
5
6
7
8
9
public abstract class Beverage {
String description = "Unknown Beverage";

public String getDescription() {
return description;
}

public abstract double cost();
}

实现一些饮料,我们先实现浓缩咖啡Espresso和综合咖啡HouseBlend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}

public double cost() {
return 1.99;
}
}

public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}

public double cost() {
return .89;
}
}

调料抽象类:

1
2
3
4
5
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;

public abstract String getDescription();
}

实现摩卡口味的咖啡:a)getDescription()中加上摩卡的描述,b)cost()如中加上了摩卡的费用,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
this.beverage = beverage;
}

public String getDescription() {
return beverage.getDescription() + ", Mocha";
}

public double cost() {
return .20 + beverage.cost();
}
}

我们来使用一下新的订单系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StarbuzzCoffee {
public static void main(String args[]) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());

Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());

Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()
+ " $" + beverage3.cost());
}
}

Java中的装饰者

Java I/O

  1. InputStream是抽象组件;
  2. FileInputStreamStringBufferInputStreamByteArrayInputStream是可以被装饰者包起来的具体组件,还有少数类没有被列出,如ObjectInputStream;
  3. PushbackInputStreamBufferedInputStreamDataInputStreamInflatorInputStreamZipInputStream是具体的装饰者;
  4. Java I/O也引出装饰者模式的一个“缺点”:利用装饰者模式,常常造成设计中大量的小类,数量实在太多,可能会造成使用此API的程序员困扰

实现输入流中所有大写字符转成小写的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}

public int read() throws IOException {
int c = in.read();
return (c == -1 ? c : Character.toLowerCase((char) c));
}

public int read(byte[] b, int offset, int len) throws IOException {
int result = in.read(b, offset, len);
for (int i = offset; i < offset + result; i++) {
b[i] = (byte) Character.toLowerCase((char) b[i]);
}
return result;
}
}

参考

  1. https://wickedlysmart.com/head-first-design-patterns/

观察者模式的定义

观察者模式定义了对象之间的一对多依赖,这样,当一个对象改变状态的时候,它的所有依赖者都会收到通知并自动更新。

场景

Weather-O—Rama的Internet气象观测站

场景描述

该气象站建立在该公司的专利WeatherData类上,WeatherData类负责跟物理气象站联系,以获得最新的天气状况(温度、湿度、气压),然后实时更新各种显示设备。目前的显示设备有三种:a)显示目前状况(温度、湿度、气压)的设备,b)显示气象统计的设备,c)显示天气预报的设备;WeatherData类还需要具备方便未来有其它显示设备以插拔的方式接入的功能。

已有WeatherData类,其中

  1. getTemperature()getHumidity()getPressure()是气象站的开发人员已经实现好的方法;
  2. 我们只需要实现measurementsChanged(),该方法一旦气象测量有更新就会被调用;

不好的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class WeatherData {
// instance variable declarations

public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();

currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}

// other WeatherData methods here
}

解决方案一

相关接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}

public interface Observer {
public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
public void display();
}

实现WeatherData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;

public WeatherData() {
observers = new ArrayList<Observer>();
}

public void registerObserver(Observer o) {
observers.add(o);
}

public void removeObserver(Observer o) {
observers.remove(o);
}

public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}

public void measurementsChanged() {
notifyObservers();
}

public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}

public float getTemperature() {
return temperature;
}

public float getHumidity() {
return humidity;
}

public float getPressure() {
return pressure;
}
}

实现显示设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;

public CurrentConditionsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}

public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}

public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
private WeatherData weatherData;

public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

public void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;

if (temp > maxTemp) {
maxTemp = temp;
}

if (temp < minTemp) {
minTemp = temp;
}

display();
}

public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}

public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
private WeatherData weatherData;

public ForecastDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

public void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;

display();
}

public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}

观测站运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();

CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);

weatherData.removeObserver(forecastDisplay);
weatherData.setMeasurements(62, 90, 28.1f);
}
}

解决方案二:使用Java内置观察者模式

注意点:

  1. java.util.Observable是类;
  2. java.util.Observable中的setChanged()方法;

参考

  1. https://wickedlysmart.com/head-first-design-patterns/

策略模式的定义

策略模式定义了算法簇,封装每一个算法,使得这些算法可以互相替换;该模式让算法的变化独立于使用算法的客户。

以下面的例子为例,Flyable定义了一个飞的算法簇,FlyWithWingsFlyNoWay这两个飞的具体算法可以互相替换。

场景

鸭子池塘模拟游戏SimUDuck

场景描述

所有鸭子都会呱呱叫quack(),也会游泳swin(),所以抽象类Duck负责实现quack()swin();而每种鸭子的外观是不同的,所以抽象类Duckdisplay()方法是抽象的。

现在,该游戏需要升级,考虑给鸭子加上飞fly()的行为(注意,并不是所有的鸭子都会飞)。假如,按照我们前面设计,直接将fly()方法在抽象类Duck中进行实现,那么就会造成,所有子类具有飞fly()的行为,而假如我们有一只橡皮鸭子(它是不能飞翔的),那么它现在也会飞了:

那么,继续在橡皮鸭子RubberDuck中重写fly()方法为“什么都不做”,是否可行呢?这样固然能解决橡皮鸭子不会飞的问题,但是假如我们又添加了一个诱饵鸭DecoyDuck,还需要再次添加对fly()方法的重写:

那么用接口实现如何?因为并不是所有鸭子都会飞fly()和叫quack(),所以把这两个行为放到单独的接口中。这样的话,重复代码会变多,因为绿头鸭和红头鸭都需要实现fly()quack(),造成代码无法复用:

解决方案

我们知道Duck类内的fly()quack()会随着鸭子的不同而改变。我们将这两个行为从Duck类中取出来,建立一套鸭子的行为类来代表每个行为。

采用这样的设计,可以让飞行fly()和呱呱叫quack()的动作被其它的对象复用,因为这些行为已经和鸭子类无关了。同时,我们可以新增一些行为,而不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;

public Duck() { }

public abstract void display();

public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}

public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}

public void display() {
System.out.println("I'm a real Mallard duck");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface FlyBehavior {
public void fly();
}

public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}

public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface QuackBehavior {
public void quack();
}

public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}

public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("<< Silence >>");
}
}

public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("Squeak");
}
}

参考

  1. https://wickedlysmart.com/head-first-design-patterns/

(待续)

Table API 和 SQL 程序的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// create a TableEnvironment for specific planner batch or streaming
val tableEnv = ... // see "Create a TableEnvironment" section

// create an input Table
tableEnv.executeSql("CREATE TEMPORARY TABLE table1 ... WITH ( 'connector' = ... )")
// register an output Table
tableEnv.executeSql("CREATE TEMPORARY TABLE outputTable ... WITH ( 'connector' = ... )")

// create a Table from a Table API query
val table2 = tableEnv.from("table1").select(...)
// create a Table from a SQL query
val table3 = tableEnv.sqlQuery("SELECT ... FROM table1 ...")

// emit a Table API result Table to a TableSink, same for SQL result
val tableResult = table2.executeInsert("outputTable")
tableResult...

创建 TableEnvironment

创建表

Connector Tables

Table API

  • 一般形式:
1
2
3
4
5
6
tableEnvironment
.connect(...)
.withFormat(...)
.withSchema(...)
.inAppendMode()
.createTemporaryTable("MyTable")
  • 示例
1

SQL

  • 一般形式:
1
tableEnvironment.executeSql("CREATE [TEMPORARY] TABLE MyTable (...) WITH (...)")
  • 示例
1

虚拟表

注册表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// get a TableEnvironment
val tEnv: TableEnvironment = ...;
tEnv.useCatalog("custom_catalog")
tEnv.useDatabase("custom_database")

val table: Table = ...;

// register the view named 'exampleView' in the catalog named 'custom_catalog'
// in the database named 'custom_database'
tableEnv.createTemporaryView("exampleView", table)

// register the view named 'exampleView' in the catalog named 'custom_catalog'
// in the database named 'other_database'
tableEnv.createTemporaryView("other_database.exampleView", table)

// register the view named 'example.View' in the catalog named 'custom_catalog'
// in the database named 'custom_database'
tableEnv.createTemporaryView("`example.View`", table)

// register the view named 'exampleView' in the catalog named 'other_catalog'
// in the database named 'other_database'
tableEnv.createTemporaryView("other_catalog.other_database.exampleView", table)

查询表

Table API

  • 示例
1
2
3
4
5
// compute revenue for all customers from France
val revenue = orders
.filter($"cCountry" === "FRANCE")
.groupBy($"cID", $"cName")
.select($"cID", $"cName", $"revenue".sum AS "revSum")

SQL

  • 示例
1
2
3
4
5
6
7
// compute revenue for all customers from France
val revenue = tableEnv.sqlQuery("""
|SELECT cID, cName, SUM(revenue) AS revSum
|FROM Orders
|WHERE cCountry = 'FRANCE'
|GROUP BY cID, cName
""".stripMargin)

输出表

Table API

  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// create an output Table
val schema = new Schema()
.field("a", DataTypes.INT())
.field("b", DataTypes.STRING())
.field("c", DataTypes.BIGINT())

tableEnv.connect(new FileSystem().path("/path/to/file"))
.withFormat(new Csv().fieldDelimiter('|').deriveSchema())
.withSchema(schema)
.createTemporaryTable("CsvSinkTable")

// compute a result Table using Table API operators and/or SQL queries
val result: Table = ...

// emit the result Table to the registered TableSink
result.executeInsert("CsvSinkTable")

SQL

  • 示例
1
2
3
4
5
6
7
8
// compute revenue for all customers from France and emit to "RevenueFrance"
tableEnv.executeSql("""
|INSERT INTO RevenueFrance
|SELECT cID, cName, SUM(revenue) AS revSum
|FROM Orders
|WHERE cCountry = 'FRANCE'
|GROUP BY cID, cName
""".stripMargin)

参考

  1. https://ci.apache.org/projects/flink/flink-docs-release-1.13/zh/docs/dev/table/common/