Windows10下搭建Mysql开发环境

0 条评论

这里的 Mysql 开发环境当然不是指开发 Mysql 程序本身, 只是开发使用 Mysql 的应用,准备个可用的 Mysql 而已。

自从 Windows 10 提供了 bash on ubuntu on windows 特性后,程序员搭建安装一些 linux(恩,知道mysql有windows版本)服务程序就特别方便了。

1. 首先确保 Windows 10 升级到了 Creators Update 或以上,并且启用 Bash on Windows 特性。(Store 里可以选择安装 Linux 发行版了,但是不知道是不是还叫做 bash on ubuntu on windows…)

2. 以安装的 ubuntu 为例, 启动 Bash on ubuntu on windows, 然后执行

sudo apt-get install mysql-server

中间会提示设置 root 密码,并执行

sudo service mysql start

就安装并启动好 mysql server了

3. 创建 database 和用户。

CREATE DATABASE todo CHARACTER SET utf8 COLLATE utf8_general_ci;

指定了字符集为 UTF8, 然后创建用户:

CREATE USER 'todo'@'localhost' identified by 'PASSWORD';

这里指定了用户名为 todo, 只允许通过 localhost 访问。

最后给新建用户赋上权限:

GRANT ALL PRIVILEGES ON todo.* TO todo@localhost;

SpringBoot 集成 Flyway 自动创建数据库表

1 条评论

使用 SpringBoot 开发 Java 应用时,如果涉及到数据库表的创建,可以集成 Flyway,在应用启动时,自动的初始化数据库表。

1. 首先当然是 Maven 工程引入 SpringBoot

参考 https://projects.spring.io/spring-boot/#quick-start 修改工程的 maven 配置,指定父工程,以及添加 spring-boot-starter-web 的依赖.

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

2. 引入数据库模块的依赖

SpringBoot内建了大量功能的自动配置能力,这些配置能力大多基于运行时的类检测机制,所以需要引入 spring-jdbc 和 tomcat-jdbc, mysql-connector-java 几个依赖。spring-jdbc 为了启用数据源的自动配置,tomcat-jdbc 为了支持数据库连接池,mysql-connector-java 是添加 mysql 数据库的驱动,依赖如下:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.5.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.7-dmr</version>
</dependency>

3. 引入 flyway 的依赖

SpringBoot 的 JDBC AutoConfiguration 已经内建了对 flyway 的支持,唯一要做的,就是添加上 flyway-core 的依赖:

<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>4.2.0</version>
</dependency>

4. 配置数据源

SpringBoot 的理念就是约定了大量的默认配置、自动装配能力。所以现在需要配置数据源不需要向以前一样配置各种 spring bean, 而只需要在 resources/application.properties (约定位置和名字,会自动加载)这个配置文件里写上数据库里连接的几个信息:

spring.datasource.url=jdbc:mysql://localhost:3306/todo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=todo
spring.datasource.password=123456

是的,连 driver 信息都不用写,因为 springboot 会自动根据 url 信息去检测。

5. 提供数据库表初始化脚本

还是约定的机制和约定的配置,只需要在工程 resources/db/migration 这个目录下面以 V{version}__{name}.sql 的方式命名数据库脚本文件即可,version 和 name 当然是可变的,举个例子: V1.0__InitializeTables.sql, 注意版本和名字中间是两个下划线。

6. 启动应用自动建表

一切都OK了,确保数据库可连接,用户拥有足够的权限,就可以启动应用,SpringBoot 会自动配置数据源,并使用 Flyway 进行数据库迁移操作。

@SpringBootApplication
public class TaskApp {
public static void main(String[] args) {
SpringApplication.run(TaskApp.class, args);
}
}

参考:

HTTP Path 匹配和参数提取

2 条评论

给定一个 REST 的 Path 规则是: /orgs/<orgName>/apis/<apiName>/assets,其中尖括号包含的部分表示是一个可变的参数,参数一般情况下允许包含字母、数字、下划线。然后要求可以匹配符合规则的 HTTP URI Path 部分,并可以根据参数名称提取出参数的值来。

参考 bottle 项目的实现方案,将整个规则转换成一个正则表达式,然后利用 named group 特性,给定正则表达式捕获的分组名称来获取参数的值。

