Freemarker 的使用

Freemarker

freemarker 是一款开源的模版引擎,可以基于模版方便的生成结果。 https://freemarker.apache.org/

Freemarker 的使用

编写 ftl 模版

以生成 postgres 查询的 sql 语句为例 编写 delete.ftl 文件,${} 中的字段是参数

1
DROP TABLE IF EXISTS ${schema}.${name};

当然实际使用中的模版可能复杂的多,以部分创建表模版为例 我们可以在模版中使用 import 引入其它模版 使用 assign 设置变量 使用 if, list 等 使用 ?? 判断是否为空,使用 !false 如果为空,默认 false

<#import "../../macro/constraints.ftl" as CONSTRAINTS>
<#assign with_clause = false>
<#if fillfactor!false || parallel_workers!false || toast_tuple_target!false || (autovacuum_custom!false && add_vacuum_settings_in_sql!false) || autovacuum_enabled == 't' || autovacuum_enabled == 'f' || (toast_autovacuum!false && add_vacuum_settings_in_sql!false) || toast_autovacuum_enabled == 't' || toast_autovacuum_enabled == 'f' >
    <#assign with_clause = true>
</#if>
<#list columns as c >

其它模版使用可以参考 https://freemarker.apache.org/docs/dgui_template.html

How To Debug PgAdmin4

下载源码

https://github.com/postgres/pgadmin4

安装环境

brew install node
brew install yarn
cd runtime
yarn install
node_modules/nw/nwjs/nwjs.app/Contents/MacOS/nwjs .
sudo mkdir "/var/log/pgadmin"
sudo chmod a+wrx "/var/log/pgadmin"
sudo mkdir "/var/lib/pgadmin"
sudo chmod a+wrx "/var/lib/pgadmin"
pip install --upgrade pip 
pip install psycopg2-binary
make install-node

最后运行 pgAdmin4.py 过程中会有一些问题,参考 https://github.com/postgres/pgadmin4 readme. 和 stack overflow

PgAmdin4 展示 DDL 语句逻辑分析

PgAmdin4 展示 DDL 语句

通过 PgAdmin4 可以获取 table 的 DDL 语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- Table: public.t_order_0

-- DROP TABLE IF EXISTS public.t_order_0;

CREATE TABLE IF NOT EXISTS public.t_order_0
(
   order_id integer NOT NULL,
   user_id integer NOT NULL,
   status character varying(45) COLLATE pg_catalog."default",
   CONSTRAINT t_order_0_pkey PRIMARY KEY (order_id)
)

TABLESPACE pg_default;

ALTER TABLE IF EXISTS public.t_order_0
   OWNER to postgres;

COMMENT ON TABLE public.t_order_0
   IS 'haha';

COMMENT ON COLUMN public.t_order_0.order_id
   IS 'haha';

PgAdmin4 是如何展示对应的 DDL 语句的呢

https://github.com/postgres/pgadmin4 翻阅源码发现 DDL 语句的展示,主要是通过以下步骤来获取 SQL 语句的。

How to get postgres create table sql

获取 postgres 的建表语句的几种方法

使用 pg_dump

可以使用 pg_dump 直接查看建表语句 以本机 docker 的 postgres 为例

docker run -it --rm  postgres pg_dump -h host.docker.internal -p 5432 -U postgres -d demo_ds_0 -s -t t_order_0

结果展示

 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
--
-- PostgreSQL database dump
--

-- Dumped from database version 14.2 (Debian 14.2-1.pgdg110+1)
-- Dumped by pg_dump version 14.2 (Debian 14.2-1.pgdg110+1)

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;

SET default_tablespace = '';

SET default_table_access_method = heap;

--
-- Name: t_order_0; Type: TABLE; Schema: public; Owner: postgres
--

CREATE TABLE public.t_order_0 (
    order_id integer NOT NULL,
    user_id integer NOT NULL,
    status character varying(45)
);


ALTER TABLE public.t_order_0 OWNER TO postgres;

--
-- Name: TABLE t_order_0; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON TABLE public.t_order_0 IS 'haha';


--
-- Name: COLUMN t_order_0.order_id; Type: COMMENT; Schema: public; Owner: postgres
--

COMMENT ON COLUMN public.t_order_0.order_id IS 'haha';


--
-- Name: t_order_0 t_order_0_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--

ALTER TABLE ONLY public.t_order_0
    ADD CONSTRAINT t_order_0_pkey PRIMARY KEY (order_id);


--
-- PostgreSQL database dump complete
--

使用自定义函数

创建函数如下

guava EventBus 使用以及注意点

EventBus 的使用及注意点

使用

  • 创建全局实例
1
static final EventBus INSTANCE = new EventBus();
  • 将含有 @Subscribe 的方法注册到全局实例上
1
INSTANCE.register(this);
  • 发送 event
1
INSTANCE.post(event);

流程分析

采用上述方式时有以下几个注意点

  • 发送事件并且接受后处理事件是同步执行的,即同一个线程执行
  • 如果在接受事件方法中有嵌套发送事件,那么该嵌套发送的事件不会立即执行,会等到第一个事件的接收方法完成后,再执行第二个事件。

EventBus 调用流程分析

  • 创建 EventBus 实例
  • 将订阅者注册到 EventBus 实例上

创建 EventBus 实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public EventBus() {
    this("default");
  }

  /**
   * Creates a new EventBus with the given {@code identifier}.
   *
   * @param identifier a brief name for this bus, for logging purposes. Should be a valid Java
   *     identifier.
   */
  public EventBus(String identifier) {
    this(
        identifier,
        // 同步执行器
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        LoggingHandler.INSTANCE);
  }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private static final class PerThreadQueuedDispatcher extends Dispatcher {

    // This dispatcher matches the original dispatch behavior of EventBus.

    /** Per-thread queue of events to dispatch. */
    private final ThreadLocal<Queue<Event>> queue =
        new ThreadLocal<Queue<Event>>() {
          @Override
          protected Queue<Event> initialValue() {
            return Queues.newArrayDeque();
          }
    };

注册 EventBus 订阅者

 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
  /**
   * Registers all subscriber methods on {@code object} to receive events.
   *
   * @param object object whose subscriber methods should be registered.
   */
  public void register(Object object) {
    subscribers.register(object);
  }

  /** Registers all subscriber methods on the given listener object. */
  void register(Object listener) {
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);

    for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {
      Class<?> eventType = entry.getKey();
      Collection<Subscriber> eventMethodsInListener = entry.getValue();

      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);

      if (eventSubscribers == null) {
        CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
        eventSubscribers =
            MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
      }

      eventSubscribers.addAll(eventMethodsInListener);
    }
  }

  /**
   * Returns all subscribers for the given listener grouped by the type of event they subscribe to.
   */
  private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
    Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
    Class<?> clazz = listener.getClass();
    for (Method method : getAnnotatedMethods(clazz)) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      Class<?> eventType = parameterTypes[0];
      methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
    }
    return methodsInListener;
  }

    /** Creates a {@code Subscriber} for {@code method} on {@code listener}. */
  static Subscriber create(EventBus bus, Object listener, Method method) {
    return isDeclaredThreadSafe(method)
        ? new Subscriber(bus, listener, method)
        // 调用方法采用 synchronized
        : new SynchronizedSubscriber(bus, listener, method);
  }

发送 event

 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
 /**
   * Posts an event to all registered subscribers. This method will return successfully after the
   * event has been posted to all subscribers, and regardless of any exceptions thrown by
   * subscribers.
   *
   * <p>If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not
   * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.
   *
   * @param event event to post.
   */
  public void post(Object event) {
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
    if (eventSubscribers.hasNext()) {
      dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
      // the event had no subscribers and was not itself a DeadEvent
      post(new DeadEvent(this, event));
    }
  }
  // 这里需要注意,如果当前线程在调用的 post 方法中,嵌套调用 post,第二个 post方法不会立刻执行,而是会加入 队列中,等到队列中第一个任务处理完,才会继续处理。
  @Override
    void dispatch(Object event, Iterator<Subscriber> subscribers) {
      checkNotNull(event);
      checkNotNull(subscribers);
      Queue<Event> queueForThread = queue.get();
      queueForThread.offer(new Event(event, subscribers));

      if (!dispatching.get()) {
        dispatching.set(true);
        try {
          Event nextEvent;
          while ((nextEvent = queueForThread.poll()) != null) {
            while (nextEvent.subscribers.hasNext()) {
              nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
            }
          }
        } finally {
          dispatching.remove();
          queue.remove();
        }
      }
    }