1. 把 /orgs/<orgName>/apis/<apiName>/assets 转换成 /orgs/(?:<orgName>[^/]+)/apis/(?:<apiName>[^/]+)/assets ,注意在 Java 7 后才支持这个正则表达式的写法,其中 (?:<orgName>[^/]+) 表示当前分组捕获结果可以用 orgName 来进行命名。为啥使用 named group 而不是直接按捕获分组顺序来查找,是因为情况再复杂一点的时候,正则表达式里的分组可能就很多很乱了,数也数不清顺序了。

2. 第一步转换成正则表达式的时候,同时把参数名称提取出来。

3. 正则表达式匹配,并根据参数名直接从正则表达式结果里获取参数

Java 代码样例:

  1. public static void main(String[] args) {
  2.     String rule = "/orgs/<orgName>/apis/<apiName>/assets";
  3.     String path = "/orgs/dev/apis/dts/assets";
  4.     Map params = match(rule, path);
  5.     System.out.println(params); // {apiName=dts, orgName=dev}
  6. }
  7. private static Map match(String rule, String path) {
  8.     StringBuilder pathRule = new StringBuilder();
  9.     List params = new ArrayList<>();
  10.     Pattern ruleSyntax = Pattern.compile("<(\\w*)>");
  11.     Matcher ruleMatcher = ruleSyntax.matcher(rule);
  12.     int offset = 0;
  13.     while (ruleMatcher.find()) {
  14.         int groupOffset = ruleMatcher.start();
  15.         pathRule.append(rule.substring(offset, groupOffset));
  16.         String groupName = ruleMatcher.group(1);
  17.         params.add(groupName);
  18.         // 拼接成 (?<name>[^/]+) 的正则表达式
  19.         pathRule.append("(?<").append(groupName).append(">[^/]+)");
  20.         offset = ruleMatcher.end();
  21.     }
  22.     if (offset < rule.length()) {
  23.         pathRule.append(rule.substring(offset, rule.length()));
  24.     }
  25.     Pattern pathPattern = Pattern.compile("^" + pathRule.toString() + "$");
  26.     Matcher pathMatcher = pathPattern.matcher(path);
  27.     Map result = new HashMap<>();
  28.     if (pathMatcher.matches()) {
  29.         for (String param : params) {
  30.             result.put(param, pathMatcher.group(param));
  31.         }
  32.     }
  33.     return result;
  34. }

Java 里 long 可以计数多久

0 条评论

long 作为 JVM 的 primitive 类型之一,是 64bit 长的有符号数字,它的大小范围是 -9223372036854775808 到 9223372036854775807 (-263 to 263- 1),而我们经常使 long (比如 AtomicLong 对象)用来计数,那么现在可以算算,在我们的使用场景下,long 可以使用多久就会达到最大值呢?

假如我们的使用场景中,每一秒使用计数器计数 10w 次,并且不间断保持计数,那么一天会计数 10w*60*60*24=8,640,000,000, 也就是约 86 亿次, 这个数字看起来很大了,我们用 long 的最大值来除以试试

9223372036854775807/8640000000=1067519911.6730064591435185185185

也就是大概可以不间断地计数 10.6 亿天,换算成年呢?

1067519911.6730064591435185185185/365=2924712.086775360162037037037037

也就是大概 292w 年,所以,如果计数器要求每天计数 86 亿次的情况下,long 可以支持我们的程序跑上几百万年,完全不用担心溢出的问题。

并发编程 Promise, Future 和 Callback

1 条评论

在并发编程中,我们通常会用到一组非阻塞的模型:Promise,Future 和 Callback。其中的 Future 表示一个可能还没有实际完成的异步任务的结果,针对这个结果可以添加 Callback 以便在任务执行成功或失败后做出对应的操作,而 Promise 交由任务执行者,任务执行者通过 Promise 可以标记任务完成或者失败。 可以说这一套模型是很多异步非阻塞架构的基础。

这一套经典的模型在 Scala、C# 中得到了原生的支持,但 JDK 中暂时还只有无 Callback 的 Future 出现,当然也并非在 JAVA 界就没有发展了,比如 Guava 就提供了ListenableFuture 接口,而 Netty 4+ 更是提供了完整的 Promise、Future 和 Listener 机制,在 Netty 的官方文档 Using as a generic library 中也介绍了将 Netty 作为一个 lib 包依赖,并且使用 Listenable futures 的示例。在实际的项目使用中,发现 Netty 的 EventLoop 机制不一定适用其他场景,因此想去除对 EventLoop 的依赖,实现一个简化版本。

参考 Scala 和 Netty 的代码重新定义了接口和实现,先介绍下和 Netty 版本的区别:

  1. 去除了对 EventLoop 的依赖,Callback 的执行策略不同:任务未完成时添加的 Callback,会在结束任务的线程执行;任务完成后添加的 Callback 会在添加 Callback 线程立即执行
  2. 一个 Callback 执行后会立即被清理
  3. Callback 可以根据任务结果添加,支持添加以下三种 Callback: onComplete, onSuccess, onFailure, 不需要和 Netty 的 FutureListener 一样大部分场景下都需要检查 future.isSuccess 等
  4. 支持 Callback 的组合,Callback 包含一些函数式的方法,比如 compose 和 andThen 可以用来组合
  5. 使用 CountdownLatch 替换掉了 Netty 的 wait/notify 实现
  6. 去掉 Netty Future 一些不常使用的方法,同时补充一些模型间关联的方法,比如 Promise.getFuture

然后再介绍几个使用这个 commons-future 的示例:

  1. 异步执行任务,获得 Future 后添加 Callback
    1. final TaskPromise promise = new DefaultTaskPromise();
    2. final TaskFuture future = promise.getFuture();
    3. final CountDownLatch latch = new CountDownLatch(1);
    4. future.onComplete(new TaskCallback() { // 添加结束 Callback
    5.     @Override
    6.     public TaskFuture apply(TaskFuture f) {
    7.         latch.countDown();
    8.         return f;
    9.     }
    10. });
    11. new Thread(new Runnable() {
    12.     @Override
    13.     public void run() {
    14.         promise.setSuccess(null);
    15.     }
    16. }).start();
    17. latch.await();
  2. 异步执行任务,获得 Future 后添加成功结束的 Callback
    1. final TaskPromise promise = new DefaultTaskPromise();
    2. final TaskFuture future = promise.getFuture();
    3. final CountDownLatch latch = new CountDownLatch(1);
    4. future.onSuccess(new TaskCallback() { // 添加成功结束 Callback
    5.     @Override
    6.     public TaskFuture apply(TaskFuture f) {
    7.         latch.countDown();
    8.         return f;
    9.     }
    10. });
    11. new Thread(new Runnable() {
    12.     @Override
    13.     public void run() {
    14.         promise.setSuccess(null);
    15.     }
    16. }).start();
    17. latch.await();
  3. 异步执行任务,获得 Future 后,添加失败结束的组合 Callback
    1. final TaskPromise promise = new DefaultTaskPromise();
    2. final TaskFuture future = promise.getFuture();
    3. final CountDownLatch latch = new CountDownLatch(2);
    4. future.onFailure(new TaskCallback() {
    5.     @Override
    6.     public TaskFuture apply(TaskFuture f) {
    7.         latch.countDown();
    8.         return f;
    9.     }
    10. }.andThen(new TaskCallback() {
    11.     @Override
    12.     public TaskFuture apply(TaskFuture f2) {
    13.         latch.countDown();
    14.         return f2;
    15.     }
    16. }));
    17. new Thread(new Runnable() {
    18.     @Override
    19.     public void run() {
    20.         promise.setFailure(new IllegalStateException("cm"));
    21.     }
    22. }).start();
    23. latch.await();
  4. 异步执行任务,获得 Future 后阻塞等待任务完成
    1. final TaskPromise promise = new DefaultTaskPromise();
    2. final TaskFuture future = promise.getFuture();
    3. new Thread(new Runnable() {
    4.     @Override
    5.     public void run() {
    6.         try {
    7.             TimeUnit.SECONDS.sleep(2);
    8.         } catch (InterruptedException e) {
    9.         }
    10.         promise.setFailure(new IllegalStateException("cm"));
    11.     }
    12. }).start();
    13. future.await();

代码仓库: https://bitbucket.org/qiyi/commons-future
参考: